2026-01-24 22:59:20 +00:00
|
|
|
// Archipelago Bitcoin Node OS - Native Backend
|
|
|
|
|
// Pure Archipelago implementation, no StartOS dependencies
|
|
|
|
|
|
chore(ci): rustfmt + clippy clean-up to unblock the Rust CI job
The .github/workflows/ci.yml Rust job runs cargo fmt --check, clippy
with -D warnings, and tests. All three were failing. This commit:
- Applies rustfmt across the tree (the bulk of the diff — untouched
since the last toolchain bump, so a wide sweep was unavoidable).
- Fixes the correctness-level clippy errors:
container/bitcoin_simulator.rs wildcard-in-or-pattern
container/manifest.rs from_str rename to parse (reserved name)
container/podman_client.rs .get(0) -> .first()
container/runtime.rs manual += collapse
archipelago/src/constants.rs doc-comment → module-doc
api/rpc/package/install.rs stray /// comment above a non-item
container/docker_packages.rs redundant field init
streaming/advertisement.rs missing Metric import in tests
tests/orchestration_tests.rs `vec!` in non-Vec contexts
mesh/listener/dispatch.rs unused store_plain_message import
api/rpc/tor/mod.rs and mesh/steganography.rs: push-after-new → vec!
- Quiets wide legacy surfaces with crate-level allows in main.rs for
stylistic lints (too_many_arguments, type_complexity, doc indent,
enum variant prefix, wildcard-in-or, assertions-on-constants,
drop_non_drop, unused_io_amount, ptr_arg) — these fired in dozens
of places with no correctness payoff and have been churning every
toolchain bump.
- Tags intentional-dead-code helpers: wallet/ and streaming/ modules
are WIP, mesh::send_chunked_payload and DM_V1_MARKER are kept for
rollback compatibility, vpn::get_nostr_vpn_status is surface-area
for a not-yet-landed RPC.
cargo fmt --check, cargo clippy --all-targets --all-features
-- -D warnings, and cargo test --all-features now all pass locally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:23:46 -04:00
|
|
|
// Crate-level clippy allowances. These are stylistic lints that fire on
|
|
|
|
|
// large legacy surfaces and offer no correctness benefit to chase on every
|
|
|
|
|
// PR — suppressing them crate-wide keeps CI gating on correctness issues
|
|
|
|
|
// without drowning in cleanup noise every time a new toolchain tightens.
|
|
|
|
|
#![allow(
|
|
|
|
|
clippy::too_many_arguments,
|
|
|
|
|
clippy::doc_lazy_continuation,
|
|
|
|
|
clippy::type_complexity,
|
|
|
|
|
clippy::enum_variant_names,
|
|
|
|
|
clippy::wildcard_in_or_patterns,
|
|
|
|
|
clippy::assertions_on_constants,
|
|
|
|
|
clippy::drop_non_drop,
|
|
|
|
|
clippy::unused_io_amount,
|
|
|
|
|
clippy::ptr_arg
|
|
|
|
|
)]
|
|
|
|
|
|
2026-03-21 01:54:35 +00:00
|
|
|
use anyhow::{Context, Result};
|
2026-01-24 22:59:20 +00:00
|
|
|
use std::net::SocketAddr;
|
2026-04-22 19:20:13 -04:00
|
|
|
use std::sync::Arc;
|
2026-03-11 11:11:02 +00:00
|
|
|
use tokio::signal;
|
2026-04-22 19:20:13 -04:00
|
|
|
use tokio::sync::Notify;
|
chore(ci): rustfmt + clippy clean-up to unblock the Rust CI job
The .github/workflows/ci.yml Rust job runs cargo fmt --check, clippy
with -D warnings, and tests. All three were failing. This commit:
- Applies rustfmt across the tree (the bulk of the diff — untouched
since the last toolchain bump, so a wide sweep was unavoidable).
- Fixes the correctness-level clippy errors:
container/bitcoin_simulator.rs wildcard-in-or-pattern
container/manifest.rs from_str rename to parse (reserved name)
container/podman_client.rs .get(0) -> .first()
container/runtime.rs manual += collapse
archipelago/src/constants.rs doc-comment → module-doc
api/rpc/package/install.rs stray /// comment above a non-item
container/docker_packages.rs redundant field init
streaming/advertisement.rs missing Metric import in tests
tests/orchestration_tests.rs `vec!` in non-Vec contexts
mesh/listener/dispatch.rs unused store_plain_message import
api/rpc/tor/mod.rs and mesh/steganography.rs: push-after-new → vec!
- Quiets wide legacy surfaces with crate-level allows in main.rs for
stylistic lints (too_many_arguments, type_complexity, doc indent,
enum variant prefix, wildcard-in-or, assertions-on-constants,
drop_non_drop, unused_io_amount, ptr_arg) — these fired in dozens
of places with no correctness payoff and have been churning every
toolchain bump.
- Tags intentional-dead-code helpers: wallet/ and streaming/ modules
are WIP, mesh::send_chunked_payload and DM_V1_MARKER are kept for
rollback compatibility, vpn::get_nostr_vpn_status is surface-area
for a not-yet-landed RPC.
cargo fmt --check, cargo clippy --all-targets --all-features
-- -D warnings, and cargo test --all-features now all pass locally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:23:46 -04:00
|
|
|
use tracing::info;
|
2026-01-24 22:59:20 +00:00
|
|
|
|
|
|
|
|
mod api;
|
|
|
|
|
mod auth;
|
2026-04-20 10:03:38 -04:00
|
|
|
mod avatar;
|
2026-03-02 08:34:13 +00:00
|
|
|
mod backup;
|
feat: Phase 1 — per-installation credential generation, eliminate hardcoded passwords
Generate unique random passwords at first boot for Bitcoin RPC, all database
services (mempool, btcpay, immich, penpot, mysql-root), and Fedimint gateway.
Credentials stored in /var/lib/archipelago/secrets/ with 600 permissions.
Scripts: first-boot-containers.sh, deploy-to-target.sh, deploy-bitcoin-knots.sh,
container-doctor.sh all read from secrets files instead of hardcoded values.
Rust backend: new bitcoin_rpc module reads password from secrets file, env var,
or dev fallback. All .basic_auth() calls and container config strings now use
the shared credential reader instead of hardcoded "archipelago123".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 00:39:52 +00:00
|
|
|
mod bitcoin_rpc;
|
2026-04-30 16:29:56 -04:00
|
|
|
mod bitcoin_status;
|
2026-04-13 08:29:44 -04:00
|
|
|
mod blobs;
|
2026-04-22 08:29:56 -04:00
|
|
|
mod bootstrap;
|
2026-01-24 22:59:20 +00:00
|
|
|
mod config;
|
chore(ci): rustfmt + clippy clean-up to unblock the Rust CI job
The .github/workflows/ci.yml Rust job runs cargo fmt --check, clippy
with -D warnings, and tests. All three were failing. This commit:
- Applies rustfmt across the tree (the bulk of the diff — untouched
since the last toolchain bump, so a wide sweep was unavoidable).
- Fixes the correctness-level clippy errors:
container/bitcoin_simulator.rs wildcard-in-or-pattern
container/manifest.rs from_str rename to parse (reserved name)
container/podman_client.rs .get(0) -> .first()
container/runtime.rs manual += collapse
archipelago/src/constants.rs doc-comment → module-doc
api/rpc/package/install.rs stray /// comment above a non-item
container/docker_packages.rs redundant field init
streaming/advertisement.rs missing Metric import in tests
tests/orchestration_tests.rs `vec!` in non-Vec contexts
mesh/listener/dispatch.rs unused store_plain_message import
api/rpc/tor/mod.rs and mesh/steganography.rs: push-after-new → vec!
- Quiets wide legacy surfaces with crate-level allows in main.rs for
stylistic lints (too_many_arguments, type_complexity, doc indent,
enum variant prefix, wildcard-in-or, assertions-on-constants,
drop_non_drop, unused_io_amount, ptr_arg) — these fired in dozens
of places with no correctness payoff and have been churning every
toolchain bump.
- Tags intentional-dead-code helpers: wallet/ and streaming/ modules
are WIP, mesh::send_chunked_payload and DM_V1_MARKER are kept for
rollback compatibility, vpn::get_nostr_vpn_status is surface-area
for a not-yet-landed RPC.
cargo fmt --check, cargo clippy --all-targets --all-features
-- -D warnings, and cargo test --all-features now all pass locally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:23:46 -04:00
|
|
|
mod constants;
|
|
|
|
|
mod container;
|
2026-03-09 07:43:12 +00:00
|
|
|
mod content_server;
|
2026-03-11 11:11:02 +00:00
|
|
|
mod crash_recovery;
|
2026-03-22 03:30:21 +00:00
|
|
|
mod credentials;
|
chore(ci): rustfmt + clippy clean-up to unblock the Rust CI job
The .github/workflows/ci.yml Rust job runs cargo fmt --check, clippy
with -D warnings, and tests. All three were failing. This commit:
- Applies rustfmt across the tree (the bulk of the diff — untouched
since the last toolchain bump, so a wide sweep was unavoidable).
- Fixes the correctness-level clippy errors:
container/bitcoin_simulator.rs wildcard-in-or-pattern
container/manifest.rs from_str rename to parse (reserved name)
container/podman_client.rs .get(0) -> .first()
container/runtime.rs manual += collapse
archipelago/src/constants.rs doc-comment → module-doc
api/rpc/package/install.rs stray /// comment above a non-item
container/docker_packages.rs redundant field init
streaming/advertisement.rs missing Metric import in tests
tests/orchestration_tests.rs `vec!` in non-Vec contexts
mesh/listener/dispatch.rs unused store_plain_message import
api/rpc/tor/mod.rs and mesh/steganography.rs: push-after-new → vec!
- Quiets wide legacy surfaces with crate-level allows in main.rs for
stylistic lints (too_many_arguments, type_complexity, doc indent,
enum variant prefix, wildcard-in-or, assertions-on-constants,
drop_non_drop, unused_io_amount, ptr_arg) — these fired in dozens
of places with no correctness payoff and have been churning every
toolchain bump.
- Tags intentional-dead-code helpers: wallet/ and streaming/ modules
are WIP, mesh::send_chunked_payload and DM_V1_MARKER are kept for
rollback compatibility, vpn::get_nostr_vpn_status is surface-area
for a not-yet-landed RPC.
cargo fmt --check, cargo clippy --all-targets --all-features
-- -D warnings, and cargo test --all-features now all pass locally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:23:46 -04:00
|
|
|
mod data_model;
|
2026-03-11 11:11:02 +00:00
|
|
|
mod disk_monitor;
|
2026-02-17 15:03:34 +00:00
|
|
|
mod electrs_status;
|
2026-03-11 11:11:02 +00:00
|
|
|
mod federation;
|
feat(fips): integrate jmcorgan/fips as preferred non-Tor transport + v1.4.0
Bakes the FIPS (Free Internetworking Peering System) mesh daemon into
the node stack, supervised by archipelago alongside Tor. Runs as a
system service, identity derives from the same BIP-39 master seed, and
user-triggered updates track upstream main.
Identity
seed.rs: new HKDF label archipelago/fips/secp256k1/v1 → dedicated
secp256k1 key, distinct from the Nostr-node key for crypto isolation
but still seed-recoverable
identity.rs: writes fips_key[.pub] to /data/identity on onboarding,
chmod 0600; fips_key_exists / load_fips_keys / fips_npub accessors
Transport
TransportKind::Fips=3 inserted between LAN and Tor (Tor bumps to 4)
→ router prefers FIPS over Tor for all peer traffic
PeerRecord gains fips_npub + last_fips fields (serde(default) for
backward-compat with older nodes)
transport/fips.rs: NodeTransport stub, reports unavailable until the
daemon is live so router falls through to Tor cleanly
Federation invites
FederatedNode and FederationInvite carry optional fips_npub
create_invite / accept_invite / peer-joined callback thread it end
to end; signature domain deliberately unchanged — FIPS Noise does
its own session auth, so the unsigned hint only affects path
selection
crate::fips
config.rs: renders /etc/fips/fips.yaml and sudo-installs key material
service.rs: systemctl status/activate/restart/mask wrappers
update.rs: GitHub API check against upstream main; apply stubbed
until per-commit .deb artefact source is decided
RPC + dashboard
fips.status / fips.check-update / fips.apply-update / fips.install /
fips.restart registered in dispatcher
HomeNetworkCard.vue shipped standalone (unmounted — place in Home.vue
when ready); shows state pill, version, FIPS npub, update button,
activate button when key is present but service is down
ISO + systemd
archipelago-fips.service: conditional on key presence, masked by
default — backend unmasks after onboarding writes the key
build-auto-installer-iso.sh: multi-stage Dockerfile builds the FIPS
.deb from jmcorgan/fips main (fail-loud), COPYs it into rootfs, apt
installs it so trixie resolves deps; unit copied + masked
Version bump: 1.3.5 → 1.4.0
Tests: 33 new/updated passing (seed, identity, transport, federation,
fips module, transport::fips).
Known gaps: fips.apply-update returns a clear stub error until
upstream publishes per-commit .deb artefacts; HomeNetworkCard is not
mounted in Home.vue by default.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 22:57:51 -04:00
|
|
|
mod fips;
|
chore(ci): rustfmt + clippy clean-up to unblock the Rust CI job
The .github/workflows/ci.yml Rust job runs cargo fmt --check, clippy
with -D warnings, and tests. All three were failing. This commit:
- Applies rustfmt across the tree (the bulk of the diff — untouched
since the last toolchain bump, so a wide sweep was unavoidable).
- Fixes the correctness-level clippy errors:
container/bitcoin_simulator.rs wildcard-in-or-pattern
container/manifest.rs from_str rename to parse (reserved name)
container/podman_client.rs .get(0) -> .first()
container/runtime.rs manual += collapse
archipelago/src/constants.rs doc-comment → module-doc
api/rpc/package/install.rs stray /// comment above a non-item
container/docker_packages.rs redundant field init
streaming/advertisement.rs missing Metric import in tests
tests/orchestration_tests.rs `vec!` in non-Vec contexts
mesh/listener/dispatch.rs unused store_plain_message import
api/rpc/tor/mod.rs and mesh/steganography.rs: push-after-new → vec!
- Quiets wide legacy surfaces with crate-level allows in main.rs for
stylistic lints (too_many_arguments, type_complexity, doc indent,
enum variant prefix, wildcard-in-or, assertions-on-constants,
drop_non_drop, unused_io_amount, ptr_arg) — these fired in dozens
of places with no correctness payoff and have been churning every
toolchain bump.
- Tags intentional-dead-code helpers: wallet/ and streaming/ modules
are WIP, mesh::send_chunked_payload and DM_V1_MARKER are kept for
rollback compatibility, vpn::get_nostr_vpn_status is surface-area
for a not-yet-landed RPC.
cargo fmt --check, cargo clippy --all-targets --all-features
-- -D warnings, and cargo test --all-features now all pass locally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:23:46 -04:00
|
|
|
mod health_monitor;
|
2026-02-17 15:03:34 +00:00
|
|
|
mod identity;
|
2026-03-09 07:43:12 +00:00
|
|
|
mod identity_manager;
|
2026-03-11 11:11:02 +00:00
|
|
|
mod marketplace;
|
|
|
|
|
mod mesh;
|
|
|
|
|
mod monitoring;
|
chore(ci): rustfmt + clippy clean-up to unblock the Rust CI job
The .github/workflows/ci.yml Rust job runs cargo fmt --check, clippy
with -D warnings, and tests. All three were failing. This commit:
- Applies rustfmt across the tree (the bulk of the diff — untouched
since the last toolchain bump, so a wide sweep was unavoidable).
- Fixes the correctness-level clippy errors:
container/bitcoin_simulator.rs wildcard-in-or-pattern
container/manifest.rs from_str rename to parse (reserved name)
container/podman_client.rs .get(0) -> .first()
container/runtime.rs manual += collapse
archipelago/src/constants.rs doc-comment → module-doc
api/rpc/package/install.rs stray /// comment above a non-item
container/docker_packages.rs redundant field init
streaming/advertisement.rs missing Metric import in tests
tests/orchestration_tests.rs `vec!` in non-Vec contexts
mesh/listener/dispatch.rs unused store_plain_message import
api/rpc/tor/mod.rs and mesh/steganography.rs: push-after-new → vec!
- Quiets wide legacy surfaces with crate-level allows in main.rs for
stylistic lints (too_many_arguments, type_complexity, doc indent,
enum variant prefix, wildcard-in-or, assertions-on-constants,
drop_non_drop, unused_io_amount, ptr_arg) — these fired in dozens
of places with no correctness payoff and have been churning every
toolchain bump.
- Tags intentional-dead-code helpers: wallet/ and streaming/ modules
are WIP, mesh::send_chunked_payload and DM_V1_MARKER are kept for
rollback compatibility, vpn::get_nostr_vpn_status is surface-area
for a not-yet-landed RPC.
cargo fmt --check, cargo clippy --all-targets --all-features
-- -D warnings, and cargo test --all-features now all pass locally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:23:46 -04:00
|
|
|
mod names;
|
|
|
|
|
mod network;
|
2026-02-17 15:03:34 +00:00
|
|
|
mod node_message;
|
|
|
|
|
mod nostr_discovery;
|
2026-03-12 00:19:30 +00:00
|
|
|
mod nostr_handshake;
|
chore(ci): rustfmt + clippy clean-up to unblock the Rust CI job
The .github/workflows/ci.yml Rust job runs cargo fmt --check, clippy
with -D warnings, and tests. All three were failing. This commit:
- Applies rustfmt across the tree (the bulk of the diff — untouched
since the last toolchain bump, so a wide sweep was unavoidable).
- Fixes the correctness-level clippy errors:
container/bitcoin_simulator.rs wildcard-in-or-pattern
container/manifest.rs from_str rename to parse (reserved name)
container/podman_client.rs .get(0) -> .first()
container/runtime.rs manual += collapse
archipelago/src/constants.rs doc-comment → module-doc
api/rpc/package/install.rs stray /// comment above a non-item
container/docker_packages.rs redundant field init
streaming/advertisement.rs missing Metric import in tests
tests/orchestration_tests.rs `vec!` in non-Vec contexts
mesh/listener/dispatch.rs unused store_plain_message import
api/rpc/tor/mod.rs and mesh/steganography.rs: push-after-new → vec!
- Quiets wide legacy surfaces with crate-level allows in main.rs for
stylistic lints (too_many_arguments, type_complexity, doc indent,
enum variant prefix, wildcard-in-or, assertions-on-constants,
drop_non_drop, unused_io_amount, ptr_arg) — these fired in dozens
of places with no correctness payoff and have been churning every
toolchain bump.
- Tags intentional-dead-code helpers: wallet/ and streaming/ modules
are WIP, mesh::send_chunked_payload and DM_V1_MARKER are kept for
rollback compatibility, vpn::get_nostr_vpn_status is surface-area
for a not-yet-landed RPC.
cargo fmt --check, cargo clippy --all-targets --all-features
-- -D warnings, and cargo test --all-features now all pass locally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:23:46 -04:00
|
|
|
mod nostr_relays;
|
2026-02-17 15:03:34 +00:00
|
|
|
mod peers;
|
chore(ci): rustfmt + clippy clean-up to unblock the Rust CI job
The .github/workflows/ci.yml Rust job runs cargo fmt --check, clippy
with -D warnings, and tests. All three were failing. This commit:
- Applies rustfmt across the tree (the bulk of the diff — untouched
since the last toolchain bump, so a wide sweep was unavoidable).
- Fixes the correctness-level clippy errors:
container/bitcoin_simulator.rs wildcard-in-or-pattern
container/manifest.rs from_str rename to parse (reserved name)
container/podman_client.rs .get(0) -> .first()
container/runtime.rs manual += collapse
archipelago/src/constants.rs doc-comment → module-doc
api/rpc/package/install.rs stray /// comment above a non-item
container/docker_packages.rs redundant field init
streaming/advertisement.rs missing Metric import in tests
tests/orchestration_tests.rs `vec!` in non-Vec contexts
mesh/listener/dispatch.rs unused store_plain_message import
api/rpc/tor/mod.rs and mesh/steganography.rs: push-after-new → vec!
- Quiets wide legacy surfaces with crate-level allows in main.rs for
stylistic lints (too_many_arguments, type_complexity, doc indent,
enum variant prefix, wildcard-in-or, assertions-on-constants,
drop_non_drop, unused_io_amount, ptr_arg) — these fired in dozens
of places with no correctness payoff and have been churning every
toolchain bump.
- Tags intentional-dead-code helpers: wallet/ and streaming/ modules
are WIP, mesh::send_chunked_payload and DM_V1_MARKER are kept for
rollback compatibility, vpn::get_nostr_vpn_status is surface-area
for a not-yet-landed RPC.
cargo fmt --check, cargo clippy --all-targets --all-features
-- -D warnings, and cargo test --all-features now all pass locally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:23:46 -04:00
|
|
|
mod port_allocator;
|
2026-03-22 03:30:21 +00:00
|
|
|
mod rate_limit;
|
chore(ci): rustfmt + clippy clean-up to unblock the Rust CI job
The .github/workflows/ci.yml Rust job runs cargo fmt --check, clippy
with -D warnings, and tests. All three were failing. This commit:
- Applies rustfmt across the tree (the bulk of the diff — untouched
since the last toolchain bump, so a wide sweep was unavoidable).
- Fixes the correctness-level clippy errors:
container/bitcoin_simulator.rs wildcard-in-or-pattern
container/manifest.rs from_str rename to parse (reserved name)
container/podman_client.rs .get(0) -> .first()
container/runtime.rs manual += collapse
archipelago/src/constants.rs doc-comment → module-doc
api/rpc/package/install.rs stray /// comment above a non-item
container/docker_packages.rs redundant field init
streaming/advertisement.rs missing Metric import in tests
tests/orchestration_tests.rs `vec!` in non-Vec contexts
mesh/listener/dispatch.rs unused store_plain_message import
api/rpc/tor/mod.rs and mesh/steganography.rs: push-after-new → vec!
- Quiets wide legacy surfaces with crate-level allows in main.rs for
stylistic lints (too_many_arguments, type_complexity, doc indent,
enum variant prefix, wildcard-in-or, assertions-on-constants,
drop_non_drop, unused_io_amount, ptr_arg) — these fired in dozens
of places with no correctness payoff and have been churning every
toolchain bump.
- Tags intentional-dead-code helpers: wallet/ and streaming/ modules
are WIP, mesh::send_chunked_payload and DM_V1_MARKER are kept for
rollback compatibility, vpn::get_nostr_vpn_status is surface-area
for a not-yet-landed RPC.
cargo fmt --check, cargo clippy --all-targets --all-features
-- -D warnings, and cargo test --all-features now all pass locally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:23:46 -04:00
|
|
|
pub mod seed;
|
|
|
|
|
mod server;
|
2026-03-06 03:26:56 +00:00
|
|
|
mod session;
|
2026-04-19 01:44:41 -04:00
|
|
|
mod settings;
|
2026-01-27 23:06:18 +00:00
|
|
|
mod state;
|
feat: streaming ecash payments + media playback overhaul
Cashu ecash protocol (BDHKE blind signatures, cashuA token format,
mint HTTP client) replacing the stub wallet. TollGate-inspired streaming
data payment system with step-based pricing (bytes/time/requests),
session management with incremental top-ups, usage metering, and
Nostr kind 10021 service advertisements.
13 new streaming.* RPC endpoints. Content server now verifies real
Cashu tokens. Profits tracking includes streaming revenue.
Frontend: GlobalAudioPlayer (persistent bottom bar across all pages),
video lightbox with full controls, audio in MediaLightbox, free file
previews (no blur), paid 10% audio/video previews, separated play
vs download buttons in PeerFiles.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 22:31:28 -04:00
|
|
|
mod streaming;
|
feat: add TOTP 2FA, API key switcher, login progress bar, and alpha hardening plan
- TOTP 2FA: full setup/confirm/disable/login flow with Argon2id + ChaCha20-Poly1305
encrypted secret storage, QR code generation, and bcrypt-hashed backup codes
- API key switcher: OAuth vs personal API key toggle in AIUI chat settings with
status indicator, key validation, and help text
- Login progress bar: server startup detection with health check polling, form
disabled until server is ready
- AI quarantine docs: comprehensive HTML page documenting all 6 security layers
- Settings: AI Data Access permission toggles with per-category control
- Alpha hardening plan: 28-task overnight automation plan across 7 phases
(onboarding, login, app install, AIUI, UI polish, security, ISO build)
- Backlog: node discovery spatial map feature for alpha demo
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:23:57 +00:00
|
|
|
mod totp;
|
chore(ci): rustfmt + clippy clean-up to unblock the Rust CI job
The .github/workflows/ci.yml Rust job runs cargo fmt --check, clippy
with -D warnings, and tests. All three were failing. This commit:
- Applies rustfmt across the tree (the bulk of the diff — untouched
since the last toolchain bump, so a wide sweep was unavoidable).
- Fixes the correctness-level clippy errors:
container/bitcoin_simulator.rs wildcard-in-or-pattern
container/manifest.rs from_str rename to parse (reserved name)
container/podman_client.rs .get(0) -> .first()
container/runtime.rs manual += collapse
archipelago/src/constants.rs doc-comment → module-doc
api/rpc/package/install.rs stray /// comment above a non-item
container/docker_packages.rs redundant field init
streaming/advertisement.rs missing Metric import in tests
tests/orchestration_tests.rs `vec!` in non-Vec contexts
mesh/listener/dispatch.rs unused store_plain_message import
api/rpc/tor/mod.rs and mesh/steganography.rs: push-after-new → vec!
- Quiets wide legacy surfaces with crate-level allows in main.rs for
stylistic lints (too_many_arguments, type_complexity, doc indent,
enum variant prefix, wildcard-in-or, assertions-on-constants,
drop_non_drop, unused_io_amount, ptr_arg) — these fired in dozens
of places with no correctness payoff and have been churning every
toolchain bump.
- Tags intentional-dead-code helpers: wallet/ and streaming/ modules
are WIP, mesh::send_chunked_payload and DM_V1_MARKER are kept for
rollback compatibility, vpn::get_nostr_vpn_status is surface-area
for a not-yet-landed RPC.
cargo fmt --check, cargo clippy --all-targets --all-features
-- -D warnings, and cargo test --all-features now all pass locally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:23:46 -04:00
|
|
|
mod transport;
|
2026-03-09 07:43:12 +00:00
|
|
|
mod update;
|
2026-03-22 03:30:21 +00:00
|
|
|
mod vpn;
|
chore(ci): rustfmt + clippy clean-up to unblock the Rust CI job
The .github/workflows/ci.yml Rust job runs cargo fmt --check, clippy
with -D warnings, and tests. All three were failing. This commit:
- Applies rustfmt across the tree (the bulk of the diff — untouched
since the last toolchain bump, so a wide sweep was unavoidable).
- Fixes the correctness-level clippy errors:
container/bitcoin_simulator.rs wildcard-in-or-pattern
container/manifest.rs from_str rename to parse (reserved name)
container/podman_client.rs .get(0) -> .first()
container/runtime.rs manual += collapse
archipelago/src/constants.rs doc-comment → module-doc
api/rpc/package/install.rs stray /// comment above a non-item
container/docker_packages.rs redundant field init
streaming/advertisement.rs missing Metric import in tests
tests/orchestration_tests.rs `vec!` in non-Vec contexts
mesh/listener/dispatch.rs unused store_plain_message import
api/rpc/tor/mod.rs and mesh/steganography.rs: push-after-new → vec!
- Quiets wide legacy surfaces with crate-level allows in main.rs for
stylistic lints (too_many_arguments, type_complexity, doc indent,
enum variant prefix, wildcard-in-or, assertions-on-constants,
drop_non_drop, unused_io_amount, ptr_arg) — these fired in dozens
of places with no correctness payoff and have been churning every
toolchain bump.
- Tags intentional-dead-code helpers: wallet/ and streaming/ modules
are WIP, mesh::send_chunked_payload and DM_V1_MARKER are kept for
rollback compatibility, vpn::get_nostr_vpn_status is surface-area
for a not-yet-landed RPC.
cargo fmt --check, cargo clippy --all-targets --all-features
-- -D warnings, and cargo test --all-features now all pass locally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:23:46 -04:00
|
|
|
mod wallet;
|
2026-03-11 12:55:13 +00:00
|
|
|
mod webhooks;
|
2026-01-24 22:59:20 +00:00
|
|
|
|
|
|
|
|
use config::Config;
|
2026-04-22 19:20:13 -04:00
|
|
|
use container::{
|
|
|
|
|
BootReconciler, ContainerOrchestrator, DevContainerOrchestrator, ProdContainerOrchestrator,
|
|
|
|
|
RECONCILER_DEFAULT_INTERVAL,
|
|
|
|
|
};
|
2026-01-24 22:59:20 +00:00
|
|
|
use server::Server;
|
|
|
|
|
|
|
|
|
|
#[tokio::main]
|
|
|
|
|
async fn main() -> Result<()> {
|
2026-03-11 11:11:02 +00:00
|
|
|
let startup_start = std::time::Instant::now();
|
2026-03-21 01:02:16 +00:00
|
|
|
crash_recovery::init_start_time();
|
2026-03-11 11:11:02 +00:00
|
|
|
|
2026-01-24 22:59:20 +00:00
|
|
|
// Initialize tracing
|
|
|
|
|
tracing_subscriber::fmt()
|
|
|
|
|
.with_env_filter(
|
|
|
|
|
tracing_subscriber::EnvFilter::try_from_default_env()
|
|
|
|
|
.unwrap_or_else(|_| "archipelago=debug,info".into()),
|
|
|
|
|
)
|
|
|
|
|
.init();
|
|
|
|
|
|
2026-03-11 11:11:02 +00:00
|
|
|
info!("Starting Archipelago Bitcoin Node OS");
|
2026-01-24 22:59:20 +00:00
|
|
|
|
2026-04-22 13:26:54 -04:00
|
|
|
// Self-heal web-ui permissions. The OTA updater in <=v1.7.38 left
|
|
|
|
|
// /opt/archipelago/web-ui as drwx------ (700) after the atomic
|
|
|
|
|
// swap — nginx (www-data) then returned 500/403 on every request
|
|
|
|
|
// until someone shelled in and chmod'd it. Check on every boot
|
|
|
|
|
// and repair if needed so a node auto-recovers after the next
|
|
|
|
|
// service restart that follows a broken OTA.
|
|
|
|
|
tokio::spawn(async move {
|
|
|
|
|
use std::os::unix::fs::PermissionsExt;
|
|
|
|
|
let web_ui = std::path::Path::new("/opt/archipelago/web-ui");
|
|
|
|
|
if let Ok(meta) = tokio::fs::metadata(web_ui).await {
|
|
|
|
|
let mode = meta.permissions().mode() & 0o777;
|
|
|
|
|
if mode & 0o005 != 0o005 {
|
2026-04-28 15:00:58 -04:00
|
|
|
tracing::warn!("web-ui perms {:o} not world-readable — self-healing", mode);
|
2026-04-22 13:26:54 -04:00
|
|
|
let _ = tokio::process::Command::new("sudo")
|
|
|
|
|
.args([
|
|
|
|
|
"-n",
|
|
|
|
|
"chmod",
|
|
|
|
|
"-R",
|
|
|
|
|
"u=rwX,go=rX",
|
|
|
|
|
"/opt/archipelago/web-ui",
|
|
|
|
|
])
|
|
|
|
|
.status()
|
|
|
|
|
.await;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-01-24 22:59:20 +00:00
|
|
|
// Load configuration
|
|
|
|
|
let config = Config::load().await?;
|
|
|
|
|
info!("📁 Data directory: {}", config.data_dir.display());
|
|
|
|
|
|
2026-04-19 01:44:41 -04:00
|
|
|
// Load user transport preferences so peer-to-peer call sites can
|
|
|
|
|
// consult them from any module without threading a handle through
|
|
|
|
|
// deep async chains. Missing/corrupt file → default (Auto everywhere).
|
|
|
|
|
if let Err(e) = settings::transport::init(&config.data_dir).await {
|
|
|
|
|
tracing::warn!(
|
|
|
|
|
"Failed to initialise transport preferences: {} — using defaults",
|
|
|
|
|
e
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-14 03:44:33 +00:00
|
|
|
// Write PID marker early so we can detect crashes on next startup
|
|
|
|
|
crash_recovery::write_pid_marker(&config.data_dir).await?;
|
2026-03-11 11:11:02 +00:00
|
|
|
|
2026-05-05 11:29:18 -04:00
|
|
|
// Run crash recovery before starting the manifest reconciler. Both paths
|
|
|
|
|
// mutate Podman; running them concurrently can corrupt transient runtime
|
|
|
|
|
// state and leave netavark/conmon unable to start containers.
|
|
|
|
|
match crash_recovery::check_for_crash(&config.data_dir).await {
|
|
|
|
|
Ok(Some(containers)) => {
|
|
|
|
|
info!(
|
|
|
|
|
"🔧 Recovering {} containers from previous crash...",
|
|
|
|
|
containers.len()
|
|
|
|
|
);
|
|
|
|
|
let report = crash_recovery::recover_containers(&containers).await;
|
|
|
|
|
info!(
|
|
|
|
|
"🔧 Recovery complete: {}/{} containers restarted (failed: {:?})",
|
|
|
|
|
report.recovered, report.total, report.failed
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
Ok(None) => {}
|
|
|
|
|
Err(e) => {
|
|
|
|
|
tracing::warn!("Crash recovery check failed: {}", e);
|
|
|
|
|
}
|
|
|
|
|
}
|
security+feat: v1.3.0 — pentest remediation, container reliability, UI overhaul
Security (33 pentest findings addressed):
- CRITICAL: backend binds 127.0.0.1, path traversal in tor.rs/dwn fixed
- HIGH: federation requires signatures, XSS login redirect, RBAC viewer restricted
- HIGH: tar slip prevention, S3 SSRF validation, backup ID validation
- MEDIUM: remember-me random secret, TOTP session rotation, password re-auth
- LOW: CSP unsafe-inline removed, CORS dev-only, onion/webhook validation
Container reliability:
- Memory limits on all 37 containers (OOM prevention)
- Exited vs stopped state distinction with health-aware status badges
- Crash recovery coordination (no more restart cascade)
- User-stopped tracking survives reboots
- Tiered boot recovery (databases → core → services → apps)
UI:
- Wallet TransactionsModal, health-aware app status badges
- Restart button on containers, exited/crashed red state
- Mesh view overhaul, glass button updates, BaseModal/ToggleSwitch
- Apps sticky header removed, dev faucet, mutable mock wallet
Infrastructure:
- LND REST port 8080 exposed over Tor (LND Connect fix)
- Nginx cookie_session fix, deploy script Tor config updated
- Dev environment: podman auto-start, boot mode simulation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:44:31 +00:00
|
|
|
|
2026-05-05 11:29:18 -04:00
|
|
|
// Start any stopped containers (handles clean reboot). This remains
|
|
|
|
|
// synchronous for the same reason: no concurrent reconciler during Podman
|
|
|
|
|
// startup/recovery operations.
|
|
|
|
|
let boot_report = crash_recovery::start_stopped_containers(&config.data_dir).await;
|
|
|
|
|
if boot_report.total > 0 {
|
|
|
|
|
info!(
|
|
|
|
|
"🔄 Boot startup: {}/{} containers started (failed: {:?})",
|
|
|
|
|
boot_report.recovered, boot_report.total, boot_report.failed
|
|
|
|
|
);
|
2026-03-14 03:44:33 +00:00
|
|
|
}
|
2026-05-05 11:29:18 -04:00
|
|
|
crash_recovery::mark_recovery_complete();
|
2026-03-11 11:11:02 +00:00
|
|
|
|
2026-04-22 19:20:13 -04:00
|
|
|
// Construct the container orchestrator once. In prod mode we load the
|
|
|
|
|
// on-disk app manifests, do an initial adoption pass, and spawn the
|
|
|
|
|
// BootReconciler loop (Step 5/6 of the rust-orchestrator migration).
|
|
|
|
|
// Dev mode uses the in-memory DevContainerOrchestrator and has no
|
|
|
|
|
// reconciler (manifests are pushed via RPC, not discovered from disk).
|
|
|
|
|
let shutdown_notify = Arc::new(Notify::new());
|
|
|
|
|
let (orchestrator, dev_orchestrator): (
|
|
|
|
|
Option<Arc<dyn ContainerOrchestrator>>,
|
|
|
|
|
Option<Arc<DevContainerOrchestrator>>,
|
|
|
|
|
) = if config.dev_mode {
|
|
|
|
|
let dev = Arc::new(DevContainerOrchestrator::new(config.clone()).await?);
|
|
|
|
|
let trait_obj: Arc<dyn ContainerOrchestrator> = dev.clone();
|
|
|
|
|
(Some(trait_obj), Some(dev))
|
|
|
|
|
} else {
|
|
|
|
|
let prod = Arc::new(ProdContainerOrchestrator::new(config.clone()).await?);
|
|
|
|
|
// Best-effort manifest load; a missing /opt/archipelago/apps is
|
|
|
|
|
// logged inside load_manifests and not fatal.
|
|
|
|
|
match prod.load_manifests().await {
|
|
|
|
|
Ok(n) => info!("📦 Loaded {n} app manifest(s) from disk"),
|
|
|
|
|
Err(e) => {
|
|
|
|
|
tracing::error!(error = %e, "prod orchestrator: load_manifests failed at startup");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Adoption pass: link existing podman containers back to their
|
|
|
|
|
// manifests so the reconciler doesn't recreate them.
|
|
|
|
|
match prod.adopt_existing().await {
|
|
|
|
|
Ok(report) => {
|
|
|
|
|
info!(
|
|
|
|
|
"🔗 Adopted {} existing container(s): {:?}",
|
|
|
|
|
report.adopted.len(),
|
|
|
|
|
report.adopted
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
Err(e) => {
|
|
|
|
|
tracing::warn!(error = %e, "prod orchestrator: adopt_existing failed (non-fatal)");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Spawn the boot reconciler loop. Runs an initial reconcile
|
|
|
|
|
// immediately, then re-checks every RECONCILER_DEFAULT_INTERVAL
|
|
|
|
|
// until shutdown_notify fires.
|
|
|
|
|
{
|
|
|
|
|
let reconciler = BootReconciler::new(
|
|
|
|
|
prod.clone(),
|
|
|
|
|
RECONCILER_DEFAULT_INTERVAL,
|
|
|
|
|
shutdown_notify.clone(),
|
|
|
|
|
);
|
|
|
|
|
tokio::spawn(reconciler.run_forever());
|
|
|
|
|
info!(
|
|
|
|
|
"🔄 Boot reconciler started (interval: {:?})",
|
|
|
|
|
RECONCILER_DEFAULT_INTERVAL
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
let trait_obj: Arc<dyn ContainerOrchestrator> = prod;
|
|
|
|
|
(Some(trait_obj), None)
|
|
|
|
|
};
|
|
|
|
|
|
2026-03-28 13:06:34 +00:00
|
|
|
// Ensure a default user exists so login works after install/onboarding.
|
|
|
|
|
// In production, the default password is "password123" (shown during install).
|
|
|
|
|
// In dev mode, the dev default password is used.
|
2026-03-29 22:44:46 +01:00
|
|
|
// Don't auto-create default user — let onboarding flow handle password setup
|
|
|
|
|
// via auth.setup RPC. The Login page detects is_setup=false and shows
|
|
|
|
|
// "Create Password" form instead of login form.
|
2026-01-27 22:37:08 +00:00
|
|
|
|
2026-01-24 22:59:20 +00:00
|
|
|
// Create server
|
2026-04-22 19:20:13 -04:00
|
|
|
let server = Server::new(config.clone(), orchestrator, dev_orchestrator).await?;
|
2026-01-24 22:59:20 +00:00
|
|
|
|
|
|
|
|
// Start server
|
|
|
|
|
let addr: SocketAddr = format!("{}:{}", config.bind_host, config.bind_port)
|
|
|
|
|
.parse()
|
2026-03-21 01:54:35 +00:00
|
|
|
.context("Invalid bind address")?;
|
2026-01-24 22:59:20 +00:00
|
|
|
|
2026-04-19 04:21:20 -04:00
|
|
|
// The FIPS peer listener is bound lazily by server::serve_with_shutdown
|
|
|
|
|
// on a 30s poll of fips0 — so a post-onboarding fips.install brings it
|
|
|
|
|
// online without needing an archipelago restart.
|
2026-04-19 01:12:39 -04:00
|
|
|
|
release(v1.7.41-alpha): post-OTA auto-rollback so a bad release cannot strand the fleet
Closes failure mode FM5 from docs/bulletproof-containers.md: the v1.7.38 +
v1.7.39 rollouts left every affected node on an unreachable UI (nginx 500)
with no recovery path short of SSH. This release adds a self-check
guardrail to the update flow.
What changed:
- apply_update() writes a pending-verify marker with old+new version and
a 150s deadline immediately before scheduling the service restart.
- verify_pending_update() runs from main.rs startup. If the marker is
present and within its freshness window, the new binary waits 15s for
nginx + backend to settle, then probes https://127.0.0.1/ every 5s for
up to 90s (self-signed certs accepted).
- On any probe success within the window, the marker is cleared and
nothing else happens.
- On window-exhaust, the new binary:
1. Moves the broken /opt/archipelago/web-ui to web-ui.failed.<ts>
(quarantined, not deleted, so we can post-mortem).
2. Restores web-ui.bak on top of web-ui.
3. Calls rollback_update() to restore the previous binary.
4. Updates state.current_version to reflect the rollback.
5. systemctl --no-block restart archipelago so the OLD binary boots.
- Markers older than 10 minutes are treated as stale and cleared without
probing, so a crashed-during-startup marker from weeks ago cannot
spontaneously roll back a healthy node on a later reboot.
- rollback_update() binary copy now goes through host_sudo instead of
tokio::fs::copy, so it escapes the service's ProtectSystem=strict
mount namespace. Without this, the rollback silently failed with
EROFS on /usr/local/bin and orphaned the rollback - the exact
opposite of what auto-rollback is for.
Tests: 4 new unit tests in update::tests covering marker round-trip,
absent-marker noop, no-panic on verify_pending_update with nothing to
verify, and an invariant assert that the 90s probe window stays below
the 600s stale threshold. All passing.
Side fix: scripts/create-release-manifest.sh was dying with exit 141
(SIGPIPE from tar tvzf pipe head pipe awk) under set -euo pipefail.
Replaced with a single awk NR==1 that doesn't short-circuit the upstream
pipe, so the release-build flow is idempotent again.
2026-04-22 16:14:35 -04:00
|
|
|
// Post-OTA verification: if apply_update() wrote a pending-verify
|
|
|
|
|
// marker right before the restart, probe the frontend now and auto-
|
|
|
|
|
// rollback if it's broken. This is the guardrail that stops fleet-
|
|
|
|
|
// wide breakage when an OTA lands a subtly-bad release (v1.7.38/39
|
|
|
|
|
// tarball-perms → nginx 500 was the trigger). Runs concurrently
|
|
|
|
|
// with normal startup — doesn't delay the server coming up.
|
|
|
|
|
{
|
|
|
|
|
let data_dir = config.data_dir.clone();
|
|
|
|
|
tokio::spawn(async move {
|
|
|
|
|
update::verify_pending_update(&data_dir).await;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-11 11:11:02 +00:00
|
|
|
// Spawn background update scheduler
|
|
|
|
|
let update_data_dir = config.data_dir.clone();
|
|
|
|
|
tokio::spawn(async move {
|
|
|
|
|
update::run_update_scheduler(update_data_dir).await;
|
|
|
|
|
});
|
|
|
|
|
|
2026-04-22 08:29:56 -04:00
|
|
|
// Synchronize host-side doctor artifacts (script + systemd units) with
|
|
|
|
|
// what's embedded in this binary. Runs in the background so it never
|
|
|
|
|
// delays server readiness; best-effort, warnings only.
|
|
|
|
|
tokio::spawn(bootstrap::ensure_doctor_installed());
|
|
|
|
|
|
2026-03-11 11:11:02 +00:00
|
|
|
// Spawn periodic container snapshot (for crash recovery)
|
|
|
|
|
crash_recovery::spawn_snapshot_task(config.data_dir.clone());
|
|
|
|
|
|
|
|
|
|
// Spawn disk space monitor (warns at 85%, auto-cleans at 90%)
|
|
|
|
|
disk_monitor::spawn_disk_monitor(config.data_dir.clone());
|
|
|
|
|
|
2026-04-20 07:10:49 -04:00
|
|
|
// Restore WireGuard peers into wg0 (kernel loses them on every reboot).
|
|
|
|
|
{
|
|
|
|
|
let data_dir = config.data_dir.clone();
|
|
|
|
|
tokio::spawn(async move {
|
|
|
|
|
vpn::restore_wg_peers(&data_dir).await;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-09 10:32:55 +02:00
|
|
|
// Spawn ElectrumX status cache (refreshes every 15s, serves cached data to avoid race conditions)
|
|
|
|
|
electrs_status::spawn_status_cache();
|
2026-04-30 16:29:56 -04:00
|
|
|
bitcoin_status::spawn_status_cache();
|
2026-04-09 10:32:55 +02:00
|
|
|
|
2026-03-11 11:11:02 +00:00
|
|
|
let startup_ms = startup_start.elapsed().as_millis();
|
chore(ci): rustfmt + clippy clean-up to unblock the Rust CI job
The .github/workflows/ci.yml Rust job runs cargo fmt --check, clippy
with -D warnings, and tests. All three were failing. This commit:
- Applies rustfmt across the tree (the bulk of the diff — untouched
since the last toolchain bump, so a wide sweep was unavoidable).
- Fixes the correctness-level clippy errors:
container/bitcoin_simulator.rs wildcard-in-or-pattern
container/manifest.rs from_str rename to parse (reserved name)
container/podman_client.rs .get(0) -> .first()
container/runtime.rs manual += collapse
archipelago/src/constants.rs doc-comment → module-doc
api/rpc/package/install.rs stray /// comment above a non-item
container/docker_packages.rs redundant field init
streaming/advertisement.rs missing Metric import in tests
tests/orchestration_tests.rs `vec!` in non-Vec contexts
mesh/listener/dispatch.rs unused store_plain_message import
api/rpc/tor/mod.rs and mesh/steganography.rs: push-after-new → vec!
- Quiets wide legacy surfaces with crate-level allows in main.rs for
stylistic lints (too_many_arguments, type_complexity, doc indent,
enum variant prefix, wildcard-in-or, assertions-on-constants,
drop_non_drop, unused_io_amount, ptr_arg) — these fired in dozens
of places with no correctness payoff and have been churning every
toolchain bump.
- Tags intentional-dead-code helpers: wallet/ and streaming/ modules
are WIP, mesh::send_chunked_payload and DM_V1_MARKER are kept for
rollback compatibility, vpn::get_nostr_vpn_status is surface-area
for a not-yet-landed RPC.
cargo fmt --check, cargo clippy --all-targets --all-features
-- -D warnings, and cargo test --all-features now all pass locally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 17:23:46 -04:00
|
|
|
info!(
|
|
|
|
|
"Server listening on http://{} (startup: {}ms)",
|
|
|
|
|
addr, startup_ms
|
|
|
|
|
);
|
2026-03-11 11:11:02 +00:00
|
|
|
info!("RPC API: http://{}/rpc/v1", addr);
|
|
|
|
|
info!("WebSocket: ws://{}/ws", addr);
|
|
|
|
|
|
2026-03-14 02:54:59 +00:00
|
|
|
// Notify systemd that we're ready (Type=notify)
|
2026-03-14 04:30:57 +00:00
|
|
|
// Note: first param `false` keeps NOTIFY_SOCKET so watchdog pings work
|
|
|
|
|
let _ = sd_notify::notify(false, &[sd_notify::NotifyState::Ready]);
|
2026-03-14 02:54:59 +00:00
|
|
|
|
2026-03-14 04:30:57 +00:00
|
|
|
// Spawn systemd watchdog ping (WatchdogSec=300, ping every 120s)
|
2026-03-14 02:54:59 +00:00
|
|
|
tokio::spawn(async {
|
2026-03-14 04:30:57 +00:00
|
|
|
let mut interval = tokio::time::interval(std::time::Duration::from_secs(120));
|
2026-03-14 02:54:59 +00:00
|
|
|
loop {
|
|
|
|
|
interval.tick().await;
|
|
|
|
|
let _ = sd_notify::notify(false, &[sd_notify::NotifyState::Watchdog]);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
2026-03-11 11:11:02 +00:00
|
|
|
// Graceful shutdown: wait for SIGTERM or SIGINT
|
2026-03-21 01:54:35 +00:00
|
|
|
let mut sigterm = signal::unix::signal(signal::unix::SignalKind::terminate())
|
|
|
|
|
.context("Failed to register SIGTERM handler")?;
|
2026-04-22 19:20:13 -04:00
|
|
|
let shutdown_notify_for_signal = shutdown_notify.clone();
|
2026-03-21 01:54:35 +00:00
|
|
|
let shutdown = async move {
|
2026-03-11 11:11:02 +00:00
|
|
|
tokio::select! {
|
|
|
|
|
_ = signal::ctrl_c() => {
|
|
|
|
|
info!("Received SIGINT (Ctrl+C), initiating graceful shutdown...");
|
|
|
|
|
}
|
|
|
|
|
_ = sigterm.recv() => {
|
|
|
|
|
info!("Received SIGTERM, initiating graceful shutdown...");
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-04-22 19:20:13 -04:00
|
|
|
// Signal the boot reconciler (and any other subscribers) to stop.
|
|
|
|
|
// `notify_one` stores a permit if no task is currently parked on
|
|
|
|
|
// `notified()`, so we don't race the reconciler's reconcile_all pass.
|
|
|
|
|
shutdown_notify_for_signal.notify_one();
|
2026-03-11 11:11:02 +00:00
|
|
|
};
|
|
|
|
|
|
2026-04-19 04:21:20 -04:00
|
|
|
server.serve_with_shutdown(addr, shutdown).await?;
|
2026-01-24 22:59:20 +00:00
|
|
|
|
2026-03-11 11:11:02 +00:00
|
|
|
// Clean shutdown: remove PID marker so next startup doesn't trigger recovery
|
|
|
|
|
crash_recovery::remove_pid_marker(&config.data_dir).await;
|
2026-01-24 22:59:20 +00:00
|
|
|
|
2026-03-11 11:11:02 +00:00
|
|
|
info!("Archipelago shut down cleanly");
|
2026-04-22 03:52:22 -04:00
|
|
|
|
|
|
|
|
// Hard-exit after logging. All business state is persisted by now
|
|
|
|
|
// (connections drained, PID marker removed, disk flushes done via
|
|
|
|
|
// tokio::fs awaits). Letting tokio try to drop the runtime instead
|
|
|
|
|
// can stall for 15s+ on non-daemon OS threads we don't directly
|
|
|
|
|
// own (mdns_sd daemon, reqwest resolver pool, etc.) — long enough
|
|
|
|
|
// for systemd's TimeoutStopSec to SIGKILL us and mark the service
|
|
|
|
|
// Failed, which makes an otherwise-successful update look like a
|
|
|
|
|
// crash in `systemctl status`.
|
|
|
|
|
std::process::exit(0);
|
2026-01-24 22:59:20 +00:00
|
|
|
}
|