Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
adac0ac
test(deploy): guard against implicit image pulls on deploy
zaytcevcom Jul 27, 2026
f635351
fix(deploy): stop the playbook from silently updating the bot image
zaytcevcom Jul 27, 2026
bfb0b24
ci: stamp the immutable build number into the image version label
zaytcevcom Jul 27, 2026
de3dab2
feat(deploy): add an opt-in image update to the playbook
zaytcevcom Jul 27, 2026
377598b
docs: document how an Ansible deploy is updated
zaytcevcom Jul 27, 2026
82e334c
docs(deploy): correct three inaccurate role comments
zaytcevcom Jul 27, 2026
63fd0de
docs(ci): stop claiming deploys track :latest
zaytcevcom Jul 27, 2026
4a1fe36
ci: set the version-label tag priority explicitly
zaytcevcom Jul 27, 2026
5f35e56
test(deploy): widen the implicit-pull guard to every spelling and file
zaytcevcom Jul 27, 2026
924237a
ci: keep Go test files out of the publish trigger and the build context
zaytcevcom Jul 27, 2026
1316bb5
test(deploy): gate the pull guard on the opt-in knob, not on any when:
zaytcevcom Jul 27, 2026
2053b12
fix(deploy): update the whole stack on an opt-in run and report what …
zaytcevcom Jul 27, 2026
7364415
docs: state what an ordinary deploy run actually guarantees
zaytcevcom Jul 27, 2026
e224d2b
ci: correct two comments in the publish workflows
zaytcevcom Jul 27, 2026
3147c59
docs(deploy): scope the sidecar update notes to what the role controls
zaytcevcom Jul 27, 2026
9903473
docs(deploy): name the cases where a run still fetches an image
zaytcevcom Jul 27, 2026
ddfa917
fix(deploy): report the opt-in fetch from its own changed flag
zaytcevcom Jul 27, 2026
f9361d5
docs: soften the deploy update note and say what the switch refreshes
zaytcevcom Jul 27, 2026
fa273b4
test(deploy): check the opt-in switch in every shipped variable source
zaytcevcom Jul 27, 2026
1382586
test(deploy): stop the pull guard firing on operator-facing text
zaytcevcom Jul 27, 2026
b1a746e
docs(deploy): stop advertising a restart entry point the role lacks
zaytcevcom Jul 27, 2026
552d9af
docs(deploy): separate the pinned-tag and floating-tag update paths
zaytcevcom Jul 27, 2026
81b26b3
docs: name the case where an ordinary deploy run still fetches
zaytcevcom Jul 27, 2026
b632b96
fix(deploy): keep an async fetch timeout reporting its own error
zaytcevcom Jul 27, 2026
86fc150
docs: say how a pinned deployment moves to a new version
zaytcevcom Jul 27, 2026
d60d6ce
test(deploy): walk the deploy tree through an fs.FS
zaytcevcom Jul 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@ adapters/telegram/.env
.gocache
.cache
flock-telegram

