security: remove all infrastructure and internal process material from the repo
Demo images / Build & push demo images (push) Failing after 2m13s
Demo images / Build & push demo images (push) Failing after 2m13s
The repo is source code and guidelines only. Nothing about how Archipelago's own fleet is run, or how the team works, stays in it. Untracked (kept on disk, gitignored) — 250 files: - .planning/ (199) and loop/ — internal development process - fleet operations tooling that targets specific nodes: deploy-to-target, deploy-tailscale, deploy-config-defaults, setup-target-dev, setup-aiui-server, setup-https-dev, debug-frontend, node-profile, fleet-fips-pair/unpair, image-recipe/sync-from-live.sh - image-recipe/INTEGRATION-GUIDE.md and docs/multinode-testing-plan.md, both of which are live-server workflow and fleet node inventories - the Phase 10 on-node verification and evidence records, which cite .planning/ as their evidence base KEY-05-ENTROPY-ENFORCEMENT.md was initially moved out with the other Phase 10 docs and then put back: it is cited as normative rationale from ten places in the codebase, including core/clippy.toml, which bans rand::thread_rng and points at it for the reason. That makes it a guideline, not an internal record. Node names removed from source (48 occurrences across comments, manifests and test fixtures): archi-dev-box, archy-x250*, shorty-s, framework-pt, zaza-optiplex, archi-thinkpad. Comments keep the engineering context and the date, which is what carried the meaning; the machine name did not. Three of those were live test values rather than comments and were replaced with valid stand-ins, not prose: two mDNS hostnames and a mesh peer name. An earlier pass substituted "a test node" into a hostname assertion, producing an invalid hostname; caught and fixed as test-node.local. Wipe mechanism: .local-only/manifest.txt inventories every local-only path and .local-only/wipe.sh deletes them on one confirmation, refusing to touch anything git still tracks. Both are themselves untracked, so the public repo does not carry a map of internal filenames. Verified: cargo check -p archipelago --all-features clean; archipelago-container 75/75 tests pass; appOrigin vitest 7/7; audit-secrets 5/5; every relative link in tracked markdown resolves (0 broken). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
cc00884b98
commit
6ba0599639
@@ -641,7 +641,7 @@ impl ApiHandler {
|
||||
// proxy INTO it, it binds 0.0.0.0, and it is explicitly on the
|
||||
// fips0 mesh allowlist (fips/app_ports.rs). So an unauthenticated
|
||||
// GET from any mesh peer, LAN host or Tailscale peer returned the
|
||||
// admin macaroon. Verified live on archi-dev-box 2026-08-02.
|
||||
// admin macaroon. Verified live on a test node 2026-08-02.
|
||||
//
|
||||
// The lesson generalises: an auth check performed by one reverse
|
||||
// proxy is not an auth check, because it only holds for traffic that
|
||||
|
||||
@@ -120,7 +120,7 @@ async fn stream_lnd_transactions(sm: &crate::state::StateManager) -> Result<()>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// LND wedge watchdog (2026-07-22, "100% uptime"): framework-pt's LND sat
|
||||
/// LND wedge watchdog (2026-07-22, "100% uptime"): a test node's LND sat
|
||||
/// for 14 HOURS with its RPC answering but the server never finishing
|
||||
/// startup — synced_to_chain=false, zero peers, every channel inactive —
|
||||
/// and nothing noticed until a human tried to open a channel. The wedge
|
||||
|
||||
@@ -80,7 +80,7 @@ impl RpcHandler {
|
||||
// a couple of seconds and still get their answer in one round trip,
|
||||
// while a slow multi-hop route flips the UI into its "settling…"
|
||||
// polling state (lnd.paymentstatus every 3s) after ~8s instead of
|
||||
// freezing the modal for two minutes with no feedback (framework-pt
|
||||
// freezing the modal for two minutes with no feedback (a test node
|
||||
// user report, 2026-07-29).
|
||||
let pay_client = reqwest::Client::builder()
|
||||
.no_proxy()
|
||||
|
||||
@@ -196,7 +196,7 @@ impl RpcHandler {
|
||||
// input to create sweep tx: input_sum=0 BTC, output_sum=…").
|
||||
// input_sum=0 with a tiny output means the wallet's coins are
|
||||
// unconfirmed or below Bitcoin's dust minimum — say that
|
||||
// (framework-pt sweep of 92 sats, 2026-08-06).
|
||||
// (a test node sweep of 92 sats, 2026-08-06).
|
||||
if msg.contains("insufficient input to create sweep tx") {
|
||||
return Err(anyhow::anyhow!(
|
||||
"Failed to send: your on-chain balance is too small or still \
|
||||
@@ -566,7 +566,7 @@ impl RpcHandler {
|
||||
|
||||
// See `build_invoice_request_body` for why `private: true` is
|
||||
// unconditional. This is the wallet UI's Receive flow / the
|
||||
// `lnd.createinvoice` RPC — the bug diagnosed on archy-x250-mad2.
|
||||
// `lnd.createinvoice` RPC — the bug diagnosed on a test node.
|
||||
let invoice_body = build_invoice_request_body(amount_sats, memo);
|
||||
|
||||
let resp = match client
|
||||
|
||||
@@ -81,7 +81,7 @@ pub(super) fn sanitize_error_message(msg: &str) -> String {
|
||||
"Insufficient funds",
|
||||
// On-chain send/sweep refusals from LND ("Failed to send: your
|
||||
// on-chain balance is too small or still unconfirmed to sweep…").
|
||||
// Masking sent the operator to journalctl again (framework-pt
|
||||
// Masking sent the operator to journalctl again (a test node
|
||||
// sweep, 2026-08-06) — same lesson as the two above.
|
||||
"Failed to send",
|
||||
// A frontend newer than the daemon calls methods it doesn't have.
|
||||
@@ -201,7 +201,7 @@ mod sanitize_tests {
|
||||
fn lightning_payment_errors_pass_through() {
|
||||
// LND's payment-failure reasons are written for the payer — masking
|
||||
// "invoice expired" as "Check server logs" left a user retrying a
|
||||
// dead invoice (framework-pt, 2026-07-23).
|
||||
// dead invoice (a test node, 2026-07-23).
|
||||
for msg in [
|
||||
"Payment failed: this invoice has expired (Valid until 2026-07-23 07:41:42 +0000 UTC). Ask the recipient for a fresh invoice and try again.",
|
||||
"Payment failed: unable to find a path to destination",
|
||||
|
||||
@@ -2240,7 +2240,7 @@ async fn cleanup_stale_pasta_port(port: &str) {
|
||||
// NEVER kill our own process. The daemon holds catalog app ports over
|
||||
// IPv6 (the mesh app-port relay), so a blunt `fuser -k <port>/tcp` would
|
||||
// terminate archipelago itself mid-install — installs failed and apps
|
||||
// vanished on framework-pt 2026-07-27. Kill every listener on the port
|
||||
// vanished on a test node 2026-07-27. Kill every listener on the port
|
||||
// EXCEPT our PID (and our process group), leaving the relay/daemon alive.
|
||||
let self_pid = std::process::id();
|
||||
let kill_listener = format!(
|
||||
|
||||
@@ -131,7 +131,7 @@ rest:
|
||||
Authorization: "Bearer {token}"
|
||||
# 15s: the mesh-message sensor only carries the LATEST message id, so two
|
||||
# messages inside one poll window coalesce and only the newest announces
|
||||
# (observed on framework-pt 2026-07-22). Halving the window halves both
|
||||
# (observed on a test node 2026-07-22). Halving the window halves both
|
||||
# the announce lag and the coalescing odds; the endpoint is local + cheap.
|
||||
scan_interval: 15
|
||||
sensor:
|
||||
@@ -352,7 +352,7 @@ const MESH_ANNOUNCE_AUTOMATION_ID: &str = "archy_mesh_announce";
|
||||
/// state before the first-ever received message (and again whenever the mesh
|
||||
/// message store restarts empty), and `unavailable` follows any endpoint
|
||||
/// blip — blocking those swallowed the first DM a fresh node ever received
|
||||
/// (framework-pt, 2026-07-22). HA restarts still can't re-announce an old
|
||||
/// (a test node, 2026-07-22). HA restarts still can't re-announce an old
|
||||
/// message: rest sensors don't restore state, so the post-restart transition
|
||||
/// is always from `unknown`.
|
||||
const MESH_ANNOUNCE_AUTOMATION: &str = r#"- id: archy_mesh_announce
|
||||
@@ -724,7 +724,7 @@ async fn seed_assist_pipeline(storage: &std::path::Path, claude_entity: Option<&
|
||||
///
|
||||
/// HA's zeroconf discovery stores a satellite as a fixed LAN IP. DHCP
|
||||
/// renumbering — or the whole node moving to a different network — strands
|
||||
/// the entry and the speaker silently drops (framework-pt 2026-07-23: entry
|
||||
/// the entry and the speaker silently drops (a test node 2026-07-23: entry
|
||||
/// pinned to 192.168.1.241 while the LAN had become 192.168.63.0/24). HA
|
||||
/// never re-resolves on its own. This keeper probes each satellite entry and,
|
||||
/// when one stops answering, sweeps the node's local /24s for the same
|
||||
|
||||
@@ -155,7 +155,7 @@ pub(super) fn detect_hidden_service_base() -> String {
|
||||
///
|
||||
/// Derived at runtime, never hardcoded: `archy-net` is created without an
|
||||
/// explicit subnet (see docker-compose.yml), so podman allocates one — it is
|
||||
/// 10.89.0.0/24 on archi-dev-box but there is no guarantee of that on another
|
||||
/// 10.89.0.0/24 on a test node but there is no guarantee of that on another
|
||||
/// node, and a hardcoded guess would fail silently by binding SOCKS to an
|
||||
/// address no container can reach.
|
||||
///
|
||||
|
||||
@@ -86,7 +86,7 @@ impl PortMap {
|
||||
/// thing that can publish a port: the FIPS mesh relay bridges the fips0
|
||||
/// ULA to `127.0.0.1` for a static port list, and it forwarded nbxplorer
|
||||
/// 32838 — declared `local` and pinned to loopback — to the mesh
|
||||
/// unauthenticated (archi-dev-box 2026-08-04). Anything that republishes
|
||||
/// unauthenticated (test node 2026-08-04). Anything that republishes
|
||||
/// a loopback port must consult this set first.
|
||||
pub fn is_declared_local(&self, port: u16) -> bool {
|
||||
self.local.contains(&port)
|
||||
@@ -135,7 +135,7 @@ fn manifest_icon(manifest: &AppManifest) -> Option<String> {
|
||||
/// from disk alone made the gate act on policy the node was no longer
|
||||
/// running: the catalog declared nbxplorer `auth: local` and pinned it to
|
||||
/// loopback, the stale disk manifest declared nothing, and the gate
|
||||
/// externally bound a deliberately host-local port (archi-dev-box
|
||||
/// externally bound a deliberately host-local port (a test node
|
||||
/// 2026-08-04).
|
||||
///
|
||||
/// After the catalog, the first directory that yields a manifest for an app
|
||||
@@ -268,7 +268,7 @@ fn classify_manifest(manifest: &AppManifest, map: &mut PortMap) {
|
||||
// and a node's installed manifests always lag the
|
||||
// repo. Binding those externally published Bitcoin
|
||||
// RPC across the LAN within seconds of deploy
|
||||
// (archi-dev-box 2026-08-03). Taking over a port is
|
||||
// (test node 2026-08-03). Taking over a port is
|
||||
// opt-in only: `auth: gated`, shipped in the same
|
||||
// manifest edit as the loopback pin.
|
||||
if port
|
||||
@@ -446,7 +446,7 @@ app:
|
||||
/// This is not hypothetical. `session` is the default, so it is what
|
||||
/// every un-migrated manifest carries, and a node's installed manifests
|
||||
/// always lag the repo. An earlier revision gated these regardless of
|
||||
/// `bind`, and within seconds of deploying to archi-dev-box the daemon
|
||||
/// `bind`, and within seconds of deploying to a test node the daemon
|
||||
/// had published Bitcoin's loopback-only RPC 8332 on the LAN, Tailscale
|
||||
/// and IPv6 addresses. Taking over a port must be opt-in.
|
||||
#[test]
|
||||
|
||||
@@ -155,7 +155,7 @@ pub async fn run(
|
||||
// RELEASED when its port leaves the gated set — a catalog refresh
|
||||
// declaring a port `local`/`none` must make the gate let go without a
|
||||
// daemon restart, or the stale bind keeps republishing a port the
|
||||
// catalog just withdrew (nbxplorer 32838, archi-dev-box 2026-08-04).
|
||||
// catalog just withdrew (nbxplorer 32838, a test node 2026-08-04).
|
||||
let mut held: HashMap<(u16, IpAddr), tokio::task::JoinHandle<()>> = HashMap::new();
|
||||
let mut interval = tokio::time::interval(SWEEP_INTERVAL);
|
||||
interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
|
||||
|
||||
@@ -528,7 +528,7 @@ fn icon_markup(app: &GatedPort) -> String {
|
||||
// A manifest that names no icon still gets one: the dashboard already
|
||||
// ships icons named after the app, so fall back to those before
|
||||
// giving up. Without this EVERY gated app showed a lettermark,
|
||||
// because no manifest declares metadata.icon (archi-dev-box,
|
||||
// because no manifest declares metadata.icon (test node,
|
||||
// 2026-08-05).
|
||||
.or_else(|| {
|
||||
icon_candidates(&app.app_id)
|
||||
|
||||
@@ -225,7 +225,7 @@ pub fn catalog_manifest_values() -> Vec<(String, serde_json::Value)> {
|
||||
/// same origin. They diverged once — the orchestrator published containers
|
||||
/// from the catalog while the gate classified from stale disk manifests, and
|
||||
/// the gate externally bound a port the catalog had declared `auth: local`
|
||||
/// (nbxplorer 32838, archi-dev-box 2026-08-04).
|
||||
/// (nbxplorer 32838, a test node 2026-08-04).
|
||||
pub fn catalog_manifest_overlay(
|
||||
app_id: &str,
|
||||
value: serde_json::Value,
|
||||
|
||||
@@ -31,7 +31,7 @@ server {
|
||||
# on the fips0 mesh allowlist (fips/app_ports.rs), so any mesh peer, LAN
|
||||
# host or Tailscale peer could POST authenticated Bitcoin Core RPC —
|
||||
# including wallet methods, with a wallet loaded. Verified live on
|
||||
# archi-dev-box 2026-08-02.
|
||||
# a test node 2026-08-02.
|
||||
#
|
||||
# `Access-Control-Allow-Origin *` is also removed: paired with a proxy that
|
||||
# injects credentials it let any web page a user visited drive this RPC.
|
||||
|
||||
@@ -122,7 +122,7 @@ impl BootReconciler {
|
||||
// A failed repair can involve registry pulls and full
|
||||
// image builds; retrying every 30s hammered unreachable
|
||||
// registries ~174×/image/day on an offline node
|
||||
// (archy-x250-dev log sweep, 2026-07-22). Back off
|
||||
// (a test node log sweep, 2026-07-22). Back off
|
||||
// exponentially while rounds keep failing — 30s doubling
|
||||
// to a 1h cap — and reset the moment a round is clean.
|
||||
failure_rounds = if failures.is_empty() {
|
||||
|
||||
@@ -40,7 +40,7 @@ const COMPANION_PULL_TIMEOUT: Duration = Duration::from_secs(300);
|
||||
/// After a failed repair (image build/pull included), leave the companion
|
||||
/// alone for this long. Without it, a node under IO pressure retried a 900s
|
||||
/// image build every 30s reconcile tick — each build pegging the disk that
|
||||
/// made the probes fail in the first place (live-diagnosed on zaza-optiplex
|
||||
/// made the probes fail in the first place (live-diagnosed on a test node
|
||||
/// 2026-07-28: load 50, podman scans starved, apps page stuck).
|
||||
const REPAIR_COOLDOWN: Duration = Duration::from_secs(600);
|
||||
|
||||
@@ -219,7 +219,7 @@ pub async fn install_one(spec: &CompanionSpec) -> Result<()> {
|
||||
// `ensure_image_present` rebuilds in place under the same tag, so the unit
|
||||
// body is byte-identical, `write_if_changed` reports no change, and
|
||||
// `enable_now` is a no-op on a running service — the container keeps the
|
||||
// old layers indefinitely. That is exactly how archi-dev-box kept serving
|
||||
// old layers indefinitely. That is exactly how a test node kept serving
|
||||
// the LND, FIPS, Electrs and Guardian screens on 0.0.0.0 after v1.7.123
|
||||
// rebuilt every one of those images to bind loopback: the images were
|
||||
// correct on disk and the running containers were three days old
|
||||
|
||||
@@ -292,7 +292,7 @@ async fn chown_for_rootless_container(uid_gid: &str, path: &str) -> Result<()> {
|
||||
// the subuid range first: `sudo chown 999` writes literal host uid 999,
|
||||
// which maps to nobody inside the userns — the app then can't open its
|
||||
// own files while the chown reported success (botfights SQLITE_CANTOPEN
|
||||
// crash-loop, framework-pt 2026-08-06). Container uid N (N>=1) lives at
|
||||
// crash-loop, a test node 2026-08-06). Container uid N (N>=1) lives at
|
||||
// subuid_base + N - 1; the fleet provisions base 100000. uid 0 and
|
||||
// already-mapped ids (>=100000) pass through untouched.
|
||||
let host_uid_gid = if uid > 0 && uid < 100_000 {
|
||||
@@ -2035,7 +2035,7 @@ impl ProdContainerOrchestrator {
|
||||
// The marker says "removed", but the container can still be
|
||||
// RUNNING: a Quadlet unit is owned by systemd, which starts it
|
||||
// on boot entirely independently of this reconciler. On
|
||||
// archi-dev-box (2026-08-02) `bitcoin-ui` sat in this exact
|
||||
// a test node (2026-08-02) `bitcoin-ui` sat in this exact
|
||||
// state — marker set, `archy-bitcoin-ui.service` active, port
|
||||
// 8334 published — and so it never received the /bitcoin-rpc/
|
||||
// auth_request gate that a05956c4 shipped INSIDE the binary.
|
||||
@@ -3423,7 +3423,7 @@ impl ProdContainerOrchestrator {
|
||||
// Mark the app so the drift check below recreates it even though
|
||||
// it is restart-sensitive. Without this the unit is rewritten but
|
||||
// never restarted, and the gateway keeps serving the compromised
|
||||
// credential indefinitely (observed on archi-dev-box 2026-08-01).
|
||||
// credential indefinitely (observed on a test node 2026-08-01).
|
||||
self.credential_rotated
|
||||
.lock()
|
||||
.await
|
||||
@@ -5430,7 +5430,7 @@ app:
|
||||
/// holding the old value, so the rotation must flag the app for recreate.
|
||||
/// Without the flag the drift check skips it as restart-sensitive and the
|
||||
/// gateway keeps serving the published default forever — observed on
|
||||
/// archi-dev-box 2026-08-01 before this was wired up.
|
||||
/// a test node 2026-08-01 before this was wired up.
|
||||
#[tokio::test]
|
||||
async fn rotating_a_compromised_credential_flags_the_app_for_recreate() {
|
||||
let rt = Arc::new(MockRuntime::default());
|
||||
@@ -6238,7 +6238,7 @@ app:
|
||||
|
||||
#[tokio::test]
|
||||
async fn reconcile_rewrites_security_config_for_a_user_uninstalled_but_running_app() {
|
||||
// Regression, archi-dev-box 2026-08-02: `bitcoin-ui` carried a durable
|
||||
// Regression, a test node 2026-08-02: `bitcoin-ui` carried a durable
|
||||
// user-uninstalled marker WHILE systemd/Quadlet kept archy-bitcoin-ui
|
||||
// running and publishing :8334. Reconcile returned on the marker before
|
||||
// reaching the pre-start hook, so the /bitcoin-rpc/ auth_request gate
|
||||
|
||||
@@ -237,7 +237,7 @@ pub async fn install(identity_dir: &Path) -> Result<()> {
|
||||
// out-of-band) default-denies inbound on fips0 — without an explicit
|
||||
// allowance the node's web UI is unreachable over the mesh (phones got
|
||||
// RST on :80 with a healthy session; root-caused 2026-07-26 on
|
||||
// framework-pt). Ship the allowance as a fips.d drop-in on every
|
||||
// a test node). Ship the allowance as a fips.d drop-in on every
|
||||
// install/upgrade so no node ever regresses to a UI-less mesh.
|
||||
sudo_install_dir("/etc/fips/fips.d").await?;
|
||||
// PEER_PORT (5679) carries ALL federation sync, cloud browse/download,
|
||||
|
||||
@@ -207,7 +207,7 @@ async fn main() -> Result<()> {
|
||||
// that window (a superseding start/restart, a start-timeout) killed a
|
||||
// half-started instance, which then exited 0 and (under the old
|
||||
// Restart=on-failure) never came back: "server starting up" forever,
|
||||
// reproduced on framework-pt installing apps on 2026-07-26. The daemon's
|
||||
// reproduced on a test node installing apps on 2026-07-26. The daemon's
|
||||
// real work (recovery, reconcile, listener) continues after READY; being
|
||||
// "active" early is honest — the process is up and doing its job.
|
||||
let _ = sd_notify::notify(false, &[sd_notify::NotifyState::Ready]);
|
||||
|
||||
@@ -1030,7 +1030,7 @@ pub(super) async fn run_mesh_session(
|
||||
// The sequence is raced against the shutdown signal: probes/handshakes
|
||||
// can take 10s+, and without this a stop() issued mid-probe (config
|
||||
// change, disable, rename) always burned the full listener-shutdown
|
||||
// timeout and ended in a hard abort — observed live on archi-dev-box
|
||||
// timeout and ended in a hard abort — observed live on a test node
|
||||
// 2026-07-28. Dropping the open future mid-probe is safe: it holds no
|
||||
// session state yet and the port guard/serial handle close with it.
|
||||
let open_fut = async {
|
||||
|
||||
@@ -2547,8 +2547,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_group_peer_twins_collapses_radio_and_federation() {
|
||||
let radio = mk_peer(42, "Archy-X250-EXP", Some("ABCD"), false);
|
||||
let fed = mk_peer(0x8000_0001, "Archy-X250-EXP", Some("abcd"), true);
|
||||
let radio = mk_peer(42, "Test-Peer-1", Some("ABCD"), false);
|
||||
let fed = mk_peer(0x8000_0001, "Test-Peer-1", Some("abcd"), true);
|
||||
let groups = group_peer_twins(&[radio, fed]);
|
||||
assert_eq!(groups.len(), 1, "twins must collapse to one conversation");
|
||||
let g = &groups[0];
|
||||
|
||||
@@ -134,7 +134,7 @@ fn daemon_program() -> (String, Option<String>) {
|
||||
/// ship the archipelago binary ahead of the packaged daemon tools, so a fleet
|
||||
/// node can run a new binary against an old daemon — whose argparse EXITS on
|
||||
/// an unknown flag, killing the mesh session on every spawn (live regression,
|
||||
/// framework-pt on v1.7.117). Probe `--help` and only pass the flag when the
|
||||
/// a test node on v1.7.117). Probe `--help` and only pass the flag when the
|
||||
/// daemon advertises it; an old daemon then runs edge-only exactly as before.
|
||||
async fn daemon_supports_enable_transport() -> bool {
|
||||
let (program, script) = daemon_program();
|
||||
@@ -602,7 +602,7 @@ impl ReticulumLink {
|
||||
self.prefix_to_hash.insert(prefix, hash);
|
||||
// Heal names persisted by pre-2026-07-28 builds, which could
|
||||
// store a raw `ARCHY:…` identity blob as the display name (seen
|
||||
// live on archi-dev-box). Blob-only announces assert no name, so
|
||||
// live on a test node). Blob-only announces assert no name, so
|
||||
// nothing would ever overwrite it — swap in the placeholder.
|
||||
let display_name = if p.display_name.starts_with("ARCHY:") {
|
||||
format!("Reticulum {}", hex::encode(&hash[..4]))
|
||||
|
||||
@@ -625,7 +625,7 @@ pub async fn detect_serial_devices_info() -> Vec<DetectedDeviceInfo> {
|
||||
// mtime here each probe/session open minted a "new" plugged_at, the
|
||||
// UI's (path, plugged_at) dismissal key never matched again, and the
|
||||
// setup modal re-fired forever on a device that never left the port
|
||||
// (observed live on archi-dev-box 2026-07-28). btime/ctime only
|
||||
// (observed live on a test node 2026-07-28). btime/ctime only
|
||||
// change when udev (re)creates/chowns the node — i.e. on real plugs.
|
||||
let plugged_at = tokio::fs::metadata(&path).await.ok().and_then(|m| {
|
||||
m.created()
|
||||
|
||||
@@ -587,7 +587,7 @@ impl Server {
|
||||
// FIPS-fast-failing/Tor path so they add us back.
|
||||
// Without this, a node that joined everyone stays
|
||||
// invisible to the whole fleet until a manual
|
||||
// re-add (the "X250-EXP missing everywhere" case).
|
||||
// re-add (the "peer missing everywhere" case).
|
||||
let they_list_us =
|
||||
state.federated_peers.iter().any(|h| h.did == local_did);
|
||||
if !they_list_us && !local_onion.is_empty() {
|
||||
@@ -1195,7 +1195,7 @@ async fn app_port_v6_relay_loop(mut shutdown_rx: tokio::sync::watch::Receiver<bo
|
||||
// * `auth: local` — host-local BY INTENT. Bridging one makes a
|
||||
// port reachable from the whole mesh that was deliberately
|
||||
// never externally reachable: nbxplorer 32838 answered HTTP
|
||||
// 200 over the mesh with no credential (archi-dev-box
|
||||
// 200 over the mesh with no credential (a test node
|
||||
// 2026-08-04) purely because it appeared in the static port
|
||||
// list below.
|
||||
//
|
||||
@@ -1233,7 +1233,7 @@ async fn app_port_v6_relay_loop(mut shutdown_rx: tokio::sync::watch::Receiver<bo
|
||||
// install's port-free step (`fuser -k <port>/tcp`) then
|
||||
// kills THIS daemon, which holds the port — the exact
|
||||
// cause of installs failing + apps vanishing on
|
||||
// framework-pt 2026-07-27. No v4 listener → skip; the
|
||||
// a test node 2026-07-27. No v4 listener → skip; the
|
||||
// next rescan picks it up once the app is up.
|
||||
let v4_up = tokio::time::timeout(
|
||||
std::time::Duration::from_millis(300),
|
||||
|
||||
@@ -557,7 +557,7 @@ pub enum PortAuth {
|
||||
/// gated any `session` port regardless of `bind`, which meant a node
|
||||
/// whose manifests had not yet been updated saw the daemon publish
|
||||
/// Bitcoin's loopback-only RPC on every host address (caught on
|
||||
/// archi-dev-box 2026-08-03, seconds after deploy). Requiring the
|
||||
/// a test node 2026-08-03, seconds after deploy). Requiring the
|
||||
/// manifest to say so means the loopback pin and the daemon takeover
|
||||
/// ship together, atomically, and a stale manifest fails safe.
|
||||
Gated,
|
||||
@@ -1380,7 +1380,7 @@ impl HostFacts {
|
||||
pub fn sample() -> Self {
|
||||
Self {
|
||||
host_ip: "192.168.1.116".to_string(),
|
||||
host_mdns: "archi-thinkpad.local".to_string(),
|
||||
host_mdns: "test-node.local".to_string(),
|
||||
disk_gb: 2000,
|
||||
bitcoin_host: "bitcoin-knots".to_string(),
|
||||
}
|
||||
@@ -2306,13 +2306,13 @@ app:
|
||||
};
|
||||
let facts = HostFacts {
|
||||
host_ip: "192.168.1.116".to_string(),
|
||||
host_mdns: "archi-thinkpad.local".to_string(),
|
||||
host_mdns: "test-node.local".to_string(),
|
||||
disk_gb: 2000,
|
||||
bitcoin_host: "bitcoin-core".to_string(),
|
||||
};
|
||||
|
||||
let out = c.resolve_derived_env(&facts);
|
||||
assert_eq!(out[0], "FM_API_URL=ws://archi-thinkpad.local:8174");
|
||||
assert_eq!(out[0], "FM_API_URL=ws://test-node.local:8174");
|
||||
assert_eq!(out[1], "INFO=192.168.1.116-2000");
|
||||
assert_eq!(out[2], "CORE_RPC_HOST=bitcoin-core");
|
||||
}
|
||||
|
||||
@@ -329,7 +329,7 @@ impl PodmanClient {
|
||||
// disagree. The earlier attempt put this decision in manifest
|
||||
// data instead, and a node whose manifests lagged the binary
|
||||
// published Bitcoin's loopback-only RPC across the LAN
|
||||
// (archi-dev-box, 2026-08-03).
|
||||
// (test node, 2026-08-03).
|
||||
//
|
||||
// A port that already declares a bind is never overridden — that
|
||||
// is exactly what keeps `bind: 127.0.0.1` ports host-local and
|
||||
@@ -343,7 +343,7 @@ impl PodmanClient {
|
||||
// * `bind` is already honoured by every publish path (here and
|
||||
// in package::install), so a manifest edit needs no code.
|
||||
// * inference here would cover only THIS path — proven on
|
||||
// archi-dev-box, where a recreate went through another one and
|
||||
// a test node, where a recreate went through another one and
|
||||
// the pin never applied.
|
||||
// * and inferring from an ABSENT field is what republished
|
||||
// Bitcoin's loopback RPC across the LAN, and came within one
|
||||
|
||||
Reference in New Issue
Block a user