21 KiB
Port coverage matrix — 0.0.1
Scope and source identity
The supplied source code, rather than the interface concept alone, defines functional
coverage. The archive contains no .git metadata, so the supplied archive SHA-256
values identify the reviewed inputs:
| Archive | SHA-256 |
|---|---|
mikrotik_backup_system-master.zip |
3be24e72b1816070a78ec7ee7336ae0a7f884db070dc019a8afdd9c229c05f47 |
mikrotik_backup_system_rust_migration.zip |
65e4ee36cd60e81e4ff25a6d12d5fd7913b9360b61c96b7301b3de550c00a104 |
The source has 36 active API method/path combinations, all represented in the
Axum router. Paths below use the default /api prefix; API_PREFIX remains
configurable.
The test column identifies source tests and their intended coverage. A test's presence is not proof of a successful run. The revision-specific validation report records actual test, interface, build, and process checks. Controlled fixture servers exercise protocols without contacting production devices. No physical MikroTik or live Pushover account was supplied.
Contract test key
These test names are in tests/api_contract.rs.
| Key | Full test name |
|---|---|
| A | auth_json_form_preferences_password_and_expiry |
| R | registration_and_login_rate_limit |
| D | devices_defaults_validation_secrets_and_disabled_operations |
| O | ownership_applies_to_every_device_and_backup_operation |
| F | files_filters_preview_diff_download_zip_and_bulk_deletion |
| S | settings_secrets_validation_reveal_scheduler_and_notification_errors |
| L | logs_retention_and_embedded_web_do_not_expose_storage |
API operations
| Method and path | Rust file / handler | Test and coverage |
|---|---|---|
POST /api/auth/register |
src/api/auth.rs / register |
R: registration disabled/enabled, conflicts, validation |
POST /api/auth/login |
src/api/auth.rs / login |
A: JSON and form; R: failed-login throttling |
GET /api/auth/me |
src/api/auth.rs / me |
A: account, missing/invalid token, no password hash in JSON |
PUT /api/auth/preferences |
src/api/auth.rs / preferences |
A: four languages, font, unsupported values |
POST /api/auth/change-password |
src/api/auth.rs / change_password |
A: current password, new login, rejection of old password |
GET /api/dashboard |
src/api/system.rs / dashboard |
O: owner-scoped counts; F: counts updated after deletion |
GET /api/routers |
src/api/devices.rs / list |
O: user list isolation |
GET /api/routers/ping-statuses |
src/api/devices.rs / ping |
D: disabled flag; ICMP tests in routeros.rs |
POST /api/routers |
src/api/devices.rs / create |
D: RouterOS/SwOS, default ports, names, secrets |
GET /api/routers/{id} |
src/api/devices.rs / get_one |
D: metadata and flags; O: another user's record |
PUT /api/routers/{id} |
src/api/devices.rs / update |
D: validation, flags, secret preservation/clearing; O |
DELETE /api/routers/{id} |
src/api/devices.rs / remove |
D: deletion and 404; O: ownership protection |
GET /api/routers/{id}/test-connection |
src/api/devices.rs / test_connection |
O: authorization; local protocol fixtures in routeros.rs |
GET /api/backups |
src/api/backups.rs / list |
F: type, device, day, search, sorting; O |
GET /api/backups/router/{id} |
src/api/backups.rs / for_router |
O: device authorization |
POST /api/backups/routers/export-all |
src/api/backups.rs / export_all |
D: disabled devices skipped; backup::run_all returns per-device results |
POST /api/backups/routers/binary-all |
src/api/backups.rs / binary_all |
D: disabled devices skipped; backup::run_all returns per-device results |
POST /api/backups/router/{id}/export |
src/api/backups.rs / export |
D: policies and unsupported SwOS export; O: ownership; SSH adapter results in validation report |
POST /api/backups/router/{id}/binary |
src/api/backups.rs / binary |
D: policies; O: ownership; SwOS fixture and SSH adapter checks |
POST /api/backups/router/{id}/upload/{backup_id} |
src/api/backups.rs / upload |
O: both resources owned; SSH/SFTP adapter check; upload does not restore |
DELETE /api/backups/{id} |
src/api/backups.rs / remove |
F: file and metadata deletion; O: ownership |
GET /api/backups/{id}/download |
src/api/backups.rs / download |
F: exact binary bytes and Content-Type; O |
GET /api/backups/{id}/view |
src/api/backups.rs / view |
F: export text and rejection of binary content; O |
POST /api/backups/{id}/email |
src/api/backups.rs / email |
O: ownership; smtp_delivery_with_attachment_against_local_fixture for SMTP |
GET /api/backups/{left}/diff/{right} |
src/api/backups.rs / diff_json |
F: statistics/text and rejection of binary content; O: both resources |
GET /api/backups/{left}/diff/{right}/html |
src/api/backups.rs / diff_html |
F: HTML; O; escapes_configuration_and_file_names |
POST /api/backups/bulk |
src/api/backups.rs / bulk |
F: ZIP, no partial mutation for an invalid ID, deduplication; O |
GET /api/settings |
src/api/system.rs / settings |
settings_secrets_round_trip_without_public_exposure; S: saved response |
GET /api/settings/scheduler-status |
src/api/system.rs / scheduler_status |
S: timezone and five jobs; cron/DST tests in scheduler.rs |
PUT /api/settings |
src/api/system.rs / update_settings |
S: cron, preserve/clear secrets, invalid input rejected before save |
POST /api/settings/reveal-ssh-key |
src/api/system.rs / reveal_ssh_key |
S: incorrect and correct account password |
POST /api/settings/test-email |
src/api/system.rs / test_email |
S: configuration errors; local SMTP provider fixture |
POST /api/settings/test-pushover |
src/api/system.rs / test_pushover |
S and missing_pushover_settings_fail_before_network; no live account delivery |
GET /api/logs |
src/api/system.rs / logs |
L: limit; original id/message/timestamp model |
DELETE /api/logs/older-than/{days} |
src/api/system.rs / delete_logs |
L: validation and actual removal of old records |
GET /api/health |
src/api/system.rs / health |
L: public health check, version, configurable prefix |
Backup list parameters are search, backup_type=export|binary, router_id,
created_on=YYYY-MM-DD, sort_by=created_at|file_name|backup_type|router_name, and
order=asc|desc. Logs retain limit in the range 1–500. Bulk operations accept
action=delete|download and backup_ids. Login accepts username/password;
password change uses current_password/new_password; SSH key reveal uses
password; preferences use preferred_language/preferred_font. A login response
contains access_token, token_type=bearer, and user.
Models and source fields
Models are explicitly typed in src/models.rs. src/db.rs implements persistence,
current-schema validation, and explicit import. src/schema.sql is the complete
fresh-install schema. Revision 4 does not run incremental schema migrations at startup.
| Model / group | Preserved or added fields | Notes |
|---|---|---|
| User | id, username, password_hash, created_at, preferred_language, preferred_font |
Password hashes are never serialized. The user DTO has the same four public fields as the source. |
| Router identity | id, owner_id, name, device_type, host, port, ssh_user, created_at |
Every device/backup access checks ownership. |
| Router credentials | ssh_key, ssh_password |
Public responses contain null and presence flags; values remain stored in the database. |
| Router SwitchOS source | use_global_switchos_credentials |
Nullable addition: true selects the global pair, false selects the local pair, null preserves legacy fallback. Changing mode preserves local secrets. |
| Router connection result | last_connection_status, last_connection_tested_at, last_connection_error, last_connection_hostname, last_connection_model, last_connection_version, last_connection_uptime, last_connection_transport, last_connection_server, last_connection_auth_mode, last_connection_http_status, last_connection_backup_available |
Unknown values remain null; errors are not presented as success. |
| Router policies | disable_all_backups, disable_export_backups, disable_binary_backups, disable_ping |
Enforced server-side, including scheduled and batch operations. |
| Router DTO | effective_username, uses_global_ssh_key, has_effective_ssh_key, uses_global_switchos_credentials, has_effective_password, has_own_password, supports_export, supports_restore_upload |
Derived from the selected credential source and device type without disclosing secrets. |
| RouterTestConnection | success, tested_at, model, uptime, hostname, version, error, transport, server, auth_mode, http_status, backup_available |
The same result is persisted in the device's last_connection fields. |
| RouterPingStatus | router_id, reachable, latency_ms, disabled; optional error |
Batch response contains items. Diagnostic errors distinguish missing ICMP permissions from an ordinary timeout. |
| Backup | id, router_id, file_path, file_name, backup_type, checksum, created_at |
Additional DTO fields: router_name, device_type, file_size. Import copies existing bytes without converting their format. |
| OperationLog | id, message, timestamp |
No invented level/component/router_id fields. |
| Dashboard | routers_count, export_count, binary_count, total_backups, storage, recent_logs |
Device/backup counts are owner-scoped; logs remain global as in the source. |
| StorageStats | total, used, free, folder_used, usage_percent |
usage_percent measures the data directory against total disk capacity, matching the source. |
| Settings retention | backup_retention_days, log_retention_days |
0 means an age threshold of zero days, matching the original retention function; it does not disable cleanup. |
| Settings scheduling | export_cron, binary_cron, retention_cron, enable_auto_export, connection_test_interval_minutes |
Five-field cron, configured timezone, Monday=0 as in APScheduler. |
| Settings device defaults | global_ssh_key, default_switchos_username, default_switchos_password |
Revealing the global key requires password verification. |
| Settings notifications | pushover_token, pushover_userkey, notify_failures_only, smtp_host, smtp_port, smtp_login, smtp_password, smtp_notifications_enabled, recipient_email |
SMTP, attachments, Pushover, provider tests, and failures-only mode. |
| Settings DTO | id, has_global_ssh_key, has_default_switchos_credentials |
Additional has_smtp_password, has_pushover_token, has_pushover_userkey, and has_default_switchos_password flags support editing masked secrets. |
| Diff JSON | left_backup_id, right_backup_id, left_file_name, right_file_name, diff_text, stats, lines |
Statistics: added/removed/modified/context. Lines: type, left_number/right_number, left_text/right_text. Standalone diff_html is generated only by the dedicated HTML endpoint to avoid duplicating large payloads in the browser response. |
| SchedulerStatus | timezone, running, jobs |
Job fields: key, label, enabled, cron, description, description_params, valid, next_runs, error. |
Client views and behavior
| Source view / behavior | Implementation |
|---|---|
/login, /register, session restoration, auth/guest guards, Bearer token |
web/js/core.js, web/js/views.js; actual auth API |
/ dashboard |
API counts, storage, recent backups/logs, and batch actions |
/devices, alias /routers |
List, search, device state, create, and edit |
/devices/:id, alias /routers/:id |
Connection test, metadata, and backups; no RSC exports or diff for SwitchOS; comparison only for RouterOS text exports |
/files |
Filters, sorting, selection, ZIP, deletion, preview, email, and diff |
/diff-configs |
Two exports, counts, line numbers, side-by-side/unified views, and search |
/logs |
Original messages and timestamps, cleanup by age in days |
/settings |
Retention, cron, global credentials, SMTP/Pushover, tests, and next runs |
/settings?tab=automation, alias /schedule |
Flat schedule controls; no separate Schedule navigation item |
/settings?tab=account, alias /change-password |
Password change in account settings; old route redirects to that tab |
/docs, /openapi.json, alias /redoc |
Local Swagger UI, full contract, authentication, and real API requests |
| Language and font | pl, en, es, no; default, adwaita_mono, hack; persisted user preferences |
| Theme, sidebar, responsive layout, toasts, confirmations | web/css/app.css, web/js/core.js, web/js/views.js; no sample devices inserted into the database |
| CSS/JavaScript delivery | Modular web/ sources bundled into one app.css and one app.js, embedded in the executable |
Default font families preserve the source stacks: Inter/system UI for content and Roboto Mono/IBM Plex Mono/SFMono-Regular/Consolas for headings. Adwaita Mono and Hack preferences select their respective stacks. Specific fonts depend on what is installed on the client device.
Services, configuration, and data
| Area | Implementation | Tests / evidence |
|---|---|---|
| Argon2id and Python password compatibility | security.rs |
new_passwords_use_argon2id, verifies_python_passlib_fixture_without_reset, verifies_legacy_werkzeug_fixture, malformed_hashes_fail_closed |
| JWT HS256, username subject, expiration, and nbf | security.rs |
jwt_has_original_username_subject, expired_and_wrong_algorithm_tokens_are_rejected, signed_malformed_or_missing_expiration_is_rejected, not_before_is_optional_but_must_be_valid_and_reached, verifies_independent_python_hmac_jwt_fixture |
| Environment, SQLite URL, timezone, initial secret | config.rs |
generated_secret_survives_restart, accepts_legacy_sqlite_url_and_custom_prefix, rejects_unsafe_configuration |
| Fresh schema and existing-schema validation | db.rs, src/schema.sql |
Fresh initialization and incompatible-schema rejection regressions in db.rs; generated_bootstrap_password_is_saved_and_valid |
| Explicit database/file import | db.rs |
import_copies_files_and_preserves_source, failed_import_does_not_publish_partial_database, orphaned_legacy_backups_are_not_silently_skipped, import_rejects_corrupted_backup_before_publication, import_rejects_backup_symlink_outside_selected_root |
| Import exclusivity and atomic publication | db.rs |
import_obeys_storage_lock_and_keeps_source_untouched, atomic_copy_publication_refuses_existing_destination |
| Import wrapper | import-python.sh, tests/import_launcher.rs |
help_and_invalid_sources_do_not_build_or_import, resolves_source_and_preserves_arguments_and_destination_settings, forwards_import_failure_without_falling_back_to_serve |
| Secret redaction and preservation | models.rs, db.rs |
settings_keep_secrets_and_clear_explicitly, invalid_patch_is_atomic, generic_serialization_does_not_leak_secrets, settings_secrets_round_trip_without_public_exposure |
| RouterOS SSH export/binary, SFTP/SCP, host keys, timeout | routeros.rs, backup.rs |
Controlled SSH/SFTP server checks in the validation report; no physical MikroTik supplied |
| Native SSH library fixes | vendor/libssh2-sys |
Patch reproduction and tests/run-security-regressions.sh; sources, hashes, and limits in dependency-security.md |
| SwOS through the existing device model | routeros.rs |
switchos_digest_probe_and_binary_transfer_against_local_fixture, switchos_basic_fallback_and_html_rejection |
| ICMP ping | routeros.rs |
native_icmp_loopback_and_disabled_flag, icmp_checksum_verifies_packet; non-root Linux requires configured net.ipv4.ping_group_range |
| Atomic files, checksums, directory boundaries, device locks | backup.rs |
checksum_matches_sha256, file_names_cannot_escape, deletion_allows_missing_file_but_rejects_escape, symlink_cannot_escape_data_directory, device_lock_releases_on_drop |
| Python/Rust timestamp retention | backup.rs |
supports_python_and_rust_timestamps, retention_uses_age_and_zero_is_not_disabled |
| Diff and escaped HTML | diff.rs |
reports_all_line_types, escapes_configuration_and_file_names, final_newline_and_crlf_are_normalized, tracks_blank_lines_in_replacements, unified_output_matches_python_difflib_golden_fixtures against 32 Python difflib fixtures |
| Cron and DST | scheduler.rs |
validates_cron_and_ranges, monday_zero_matches_apscheduler, timezone_and_dst_preview, named_weekdays_and_last_day, rare_calendar_schedule_is_not_lost |
| SMTP/Pushover/error redaction | notifications.rs |
smtp_delivery_with_attachment_against_local_fixture, missing_pushover_settings_fail_before_network, error_redaction_covers_global_secret_fields |
All source environment variables are mapped: APP_NAME, APP_ENV, SECRET_KEY,
JWT_ALGORITHM (HS256 only), ACCESS_TOKEN_EXPIRE_MINUTES, DATABASE_URL, DATA_DIR,
ALLOW_REGISTRATION, API_PREFIX, TIMEZONE, DEFAULT_ADMIN_USERNAME,
DEFAULT_ADMIN_PASSWORD, SMTP_STARTTLS, SMTP_TIMEOUT_SECONDS, and CORS_ORIGINS.
APP_PORT controls the default port. LISTEN_ADDR, DATABASE_PATH,
SSH_TIMEOUT_SECONDS, SSH_HOST_KEY_POLICY, and MAX_PARALLEL_BACKUPS are explicit
deployment extensions.
Intentional security and reliability changes
- No shared
admin/adminpassword. A fresh installation generates an initial password in a private file unless one is configured. Registration is disabled by default and can be explicitly enabled withALLOW_REGISTRATION=true. - Public responses redact secrets. Blank/null secret input preserves the value;
explicit
clear_*removes it. Upload and diff check ownership of both resources. - SSH host keys are verified. Default
accept-newpermits the first connection, records the key, and rejects changes. Explicitstrictrequires a trusted known_hosts entry. A host key is distinct from a user's authentication private key. - Directory and symlink checks protect backups. ZIP responses stream a temporary file rather than buffering the complete archive in memory. Bulk downloads are limited to 2 GiB, and email attachments to 25 MiB; exceeding a limit is an error.
- New backup filenames include a finer-grained timestamp and random suffix so concurrent operations cannot overwrite each other. Imported names and contents remain supported.
- External service failures return controlled HTTP 502 responses with secret values redacted. A notification failure does not remove or invalidate a successful backup.
- Naive SQLite timestamps are presented as UTC ISO 8601, preserving the source's actual time without relying on the browser timezone to interpret it.
- Structured diff corrects old blank-line numbering in replacement blocks: an empty existing line is not treated as a missing line. Unified output is checked against 32 file-based fixtures generated by Python difflib.
- Empty cron expressions disable their schedules. Invalid expressions, invalid ports, negative retention, and excessive request sizes are rejected. Jobs are prevented from overlapping themselves.
- Revision 4 initializes only a fresh schema and validates an existing database before use. There are no startup migrations or backup-layout conversions. Python import is explicit, reads the source without modifying it, and requires a new destination. See data-import.md.
- Runtime is one Rust process with SQLite, scheduling, and an embedded interface. Python, Node, Angular, and nginx are not required application processes.
Concept image and inactive source code
| Item | Decision |
|---|---|
| Git integration / commits / synchronization | No source model, endpoint, or service exists; the concept screen is not represented as a working feature. |
| Arbitrary job CRUD | The source has system jobs controlled through settings; that behavior is preserved. |
/swos-beta/probe, /swos-beta/download |
The router was not included in backend/app/api/__init__.py; no new public beta API is activated. |
Existing switchos type |
Preserved: ordinary connection tests and binary backups invoke SwOS code through the original RouterService despite the inactive separate beta router. |
| Concept's restore action | The source only transfers a binary file to RouterOS. Upload is preserved without automatic restore or restart. |
| Invented network traffic or log levels | Views use actual API data rather than demo measurements or fields absent from the model. |
Evidence limits
No production database, physical routers, device host keys, or provider accounts were supplied. Local fixture checks do not establish compatibility with every firmware version. For a deployment transition, follow the explicit import procedure, verify a connection to one real device, and confirm that its backup can be downloaded. Starting the new server activates schedules from the imported settings, so stop the old processes first. Release tests do not automatically restore any device configuration.