- 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>
5.5 KiB
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.orgcertificate, 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, rundpkg-reconfigure devdesk, update NPM, thensystemctl 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"todns.hostsinpve-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:
- Generate a secret.
- Add this hook to
/etc/webhook/hooks.json, next to126-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"}}} ]} } - Run
systemctl restart webhook. - 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)
- Deploy a hookshot transform named
Devdesk(hookshot/devdesk.jsin the matrix repo; pushing it tomainauto-deploys it on LXC 151). Look up the hook UUID as described in that repo's README. umask 077; echo https://matrix.lotusguild.org/webhook/<uuid> > /etc/devdesk/matrix-webhook.- Nothing to restart: the next wait that lasts
DEVDESK_MATRIX_DELAYseconds 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) |