- Go 79.4%
- JavaScript 9.4%
- CSS 7.7%
- HTML 3.5%
The bridge installer was gated on a DOMContentLoaded listener. On a back-forward cache restore (and any other path where the body / script state outlives the original parse), DOMContentLoaded never fires again, so installBridge never runs, no EventSource opens, and live updates silently stop. Run installBridge immediately when document.readyState is already past "loading"; otherwise keep the DOMContentLoaded handler for the fresh-load case. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| cmd/chat-posta | ||
| docs/adr | ||
| internal | ||
| CONTEXT.md | ||
| go.mod | ||
| go.sum | ||
| PRD.md | ||
| README.md | ||
chat-posta
The browser client for the posta federated chat ecosystem. chat-posta is
a single Go binary that runs at chat.posta.no behind Caddy and acts as a
multi-tenant Backend-For-Frontend over each user's personal
posta-server. The browser speaks
HTMX-flavoured HTML to chat.posta.no; chat.posta.no holds the user's
session and proxies the posta-server REST + SSE API on their behalf.
See PRD.md (mirrored from the project tracker) for the full specification. Issues and slice tickets live on the GitHub-equivalent tracker.
Build
go build ./cmd/chat-posta
Static, cgo-free build for the Alpine deployment:
CGO_ENABLED=0 go build -ldflags="-X main.version=$(git describe --tags --dirty)" \
-o chat-posta ./cmd/chat-posta
Run
chat-posta serve --listen :8080 --db /tmp/chat.db
chat-posta version prints the linker-stamped version.
Config
Layered, precedence flag > env > config > default:
| Flag | Env | TOML key | Default |
|---|---|---|---|
--listen |
CHAT_POSTA_LISTEN |
listen |
0.0.0.0:80 |
--db |
CHAT_POSTA_DB |
db |
/var/lib/chat-posta/state.db |
--config |
— | — | /etc/chat-posta/config.toml |
Layout
cmd/chat-posta/ cobra root, `serve`, `version`
internal/web/ HTTP handlers, html/template, embed.FS assets
internal/config/ TOML + env + flag layering
internal/store/ SQLite open, migration framework, schema
internal/auth/ WebAuthn + AES-GCM (stubbed; slices 3-4)
internal/posta/ typed client for posta-server (stubbed; slice 3+)
internal/sse/ per-user SSE multiplexer (stubbed; slice 8)
internal/admin/ invite/user CRUD for cobra subcommands (slice 2)
Templates and CSS/fonts are embedded via embed.FS so the binary is
self-contained.
Deploy
Production chat.posta.no runs as a non-root user inside an incus container
on fismen. The service listens on :80, which a non-root user cannot
bind without the cap_net_bind_service file capability on the binary.
cp and cp -a do not preserve file capabilities — they are an
xattr the kernel does not include in -a. Likewise, incus file push
lands a fresh file with no caps. Every binary swap must therefore
re-apply the capability before restarting the service. Without it, the
service comes up and immediately exits with
listen tcp :80: bind: permission denied.
# Build static binary (see Build above).
scp ./chat-posta fismen:/tmp/chat-posta-new
# Stage inside the container, atomically swap, RE-APPLY THE CAPABILITY,
# then restart. The mv is atomic on the same filesystem; the kernel
# keeps the old inode mapped for the still-running process.
ssh fismen incus file push /tmp/chat-posta-new \
chat-posta/usr/local/bin/chat-posta.new \
--mode=0755 --uid=1000 --gid=1000
ssh fismen incus exec chat-posta -- \
mv /usr/local/bin/chat-posta.new /usr/local/bin/chat-posta
ssh fismen incus exec chat-posta -- \
setcap cap_net_bind_service=+ep /usr/local/bin/chat-posta
ssh fismen incus exec chat-posta -- rc-service chat-posta restart
# Verify.
ssh fismen incus exec chat-posta -- rc-service chat-posta status
curl -fsS https://chat.posta.no/healthz
Do not rely on cp -a to make a rollback copy of the running binary —
the copy will lack the capability and will not bind :80. If a rollback
is needed, re-apply setcap to the restored file before restarting.