diff --git a/.gitea/workflows/build-deb.yml b/.gitea/workflows/build-deb.yml index 1254890..c7b155e 100644 --- a/.gitea/workflows/build-deb.yml +++ b/.gitea/workflows/build-deb.yml @@ -149,10 +149,32 @@ jobs: "$api/releases/$id/assets?name=$name" >/dev/null echo "release $TAG: $GITEA_URL/$REPO/releases/tag/$TAG" + deploy: + name: Deploy to CT 126 + runs-on: ubuntu-latest + needs: [build] + if: startsWith(github.ref, 'refs/tags/v') + steps: + # CT 126's webhook listener runs /usr/lib/devdesk/devdesk-deploy, which + # waits for this version in the apt registry and upgrades the package. + - name: Trigger devdesk-deploy webhook + env: + WEBHOOK_SECRET: ${{ secrets.WEBHOOK_SECRET }} + GIT_REF: ${{ github.ref }} + run: | + PAYLOAD="{\"ref\":\"${GIT_REF}\"}" + SIG=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" | awk '{print $2}') + curl -sf --connect-timeout 10 \ + -X POST \ + -H "Content-Type: application/json" \ + -H "X-Gitea-Signature: ${SIG}" \ + -d "$PAYLOAD" \ + "http://10.10.10.58:9000/hooks/devdesk-deploy" + notify-failure: name: Notify on failure runs-on: ubuntu-latest - needs: [build] + needs: [build, deploy] if: failure() && github.event_name == 'push' steps: - name: Send Matrix alert diff --git a/Makefile b/Makefile index 63ae6a9..804cd46 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,8 @@ install: all install -m 0755 bin/devdesk $(DESTDIR)$(PREFIX)/bin/devdesk install -m 0644 libexec/devdesk-lib.sh $(DESTDIR)$(LIBDIR)/devdesk-lib.sh install -m 0755 libexec/devdesk-picker libexec/devdesk-claude-run libexec/devdesk-session-hook \ - libexec/devdesk-tmux-ready libexec/devdesk-serve libexec/devdesk-statusline $(DESTDIR)$(LIBDIR)/ + libexec/devdesk-tmux-ready libexec/devdesk-serve libexec/devdesk-statusline \ + libexec/devdesk-deploy $(DESTDIR)$(LIBDIR)/ install -m 0755 build/ttyd $(DESTDIR)$(LIBDIR)/bin/ttyd install -m 0644 dist/index.html share/tmux.conf share/claude-settings.json $(DESTDIR)$(SHAREDIR)/ install -m 0644 etc/env $(DESTDIR)$(SYSCONFDIR)/devdesk/env diff --git a/README.md b/README.md index 7b2ec1d..ce8c4b0 100644 --- a/README.md +++ b/README.md @@ -71,8 +71,8 @@ apt install ../devdesk_*_amd64.deb CI (`.gitea/workflows/build-deb.yml`): - Every push lints, tests, builds and smoke-tests the package and uploads it as an artifact. -- A `v*` tag also publishes to `https://code.lotusguild.org/api/packages/LotusGuild/debian` and creates a release. +- A `v*` tag also publishes to `https://code.lotusguild.org/api/packages/LotusGuild/debian`, creates a release, and auto-deploys to CT 126. A signed webhook runs `devdesk-deploy`, which upgrades the package; see [docs/DEPLOY.md](docs/DEPLOY.md). ## Roadmap -- **v1.1:** `devdesk-api` sidecar for the Repo/CI panel, Matrix notifications when you're away, and auto-deploy. +- **Next:** Matrix notifications when you're away (needs the Ci-Alert hookshot URL). - **v2:** Ops Alerts (Grafana) with "Ask Claude" prompts, and file drop. diff --git a/debian/changelog b/debian/changelog index a7a9235..a84cad3 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,11 @@ +devdesk (0.3.1) bookworm; urgency=medium + + * Auto-deploy: tagged releases trigger CT 126's webhook listener (house HMAC + pattern), which runs /usr/lib/devdesk/devdesk-deploy to upgrade the + package from the apt registry. Running sessions are untouched. + + -- Jared Vititoe Wed, 07 Oct 2026 13:20:58 -0400 + devdesk (0.3.0) bookworm; urgency=medium * Repo / CI panel is live: branch, ahead/behind, uncommitted changes, the diff --git a/docs/DEPLOY.md b/docs/DEPLOY.md index 3af2d22..5493cb4 100644 --- a/docs/DEPLOY.md +++ b/docs/DEPLOY.md @@ -36,7 +36,30 @@ Then run `authelia validate-config --config /etc/authelia/configuration.yml && s - **Cloudflare:** add a CNAME `dev` → apex, DNS-only (grey cloud). - **Pi-hole:** add `"10.10.10.27 dev.lotusguild.org"` to `dns.hosts` in `pve-infra/containers/163-pihole/etc/pihole/pihole.toml`. A push auto-deploys it, and LAN clients then reach NPM directly instead of hairpinning. -## 5. Optional hardening +## 5. Auto-deploy on release +A `v*` tag makes CI publish the package, then send a signed webhook (the house HMAC pattern) to CT 126's existing `webhook` listener on :9000. The listener runs `/usr/lib/devdesk/devdesk-deploy `, which waits for that version in the apt registry and upgrades only `devdesk` with `--force-confold`. Running Claude sessions are untouched. The log is `/var/log/devdesk-deploy.log`. + +**Setup, done once:** +1. Generate a secret. +2. Add this hook to `/etc/webhook/hooks.json`, next to `126-config-deploy`. The file is local and untracked because it holds secrets. + ```json + { + "id": "devdesk-deploy", + "execute-command": "/usr/lib/devdesk/devdesk-deploy", + "command-working-directory": "/", + "pass-arguments-to-command": [{"source": "payload", "name": "ref"}], + "trigger-rule": {"and": [ + {"match": {"type": "payload-hash-sha256", "secret": "", + "parameter": {"source": "header", "name": "X-Gitea-Signature"}}}, + {"match": {"type": "regex", "regex": "^refs/tags/v[0-9]+\\.[0-9]+\\.[0-9]+$", + "parameter": {"source": "payload", "name": "ref"}}} + ]} + } + ``` +3. Run `systemctl restart webhook`. +4. Store the same secret as the devdesk repo's Actions secret `WEBHOOK_SECRET`. + +## 6. Optional hardening Enable the Proxmox firewall on CT 126 (`firewall=1` on net0) and allow tcp/7681 only from 10.10.10.27. The same rule suits code-server on :8680. ## Operations diff --git a/libexec/devdesk-deploy b/libexec/devdesk-deploy new file mode 100755 index 0000000..da4aca2 --- /dev/null +++ b/libexec/devdesk-deploy @@ -0,0 +1,54 @@ +#!/bin/bash +# Upgrade DevDesk from the LotusGuild apt registry. Run by the host's webhook +# listener (hook "devdesk-deploy") when CI publishes a v* tag; the tag's ref is +# passed as $1. Safe for running sessions: package upgrades restart only +# devdesk.service (ttyd), never devdesk-tmux.service where Claude lives. +set -euo pipefail + +LOG=${DEVDESK_DEPLOY_LOG:-/var/log/devdesk-deploy.log} +SOURCES=${DEVDESK_APT_SOURCES:-sources.list.d/lotusguild.list} + +log() { + printf '%s %s\n' "$(date '+%F %T')" "$*" >>"$LOG" +} + +exec 9>/run/devdesk-deploy.lock +flock -w 900 9 || { + log "another deploy is still running; giving up" + exit 1 +} + +want=${1:-} +want=${want#refs/tags/} +want=${want#v} +before=$(dpkg-query -W -f='${Version}' devdesk 2>/dev/null || echo none) +log "=== deploy: installed $before, release ${want:-latest}" + +refresh() { + apt-get update -q -o Dir::Etc::sourcelist="$SOURCES" -o Dir::Etc::sourceparts=- \ + -o APT::Get::List-Cleanup=0 >>"$LOG" 2>&1 +} + +# The registry can lag the release by a few seconds: wait for the tagged version. +candidate="" +for _ in $(seq 1 18); do + refresh || true + candidate=$(apt-cache policy devdesk | awk '/Candidate:/ {print $2}') + if [ -z "$want" ] || [ "$candidate" = "$want" ]; then + break + fi + sleep 10 +done + +if [ -n "$want" ] && [ "$candidate" != "$want" ]; then + log "registry still offers $candidate, not $want; giving up" + exit 1 +fi +if [ "$candidate" = "$before" ]; then + log "already at $before; nothing to do" + exit 0 +fi + +DEBIAN_FRONTEND=noninteractive apt-get install -y -q --only-upgrade \ + -o Dpkg::Options::=--force-confold devdesk >"$LOG" 2>&1 +log "devdesk $before -> $(dpkg-query -W -f='${Version}' devdesk)"