# Go tests: `go build` ignores them, so they only add churn — a test-only edit would
# otherwise invalidate the COPY layers and force a full rebuild of an identical image.
**/*_test.go
21 changes: 17 additions & 4 deletions .github/workflows/publish-telegram.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ on:
tags: ["v*"]
# Only rebuild when something that actually lands in the image changes: the Go
# sources (cmd/, internal/, core/, go.mod/go.sum) or the adapter build
# (Dockerfile/.dockerignore). Docs and the Ansible deploy dir don't affect the
# image, so they don't trigger a build.
# (Dockerfile/.dockerignore). Docs, the Ansible deploy dir and Go test files
# don't affect the image (.dockerignore keeps _test.go out of the build context),
# so they don't trigger a build. The last matching pattern wins, so each negation
# has to sit after the pattern it carves out of; the entries below them overlap
# with neither negation.
paths:
- 'cmd/**'
- 'internal/**'
Expand All @@ -18,6 +21,7 @@ on:
- 'go.sum'
- 'adapters/telegram/**'
- '!adapters/telegram/deploy/**'
- '!**/*_test.go'
- '.dockerignore'
- '.github/workflows/publish-telegram.yml'
workflow_dispatch:
Expand Down Expand Up @@ -48,15 +52,24 @@ jobs:
with:
images: ghcr.io/duckbugio/flock-telegram
# Tags produced per build:
# :latest — rolling, default branch only (what deploys track)
# :0.1.<run_number> — ordered, human-readable auto-version for every main
# build (no manual step); the number increments per
# publish run so images sort and read sanely
# :latest — rolling, default branch only; an Ansible deploy resolves
# it at install and follows it again on an operator's pull,
# or when the host has no copy of the tag left to start from
# :vX.Y.Z — milestone release: push a git tag vX.Y.Z and it lands
# :sha-<commit> — exact, immutable pin for traceability/rollback
# metadata-action stamps the highest-priority tag into
# org.opencontainers.image.version. priority=250 lifts the immutable
# 0.1.<run_number> above :latest (type=raw defaults to 200) so that label names
# the exact build instead of a worthless "latest". Stated explicitly rather than
# left to the listing order, which only breaks ties and would silently regress on
# a reshuffle. A vX.Y.Z tag push still wins (type=ref defaults to 600) and the
# pushed tag set is unchanged.
tags: |
type=raw,value=0.1.${{ github.run_number }},enable={{is_default_branch}},priority=250
type=raw,value=latest,enable={{is_default_branch}}
type=raw,value=0.1.${{ github.run_number }},enable={{is_default_branch}}
type=ref,event=tag
type=sha,format=short

Expand Down
21 changes: 17 additions & 4 deletions .github/workflows/publish-vk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ on:
tags: ["v*"]
# Only rebuild when something that actually lands in the image changes: the Go
# sources (cmd/, internal/, core/, go.mod/go.sum) or the adapter build
# (Dockerfile/.dockerignore). Docs and the Ansible deploy dir don't affect the
# image, so they don't trigger a build.
# (Dockerfile/.dockerignore). Docs, the Ansible deploy dir and Go test files
# don't affect the image (.dockerignore keeps _test.go out of the build context),
# so they don't trigger a build. The last matching pattern wins, so each negation
# has to sit after the pattern it carves out of; the entries below them overlap
# with neither negation.
paths:
- 'cmd/**'
- 'internal/**'
Expand All @@ -18,6 +21,7 @@ on:
- 'go.sum'
- 'adapters/vk/**'
- '!adapters/vk/deploy/**'
- '!**/*_test.go'
- '.dockerignore'
- '.github/workflows/publish-vk.yml'
workflow_dispatch:
Expand Down Expand Up @@ -48,15 +52,24 @@ jobs:
with:
images: ghcr.io/duckbugio/flock-vk
# Tags produced per build:
# :latest — rolling, default branch only (what deploys track)
# :0.1.<run_number> — ordered, human-readable auto-version for every main
# build (no manual step); the number increments per
# publish run so images sort and read sanely
# :latest — rolling, default branch only; an Ansible deploy resolves
# it at install and follows it again on an operator's pull,
# or when the host has no copy of the tag left to start from
# :vX.Y.Z — milestone release: push a git tag vX.Y.Z and it lands
# :sha-<commit> — exact, immutable pin for traceability/rollback
# metadata-action stamps the highest-priority tag into
# org.opencontainers.image.version. priority=250 lifts the immutable
# 0.1.<run_number> above :latest (type=raw defaults to 200) so that label names
# the exact build instead of a worthless "latest". Stated explicitly rather than
# left to the listing order, which only breaks ties and would silently regress on
# a reshuffle. A vX.Y.Z tag push still wins (type=ref defaults to 600) and the
# pushed tag set is unchanged.
tags: |
type=raw,value=0.1.${{ github.run_number }},enable={{is_default_branch}},priority=250
type=raw,value=latest,enable={{is_default_branch}}
type=raw,value=0.1.${{ github.run_number }},enable={{is_default_branch}}
type=ref,event=tag
type=sha,format=short

