Files
2026-10-28 09:16:54 +01:00

135 lines
8.1 KiB
Markdown

# Architecture — release 0.0.1
## One process with explicit module boundaries
Axum serves the HTTP API and embedded interface assets. Tokio manages requests,
scheduling, and notification tasks. Local SQLite access uses `rusqlite`; a mutex
protects a single connection configured with foreign keys, WAL, and a busy timeout.
The SSH library is synchronous, so blocking sessions run through `spawn_blocking`.
The application does not launch Python, Node, `ssh`, `scp`, `ping`, or system cron
subprocesses.
Interface sources remain separated by responsibility. `web/assets.list` specifies
concatenation order. `build.rs` produces one `app.js`, one `app.css`, and the HTML in
Cargo's `OUT_DIR`; the compiler embeds them in the executable. Deployment does not
read `web/` from disk or fetch UI assets from a CDN. A source change requires a new
build. The distribution contains source only, and `start.sh` builds before launching.
CSS and JavaScript URLs include content-derived identifiers so browsers fetch changed
assets after an update without requiring manual cache clearing.
The same process serves `/docs` and `/openapi.json`. The API documentation module
embeds checked-in files directly with `include_str!`, combines the local Swagger UI
and integration assets into one CSS and one JavaScript response, and supplies
content-derived cache identifiers. This path does not depend on generated
`OUT_DIR/docs.*` files. Documentation assets load only on the documentation page.
The specification covers 36 operations and uses the configured API prefix. Assets
and validators are not fetched from external services. See
[API documentation integration](api-documentation.md).
## Operations and data consistency
Requests validate an HS256 JWT, the current user's existence, and device/backup
ownership. Password verification and hashing have a separate four-operation pool;
saturation returns 429 without blocking the rest of the API. Login throttling also
limits incorrect password attempts. Global settings and logs retain the source
application's scope: authenticated operators.
Reading a complete request body has a 1 MiB limit and a 15-second deadline. An
incomplete request cannot indefinitely prevent shutdown. This deadline does not
limit the execution time of a backup operation.
A backup acquires a device lock and a slot in the operation pool. A tracked task owns
the lock, so a disconnected HTTP client cannot release it halfway through a transfer.
Data is written to a private temporary file under `DATA_DIR`. After the transfer,
the application validates size, synchronizes the file, calculates SHA-256, and
publishes it. The SQLite record is committed last; a database failure removes the
published file. Deletion stages the file under a temporary name and compensates if
the database update fails.
Each backup access checks `DATA_DIR` boundaries, names, and symlinks. Bulk ZIP creation
has an aggregate size limit and uses a temporary file; the response streams that
file instead of holding the complete archive in memory. The data directory must
remain private: protection against a local user concurrently changing files depends
on preventing shared write access to it.
## Protocols and error handling
RouterOS operations use SSH, pinned host keys, password/private-key authentication,
`/export`, `/system backup save`, and SFTP with an SCP fallback. Device-side temporary
files are removed after transfer. Upload stops after writing the file; it never
issues a restore command. The default `accept-new` policy records the first observed
host key and requires the same key later. Explicit `strict` requires an already
trusted entry. Host identity keys and client authentication keys serve different
purposes. A timeout covers the entire session; a separate timer closes the socket
if the peer stalls an operation.
SwitchOS uses HTTP(S), Digest or Basic authentication, response validation, and a
size limit. Actual HTML is rejected as a backup regardless of its MIME type, while
otherwise nonempty firmware-specific `.swb` content is not rejected by MIME/signature
heuristics. A public HTTP 200 page does not prove backup authentication. When needed,
the adapter obtains a Digest challenge from `/sys.b` and signs the actual
`/backup.swb` request URI. Connection tests report web reachability separately from
backup availability. Bounded same-host redirects are followed; redirects to a
different host or an HTTPS-to-HTTP downgrade are stopped.
SwitchOS credentials have an explicit global or device-local source. Imported rows
without a selection preserve their original field-by-field fallback. Selecting a
source does not erase the other stored credentials. The global mode reads current
settings on each connection. See [protocol details and sources](switchos.md).
ICMP uses a native socket; missing socket permissions produce an explicit diagnostic
error. Device connections are initiated only to hosts configured by operators.
Known secrets are redacted from public responses and logs. Notification failures do
not invalidate a successfully stored backup. Batch operations return a result for
each device, including failures and skips.
## Scheduling and shutdown
The scheduler reads settings once per second, evaluates five-field cron expressions
in the configured timezone, and prevents overlapping instances of the same job.
Numeric weekdays preserve APScheduler semantics: Monday is 0. Export and binary
jobs respect device policy flags. After a restart, the next execution is calculated;
the application does not replay a complete queue of jobs missed while it was off.
A delayed tick tolerates a scheduling delay of up to five minutes.
`SIGTERM` and `Ctrl+C` stop accepting HTTP requests, prevent new pooled operations,
stop scheduling, and wait for operations already in progress. A WAL checkpoint runs
before exit. The process holds `application.lock`; the explicit importer uses the
same storage lock. Deployments should allow in-progress operations to finish.
Compose and systemd use a 300-second stop timeout by default; increase this if
operation timeouts or the number of devices require a longer shutdown window.
## Fresh initialization and explicit import
`src/schema.sql` contains the complete current schema. If the database file does not
exist, startup creates a fresh database. If a database file exists, startup first
opens it read-only and validates that it already has the supported schema. An older
or unrelated schema is rejected with instructions to use a fresh installation and
explicit import. Startup does not run `ALTER TABLE`, create upgrade snapshots, add
migration journals, or move backup files.
The separate `import-python --source DIR [--database PATH] [--backup-root DIR]`
command imports into a new destination. The `import-python.sh` wrapper builds the
application and invokes that command. Import opens the source read-only, preserves
IDs, ownership, password hashes, settings, stored credentials, and operation logs,
and copies validated backup content to the destination. Existing destination
databases or files are not overwritten. See the full
[Python import procedure](data-import.md).
`DATA_DIR` contains the database by default, backups, the JWT secret, trusted host
keys, and locks. A separately configured `DATABASE_PATH` must also be included in
operational backups. These files are deployment data, not release source. Persistent
changes should go through the application; a consistent copy of an active database
requires the SQLite Online Backup API or stopping the service first.
Binary files use `backups/binary/<router_id>/`, and text exports use
`backups/exports/<router_id>/`. Stable device IDs keep paths unchanged when devices
are renamed. Fresh backups and explicitly imported files use this layout; startup
does not convert an older layout automatically.
`Cargo.lock`, the pinned toolchain, the schema/import implementation, and documented
libssh2 backports are part of the release. [The port matrix](port-matrix.md) identifies
both source archives by SHA-256. Dependency checks must be repeated for future
releases; an audit result is evidence for a specific dependency set and date, not a
claim that every possible defect has been excluded.