Files
jaredandClaude Opus 5.5 74927a7640
build-deb / Lint, test, package (push) Successful in 3m53s
build-deb / Deploy to CT 126 (push) Successful in 10s
build-deb / Notify on failure (push) Skipped
fix: v0.9.1 robustness cleanups
- run_resume falls back to a new conversation only when the transcript file
  is gone; other failures end at the menu with the recorded id intact.
- The SessionStart hook resolves the session name under the registry lock;
  rename holds it across tmux rename-session and the registry move
  (dd_registry_locked, nestable).
- dd_load_env parses KEY=value like systemd instead of sourcing the files.
- devdesk-deploy: DPkg::Lock::Timeout, explicit FAILED lines on stdout and in
  the log; the hook answers synchronously and CI checks the HTTP status.
  logrotate for the deploy log.
- postinst drops duplicate terminal-features entries; hooks sweep stale
  temp files.

Closes #12

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 01:01:51 -04:00

5.5 KiB
Raw Permalink Blame History

Deploying DevDesk (CT 126 · dev.lotusguild.org)

1. Install the package

Add the Gitea apt repository once:

curl -fsSL https://code.lotusguild.org/api/packages/LotusGuild/debian/repository.key \
  -o /etc/apt/keyrings/gitea-LotusGuild.asc
echo "deb [signed-by=/etc/apt/keyrings/gitea-LotusGuild.asc] https://code.lotusguild.org/api/packages/LotusGuild/debian bookworm main" \
  > /etc/apt/sources.list.d/lotusguild.list
apt update && apt install devdesk

On install, /etc/devdesk/secret.env is generated, devdesk-tmux.service and devdesk.service are enabled and started, and ttyd listens on :7681. Requests without the credential get a 401.

2. Nginx Proxy Manager (CT 139)

  • Proxy host: dev.lotusguild.org → http://10.10.10.58:7681.
    • Websockets Support on.
    • SSL: the existing wildcard *.lotusguild.org certificate, with Force SSL.
  • Advanced tab: paste npm-advanced.conf, replacing __DEVDESK_BASIC__ with the output of:
    . /etc/devdesk/secret.env && printf '%s' "$DEVDESK_CREDENTIAL" | base64 -w0
    
  • Why the credential matters: ttyd only checks the credential, so anything on the LAN that bypasses NPM is refused. If you rotate it, delete secret.env, run dpkg-reconfigure devdesk, update NPM, then systemctl restart devdesk.

3. Authelia (CT 167)

Add these to access_control.rules in /etc/authelia/configuration.yml, above any broader *.lotusguild.org rule:

- domain: dev.lotusguild.org
  policy: two_factor
  subject: ['user:jared']
- domain: dev.lotusguild.org
  policy: deny

Then run authelia validate-config --config /etc/authelia/configuration.yml && systemctl restart authelia.

4. DNS

  • 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. 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 <ref>, 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 (rotated monthly).

The hook answers when the deploy has finished, with the script's last line, and with HTTP 500 if it failed. CI's Deploy to CT 126 job therefore fails when the install fails, and the failure alert goes to Matrix.

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.
    {
      "id": "devdesk-deploy",
      "execute-command": "/usr/lib/devdesk/devdesk-deploy",
      "command-working-directory": "/",
      "pass-arguments-to-command": [{"source": "payload", "name": "ref"}],
      "include-command-output-in-response": true,
      "include-command-output-in-response-on-error": true,
      "trigger-rule": {"and": [
        {"match": {"type": "payload-hash-sha256", "secret": "<WEBHOOK_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. Firewall (required)

The ttyd credential is the only thing between the LAN and a root shell, so only NPM may reach :7681. CT 126 ships this in pve-infra (containers/126-codingenvironment/etc/nftables.conf, installed by its deploy.sh):

table inet devdesk {
    chain input {
        type filter hook input priority filter - 10; policy accept;
        tcp dport 7681 ip saddr { 127.0.0.1, 10.10.10.27 } accept
        tcp dport 7681 ip6 saddr ::1 accept
        tcp dport 7681 counter reject with tcp reset
    }
}

Check from another LAN host: curl -m3 http://10.10.10.58:7681/ must fail with "connection refused". To rotate the credential: rm /etc/devdesk/secret.env && dpkg-reconfigure devdesk, put the new Authorization value in NPM proxy host 58's Advanced tab, then systemctl restart devdesk.

7. Matrix "Claude needs you" (optional)

  1. Deploy a hookshot transform named Devdesk (hookshot/devdesk.js in the matrix repo; pushing it to main auto-deploys it on LXC 151). Look up the hook UUID as described in that repo's README.
  2. umask 077; echo https://matrix.lotusguild.org/webhook/<uuid> > /etc/devdesk/matrix-webhook.
  3. Nothing to restart: the next wait that lasts DEVDESK_MATRIX_DELAY seconds with nobody attached posts a message.

Operations

Task Command
Sessions devdesk ls
Attach over SSH devdesk attach <name> (or devdesk pick)
New session (optionally in a project) devdesk new <name> [project], or the Sessions panel form
Rename a session devdesk rename <old> <new>, or ✎ in the Sessions panel
End a session devdesk end <name>, × in the Sessions panel, or [x] in the Claude window's exit menu
Restart the web side (safe) systemctl restart devdesk
Stop every session systemctl stop devdesk-tmux
Logs journalctl -u devdesk -u devdesk-tmux
UI-only update replace /usr/share/devdesk/index.html (ttyd re-reads it per request)