Planned Features
Project Overview
Nixstasis needs a Compose development harness that exercises the same remote-access boundaries operators rely on in deployment. Local development must be able to test dynamic TLS approval and browser-driven SSH terminal flows without waiting for a production-like environment.
Goals
- Provide a repeatable Compose development harness for end-to-end remote-access validation.
- Make Caddy on-demand TLS approval testable from local development workflows.
- Make UI-launched SSH terminal sessions testable from local development workflows.
- Keep the development workflow aligned with documented runtime boundaries for Phoenix, Caddy, FRPS, FRPC, and managed-device identity.
- Support an optional public-fidelity mode using DuckDNS or a real operator-owned domain when developers need to validate public ACME behavior.
Non-Goals
- Replacing the supported production Compose deployment path.
- Changing production TLS policy or public ingress requirements.
- Building a full hosted staging environment.
- Making local development depend on public DNS or public certificate issuance when a local equivalent can validate the same application behavior.
- Requiring ngrok, localtunnel, or another third-party tunnel provider for the default development workflow.
Global Constraints
- The supported production deployment path remains
deploy/compose. - Development overrides should use Compose file composition, not mutable release
image tags in
.env. - The Phoenix application remains public only through Caddy in deployment-shaped flows.
- SSH terminal testing must exercise the browser UI, Phoenix Channels, server-side SSH process boundary, and FRP TCP mux path rather than bypassing them with direct shell access.
- TLS testing must exercise
GET /api/v1/check_domainapproval behavior. - Default laptop mode uses Caddy’s internal CA/local certificates and local host routing so development does not require public DNS or public ingress.
- Optional public-fidelity mode may use DuckDNS or a real domain with DNS-based ACME validation to test publicly trusted certificate behavior.
Cross-Cutting Decisions
- Treat local dynamic TLS and UI SSH validation as one development-environment feature because both depend on the same local ingress, DNS/host routing, FRPS, and device-client simulation shape.
- Default development mode uses local-only trust and routing mechanisms instead of public DNS or real public certificate issuance.
- DuckDNS or a real domain is an optional validation path for public ACME fidelity, not a prerequisite for local feature development.
- Keep production Compose docs and Compose dev-harness docs explicitly separated so test affordances do not become accidental production guidance.
Resolved Questions
- The managed test device runs as a containerized client with systemd, sshd, frpc, and the Go client binary — matching real device lifecycle.
- Reserved local hostnames:
nixstasis.localhost,auth.localhost,frp-admin.localhost, andatom-<normalized-device-id>.localhost. - Terminal smoke test covers session open, command execution, session close, and reopen via ExUnit LiveView integration test with a fake SSH client.
- DuckDNS support is documented as optional manual setup guidance; no DNS-provider abstraction was added.
Backlog
- Document
exec_cmdintent as deny-by-default and allowlist-gated by absolute executable path. - Move bespoke Phoenix controller APIs under Ash-backed actions/resources where
practical so their OpenAPI contracts can be generated from the same source of
truth as the
/api/jsonsurface. - Define the production AuthCrunch/Phoenix role and claim contract, including header mapping, LiveView authorization behavior, and operator role semantics.
- Add production operations runbooks for backup/restore, secret rotation, incident response, HA expectations, and production monitoring.
- Add richer API examples for common success, validation-error, authorization, and edge-case responses across maintained API contracts.
Feature Map
compose-dev-harness
- Status: delivered
- Overview:
- Create a default Compose development harness that can run the server-side stack, register or simulate a managed client, validate Caddy dynamic TLS approval with local certificates, and open SSH terminal sessions from the Phoenix UI through the FRP path. Add optional guidance for DuckDNS or a real domain when public ACME fidelity is required.
- Requirements:
- Provide documented startup and teardown commands for the local development environment.
- Provide local host routing/DNS guidance for the app host, AuthCrunch host, FRPS admin host, and device wildcard hostnames.
- Provide a local TLS validation path that exercises Caddy on-demand approval via
Phoenix
GET /api/v1/check_domainusing Caddy internal CA/local certificates by default. - Provide optional DuckDNS or real-domain guidance for validating public ACME behavior without making that path required for normal development.
- Provide a test managed-device path that can register, connect FRPC to FRPS, and expose SSH in a way the UI terminal can reach.
- Provide validation steps for opening a terminal from
/devices/:idand running a harmless command through the browser UI. - Document how the development workflow differs from production Compose.
- Constraints:
- Must not weaken or bypass production ingress/authentication requirements.
- Must not require public DNS or public internet exposure for the core local validation path.
- Must keep DuckDNS and real-domain support optional and clearly marked as public-fidelity validation.
- Must preserve Compose-file-composition as the development override mechanism.
- Must keep generated certificates, local keys, and runtime state out of source control.
- Non-goals:
- Making production certificate issuance validation mandatory for local development.
- Multi-user staging operations.
- Load, performance, or HA validation.
- Replacing existing E2E API protocol validation.
- Success criteria:
- A developer can start the local stack from a clean checkout using documented commands and local-only configuration.
- Visiting the local app through Caddy with local certificates causes TLS approval behavior to be exercised and observable.
- Optional DuckDNS or real-domain instructions identify how to run a higher fidelity public ACME validation path and how it differs from default laptop mode.
- A test device appears in the UI with remote-access state sufficient to launch a terminal.
- A browser-launched terminal session can run a harmless command through FRP and SSH without direct shell shortcuts.
- The docs clearly separate development-only shortcuts from production deployment guidance.
- Risks and tradeoffs:
- Local TLS can become misleading if it validates only certificate plumbing and not domain approval behavior.
- Device simulation can hide real packaging/client defects if it bypasses the Go client and FRPC process model.
- Hostname routing can become fragile across macOS, Linux, Docker, Podman, and Apple Container unless the spec chooses supported paths explicitly.
- Using real public DNS would increase fidelity but adds cost, secrets, and operational burden for developers.
- DuckDNS reduces domain cost but adds account tokens, DNS propagation delay, and external-service availability concerns.
- Tunnel providers such as ngrok or localtunnel are useful for reachability demos but can mask Caddy-owned TLS behavior if they terminate TLS before Caddy.
- Dependencies:
deploy/compose/docker-compose.ymldeploy/compose/caddy/Caddyfiledeploy/compose/frps/frps.tomlpackages/server/lib/nixstasis_web/controllers/tls_controller.expackages/server/lib/nixstasis_web/channels/terminal_channel.expackages/server/lib/nixstasis/devices/ssh_client.expackages/client/internal/frp/manager.gopackages/client/cmd/nixstasis/register.gopackages/client/cmd/nixstasis/poll.go- Suggested validation:
- Static validation for generated Compose development overrides.
- A local smoke test that confirms Caddy reaches Phoenix TLS approval.
- A local smoke test that confirms Caddy serves local certificates through the default laptop hostnames.
- A local smoke test that confirms FRPC connects to FRPS using the development configuration.
- Optional DuckDNS or real-domain validation that documents certificate issuance, DNS challenge behavior, and expected failure modes.
- A browser/UI or E2E journey that launches a terminal and executes a harmless command through SSH.
- Suggested first workflow command:
/start-feature compose-dev-harness
self-extracting-installer
- Status: completed
- Overview:
- Build a CI-produced self-extracting installer archive for non-Nix, non-deb,
non-rpm installs. The archive bundles the client binary, arch-matched
frpc, configs, systemd units, and an artifact manifest into a single.runfile that extracts and installs to FHS paths. - Requirements:
- Produce a self-extracting archive per supported architecture as part of the client release workflow.
- Include an install script that copies files to
/usr/bin/,/usr/libexec/nixstasis/,/etc/nixstasis/,/usr/share/nixstasis/, and/lib/systemd/system/. - Include an
artifacts.jsonmanifest with version, arch, sha256 per file, file modes, and build timestamp. - Consume
frpcfrompackages/frpvia the shared acquisition path, not a separate download. - Extend client release CI so
.runfiles are produced and verified alongside existing archives,.deb, and.rpmpackages. - Extend
verify_artifacts.shto validate.runarchive contents and manifest integrity. - Constraints:
- Do not embed
frpcin the Go client binary. FRPS_SERVER_ADDRremains a runtime env var, not baked into the archive.- Systemd units must use
PrivateTmp=true. build/root-dirstays as the GoReleaser staging source.packages/frpremains the shared source of truth for FRP version and checksums.- Non-goals:
- Replacing
.debor.rpmpackaging for distros that support them. - Interactive TUI installer or configuration wizard.
- Automatic service enablement or start on install.
- Uninstall support.
- Success criteria:
- A
.runfile for each release architecture is published to GitHub Releases. - Running the
.runfile on a clean Linux system installs all required files to their FHS paths. - Existing
/etc/nixstasis/config.yamlis preserved on upgrade unless the installer is explicitly forced to replace it; client-ownedfrpc.tomlis updated on every upgrade from/usr/share/nixstasis/frpc.toml. artifacts.jsonin the archive matches the installed bundle contents by sha256.verify_artifacts.shcatches content or manifest drift in CI.- Risks and tradeoffs:
makeselfadds a release CI dependency.- Self-extracting archives are less auditable than plain tarballs, so the installer must support no-exec extraction for inspection.
- Install script upgrade behavior must avoid overwriting operator-owned config.
- Dependencies:
packages/frp/bin/download_frp.shpackages/client/scripts/fetch_frpc.sh.github/workflows/release_client.ymlpackages/client/scripts/release/verify_artifacts.shpackages/client/build/root-dir/prod.env- Suggested validation:
- CI step that builds the
.runarchive from snapshot artifacts and verifies extraction plus manifest integrity. - Fresh-install and upgrade tests for file placement, modes, and config preservation.
- Manual smoke test on Alpine or Arch to confirm FHS placement without
dpkg/rpm. - Suggested first workflow command:
/start-feature self-extracting-installer
server-provided-frps-token
- Status: implemented
- Overview:
- Move the remote-access trigger from a boolean heartbeat response flag to a server-provided FRPS auth token. When the server wants a client to open remote access, the heartbeat response includes the shared FRPS token. The client treats the presence of that token as the start signal, passes it to the transient unit through a systemd credential, and stops FRPC when the token is absent.
- Requirements:
- Replace
remote_access_requestedin the device heartbeat response contract withremote_access_token. - Include
remote_access_tokenonly when remote access is currently requested for the device. - Make the Phoenix server read the shared FRPS token from deployment configuration and expose it only to authenticated device heartbeat responses that need remote access.
- Make the Compose
nixstasisservice receive the sameFRPS_AUTH_TOKENas thefrpsservice. - Make the Go client start FRPC when
remote_access_tokenis non-empty and use that value asFRPS_AUTH_TOKENfor frpc template expansion. - Make the Go client stop FRPC when
remote_access_tokenis absent or empty. - Keep the device runtime API token separate from the FRPS auth token.
- Constraints:
- The current FRPS deployment uses upstream FRP token auth with one shared
FRPS_AUTH_TOKEN. - Do not add an FRPS authentication plugin or per-device FRPS tokens in this feature.
- Do not persist the FRPS token in client config or identity files.
- The client-owned
frpc.tomlcontinues to use{{ .Envs.FRPS_AUTH_TOKEN }}and frpc-native environment expansion. - The client continues launching FRPC through the
nixstasis-frpctransient systemd unit. - Non-goals:
- Replacing FRP token authentication.
- Implementing per-device FRPS auth or revocation.
- Changing browser terminal authorization.
- Changing how operators request or close remote access in the UI beyond the heartbeat response payload.
- Success criteria:
- A heartbeat for a device without requested remote access returns no FRPS token and the client stops or leaves FRPC stopped.
- A heartbeat for a device with requested remote access returns the configured FRPS token and the client starts FRPC with that token in the transient unit credential path.
- The device API token is never used as the FRPS token.
- Server and client tests cover both token-present and token-absent response paths.
- Runtime contract documentation identifies
FRPS_AUTH_TOKENas consumed by bothfrpsandnixstasis. - Risks and tradeoffs:
- Returning a shared FRPS token to a device exposes that token to the managed host during the active remote-access lease.
- A shared token keeps FRPS deployment simple but cannot revoke a single device independently at the FRPS layer.
- If
FRPS_AUTH_TOKENis missing from the server environment while remote access is requested, clients cannot open FRPC even though the UI requested access. - Dependencies:
packages/server/lib/nixstasis_web/controllers/heartbeat_json.expackages/server/test/nixstasis_web/controllers/heartbeat_controller_test.exspackages/client/internal/transport/client.gopackages/client/cmd/nixstasis/poll.gopackages/client/cmd/nixstasis/poll_test.gopackages/client/internal/frp/manager.godeploy/compose/docker-compose.ymldeploy/compose/scripts/check_runtime_contract.shdocs/src/client-server-interface.mddocs/src/modules/deployment-compose.md- Suggested validation:
- Server controller tests for
remote_access_tokenomitted when remote access is false and present when true withFRPS_AUTH_TOKENconfigured. - Client transport/poll tests for starting FRPC with heartbeat-provided token.
- Client poll tests for stopping FRPC when the token is absent.
- Runtime contract check proving the Phoenix service receives
FRPS_AUTH_TOKEN. - Suggested first workflow command:
/start-feature server-provided-frps-token
ash-api-contract-unification
- Status: planned
- Overview:
- Rework the custom Phoenix controller APIs that represent durable product contracts so they are exposed through Ash actions/resources where practical, allowing OpenAPI generation to become the source of truth for those APIs. Keep explicitly workflow-only endpoints as Phoenix controllers only when Ash would make the contract less clear.
- Requirements:
- Inventory every bespoke route under
/api/v1and/e2eand classify it as resource/action-oriented or workflow-only. - Move resource/action-oriented device, builder, and E2E APIs to Ash-backed actions/resources or Ash JSON:API routes where the behavior maps cleanly.
- Preserve current wire contracts for the Go client, Caddy
check_domain, and E2E harness unless a deliberate versioned contract change is documented. - Generate OpenAPI docs for the migrated Ash-backed APIs and remove duplicate hand-maintained OpenAPI sections when the generated docs cover them fully.
- Keep any remaining hand-written Phoenix controller contracts under
docs/src/reference/openapi/with an explicit reason why they are not Ash-generated. - Constraints:
- Do not break existing Go client registration, heartbeat, command result, or command payload behavior without a versioned migration plan.
- Do not force terminal, Caddy TLS approval, or E2E workflow endpoints into Ash if a controller boundary is clearer or safer.
- Maintain authentication and authorization semantics for device API keys, Caddy/AuthCrunch, and E2E enablement gates.
- Non-goals:
- Replacing Ash JSON:API with a separate OpenAPI generator.
- Converting browser LiveView routes to API routes.
- Changing the public deployment or FRP runtime contract.
- Success criteria:
- Generated OpenAPI covers every API route that is implemented as an Ash-backed product contract.
- Remaining bespoke OpenAPI files only document endpoints that intentionally stay outside Ash, with rationale in the reference docs.
- Client/server integration tests prove the Go client and E2E harness still work against the migrated API surface.
- The Reference section clearly distinguishes generated Ash OpenAPI from any retained bespoke contracts.
- Risks and tradeoffs:
- Some endpoints are protocol workflows rather than CRUD resources, and forcing them into Ash may obscure behavior or complicate error handling.
- Moving stable client APIs can create compatibility risk unless responses, status codes, and auth failures remain byte-for-byte compatible or versioned.
- Dependencies:
packages/server/lib/nixstasis_web/router.expackages/server/lib/nixstasis_web/controllers/device_controller.expackages/server/lib/nixstasis_web/controllers/heartbeat_controller.expackages/server/lib/nixstasis_web/controllers/device_command_controller.expackages/server/lib/nixstasis_web/controllers/e2e_run_controller.expackages/server/lib/nixstasis_web/controllers/e2e_run_result_controller.expackages/server/lib/nixstasis_web/controllers/builder_schema_controller.expackages/server/lib/nixstasis_web/controllers/builder_config_validation_controller.expackages/server/lib/nixstasis/domain.expackages/server/priv/static/openapi.yamldocs/src/reference/openapi/- Suggested validation:
- Diff generated OpenAPI before/after and confirm migrated paths appear in the generated document.
- Run Go client transport tests and server controller/domain tests for each migrated route.
- Run the E2E harness against the migrated
/e2econtract if E2E routes are moved or wrapped by Ash. - Run
mdbook build docsand ensure Reference links describe the final contract source of truth. - Suggested first workflow command:
/start-feature ash-api-contract-unification
authcrunch-role-contract
- Status: planned
- Overview:
- Define the production authorization contract between Caddy/AuthCrunch and the Phoenix application. Document which claims or headers Caddy forwards, how roles/groups map to operator capabilities, and whether LiveView screens use those claims for role-aware behavior.
- Requirements:
- Inventory AuthCrunch-related Caddy configuration, Phoenix request handling, LiveView session data, and any existing role/group assumptions.
- Define the canonical forwarded headers or session fields Phoenix may trust after Caddy/AuthCrunch authentication.
- Define operator roles and the capabilities each role grants for dashboard, devices, remote access, alerts, reports, settings, and E2E surfaces.
- Document behavior for missing, malformed, or insufficient claims.
- Decide whether role-aware UI behavior belongs in LiveView assigns, plugs, policies, or a separate authorization module.
- Update operations docs so deployment operators know which AuthCrunch groups or claims must be configured.
- Constraints:
- Do not weaken the requirement that public browser traffic reaches Phoenix through Caddy/AuthCrunch in the supported deployment.
- Do not treat device API tokens, E2E enablement, or terminal session tokens as substitutes for browser/operator authorization.
- Keep local development shortcuts clearly separate from production role enforcement.
- Non-goals:
- Replacing AuthCrunch as the browser authentication edge.
- Changing the device runtime API authentication contract.
- Implementing a full multi-tenant RBAC product unless the role inventory proves it is required.
- Success criteria:
- Operators can configure AuthCrunch groups/claims and know which Nixstasis capabilities each role enables.
- Phoenix behavior for missing or insufficient role claims is documented and tested.
- The docs clearly distinguish browser/operator authorization from device API, E2E, and terminal-session authentication.
- Risks and tradeoffs:
- Browser authorization rules can drift if they are encoded only in Caddy and not visible to Phoenix UI logic.
- Over-modeling roles too early can add complexity before production operator needs are proven.
- Dependencies:
deploy/compose/caddy/Caddyfilepackages/server/lib/nixstasis_web/router.expackages/server/lib/nixstasis_web/controllers/packages/server/lib/nixstasis_web/live/docs/src/modules/edge-caddy.mddocs/src/client-server-interface.md- Suggested validation:
- Add request/LiveView tests for allowed and denied role scenarios once the contract is implemented.
- Run
mdbook build docsand ensure Architecture, Operations, and Reference pages all point to the final authorization contract. - Suggested first workflow command:
/start-feature authcrunch-role-contract
production-operations-runbooks
- Status: planned
- Overview:
- Add production operations runbooks that go beyond the Compose deployment contract. Cover backup/restore, secret rotation, incident response, monitoring, upgrade checks, and explicit HA/non-HA expectations for the supported deployment shape.
- Requirements:
- Document PostgreSQL backup and restore workflows for bundled and external database modes.
- Document secret rotation procedures for Phoenix secrets, AuthCrunch/OIDC values, JWT key material, FRPS auth/dashboard credentials, and database credentials.
- Document operational health checks for Phoenix, Caddy, FRPS, PostgreSQL, device heartbeat freshness, E2E retention, and remote-access availability.
- Document incident-response playbooks for failed migrations, broken TLS approval, FRPS token exposure, device credential compromise, and E2E retention/log failures.
- Document upgrade and rollback validation steps for Compose services and client release artifacts.
- State HA/scaling boundaries clearly: what the supported Compose deployment does and does not guarantee.
- Constraints:
- Do not imply unsupported HA or clustered deployment semantics unless they are implemented and tested.
- Keep production runbooks separate from local development harness guidance.
- Preserve
deploy/composeas the supported server deployment path. - Non-goals:
- Building a hosted operations platform.
- Replacing operator-specific backup tooling.
- Implementing HA as part of the documentation feature.
- Success criteria:
- A production operator can restore service from backup using documented steps.
- A production operator can rotate each documented secret without guessing which services must restart.
- The docs identify observable symptoms, immediate mitigations, and validation checks for common incidents.
- HA and scaling expectations are explicit rather than implied.
- Risks and tradeoffs:
- Runbooks can become stale if they duplicate scripts without linking to source validation.
- Over-prescriptive backup tooling can conflict with an operator’s managed database platform.
- Dependencies:
deploy/compose/docker-compose.ymldeploy/compose/scripts/check_runtime_contract.shdeploy/compose/README.mddocs/src/modules/deployment-compose.mddocs/src/runtime-boundaries.md- Suggested validation:
- Exercise backup/restore against a disposable Compose stack.
- Run runtime contract checks before and after documented secret rotation steps.
- Run
mdbook build docsand verify Operations navigation points to the new runbooks. - Suggested first workflow command:
/start-feature production-operations-runbooks
rich-api-examples
- Status: planned
- Overview:
- Add example-rich API documentation for maintained HTTP contracts. Provide
representative requests and responses for successful calls, validation
errors, authorization failures, conflict/locking behavior, and important
edge cases across
/api/v1,/e2e, builder APIs, and retained bespoke OpenAPI files. - Requirements:
- Add examples for device registration, pending approval, approved credential
issuance, heartbeat, command delivery, command results, deferred payload
fetches, and Caddy
check_domaindecisions. - Add examples for E2E run creation, idempotent reuse, environment lock conflicts, protocol mismatch, seed failures, result submission, cancellation, and missing/pruned logs.
- Add examples for builder schema option lookup, validation success, validation failure, stale selections, missing schemas, and authorization failures where applicable.
- Add examples for report and alert-rule API surfaces that remain hand-maintained outside generated Ash OpenAPI.
- Keep OpenAPI examples and prose examples synchronized, or link one canonical source from the other.
- Constraints:
- Do not invent behavior that is not implemented or tested.
- Distinguish Ash-generated
/api/jsonexamples from bespoke/api/v1and/e2econtroller examples. - Keep secrets, real tokens, hostnames, and operator data out of examples.
- Non-goals:
- Replacing generated Ash OpenAPI.
- Changing API behavior.
- Creating exhaustive API tutorials for every LiveView browser route.
- Success criteria:
- API consumers can copy representative request/response shapes for each durable runtime contract.
- Error examples cover the common failure classes operators and client authors must handle.
mdbook build docssucceeds and OpenAPI references remain linked from the Reference section.- Risks and tradeoffs:
- Examples can drift unless they are derived from tests or reviewed when controllers change.
- Too many examples can obscure the canonical contract if not organized by API surface.
- Dependencies:
docs/src/client-server-interface.mddocs/src/reference/openapi/packages/server/priv/static/openapi.yamlpackages/server/lib/nixstasis_web/controllers/packages/client/internal/transport/client.go- Suggested validation:
- Compare examples against controller tests and client transport tests.
- Run
mdbook build docsand, where practical, OpenAPI validation for example payloads. - Suggested first workflow command:
/start-feature rich-api-examples