feat: DevDesk v0.1.0, a persistent browser terminal for Claude Code
build-deb / Lint, test, package (push) Canceled after 1m13s
build-deb / Notify on failure (push) Canceled after 0s

ttyd serves a single-file xterm.js UI styled with the LotusGuild TDS
(web_template base.css). Sessions live in a dedicated tmux server unit
(devdesk-tmux.service), so restarting or upgrading the web unit never
stops Claude. Each named session maps to a project directory. A Claude
SessionStart hook records the conversation ID, so after a reboot the
session resumes that exact conversation.

- bin/devdesk: attach / new / ls / end / pick, with an fzf picker
- ttyd requires a credential that NPM injects; origin check enabled
- debian/: dh 13 with --no-stop-on-upgrade for the tmux unit, bundled
  sha256-verified ttyd 1.7.7, lintian overrides, man page
- CI: lint, test, build, smoke-test, artifact; v* tags publish to the
  Gitea apt registry and create a release

Refs: tinker ticket 887531976

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-06 23:11:07 -04:00
co-authored by Claude Opus 5.5
commit 07a41bd625
52 changed files with 10769 additions and 0 deletions
+51
View File
@@ -0,0 +1,51 @@
# Deploying DevDesk (CT 126 · dev.lotusguild.org)
## 1. Install the package
Add the Gitea apt repository once:
```bash
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`](npm-advanced.conf), replacing `__DEVDESK_BASIC__` with the output of:
```bash
. /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:
```yaml
- 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. 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
| Task | Command |
|---|---|
| Sessions | `devdesk ls` |
| Attach over SSH | `devdesk attach <name>` (or `devdesk pick`) |
| End a session | `devdesk end <name>` (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) |
+60
View File
@@ -0,0 +1,60 @@
.TH DEVDESK 1 "October 2026" "devdesk" "User Commands"
.SH NAME
devdesk \- persistent Claude Code sessions in tmux, shared by the browser and SSH
.SH SYNOPSIS
.B devdesk attach
.RI [ NAME ]
.br
.B devdesk new
.I NAME
.br
.B devdesk ls
.br
.B devdesk pick
.br
.B devdesk end
.I NAME
.SH DESCRIPTION
.B devdesk
manages the named sessions served at https://dev.lotusguild.org. Each session is
a tmux session on the DevDesk server (\fI/run/devdesk/tmux.sock\fR, run by
\fBdevdesk-tmux.service\fR) with a \fIclaude\fR window and a \fIshell\fR window,
rooted in a project directory under \fBDEVDESK_PROJECTS_ROOT\fR.
.PP
Claude's conversation ID is recorded per session, so after a reboot the session
resumes its own conversation.
.SH COMMANDS
.TP
.B attach \fR[\fINAME\fR]
Attach to session \fINAME\fR, creating it if needed. The default session
(\fBmain\fR) uses the projects root; other names map to the project directory of
that name or slug (\(lqtest code\(rq becomes \fBtest-code\fR). Without \fINAME\fR,
opens the picker.
.TP
.B attach --list
Print sessions and projects as JSON (used by the web UI).
.TP
.B new \fINAME\fR
Create session \fINAME\fR detached, if it does not exist.
.TP
.B ls
List running (\(bu) and dormant (\(ci) sessions.
.TP
.B pick
Interactive picker (fzf).
.TP
.B end \fINAME\fR
Stop the session and forget its recorded conversation.
.SH FILES
.TP
.I /etc/devdesk/env
Port, projects root, Claude binary and arguments, Remote Control switch.
.TP
.I /etc/devdesk/secret.env
ttyd credential; the reverse proxy must send it as HTTP basic auth.
.TP
.I /var/lib/devdesk/registry.json
Session to directory and Claude session ID mapping.
.SH SEE ALSO
.BR tmux (1),
.BR claude (1)
+35
View File
@@ -0,0 +1,35 @@
# Nginx Proxy Manager → Proxy Host dev.lotusguild.org → Advanced tab.
# Target: http://10.10.10.58:7681, Websockets Support ON, wildcard *.lotusguild.org cert, Force SSL.
#
# Replace __DEVDESK_BASIC__ with the base64 of the credential in /etc/devdesk/secret.env:
# . /etc/devdesk/secret.env && printf '%s' "$DEVDESK_CREDENTIAL" | base64 -w0
# ttyd rejects (401) every request that does not carry it, so only traffic that
# passed Authelia here can reach the terminal.
include /snippets/authelia-location.conf;
location / {
add_header Strict-Transport-Security $hsts_header always;
auth_request /authelia;
auth_request_set $user $upstream_http_remote_user;
auth_request_set $groups $upstream_http_remote_groups;
auth_request_set $name $upstream_http_remote_name;
auth_request_set $email $upstream_http_remote_email;
proxy_set_header Remote-User $user;
proxy_set_header Remote-Groups $groups;
proxy_set_header Remote-Name $name;
proxy_set_header Remote-Email $email;
error_page 401 =302 https://auth.lotusguild.org/?rd=$scheme://$http_host$request_uri;
proxy_set_header Authorization "Basic __DEVDESK_BASIC__";
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_http_version 1.1;
proxy_buffering off;
include /etc/nginx/conf.d/include/proxy.conf;
}