Compare commits

..
10 changed files with 177 additions and 61 deletions
+18
View File
@@ -1,5 +1,23 @@
# Changelog # Changelog
## v1.7.64-alpha (2026-05-18)
- Update apply rate limiting is relaxed for authenticated admins from 2 attempts per 10 minutes to 10 attempts per minute, preventing the System Update page from getting stuck behind `429 Too Many Requests` during legitimate OTA retry/troubleshooting flows.
- The corrected backend artifact rebuild protection from `v1.7.63-alpha` remains in place, so this release is built from a fresh Rust backend binary before publishing.
## v1.7.63-alpha (2026-05-18)
- Release automation now rebuilds the Rust backend after bumping the version and before hashing release artifacts, preventing OTA manifests from pointing at a stale backend binary.
- This corrected release carries the Nginx Proxy Manager stale-port repair in an updated backend binary, so nodes running `1.7.61-alpha` can actually receive and execute the fix.
- Validation confirmed the previously published `v1.7.62-alpha` backend artifact still contained `1.7.61-alpha`, explaining why nodes did not advance after applying that update.
## v1.7.62-alpha (2026-05-18)
- Nginx Proxy Manager start and restart now repair stale Podman containers that still publish the admin UI on host port `81`, which conflicts with host nginx on updated nodes.
- The repair recreates only the stale Nginx Proxy Manager container metadata while preserving `/var/lib/archipelago/nginx-proxy-manager` data and using the current `8081:81`, `8084:80`, and `8444:443` mappings.
- Runtime stale-listener cleanup for Nginx Proxy Manager is shared across start and restart paths so rootless port helper leftovers are still cleared before lifecycle retries.
- Validation passed with `cargo fmt --all --check --manifest-path core/Cargo.toml` and `cargo check -p archipelago --manifest-path core/Cargo.toml`.
## v1.7.61-alpha (2026-05-18) ## v1.7.61-alpha (2026-05-18)
- Multi-container stack installs now keep their app card in the `Installing` state for up to 20 minutes while dependency containers are being pulled and prepared. - Multi-container stack installs now keep their app card in the `Installing` state for up to 20 minutes while dependency containers are being pulled and prepared.
+1 -1
View File
@@ -80,7 +80,7 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]] [[package]]
name = "archipelago" name = "archipelago"
version = "1.7.61-alpha" version = "1.7.64-alpha"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"archipelago-container", "archipelago-container",
+1 -1
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "archipelago" name = "archipelago"
version = "1.7.61-alpha" version = "1.7.64-alpha"
edition = "2021" edition = "2021"
description = "Archipelago Bitcoin Node OS - Native backend" description = "Archipelago Bitcoin Node OS - Native backend"
authors = ["Archipelago Team"] authors = ["Archipelago Team"]
@@ -1,4 +1,7 @@
use super::config::{get_containers_for_app, get_data_dirs_for_app, is_valid_docker_image}; use super::config::{
get_app_capabilities, get_containers_for_app, get_data_dirs_for_app, get_health_check_args,
get_memory_limit, is_valid_docker_image,
};
use super::dependencies::ordered_containers_for_start; use super::dependencies::ordered_containers_for_start;
use super::install::install_log; use super::install::install_log;
use super::validation::validate_app_id; use super::validation::validate_app_id;
@@ -863,11 +866,98 @@ async fn repair_before_package_start(container_name: &str) {
repair_nextcloud_dirs().await; repair_nextcloud_dirs().await;
cleanup_stale_pasta_port("8085").await; cleanup_stale_pasta_port("8085").await;
} }
"nginx-proxy-manager" => repair_nginx_proxy_manager_container().await,
"gitea" => cleanup_gitea_stale_ports().await, "gitea" => cleanup_gitea_stale_ports().await,
_ => {} _ => {}
} }
} }
async fn repair_nginx_proxy_manager_container() {
if !nginx_proxy_manager_has_legacy_admin_port().await {
cleanup_nginx_proxy_manager_ports().await;
return;
}
install_log(
"START REPAIR: nginx-proxy-manager - recreating stale container using host port 8081",
)
.await;
let _ = podman_control(&["rm", "-f", "nginx-proxy-manager"]).await;
cleanup_nginx_proxy_manager_ports().await;
if let Err(err) = recreate_nginx_proxy_manager_container().await {
tracing::warn!(error = %err, "failed to recreate stale nginx-proxy-manager container");
}
}
async fn nginx_proxy_manager_has_legacy_admin_port() -> bool {
let Ok(output) = podman_control(&["port", "nginx-proxy-manager", "81/tcp"]).await else {
return false;
};
if !output.status.success() {
return false;
}
String::from_utf8_lossy(&output.stdout).lines().any(|line| {
line.rsplit(':')
.next()
.is_some_and(|port| port.trim() == "81")
})
}
async fn recreate_nginx_proxy_manager_container() -> Result<()> {
tokio::process::Command::new("sudo")
.args([
"mkdir",
"-p",
"/var/lib/archipelago/nginx-proxy-manager/data",
"/var/lib/archipelago/nginx-proxy-manager/letsencrypt",
])
.output()
.await
.context("failed to create nginx-proxy-manager data directories")?;
let image = crate::container::image_versions::pinned_image_for_app("nginx-proxy-manager")
.unwrap_or_else(|| "docker.io/jc21/nginx-proxy-manager:latest".to_string());
let mut args = vec![
"run".to_string(),
"-d".to_string(),
"--name".to_string(),
"nginx-proxy-manager".to_string(),
"--restart=unless-stopped".to_string(),
"--network=slirp4netns:allow_host_loopback=true".to_string(),
"--cap-drop=ALL".to_string(),
"--security-opt=no-new-privileges:true".to_string(),
"--pids-limit=4096".to_string(),
];
args.extend(get_app_capabilities("nginx-proxy-manager"));
args.extend([
"-p".to_string(),
"8081:81".to_string(),
"-p".to_string(),
"8084:80".to_string(),
"-p".to_string(),
"8444:443".to_string(),
"-v".to_string(),
"/var/lib/archipelago/nginx-proxy-manager/data:/data".to_string(),
"-v".to_string(),
"/var/lib/archipelago/nginx-proxy-manager/letsencrypt:/etc/letsencrypt".to_string(),
"--memory".to_string(),
get_memory_limit("nginx-proxy-manager").to_string(),
"--cpus=2".to_string(),
]);
args.extend(get_health_check_args("nginx-proxy-manager", ""));
args.push(image);
let refs = args.iter().map(String::as_str).collect::<Vec<_>>();
let output = podman_control(&refs).await?;
if !output.status.success() {
anyhow::bail!(
"podman run nginx-proxy-manager failed: {}",
String::from_utf8_lossy(&output.stderr).trim()
);
}
Ok(())
}
async fn ensure_runtime_host_port_listener(container_name: &str) -> Result<()> { async fn ensure_runtime_host_port_listener(container_name: &str) -> Result<()> {
let Some(port) = runtime_required_host_port(container_name) else { let Some(port) = runtime_required_host_port(container_name) else {
return Ok(()); return Ok(());
@@ -1075,15 +1165,17 @@ async fn cleanup_start_conflict(container_name: &str, stderr: &str) {
"homeassistant" | "home-assistant" => cleanup_stale_pasta_port("8123").await, "homeassistant" | "home-assistant" => cleanup_stale_pasta_port("8123").await,
"vaultwarden" => cleanup_stale_pasta_port("8082").await, "vaultwarden" => cleanup_stale_pasta_port("8082").await,
"nextcloud" => cleanup_stale_pasta_port("8085").await, "nextcloud" => cleanup_stale_pasta_port("8085").await,
"nginx-proxy-manager" => { "nginx-proxy-manager" => cleanup_nginx_proxy_manager_ports().await,
cleanup_stale_pasta_port("8081").await;
cleanup_stale_pasta_port("8084").await;
cleanup_stale_pasta_port("8444").await;
}
_ => {} _ => {}
} }
} }
async fn cleanup_nginx_proxy_manager_ports() {
cleanup_stale_pasta_port("8081").await;
cleanup_stale_pasta_port("8084").await;
cleanup_stale_pasta_port("8444").await;
}
async fn cleanup_stale_pasta_port(port: &str) { async fn cleanup_stale_pasta_port(port: &str) {
let kill_listener = format!( let kill_listener = format!(
"ss -ltnp 'sport = :{}' 2>/dev/null | sed -n 's/.*pid=\\([0-9]*\\).*/\\1/p' | xargs -r kill 2>/dev/null || true", "ss -ltnp 'sport = :{}' 2>/dev/null | sed -n 's/.*pid=\\([0-9]*\\).*/\\1/p' | xargs -r kill 2>/dev/null || true",
+4 -1
View File
@@ -80,7 +80,10 @@ impl EndpointRateLimiter {
limits.insert("backup.upload-s3".to_string(), (3, 600)); limits.insert("backup.upload-s3".to_string(), (3, 600));
limits.insert("backup.download-s3".to_string(), (3, 600)); limits.insert("backup.download-s3".to_string(), (3, 600));
// System operations // System operations
limits.insert("update.apply".to_string(), (2, 600)); // Update apply is an authenticated local admin action. Keep a guard
// against accidental button storms without locking operators out for
// ten minutes during OTA troubleshooting.
limits.insert("update.apply".to_string(), (10, 60));
limits.insert("system.reboot".to_string(), (2, 300)); limits.insert("system.reboot".to_string(), (2, 300));
limits.insert("system.shutdown".to_string(), (2, 300)); limits.insert("system.shutdown".to_string(), (2, 300));
// Password and TOTP changes // Password and TOTP changes
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "neode-ui", "name": "neode-ui",
"version": "1.7.61-alpha", "version": "1.7.64-alpha",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "neode-ui", "name": "neode-ui",
"version": "1.7.61-alpha", "version": "1.7.64-alpha",
"dependencies": { "dependencies": {
"@types/dompurify": "^3.0.5", "@types/dompurify": "^3.0.5",
"@vue-leaflet/vue-leaflet": "^0.10.1", "@vue-leaflet/vue-leaflet": "^0.10.1",
+1 -1
View File
@@ -1,7 +1,7 @@
{ {
"name": "neode-ui", "name": "neode-ui",
"private": true, "private": true,
"version": "1.7.61-alpha", "version": "1.7.64-alpha",
"type": "module", "type": "module",
"scripts": { "scripts": {
"start": "./start-dev.sh", "start": "./start-dev.sh",
+15 -17
View File
@@ -1,28 +1,26 @@
{ {
"version": "1.7.61-alpha", "version": "1.7.64-alpha",
"release_date": "2026-05-18", "release_date": "2026-05-17",
"changelog": [ "changelog": [
"Multi-container stack installs now keep their app card in the `Installing` state for up to 20 minutes while dependency containers are being pulled and prepared.", "Update apply rate limiting is relaxed for authenticated admins from 2 attempts per 10 minutes to 10 attempts per minute, preventing the System Update page from getting stuck behind `429 Too Many Requests` during legitimate OTA retry/troubleshooting flows.",
"BTCPay Server installs no longer appear to vanish or fail after two minutes while Postgres and NBXplorer are still being created before the primary `btcpay-server` container exists.", "The corrected backend artifact rebuild protection from `v1.7.63-alpha` remains in place, so this release is built from a fresh Rust backend binary before publishing."
"The stale-transition escape hatch remains short for start, stop, restart, update, and removal operations, so genuinely wedged lifecycle actions still recover quickly.",
"Live validation on `100.70.96.88` confirmed BTCPay Server completed installation and responds on port `23000` with the expected HTTP redirect."
], ],
"components": [ "components": [
{ {
"name": "archipelago", "name": "archipelago",
"current_version": "1.7.61-alpha", "current_version": "1.7.64-alpha",
"new_version": "1.7.61-alpha", "new_version": "1.7.64-alpha",
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.61-alpha/archipelago", "download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.64-alpha/archipelago",
"sha256": "5b08f66aa9a685475b86cdcbabe96c3132f3cc0e72d8daaeb0ddd52a01ca87b6", "sha256": "158a3cb659f82110780b0549382a9ac71320b1cb530e6a31a314959bbabd6c6c",
"size_bytes": 42738544 "size_bytes": 42935024
}, },
{ {
"name": "archipelago-frontend-1.7.61-alpha.tar.gz", "name": "archipelago-frontend-1.7.64-alpha.tar.gz",
"current_version": "1.7.61-alpha", "current_version": "1.7.64-alpha",
"new_version": "1.7.61-alpha", "new_version": "1.7.64-alpha",
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.61-alpha/archipelago-frontend-1.7.61-alpha.tar.gz", "download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.64-alpha/archipelago-frontend-1.7.64-alpha.tar.gz",
"sha256": "80c5bef31460ece8c1416a8f4e5e88626d1ef4103d40fec0e4872aafb3332dcc", "sha256": "d9be64980eede352c0b18402c42cff3a98a1ca881514082319d126cf4c91dcc3",
"size_bytes": 166470847 "size_bytes": 166470643
} }
] ]
} }
+15 -17
View File
@@ -1,28 +1,26 @@
{ {
"version": "1.7.61-alpha", "version": "1.7.64-alpha",
"release_date": "2026-05-18", "release_date": "2026-05-17",
"changelog": [ "changelog": [
"Multi-container stack installs now keep their app card in the `Installing` state for up to 20 minutes while dependency containers are being pulled and prepared.", "Update apply rate limiting is relaxed for authenticated admins from 2 attempts per 10 minutes to 10 attempts per minute, preventing the System Update page from getting stuck behind `429 Too Many Requests` during legitimate OTA retry/troubleshooting flows.",
"BTCPay Server installs no longer appear to vanish or fail after two minutes while Postgres and NBXplorer are still being created before the primary `btcpay-server` container exists.", "The corrected backend artifact rebuild protection from `v1.7.63-alpha` remains in place, so this release is built from a fresh Rust backend binary before publishing."
"The stale-transition escape hatch remains short for start, stop, restart, update, and removal operations, so genuinely wedged lifecycle actions still recover quickly.",
"Live validation on `100.70.96.88` confirmed BTCPay Server completed installation and responds on port `23000` with the expected HTTP redirect."
], ],
"components": [ "components": [
{ {
"name": "archipelago", "name": "archipelago",
"current_version": "1.7.61-alpha", "current_version": "1.7.64-alpha",
"new_version": "1.7.61-alpha", "new_version": "1.7.64-alpha",
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.61-alpha/archipelago", "download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.64-alpha/archipelago",
"sha256": "5b08f66aa9a685475b86cdcbabe96c3132f3cc0e72d8daaeb0ddd52a01ca87b6", "sha256": "158a3cb659f82110780b0549382a9ac71320b1cb530e6a31a314959bbabd6c6c",
"size_bytes": 42738544 "size_bytes": 42935024
}, },
{ {
"name": "archipelago-frontend-1.7.61-alpha.tar.gz", "name": "archipelago-frontend-1.7.64-alpha.tar.gz",
"current_version": "1.7.61-alpha", "current_version": "1.7.64-alpha",
"new_version": "1.7.61-alpha", "new_version": "1.7.64-alpha",
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.61-alpha/archipelago-frontend-1.7.61-alpha.tar.gz", "download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.64-alpha/archipelago-frontend-1.7.64-alpha.tar.gz",
"sha256": "80c5bef31460ece8c1416a8f4e5e88626d1ef4103d40fec0e4872aafb3332dcc", "sha256": "d9be64980eede352c0b18402c42cff3a98a1ca881514082319d126cf4c91dcc3",
"size_bytes": 166470847 "size_bytes": 166470643
} }
] ]
} }
+22 -15
View File
@@ -28,11 +28,12 @@ for arg in "$@"; do
echo "Steps performed:" echo "Steps performed:"
echo " 1. Validate version format (SemVer)" echo " 1. Validate version format (SemVer)"
echo " 2. Bump version in Cargo.toml and package.json" echo " 2. Bump version in Cargo.toml and package.json"
echo " 3. Build frontend" echo " 3. Build backend"
echo " 4. Generate changelog from git log" echo " 4. Build frontend"
echo " 5. Create release manifest" echo " 5. Generate changelog from git log"
echo " 6. Commit version bump" echo " 6. Create release manifest"
echo " 7. Create git tag v{VERSION}" echo " 7. Commit version bump"
echo " 8. Create git tag v{VERSION}"
echo "" echo ""
echo "Options:" echo "Options:"
echo " --dry-run Show what would be done without making changes" echo " --dry-run Show what would be done without making changes"
@@ -95,11 +96,12 @@ if $DRY_RUN; then
echo "[DRY RUN] Would perform the following:" echo "[DRY RUN] Would perform the following:"
echo " 1. Update core/archipelago/Cargo.toml version to $VERSION" echo " 1. Update core/archipelago/Cargo.toml version to $VERSION"
echo " 2. Update neode-ui/package.json version to $VERSION" echo " 2. Update neode-ui/package.json version to $VERSION"
echo " 3. Build frontend (npm run build)" echo " 3. Build backend (cargo build --release -p archipelago)"
echo " 4. Generate changelog from git log since v${CURRENT_CARGO_VERSION}" echo " 4. Build frontend (npm run build)"
echo " 5. Create release manifest" echo " 5. Generate changelog from git log since v${CURRENT_CARGO_VERSION}"
echo " 6. Commit: 'chore: release v${VERSION}'" echo " 6. Create release manifest"
echo " 7. Tag: v${VERSION}" echo " 7. Commit: 'chore: release v${VERSION}'"
echo " 8. Tag: v${VERSION}"
echo "" echo ""
echo "After this script, you would:" echo "After this script, you would:"
echo " - Push: git push && git push --tags" echo " - Push: git push && git push --tags"
@@ -123,12 +125,17 @@ cd "$PROJECT_ROOT/neode-ui"
npm version "$VERSION" --no-git-tag-version --allow-same-version 2>/dev/null || true npm version "$VERSION" --no-git-tag-version --allow-same-version 2>/dev/null || true
cd "$PROJECT_ROOT" cd "$PROJECT_ROOT"
echo "[3/7] Building frontend..." echo "[3/8] Building backend..."
cd "$PROJECT_ROOT/core"
cargo build --release -p archipelago
cd "$PROJECT_ROOT"
echo "[4/8] Building frontend..."
cd "$PROJECT_ROOT/neode-ui" cd "$PROJECT_ROOT/neode-ui"
npm run build 2>&1 | tail -3 npm run build 2>&1 | tail -3
cd "$PROJECT_ROOT" cd "$PROJECT_ROOT"
echo "[4/7] Validating curated changelog..." echo "[5/8] Validating curated changelog..."
CHANGELOG_FILE="$PROJECT_ROOT/CHANGELOG.md" CHANGELOG_FILE="$PROJECT_ROOT/CHANGELOG.md"
RELEASE_DATE=$(date +%Y-%m-%d) RELEASE_DATE=$(date +%Y-%m-%d)
@@ -144,12 +151,12 @@ if [ ! -f "$CHANGELOG_FILE" ] || ! grep -q "^## v${VERSION} (" "$CHANGELOG_FILE"
exit 1 exit 1
fi fi
echo "[5/7] Creating release manifest..." echo "[6/8] Creating release manifest..."
mkdir -p "$PROJECT_ROOT/releases" mkdir -p "$PROJECT_ROOT/releases"
"$SCRIPT_DIR/create-release-manifest.sh" --version "$VERSION" --date "$RELEASE_DATE" --output "$PROJECT_ROOT/releases/manifest.json" 2>&1 | grep -v "^$" "$SCRIPT_DIR/create-release-manifest.sh" --version "$VERSION" --date "$RELEASE_DATE" --output "$PROJECT_ROOT/releases/manifest.json" 2>&1 | grep -v "^$"
cp "$PROJECT_ROOT/releases/manifest.json" "$PROJECT_ROOT/release-manifest.json" cp "$PROJECT_ROOT/releases/manifest.json" "$PROJECT_ROOT/release-manifest.json"
echo "[6/7] Committing version bump..." echo "[7/8] Committing version bump..."
git -C "$PROJECT_ROOT" add \ git -C "$PROJECT_ROOT" add \
core/archipelago/Cargo.toml \ core/archipelago/Cargo.toml \
neode-ui/package.json \ neode-ui/package.json \
@@ -161,7 +168,7 @@ git -C "$PROJECT_ROOT" add \
git -C "$PROJECT_ROOT" commit -m "chore: release v${VERSION}" git -C "$PROJECT_ROOT" commit -m "chore: release v${VERSION}"
echo "[7/7] Creating git tag..." echo "[8/8] Creating git tag..."
git -C "$PROJECT_ROOT" tag -a "v${VERSION}" -m "Release v${VERSION}" git -C "$PROJECT_ROOT" tag -a "v${VERSION}" -m "Release v${VERSION}"
echo "" echo ""