Architecture Overview
High-Level Architecture
flowchart TB
Browser[Browser/User] -->|HTTPS/WSS| Caddy[Caddy + AuthCrunch]
Caddy -->|reverse_proxy| Phoenix[Phoenix Server]
Caddy -->|wildcard proxy| FRPS[FRPS]
Client[nixstasis Go client] -->|HTTP JSON| Phoenix
Client -->|starts/stops| FRPC[frpc]
FRPC -->|FRP tunnel| FRPS
Phoenix -->|queries| DB[(PostgreSQL)]
Components
- Server application:
- Elixir OTP application
:nixstasis. - Phoenix HTTP endpoint and LiveView UI.
- Ash domain and Ash resources for devices, commands, alerts, telemetry, reports, and settings.
- Ecto/PostgreSQL persistence through
Nixstasis.Repo.
- Elixir OTP application
- Client application:
- Go CLI binary
nixstasisusing Cobra. - Device registration, telemetry polling, script execution, command handling, and FRP lifecycle management.
- Embedded Starlark runtime for telemetry scripts.
- Go CLI binary
- Edge layer:
- Caddy handles public HTTPS ingress, on-demand TLS, AuthCrunch authentication/authorization, and reverse proxying.
- FRPS accepts FRPC tunnels and exposes device services through Caddy-routed wildcard hosts and TCP muxing.
- Deployment layer:
deploy/compose/docker-compose.ymldefinesnixstasis,caddy,frps, and optionalpostgresservices.
Repository Structure
Nixstasis is organized around deployable runtime boundaries rather than one monolithic application tree.
packages/server: Phoenix, LiveView, Ash, Ecto/PostgreSQL, OTP workers, device APIs, E2E APIs, and the browser UI.packages/client: Go CLI/client runtime for registration, polling, local identity, Stary/Starlark scripts, command execution, FRPC lifecycle, and E2E journeys.deploy/compose: supported server deployment path and runtime contract for Phoenix, Caddy, FRPS, and PostgreSQL.packages/caddy: Caddy build with the AuthCrunch plugin used by the public edge.packages/frp: FRP image/package build assets and pinned FRP acquisition.packages/shared/e2e_log_viewer: shared static E2E report/log viewer assets.docs/src/features: docs-driven feature designs and task history.
See Project Structure for path-by-path details.
Component Relationships
- Caddy routes
nixstasis.<base-domain>to Phoenix atnixstasis:4000. - Caddy routes
frp-admin.<base-domain>to the FRPS dashboard port. - Caddy routes
*.{$BASE_DOMAIN}to FRPS HTTP vhost port. - Caddy on-demand TLS calls
http://nixstasis:4000/api/v1/check_domain. - The Go client calls Phoenix JSON endpoints under
/api/v1/devices/.... - The Go client starts
frpcwhen the server heartbeat response includes a non-emptyremote_access_token. - Phoenix queues device commands as pending commands and returns them in heartbeat responses.
- The Go client executes supported command types and posts command results back to Phoenix.
- Browser terminal sessions connect through Phoenix Channels on
terminal:*and server-sideNixstasis.Devices.SshClientopens an SSH process through FRP TCP muxing.
Product Data Model
The server treats managed devices as long-lived identities with dynamic telemetry payloads.
- Registration is keyed by device identity such as MAC address and product context; re-registration updates the existing device rather than creating a duplicate identity.
- Unknown or unapproved devices enter the pending-approval workflow and do not receive runtime API credentials until approved.
- Approved devices receive a persistent runtime API token used by heartbeat, command-result, and deferred command-payload endpoints.
- Device telemetry is stored as dynamic JSON payloads so Stary/Starlark scripts and product schemas can evolve without one table per product type.
- Alert rules and report builders use schema-aware fields where practical, while runtime telemetry remains flexible enough for product-specific payloads.
Device lifecycle details live in Data Flow, API payloads live in Client-Server Interface, and package internals live in Server Devices and Client Identity.
API And Authentication Surfaces
Nixstasis intentionally has multiple API surfaces with different consumers.
- Browser routes and LiveView sockets are reached through Caddy/AuthCrunch in the supported deployment. Caddy is the public authentication edge.
- Device runtime APIs live under
/api/v1/devices/...and are used by the Go client. Registration issues credentials; heartbeat, command results, and payload fetches use the approved device API token. - Caddy on-demand TLS approval calls
GET /api/v1/check_domainfrom inside the Compose network. - Ash JSON:API routes live under
/api/jsonand have generated OpenAPI inpackages/server/priv/static/openapi.yaml. - E2E harness APIs live under
/e2eand are gated byNixstasisWeb.Plugs.E2EEnabled.
The current docs distinguish these contracts in API & Runtime Contracts. Future consolidation of practical bespoke APIs into Ash-backed generated OpenAPI is tracked as planned work, not as current architecture.
AuthCrunch claim and role mapping is deployment-sensitive and remains an area to document more explicitly as the authorization model hardens.
Traceable references:
deploy/compose/caddy/Caddyfile:42-75deploy/compose/docker-compose.yml:1-90packages/client/internal/transport/client.go:84-212packages/client/cmd/nixstasis/poll.go:128-154packages/server/lib/nixstasis_web/router.ex:50-79packages/server/lib/nixstasis_web/live/device_live/show.ex:57-80packages/server/lib/nixstasis_web/channels/terminal_channel.ex:20-50packages/server/lib/nixstasis/devices/ssh_client.ex:26-63
System Boundaries
- HTTP boundary:
NixstasisWeb.EndpointandNixstasisWeb.Routerexpose browser, API, JSON:API, channel, and E2E surfaces.
- Domain boundary:
Nixstasis.Domaindefines Ash resources and domain APIs.- Context modules (
Nixstasis.Devices,Nixstasis.Monitoring,Nixstasis.Reporting,Nixstasis.E2E) call Ash domain APIs and Ecto where needed.
- Client boundary:
internal/transport.Clientis the Go client HTTP boundary to Phoenix.internal/script.Runtimeis the Starlark execution boundary.internal/frp.Manageris the process boundary forfrpc.
- Edge boundary:
- Caddy is the public ingress point in the supported Compose deployment.
- FRPS is externally exposed for FRPC tunnel transport ports and internally exposed to Caddy for dashboard and HTTP vhost traffic.
Key Design Patterns
OTP
- The server starts supervised children from
Nixstasis.Application. - Supervised processes include
NixstasisWeb.Telemetry,Nixstasis.Repo, optionalNixstasis.E2E.RetentionWorker,DNSCluster,Phoenix.PubSub,Nixstasis.Monitoring.OfflineChecker, andNixstasisWeb.Endpoint. - GenServers present in application code:
Nixstasis.Monitoring.OfflineCheckerNixstasis.E2E.RetentionWorkerNixstasis.Devices.SshClient
Traceable references:
packages/server/lib/nixstasis/application.ex:9-29packages/server/lib/nixstasis/monitoring/offline_checker.ex:1-32packages/server/lib/nixstasis/e2e/retention_worker.ex:1-51packages/server/lib/nixstasis/devices/ssh_client.ex:1-125
LiveView Interaction Model
- Browser routes use the
:browserpipeline with session fetch, LiveView flash, CSRF protection, and secure browser headers. - LiveViews implement
mount/3,handle_params/3, andhandle_event/3for UI state and user interactions. - Observable event examples:
- Device list search/filter/sort/bulk approval in
DeviceLive.Index. - Device detail tab change, retry session, and SSH session start in
DeviceLive.Show. - Alert rule validation/save/delete in alert LiveViews.
- Report sorting/filtering/deletion in report LiveViews.
- Device list search/filter/sort/bulk approval in
Traceable references:
packages/server/lib/nixstasis_web/router.ex:4-11packages/server/lib/nixstasis_web/live/device_live/index.expackages/server/lib/nixstasis_web/live/device_live/show.expackages/server/lib/nixstasis_web/live/alerts/index_live.expackages/server/lib/nixstasis_web/live/reports/index_live.ex
Ash Domain Layering
Nixstasis.DomainusesAsh.DomainwithAshJsonApi.DomainandAshPhoenixextensions.- JSON:API routes are declared for devices, pending commands, alerts, alert rules, telemetry events, custom reports, and system settings.
- Domain-level functions are defined for common resource actions such as
list_devices,get_device,register_device,create_pending_command,list_alerts, andlist_custom_reports. - Context modules call
Nixstasis.Domainfunctions and Ash queries to implement application behavior.
Traceable references:
packages/server/lib/nixstasis/domain.ex:1-122packages/server/lib/nixstasis/devices.expackages/server/lib/nixstasis/monitoring.expackages/server/lib/nixstasis/reporting.ex