Expand Down
2 changes: 1 addition & 1 deletion README.de.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ Der **Poller** ist die empfohlene Methode, um auf Review-Kommentare zu reagieren
- **Sprachnachrichten:** `ENABLE_VOICE_MESSAGES=true`, `VOICE_PROVIDER=mistral|openai|local`, plus `MISTRAL_API_KEY` (oder `OPENAI_API_KEY`). Werden transkribiert und als Befehle ausgeführt.
- **dind-Sidecar:** `docker compose --profile dind up -d` stellt dem Team dockerisierte Linter/Tests bereit (setze `DOCKER_HOST=tcp://dind:2375`).
- **Isolation pro Chat:** Jeder Chat erhält `/workspace/chat_<id>` (1:1 → privat; Gruppe → ein gemeinsamer Workspace); Chats sind vollständig isoliert und laufen parallel, begrenzt durch `MAX_CONCURRENT_CHAT_RUNS`. Setze in Gruppen `REQUIRE_GROUP_MENTION=true`, damit der Bot nur antwortet, wenn er per @mention erwähnt wird oder auf ihn geantwortet wird.
- **Ansible-Deployment** (Telegram): Ein-Befehl-VPS-Provisionierung aus `adapters/telegram/deploy` — kopiere `inventories/example` in dein eigenes `inventories/<name>/` (gitignored), fülle Inventory/Vars/Vault aus, dann `ansible-playbook -i inventories/<name>/inventory.ini playbook.yml`. Die Rolle zieht das vorgefertigte Image; setze `bot_image`, um einen Tag festzuhalten.
- **Ansible-Deployment** (Telegram): Ein-Befehl-VPS-Provisionierung aus `adapters/telegram/deploy` — kopiere `inventories/example` in dein eigenes `inventories/<name>/` (gitignored), fülle Inventory/Vars/Vault aus, dann `ansible-playbook -i inventories/<name>/inventory.ini playbook.yml`. Die Rolle lädt das vorgefertigte Image beim ersten Lauf und behält es danach: Ein gewöhnlicher erneuter Lauf sucht nicht nach einem neueren Build — er startet den Container mit dem Image neu, auf das das Tag auf diesem Host bereits lokal zeigt, es sei denn, auf dem Host ist keine Kopie dieses Tags mehr vorhanden (Erstinstallation oder ein `compose down` plus der wöchentliche Prune) — dann holt Compose es erneut, um den Stack zu starten. Zum Aktualisieren mit `-e bot_image_pull=true` erneut ausführen — das aktualisiert den gesamten Stack (den Bot plus die aktivierten Sidecars — dind, Caddy) und erstellt die Container neu; setze `bot_image`, um einen Tag festzuhalten — ein festgehaltenes Deployment wechselt die Version dann durch Ändern dieses Werts und einen gewöhnlichen Lauf, nicht über den Schalter: Für einen festen Tag gibt es nichts neu aufzulösen.

## Sicherheit

