feat: DevDesk v0.1.0, a persistent browser terminal for Claude Code
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:
@@ -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) |
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user