Compose Dev Harness
Feature Name
compose-dev-harness
Goal
Provide a repeatable Compose development harness that can validate the Nixstasis remote-access stack without a public domain by default. The workflow must run the server-side stack, exercise Caddy dynamic TLS approval with local certificates, register or simulate a managed device, connect FRPC to FRPS, and launch an SSH terminal from the Phoenix UI through the FRP path.
Optional public-fidelity validation may use DuckDNS or a real operator-owned domain to test DNS-based ACME behavior, but that path must not be required for normal development.
Source Of Intent
docs/src/planned-features.md, featurecompose-dev-harnessdocs/src/runtime-boundaries.mddocs/src/modules/deployment-compose.mddocs/src/modules/server-web.md
Users
- Developers validating remote-access behavior from a laptop.
- Maintainers reviewing TLS approval and terminal regressions before release.
- Operators who need clear separation between local validation and production deployment guidance.
Requirements
- Provide documented startup and teardown commands for a default laptop mode.
- Default laptop mode must use local host routing and Caddy internal CA/local certificates rather than public DNS or public certificate issuance.
- Default laptop mode must exercise Caddy on-demand approval through Phoenix
GET /api/v1/check_domain. - Provide a managed test-device path that registers with the server, runs or simulates FRPC, and exposes SSH through FRP so the UI terminal can connect.
- Provide validation steps for opening a terminal from
/devices/:idand running a harmless command through the browser UI. - Provide optional public-fidelity guidance for DuckDNS or a real domain using DNS-based ACME validation.
- Clearly separate development-only shortcuts from production Compose guidance.
Constraints
- Do not weaken production ingress or authentication requirements.
- Do not require public DNS, public ingress, ngrok, localtunnel, or similar tunnel providers for default laptop mode.
- Preserve Compose-file composition as the development override mechanism.
- Keep generated certificates, local keys, DNS tokens, and runtime state out of source control.
- The Phoenix app remains reached through Caddy in deployment-shaped flows.
- SSH terminal validation must exercise the browser UI, Phoenix Channels, server-side SSH process boundary, and FRP TCP mux path.
Non-Goals
- Replacing the supported production Compose deployment path.
- Making production Let’s Encrypt validation mandatory for local development.
- Building a hosted staging environment.
- Load, performance, or high-availability validation.
- Replacing existing E2E API protocol validation.
Proposed Design
One-Command Dev Lab
The fastest local path is a single-command dev lab (dev-lab.sh up --devices N)
that starts the server stack and seeds N pre-approved virtual devices via release
RPC. Virtual devices are seeded idempotently by MAC address and bypass
registration, polling, and FRPC entirely. This path validates server UI, database,
and API behavior but does not exercise the Go client or FRP tunnel path. The dev
lab uses a tracked dev.env with hardcoded development defaults (no template
secrets), uses NIXSTASIS_FORCE_SSL=false, and the Compose development harness
with docker compose --env-file dev.env.
Default Laptop Mode
Default laptop mode is local-first and deterministic:
- Use a single
docker-compose.ymlwith environment-file-driven configuration. - Run Phoenix, Caddy, FRPS, PostgreSQL, and client containers using the same
compose file with
docker compose --env-file dev.env. - Use Caddy local certificates or internal CA for HTTPS.
- Use local host routing for reserved app hosts and device wildcard hosts.
- Configure Caddy on-demand TLS with the existing Phoenix ask endpoint so domain approval remains part of the flow.
- Run a test device using a containerized client with systemd, sshd, frpc, and the Go client binary — matching real device lifecycle.
- The client container acts as both the Go client and the SSH target reachable through FRP.
- Set
NIXSTASIS_FORCE_SSL=falseso Phoenix does not enforce SSL redirects in local mode.
Optional Public-Fidelity Mode
Public-fidelity mode should be documented as a separate validation path:
- DuckDNS may be used for low-cost DNS and TXT-record ACME challenge testing.
- A real operator-owned domain may be used when available.
- DNS provider credentials must stay outside source control.
- Public-fidelity mode validates DNS challenge behavior and public certificate issuance, but it does not replace default laptop mode.
Hostnames
Default laptop mode reserves these local hostnames:
nixstasis.localhostfor the Phoenix app through Caddy.auth.localhostfor AuthCrunch through Caddy.frp-admin.localhostfor the FRPS dashboard through Caddy.atom-<normalized-device-id>.localhostfor device HTTP routes through FRPS and Caddy.
These names intentionally mirror the existing production reserved-host pattern of
nixstasis.<base-domain>, auth.<base-domain>, frp-admin.<base-domain>, and
wildcard device hosts while keeping default routing local-only. Scripts, examples,
and validation steps must reuse these names consistently.
Managed Test Device
The managed-device path uses a containerized client that runs Ubuntu with systemd
as PID 1, sshd for remote access, frpc for tunnel connectivity, and the Go client
binary started via systemd units. This matches the real device lifecycle including
registration, polling, FRPC process management, and SSH key authorization. Scale
client containers with --clients N or docker compose --scale client=N.
TLS Observation Diagnostics
A development-only TLS observation system records Caddy ask calls in an
ETS-backed GenServer (Nixstasis.TLSObservations). Observations are exposed via
/_nixstasis/laptop/tls_observations (GET to list, DELETE to clear) and gated by
NIXSTASIS_TLS_OBSERVATIONS_ENABLED (routed through runtime.exs into app
config) and NIXSTASIS_TLS_OBSERVATIONS_TOKEN. The observation store is capped at
50 entries and is not persisted. Validation scripts use this endpoint to
programmatically confirm Caddy reached Phoenix for domain approval.
Terminal Smoke Coverage
The minimum terminal smoke test must launch the terminal from /devices/:id, run a
harmless command such as whoami or printf nixstasis-smoke, close the session,
and reopen a terminal for the same test device. This is implemented as an ExUnit
LiveView integration test using a fake SSH client, covering command execution and
session lifecycle behavior without requiring a running FRP tunnel or browser
automation.
Public-Fidelity Guidance
DuckDNS and real-domain public-fidelity support should be documented as optional manual setup guidance for this feature. Do not add a DNS-provider abstraction until there is a concrete implementation need beyond documenting validation steps.
Risks And Tradeoffs
- Local TLS can prove Caddy and approval plumbing without proving public CA issuance.
- DuckDNS improves public-fidelity coverage but adds account tokens, DNS propagation delays, and external availability risk.
- Device simulation can hide packaging or client defects if it bypasses the Go client and FRPC process model.
- Host routing varies across macOS, Linux, Docker, Podman, and Apple Container.
- Tunnel providers such as ngrok are useful for reachability demos but can mask Caddy-owned TLS behavior if TLS terminates before Caddy.
Dependencies
deploy/compose/docker-compose.ymldeploy/compose/dev.envdeploy/compose/.env.exampledeploy/compose/caddy/Caddyfiledeploy/compose/caddy/Caddyfile.laptopdeploy/compose/frps/frps.tomldeploy/compose/scripts/dev-lab.shdeploy/compose/scripts/check_runtime_contract.shdeploy/compose/scripts/validate_stack.shpackages/client/Dockerfilepackages/server/Dockerfilepackages/server/lib/nixstasis_web/controllers/tls_controller.expackages/server/lib/nixstasis/tls_observations.expackages/server/lib/nixstasis/deployment.expackages/server/lib/nixstasis_web/channels/terminal_channel.expackages/server/lib/nixstasis/devices/ssh_client.expackages/client/internal/frp/manager.gopackages/client/internal/config/config.gopackages/client/cmd/nixstasis/register.gopackages/client/cmd/nixstasis/poll.go
Likely Affected Docs
docs/src/planned-features.mddocs/src/modules/deployment-compose.mddocs/src/runtime-boundaries.mddocs/src/modules/server-web.mddeploy/compose/README.mdpackages/client/README.mdpackages/server/README.md
Validation
- Static validation for generated Compose development overrides.
- Local smoke test confirming Caddy reaches Phoenix TLS approval.
- Local smoke test confirming Caddy serves local certificates through default laptop hostnames.
- Local smoke test confirming FRPC connects to FRPS using development config.
- ExUnit LiveView integration test that launches a terminal and runs a harmless command through a fake SSH client.
- Dev-lab one-command flow seeding virtual devices and confirming server UI accessibility.
- Optional DuckDNS or real-domain validation that documents certificate issuance, DNS challenge behavior, and expected failure modes.