135 lines
8.1 KiB
Markdown
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.
|