fix(bootstrap): deliver the tor-helper and nginx ws fix to EXISTING nodes
An OTA updates the binary and web assets — not /etc/nginx, and not /opt/archipelago/scripts. Auditing the 2026-08-09 fixes' delivery paths found two that would silently reach nobody already installed: - scripts/tor-helper.sh (reset-failed + truthful restart result) shipped only via ISO builds and manual deploys. Now embedded via include_str! like the doctor script, staged and installed at boot when the on-disk copy differs. - The /app/mempool/ nginx Upgrade/Connection headers existed only in repo snippet sources consumed at image build time. A boot repair now idempotently patches any mempool location block missing them — in the live vhost (archipelago-http, the one sites-enabled actually links to), the legacy file, and the installed snippet — and reloads nginx once. Without this, every fleet node's mempool UI keeps loading-but-never- connecting after the OTA that supposedly fixed it. Both are non-fatal boot repairs in the existing bootstrap chain, no-ops when everything is already current. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
44fbdd381b
commit
e898f138d1
@@ -166,11 +166,21 @@ pub async fn ensure_doctor_installed() {
|
||||
Ok(false) => debug!("/opt/archipelago/apps already populated (or no installer copy)"),
|
||||
Err(e) => warn!("Apps dir repair failed (non-fatal): {:#}", e),
|
||||
}
|
||||
match run_tor_helper_sync().await {
|
||||
Ok(true) => info!("tor-helper.sh synchronized with binary"),
|
||||
Ok(false) => debug!("tor-helper.sh already current"),
|
||||
Err(e) => warn!("tor-helper sync failed (non-fatal): {:#}", e),
|
||||
}
|
||||
match run_tor_torrc_repair().await {
|
||||
Ok(true) => info!("Tor healed at boot (torrc rebuilt and/or daemon restarted)"),
|
||||
Ok(false) => debug!("Tor healthy and torrc in sync — no heal needed"),
|
||||
Err(e) => warn!("Tor boot heal failed (non-fatal): {:#}", e),
|
||||
}
|
||||
match run_nginx_mempool_ws_repair().await {
|
||||
Ok(true) => info!("nginx mempool websocket headers repaired and reloaded"),
|
||||
Ok(false) => debug!("nginx mempool websocket headers already present"),
|
||||
Err(e) => warn!("nginx mempool ws repair failed (non-fatal): {:#}", e),
|
||||
}
|
||||
match run_polkit_networkmanager_repair().await {
|
||||
Ok(true) => info!(
|
||||
"Installed NetworkManager polkit rule for the archipelago user — Wi-Fi setup enabled"
|
||||
@@ -637,6 +647,84 @@ exit 2
|
||||
///
|
||||
/// Non-fatal and conservative: it only restarts Tor when the regenerated torrc
|
||||
/// differs from the live one, or Tor is not answering on 9050.
|
||||
/// The privileged Tor helper, embedded so the OTA actually delivers it.
|
||||
/// scripts/tor-helper.sh previously reached nodes only through ISO builds and
|
||||
/// manual deploys — the 2026-08-09 helper fix (reset-failed + truthful result)
|
||||
/// would have shipped to nobody. Same include_str! pattern as the doctor.
|
||||
const TOR_HELPER_SH: &str = include_str!("../../../scripts/tor-helper.sh");
|
||||
const TOR_HELPER_PATH: &str = "/opt/archipelago/scripts/tor-helper.sh";
|
||||
|
||||
async fn run_tor_helper_sync() -> Result<bool> {
|
||||
let current = tokio::fs::read_to_string(TOR_HELPER_PATH)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
if current == TOR_HELPER_SH {
|
||||
return Ok(false);
|
||||
}
|
||||
let staged = "/var/lib/archipelago/tor-config/tor-helper.staged";
|
||||
if let Some(dir) = Path::new(staged).parent() {
|
||||
tokio::fs::create_dir_all(dir).await.ok();
|
||||
}
|
||||
tokio::fs::write(staged, TOR_HELPER_SH)
|
||||
.await
|
||||
.context("stage tor-helper.sh")?;
|
||||
let script = format!(
|
||||
"set -eu\ninstall -m 0755 {staged} {dest}\nexit 0\n",
|
||||
staged = staged,
|
||||
dest = TOR_HELPER_PATH
|
||||
);
|
||||
host_sudo(&["sh", "-lc", &script])
|
||||
.await
|
||||
.context("install tor-helper.sh")?;
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Existing nodes' nginx configs never receive repo snippet fixes — the OTA
|
||||
/// updates the binary and web assets, not /etc/nginx. The mempool UI is
|
||||
/// websocket-driven, and every fleet node's /app/mempool/ proxy block strips
|
||||
/// the Upgrade handshake, so the page loads and never connects (three-layer
|
||||
/// outage, 2026-08-09). Idempotently add the two headers to any mempool block
|
||||
/// missing them, in every nginx file that has one, then reload once.
|
||||
async fn run_nginx_mempool_ws_repair() -> Result<bool> {
|
||||
let script = r#"
|
||||
set -eu
|
||||
changed=0
|
||||
for f in /etc/nginx/sites-available/archipelago-http \
|
||||
/etc/nginx/sites-available/archipelago \
|
||||
/etc/nginx/snippets/archipelago-https-app-proxies.conf; do
|
||||
[ -f "$f" ] || continue
|
||||
grep -q 'location /app/mempool/' "$f" || continue
|
||||
python3 - "$f" <<'PYEOF'
|
||||
import re, sys
|
||||
p = sys.argv[1]
|
||||
src = open(p).read()
|
||||
def fix(m):
|
||||
b = m.group(0)
|
||||
if 'Upgrade $http_upgrade' in b:
|
||||
return b
|
||||
return b.replace('proxy_http_version 1.1;',
|
||||
'proxy_http_version 1.1;\n proxy_set_header Upgrade $http_upgrade;\n proxy_set_header Connection "upgrade";', 1)
|
||||
new = re.sub(r'location /app/mempool/ \{[^}]*\}', fix, src, flags=re.S)
|
||||
if new != src:
|
||||
open(p, 'w').write(new)
|
||||
sys.exit(3)
|
||||
PYEOF
|
||||
rc=$?
|
||||
[ "$rc" = 3 ] && changed=1
|
||||
[ "$rc" = 0 ] || [ "$rc" = 3 ] || exit "$rc"
|
||||
done
|
||||
if [ "$changed" = 1 ]; then
|
||||
nginx -t >/dev/null 2>&1 && systemctl reload nginx || true
|
||||
exit 3
|
||||
fi
|
||||
exit 0
|
||||
"#;
|
||||
let status = host_sudo(&["sh", "-lc", script])
|
||||
.await
|
||||
.context("nginx mempool ws repair")?;
|
||||
Ok(status.code() == Some(3))
|
||||
}
|
||||
|
||||
async fn run_tor_torrc_repair() -> Result<bool> {
|
||||
// Same location the RPC handlers use (Config::data_dir); bootstrap runs
|
||||
// before the server owns a Config, and this path is fixed on real installs.
|
||||
|
||||
Reference in New Issue
Block a user