Expand Down
2 changes: 1 addition & 1 deletion README.es.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ El **poller** es la forma recomendada de reaccionar a los comentarios de revisi
- **Mensajes de voz:** `ENABLE_VOICE_MESSAGES=true`, `VOICE_PROVIDER=mistral|openai|local`, más `MISTRAL_API_KEY` (o `OPENAI_API_KEY`). Se transcriben y se ejecutan como comandos.
- **Sidecar dind:** `docker compose --profile dind up -d` le da al equipo linters/pruebas dockerizados (establece `DOCKER_HOST=tcp://dind:2375`).
- **Aislamiento por chat:** cada chat obtiene `/workspace/chat_<id>` (1:1 → privado; grupo → un espacio de trabajo compartido); los chats están totalmente aislados y se ejecutan en paralelo, limitado por `MAX_CONCURRENT_CHAT_RUNS`. En los grupos, establece `REQUIRE_GROUP_MENTION=true` para responder solo cuando se te @menciona o se te responde.
- **Despliegue con Ansible** (Telegram): aprovisionamiento de un VPS con un solo comando desde `adapters/telegram/deploy` — copia `inventories/example` a tu propio `inventories/<name>/` (en gitignore), rellena el inventario/vars/vault, luego `ansible-playbook -i inventories/<name>/inventory.ini playbook.yml`. El rol descarga la imagen precompilada; establece `bot_image` para fijar una etiqueta.
- **Despliegue con Ansible** (Telegram): aprovisionamiento de un VPS con un solo comando desde `adapters/telegram/deploy` — copia `inventories/example` a tu propio `inventories/<name>/` (en gitignore), rellena el inventario/vars/vault, luego `ansible-playbook -i inventories/<name>/inventory.ini playbook.yml`. El rol descarga la imagen precompilada en la primera ejecución y luego la conserva: una reejecución normal no va a buscar una compilación más nueva — reinicia el contenedor con la imagen a la que esa etiqueta ya apunta localmente en ese host, salvo que en el host ya no quede una copia de esa etiqueta (primera instalación, o un `compose down` más la limpieza semanal): entonces Compose la descarga de nuevo para poder levantar la pila. Para actualizar, vuelve a ejecutarlo con `-e bot_image_pull=true`, lo que actualiza toda la pila (el bot más los sidecars en uso — dind, Caddy) y recrea los contenedores; establece `bot_image` para fijar una etiqueta — un despliegue fijado cambia de versión editando ese valor y reejecutando, no con el interruptor: una etiqueta fija no tiene nada más nuevo que resolver.

## Seguridad

Expand Down
2 changes: 1 addition & 1 deletion README.fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ Le **poller** est le moyen recommandé pour réagir aux commentaires de revue
- **Messages vocaux :** `ENABLE_VOICE_MESSAGES=true`, `VOICE_PROVIDER=mistral|openai|local`, plus `MISTRAL_API_KEY` (ou `OPENAI_API_KEY`). Transcrits et exécutés comme des commandes.
- **Sidecar dind :** `docker compose --profile dind up -d` fournit à l'équipe des linters/tests dockerisés (définissez `DOCKER_HOST=tcp://dind:2375`).
- **Isolation par conversation :** chaque conversation reçoit `/workspace/chat_<id>` (1:1 → privé ; groupe → un espace de travail partagé) ; les conversations sont totalement isolées et s'exécutent en parallèle, plafonnées par `MAX_CONCURRENT_CHAT_RUNS`. Dans les groupes, définissez `REQUIRE_GROUP_MENTION=true` pour ne répondre que lorsque le bot est @mentionné ou cité en réponse.
- **Déploiement Ansible** (Telegram) : provisionnement d'un VPS en une commande depuis `adapters/telegram/deploy` — copiez `inventories/example` vers votre propre `inventories/<name>/` (ignoré par git), remplissez inventory/vars/vault, puis `ansible-playbook -i inventories/<name>/inventory.ini playbook.yml`. Le rôle récupère l'image pré-construite ; définissez `bot_image` pour épingler un tag.
- **Déploiement Ansible** (Telegram) : provisionnement d'un VPS en une commande depuis `adapters/telegram/deploy` — copiez `inventories/example` vers votre propre `inventories/<name>/` (ignoré par git), remplissez inventory/vars/vault, puis `ansible-playbook -i inventories/<name>/inventory.ini playbook.yml`. Le rôle récupère l'image pré-construite lors de la première exécution puis la conserve : une réexécution ordinaire ne va pas chercher une build plus récente — elle redémarre le conteneur sur l'image vers laquelle ce tag pointe déjà localement sur cet hôte, sauf s'il ne reste plus de copie de ce tag sur l'hôte (première installation, ou un `compose down` suivi du nettoyage hebdomadaire) : Compose la récupère alors de nouveau pour pouvoir démarrer la stack. Pour mettre à jour, relancez avec `-e bot_image_pull=true`, ce qui met à jour toute la stack (le bot plus les sidecars utilisés — dind, Caddy) et recrée les conteneurs ; définissez `bot_image` pour épingler un tag — un déploiement épinglé change alors de version en modifiant cette valeur puis en relançant normalement, pas via l'option : un tag épinglé n'a rien de plus récent à résoudre.

## Sécurité

Expand Down
2 changes: 1 addition & 1 deletion README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ GITEA_POLL_INTERVAL=90
- **音声メッセージ:** `ENABLE_VOICE_MESSAGES=true`、`VOICE_PROVIDER=mistral|openai|local`、加えて `MISTRAL_API_KEY`(または `OPENAI_API_KEY`)。文字起こしされてコマンドとして実行されます。
- **dind サイドカー:** `docker compose --profile dind up -d` で、Docker 化されたリンター/テストをチームに提供します(`DOCKER_HOST=tcp://dind:2375` を設定)。
- **チャットごとの隔離:** 各チャットには `/workspace/chat_<id>` が割り当てられます(1:1 → プライベート、グループ → 1 つの共有ワークスペース)。チャットは完全に隔離され、`MAX_CONCURRENT_CHAT_RUNS` を上限として並列実行されます。グループでは `REQUIRE_GROUP_MENTION=true` を設定すると、@メンションまたは返信されたときのみ応答します。
- **Ansible デプロイ**(Telegram): `adapters/telegram/deploy` からワンコマンドで VPS をプロビジョニングします。`inventories/example` を自分用の `inventories/<name>/`(gitignore 対象)にコピーし、inventory/vars/vault を埋めてから `ansible-playbook -i inventories/<name>/inventory.ini playbook.yml` を実行します。ロールはビルド済みイメージを取得します。タグを固定するには `bot_image` を設定してください
- **Ansible デプロイ**(Telegram): `adapters/telegram/deploy` からワンコマンドで VPS をプロビジョニングします。`inventories/example` を自分用の `inventories/<name>/`(gitignore 対象)にコピーし、inventory/vars/vault を埋めてから `ansible-playbook -i inventories/<name>/inventory.ini playbook.yml` を実行します。ロールは初回実行でビルド済みイメージを取得し、その後はそのイメージを保持します。通常の再実行は新しいビルドを探しにいかず、そのタグがこのホスト上でローカルに指しているイメージでコンテナを再起動するだけです。ただし、そのタグのコピーがホストに残っていない場合(初回インストール、または `compose down` と週次プルーンの後)は、スタックを起動するために Compose が改めて取得します。更新するには `-e bot_image_pull=true` を付けて再実行してください(スタック全体 — ボットと有効なサイドカー(dind・Caddy)— が更新され、コンテナが再作成されます)。タグを固定するには `bot_image` を設定します。固定した場合、バージョンの移行はこのスイッチではなく `bot_image` の値を書き換えて通常どおり実行することで行います(固定タグには解決し直す新しい先がないためです)

## セキュリティ

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ The **poller** is the recommended way to react to review comments — it reaches
- **Voice messages:** `ENABLE_VOICE_MESSAGES=true`, `VOICE_PROVIDER=mistral|openai|local`, plus `MISTRAL_API_KEY` (or `OPENAI_API_KEY`). Transcribed and run as commands.
- **dind sidecar:** `docker compose --profile dind up -d` gives the team dockerized linters/tests (set `DOCKER_HOST=tcp://dind:2375`). Ansible deploys enable dind by default and inject `DOCKER_HOST` automatically.
- **Per-chat isolation:** each chat gets `/workspace/chat_<id>` (1:1 → private; group → one shared workspace); chats are fully isolated and run in parallel, capped by `MAX_CONCURRENT_CHAT_RUNS`. In groups, set `REQUIRE_GROUP_MENTION=true` to respond only when @mentioned or replied to.
- **Ansible deploy** (Telegram): one-command VPS provision from `adapters/telegram/deploy` — copy `inventories/example` to your own `inventories/<name>/` (gitignored), fill inventory/vars/vault, then `ansible-playbook -i inventories/<name>/inventory.ini playbook.yml`. The role pulls the prebuilt image; set `bot_image` to pin a tag.
- **Ansible deploy** (Telegram): one-command VPS provision from `adapters/telegram/deploy` — copy `inventories/example` to your own `inventories/<name>/` (gitignored), fill inventory/vars/vault, then `ansible-playbook -i inventories/<name>/inventory.ini playbook.yml`. The role installs the prebuilt image on the first run and then keeps it: an ordinary re-run doesn't go looking for a newer build — it restarts the container on the image that tag already points to on that host, unless this host no longer has a copy of that tag (first install, or a `compose down` plus the weekly prune), where Compose fetches it again in order to start the stack. To update, re-run with `-e bot_image_pull=true`, which refreshes the whole stack (the bot plus the sidecars in use — dind, Caddy) and recreates the containers; set `bot_image` to pin a tag — a pinned deployment then moves by editing that value and re-running, not by the switch, since a pinned tag has nothing newer to resolve to.

## Security

Expand Down
2 changes: 1 addition & 1 deletion README.pt-BR.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ O **poller** é a forma recomendada de reagir a comentários de revisão — ele
- **Mensagens de voz:** `ENABLE_VOICE_MESSAGES=true`, `VOICE_PROVIDER=mistral|openai|local`, mais `MISTRAL_API_KEY` (ou `OPENAI_API_KEY`). Transcritas e executadas como comandos.
- **Sidecar dind:** `docker compose --profile dind up -d` dá ao time linters/testes dockerizados (defina `DOCKER_HOST=tcp://dind:2375`).
- **Isolamento por chat:** cada chat recebe `/workspace/chat_<id>` (1:1 → privado; grupo → um workspace compartilhado); os chats são totalmente isolados e rodam em paralelo, limitados por `MAX_CONCURRENT_CHAT_RUNS`. Em grupos, defina `REQUIRE_GROUP_MENTION=true` para responder apenas quando for @mencionado ou respondido.
- **Deploy com Ansible** (Telegram): provisionamento de um VPS em um comando a partir de `adapters/telegram/deploy` — copie `inventories/example` para o seu próprio `inventories/<name>/` (no gitignore), preencha inventory/vars/vault e então `ansible-playbook -i inventories/<name>/inventory.ini playbook.yml`. O role baixa a imagem pré-construída; defina `bot_image` para fixar uma tag.
- **Deploy com Ansible** (Telegram): provisionamento de um VPS em um comando a partir de `adapters/telegram/deploy` — copie `inventories/example` para o seu próprio `inventories/<name>/` (no gitignore), preencha inventory/vars/vault e então `ansible-playbook -i inventories/<name>/inventory.ini playbook.yml`. O role baixa a imagem pré-construída na primeira execução e depois a mantém: uma reexecução comum não sai atrás de uma build mais nova — ela reinicia o contêiner na imagem para a qual essa tag já aponta localmente naquele host, a menos que o host não tenha mais uma cópia dessa tag (primeira instalação, ou um `compose down` mais a limpeza semanal): aí o Compose a baixa de novo para conseguir subir a pilha. Para atualizar, execute novamente com `-e bot_image_pull=true`, o que atualiza a pilha inteira (o bot mais os sidecars em uso — dind, Caddy) e recria os contêineres; defina `bot_image` para fixar uma tag — um deploy fixado passa a mudar de versão editando esse valor e reexecutando, não pelo interruptor: uma tag fixa não tem nada mais novo a resolver.

## Segurança

Expand Down
Loading