Compare commits
13
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b49d8f1f8a | ||
|
|
ec36ac7e2c | ||
|
|
7104ba0cbf | ||
|
|
d0b08d2790 | ||
|
|
76288f541e | ||
|
|
b701e125b4 | ||
|
|
837ba63466 | ||
|
|
8191d92bed | ||
|
|
ae8359da4b | ||
|
|
d91b858d9b | ||
|
|
19f2125a4d | ||
|
|
a992abcd06 | ||
|
|
4d6b4f76af |
@@ -1,5 +1,43 @@
|
||||
# Changelog
|
||||
|
||||
## v1.7.65-alpha (2026-05-18)
|
||||
|
||||
- Orchestrator-backed app starts now run the same pre-start repairs as the legacy Podman path, so Nginx Proxy Manager stale `81:81` container metadata is removed and recreated before the orchestrator tries to start it.
|
||||
- Live diagnostics on `100.70.96.88` confirmed host nginx is healthy while Nginx Proxy Manager has no listeners on `8081`, `8084`, or `8444`, causing host nginx `502` responses for NPM proxy paths.
|
||||
|
||||
## 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)
|
||||
|
||||
- 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.
|
||||
- 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 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.
|
||||
|
||||
## v1.7.60-alpha (2026-05-18)
|
||||
|
||||
- Meshtastic serial detection now rejects malformed or incomplete handshakes instead of accepting unrelated serial devices as a fallback Meshtastic radio.
|
||||
- Mesh radio auto-detection now skips known non-mesh serial devices such as Sierra Wireless LTE modems and Zooz/Z-Wave sticks, avoiding interference with production peripherals.
|
||||
- Meshtastic config sync now sends `want_config_id` with the correct protobuf wire type, fixing radio-side `ignore malformed toradio` errors and allowing node-info/contact ingestion.
|
||||
- The stable `/dev/mesh-radio` udev rule no longer claims every `ttyACM*` device; it only matches known mesh USB serial adapters and known USB CDC ACM radio vendors.
|
||||
- Live validation on `100.70.96.88` confirmed Archipelago selects `/dev/ttyUSB0`, identifies the Meshtastic node, and refreshes 103 mesh contacts.
|
||||
|
||||
## v1.7.59-alpha (2026-05-17)
|
||||
|
||||
- Mobile app launching now keeps known container apps inside Archipelago's app-session flow instead of forcing desktop-only new-tab behavior on phones.
|
||||
|
||||
Generated
+1
-1
@@ -80,7 +80,7 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||
|
||||
[[package]]
|
||||
name = "archipelago"
|
||||
version = "1.7.59-alpha"
|
||||
version = "1.7.65-alpha"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"archipelago-container",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "archipelago"
|
||||
version = "1.7.59-alpha"
|
||||
version = "1.7.65-alpha"
|
||||
edition = "2021"
|
||||
description = "Archipelago Bitcoin Node OS - Native backend"
|
||||
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::install::install_log;
|
||||
use super::validation::validate_app_id;
|
||||
@@ -665,6 +668,7 @@ async fn do_orchestrator_package_start(
|
||||
if i > 0 {
|
||||
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
|
||||
}
|
||||
repair_before_package_start(name).await;
|
||||
match orchestrator.start(name).await {
|
||||
Ok(()) => wait_after_orchestrator_start(name).await,
|
||||
Err(e) if is_unknown_app_id_error(&e) => {
|
||||
@@ -863,11 +867,98 @@ async fn repair_before_package_start(container_name: &str) {
|
||||
repair_nextcloud_dirs().await;
|
||||
cleanup_stale_pasta_port("8085").await;
|
||||
}
|
||||
"nginx-proxy-manager" => repair_nginx_proxy_manager_container().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<()> {
|
||||
let Some(port) = runtime_required_host_port(container_name) else {
|
||||
return Ok(());
|
||||
@@ -1075,15 +1166,17 @@ async fn cleanup_start_conflict(container_name: &str, stderr: &str) {
|
||||
"homeassistant" | "home-assistant" => cleanup_stale_pasta_port("8123").await,
|
||||
"vaultwarden" => cleanup_stale_pasta_port("8082").await,
|
||||
"nextcloud" => cleanup_stale_pasta_port("8085").await,
|
||||
"nginx-proxy-manager" => {
|
||||
cleanup_stale_pasta_port("8081").await;
|
||||
cleanup_stale_pasta_port("8084").await;
|
||||
cleanup_stale_pasta_port("8444").await;
|
||||
}
|
||||
"nginx-proxy-manager" => cleanup_nginx_proxy_manager_ports().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) {
|
||||
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",
|
||||
|
||||
@@ -113,7 +113,12 @@ impl MeshtasticDevice {
|
||||
anyhow::bail!("No Meshtastic serial API response");
|
||||
}
|
||||
|
||||
let node_id = self.node_num.unwrap_or(0);
|
||||
let node_id = self
|
||||
.node_num
|
||||
.ok_or_else(|| anyhow::anyhow!("Meshtastic serial API did not provide MyInfo"))?;
|
||||
if self.user_id.is_none() && self.long_name.is_none() && self.short_name.is_none() {
|
||||
anyhow::bail!("Meshtastic serial API did not provide node identity");
|
||||
}
|
||||
let firmware_version = self
|
||||
.long_name
|
||||
.clone()
|
||||
@@ -331,7 +336,7 @@ fn decode_serial_frame(buf: &mut Vec<u8>) -> Option<Vec<u8>> {
|
||||
}
|
||||
|
||||
fn encode_want_config() -> Vec<u8> {
|
||||
encode_to_radio_variant(TO_RADIO_WANT_CONFIG_ID, &encode_varint_field(1, 1))
|
||||
encode_varint_field(TO_RADIO_WANT_CONFIG_ID, 1)
|
||||
}
|
||||
|
||||
fn encode_heartbeat() -> Vec<u8> {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
use super::protocol::{self, InboundFrame};
|
||||
use super::types::DeviceInfo;
|
||||
use anyhow::{Context, Result};
|
||||
use std::path::Path;
|
||||
use std::time::Duration;
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
@@ -400,12 +401,43 @@ const SERIAL_CANDIDATES: &[&str] = &[
|
||||
"/dev/ttyACM2",
|
||||
];
|
||||
|
||||
const SKIP_SERIAL_MODEL_SUBSTRINGS: &[&str] = &["Sierra_Wireless", "Z-Wave", "Zooz"];
|
||||
|
||||
fn likely_non_mesh_serial_device(path: &str) -> bool {
|
||||
let Some(name) = Path::new(path).file_name().and_then(|s| s.to_str()) else {
|
||||
return false;
|
||||
};
|
||||
let by_id = Path::new("/dev/serial/by-id");
|
||||
let Ok(entries) = std::fs::read_dir(by_id) else {
|
||||
return false;
|
||||
};
|
||||
for entry in entries.flatten() {
|
||||
let file_name = entry.file_name().to_string_lossy().to_string();
|
||||
if !SKIP_SERIAL_MODEL_SUBSTRINGS
|
||||
.iter()
|
||||
.any(|needle| file_name.contains(needle))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if let Ok(target) = std::fs::read_link(entry.path()) {
|
||||
if target.file_name().and_then(|s| s.to_str()) == Some(name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Scan for serial devices that could be Meshcore radios.
|
||||
/// Returns paths to existing serial device files.
|
||||
pub async fn detect_serial_devices() -> Vec<String> {
|
||||
let mut devices = Vec::new();
|
||||
for path in SERIAL_CANDIDATES {
|
||||
if tokio::fs::metadata(path).await.is_ok() {
|
||||
if likely_non_mesh_serial_device(path) {
|
||||
debug!(path = %path, "Skipping known non-mesh serial device");
|
||||
continue;
|
||||
}
|
||||
devices.push(path.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,10 @@ impl EndpointRateLimiter {
|
||||
limits.insert("backup.upload-s3".to_string(), (3, 600));
|
||||
limits.insert("backup.download-s3".to_string(), (3, 600));
|
||||
// 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.shutdown".to_string(), (2, 300));
|
||||
// Password and TOTP changes
|
||||
|
||||
@@ -840,6 +840,20 @@ const CONTAINER_ABSENCE_THRESHOLD: u32 = 3;
|
||||
/// scanner's authoritative view. Applies to all transitional variants.
|
||||
const TRANSITIONAL_STUCK_TIMEOUT: Duration = Duration::from_secs(120);
|
||||
|
||||
/// Multi-container installs can legitimately spend several minutes before the
|
||||
/// primary user-facing container exists. BTCPay, for example, pulls/starts
|
||||
/// Postgres and NBXplorer before `btcpay-server`; do not erase its installing
|
||||
/// card just because the primary container is absent during that setup window.
|
||||
const INSTALLING_STUCK_TIMEOUT: Duration = Duration::from_secs(20 * 60);
|
||||
|
||||
fn transitional_stuck_timeout(state: &crate::data_model::PackageState) -> Duration {
|
||||
if *state == crate::data_model::PackageState::Installing {
|
||||
INSTALLING_STUCK_TIMEOUT
|
||||
} else {
|
||||
TRANSITIONAL_STUCK_TIMEOUT
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if `state` is one of the transitional variants that a
|
||||
/// `spawn_transitional`-style background task owns. While such a state is
|
||||
/// set, the package scanner must not overwrite it with whatever podman
|
||||
@@ -961,13 +975,14 @@ async fn scan_and_update_packages(
|
||||
let overwrite = match existing {
|
||||
Some(existing_entry) if is_transitional(&existing_entry.state) => {
|
||||
let entered = *transitional_since.entry(id.clone()).or_insert(now);
|
||||
let stuck = now.duration_since(entered) > TRANSITIONAL_STUCK_TIMEOUT;
|
||||
let timeout = transitional_stuck_timeout(&existing_entry.state);
|
||||
let stuck = now.duration_since(entered) > timeout;
|
||||
if stuck {
|
||||
warn!(
|
||||
"Container {} stuck in {:?} for >{}s; overriding with scan state {:?}",
|
||||
id,
|
||||
existing_entry.state,
|
||||
TRANSITIONAL_STUCK_TIMEOUT.as_secs(),
|
||||
timeout.as_secs(),
|
||||
pkg.state
|
||||
);
|
||||
transitional_since.remove(id);
|
||||
@@ -1015,12 +1030,13 @@ async fn scan_and_update_packages(
|
||||
if let Some(entry) = merged.get(&id) {
|
||||
if is_transitional(&entry.state) {
|
||||
let entered = *transitional_since.entry(id.clone()).or_insert(now);
|
||||
if now.duration_since(entered) > TRANSITIONAL_STUCK_TIMEOUT {
|
||||
let timeout = transitional_stuck_timeout(&entry.state);
|
||||
if now.duration_since(entered) > timeout {
|
||||
warn!(
|
||||
"Container {} stuck in {:?} and absent for >{}s; removing stale transitional state",
|
||||
id,
|
||||
entry.state,
|
||||
TRANSITIONAL_STUCK_TIMEOUT.as_secs()
|
||||
timeout.as_secs()
|
||||
);
|
||||
merged.remove(&id);
|
||||
transitional_since.remove(&id);
|
||||
@@ -1247,4 +1263,13 @@ mod merge_tests {
|
||||
assert!(!is_transitional(&s), "{:?} should NOT be transitional", s);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn installing_uses_longer_stale_timeout_than_other_transitions() {
|
||||
assert!(transitional_stuck_timeout(&PackageState::Installing) > TRANSITIONAL_STUCK_TIMEOUT);
|
||||
assert_eq!(
|
||||
transitional_stuck_timeout(&PackageState::Stopping),
|
||||
TRANSITIONAL_STUCK_TIMEOUT
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# Stable symlink for USB serial adapters used as mesh radios.
|
||||
# Creates /dev/mesh-radio pointing to the underlying ttyUSB device.
|
||||
# Supports MeshCore and Meshtastic radios using CP2102 (Heltec V3),
|
||||
# CH340 (T-Beam), FTDI (RAK WisBlock), and USB CDC ACM devices.
|
||||
# CH340 (T-Beam), FTDI (RAK WisBlock), and known USB CDC ACM radios.
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", SYMLINK+="mesh-radio", MODE="0660", GROUP="dialout"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", SYMLINK+="mesh-radio", MODE="0660", GROUP="dialout"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", SYMLINK+="mesh-radio", MODE="0660", GROUP="dialout"
|
||||
SUBSYSTEM=="tty", KERNEL=="ttyACM[0-9]*", SYMLINK+="mesh-radio", MODE="0660", GROUP="dialout"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="239a", KERNEL=="ttyACM[0-9]*", SYMLINK+="mesh-radio", MODE="0660", GROUP="dialout"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="2e8a", KERNEL=="ttyACM[0-9]*", SYMLINK+="mesh-radio", MODE="0660", GROUP="dialout"
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "neode-ui",
|
||||
"version": "1.7.59-alpha",
|
||||
"version": "1.7.65-alpha",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "neode-ui",
|
||||
"version": "1.7.59-alpha",
|
||||
"version": "1.7.65-alpha",
|
||||
"dependencies": {
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@vue-leaflet/vue-leaflet": "^0.10.1",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "neode-ui",
|
||||
"private": true,
|
||||
"version": "1.7.59-alpha",
|
||||
"version": "1.7.65-alpha",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "./start-dev.sh",
|
||||
|
||||
+15
-20
@@ -1,31 +1,26 @@
|
||||
{
|
||||
"version": "1.7.59-alpha",
|
||||
"release_date": "2026-05-17",
|
||||
"version": "1.7.65-alpha",
|
||||
"release_date": "2026-05-18",
|
||||
"changelog": [
|
||||
"Mobile app launching now keeps known container apps inside Archipelago's app-session flow instead of forcing desktop-only new-tab behavior on phones.",
|
||||
"App sessions on mobile now respect the status-bar safe area so foreground iframe content starts below the device chrome while the fullscreen backdrop remains edge-to-edge.",
|
||||
"Prepackaged website launch buttons now resolve their curated website URLs before website-container fallback logic, restoring launches for the L484 sites and adding the Arch Presentation bookmark.",
|
||||
"Meshtastic contact discovery now drains the radio config stream through completion and retries config sync when the contact cache is empty, so nearby nodes already known by the radio are more likely to appear in Archipelago.",
|
||||
"The Apps page now includes a compact sideload button and modal for installing trusted Docker images with optional title, description, and port mapping metadata.",
|
||||
"Sideloaded app title and description metadata now persist through the backend app-config file so refreshed package scans do not collapse custom apps back to generic IDs.",
|
||||
"Validation passed with `npm test -- appLauncher`, `npm run build`, `cargo check -p archipelago`, and `cargo fmt --all --check`."
|
||||
"Orchestrator-backed app starts now run the same pre-start repairs as the legacy Podman path, so Nginx Proxy Manager stale `81:81` container metadata is removed and recreated before the orchestrator tries to start it.",
|
||||
"Live diagnostics on `100.70.96.88` confirmed host nginx is healthy while Nginx Proxy Manager has no listeners on `8081`, `8084`, or `8444`, causing host nginx `502` responses for NPM proxy paths."
|
||||
],
|
||||
"components": [
|
||||
{
|
||||
"name": "archipelago",
|
||||
"current_version": "1.7.59-alpha",
|
||||
"new_version": "1.7.59-alpha",
|
||||
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.59-alpha/archipelago",
|
||||
"sha256": "8d6d33e46fa82638253d53b47149ed6177d183130e702cbb9f94d8dfac4950c0",
|
||||
"size_bytes": 42909248
|
||||
"current_version": "1.7.65-alpha",
|
||||
"new_version": "1.7.65-alpha",
|
||||
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.65-alpha/archipelago",
|
||||
"sha256": "bc4197e99bdea6e63da19915c9cb9e06b84248fb2c627f2b5973e42532ef7b62",
|
||||
"size_bytes": 42939312
|
||||
},
|
||||
{
|
||||
"name": "archipelago-frontend-1.7.59-alpha.tar.gz",
|
||||
"current_version": "1.7.59-alpha",
|
||||
"new_version": "1.7.59-alpha",
|
||||
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.59-alpha/archipelago-frontend-1.7.59-alpha.tar.gz",
|
||||
"sha256": "c3d5804d7647513e01634102506398ad3ce14ace9872547bc29a5c11b771a7dd",
|
||||
"size_bytes": 166469247
|
||||
"name": "archipelago-frontend-1.7.65-alpha.tar.gz",
|
||||
"current_version": "1.7.65-alpha",
|
||||
"new_version": "1.7.65-alpha",
|
||||
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.65-alpha/archipelago-frontend-1.7.65-alpha.tar.gz",
|
||||
"sha256": "a76778934b747405410dfc92b1fffc2df4b44c0067f71652e23a86c9b77ab7ba",
|
||||
"size_bytes": 166470001
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+15
-20
@@ -1,31 +1,26 @@
|
||||
{
|
||||
"version": "1.7.59-alpha",
|
||||
"release_date": "2026-05-17",
|
||||
"version": "1.7.65-alpha",
|
||||
"release_date": "2026-05-18",
|
||||
"changelog": [
|
||||
"Mobile app launching now keeps known container apps inside Archipelago's app-session flow instead of forcing desktop-only new-tab behavior on phones.",
|
||||
"App sessions on mobile now respect the status-bar safe area so foreground iframe content starts below the device chrome while the fullscreen backdrop remains edge-to-edge.",
|
||||
"Prepackaged website launch buttons now resolve their curated website URLs before website-container fallback logic, restoring launches for the L484 sites and adding the Arch Presentation bookmark.",
|
||||
"Meshtastic contact discovery now drains the radio config stream through completion and retries config sync when the contact cache is empty, so nearby nodes already known by the radio are more likely to appear in Archipelago.",
|
||||
"The Apps page now includes a compact sideload button and modal for installing trusted Docker images with optional title, description, and port mapping metadata.",
|
||||
"Sideloaded app title and description metadata now persist through the backend app-config file so refreshed package scans do not collapse custom apps back to generic IDs.",
|
||||
"Validation passed with `npm test -- appLauncher`, `npm run build`, `cargo check -p archipelago`, and `cargo fmt --all --check`."
|
||||
"Orchestrator-backed app starts now run the same pre-start repairs as the legacy Podman path, so Nginx Proxy Manager stale `81:81` container metadata is removed and recreated before the orchestrator tries to start it.",
|
||||
"Live diagnostics on `100.70.96.88` confirmed host nginx is healthy while Nginx Proxy Manager has no listeners on `8081`, `8084`, or `8444`, causing host nginx `502` responses for NPM proxy paths."
|
||||
],
|
||||
"components": [
|
||||
{
|
||||
"name": "archipelago",
|
||||
"current_version": "1.7.59-alpha",
|
||||
"new_version": "1.7.59-alpha",
|
||||
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.59-alpha/archipelago",
|
||||
"sha256": "8d6d33e46fa82638253d53b47149ed6177d183130e702cbb9f94d8dfac4950c0",
|
||||
"size_bytes": 42909248
|
||||
"current_version": "1.7.65-alpha",
|
||||
"new_version": "1.7.65-alpha",
|
||||
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.65-alpha/archipelago",
|
||||
"sha256": "bc4197e99bdea6e63da19915c9cb9e06b84248fb2c627f2b5973e42532ef7b62",
|
||||
"size_bytes": 42939312
|
||||
},
|
||||
{
|
||||
"name": "archipelago-frontend-1.7.59-alpha.tar.gz",
|
||||
"current_version": "1.7.59-alpha",
|
||||
"new_version": "1.7.59-alpha",
|
||||
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.59-alpha/archipelago-frontend-1.7.59-alpha.tar.gz",
|
||||
"sha256": "c3d5804d7647513e01634102506398ad3ce14ace9872547bc29a5c11b771a7dd",
|
||||
"size_bytes": 166469247
|
||||
"name": "archipelago-frontend-1.7.65-alpha.tar.gz",
|
||||
"current_version": "1.7.65-alpha",
|
||||
"new_version": "1.7.65-alpha",
|
||||
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.65-alpha/archipelago-frontend-1.7.65-alpha.tar.gz",
|
||||
"sha256": "a76778934b747405410dfc92b1fffc2df4b44c0067f71652e23a86c9b77ab7ba",
|
||||
"size_bytes": 166470001
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+22
-15
@@ -28,11 +28,12 @@ for arg in "$@"; do
|
||||
echo "Steps performed:"
|
||||
echo " 1. Validate version format (SemVer)"
|
||||
echo " 2. Bump version in Cargo.toml and package.json"
|
||||
echo " 3. Build frontend"
|
||||
echo " 4. Generate changelog from git log"
|
||||
echo " 5. Create release manifest"
|
||||
echo " 6. Commit version bump"
|
||||
echo " 7. Create git tag v{VERSION}"
|
||||
echo " 3. Build backend"
|
||||
echo " 4. Build frontend"
|
||||
echo " 5. Generate changelog from git log"
|
||||
echo " 6. Create release manifest"
|
||||
echo " 7. Commit version bump"
|
||||
echo " 8. Create git tag v{VERSION}"
|
||||
echo ""
|
||||
echo "Options:"
|
||||
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 " 1. Update core/archipelago/Cargo.toml version to $VERSION"
|
||||
echo " 2. Update neode-ui/package.json version to $VERSION"
|
||||
echo " 3. Build frontend (npm run build)"
|
||||
echo " 4. Generate changelog from git log since v${CURRENT_CARGO_VERSION}"
|
||||
echo " 5. Create release manifest"
|
||||
echo " 6. Commit: 'chore: release v${VERSION}'"
|
||||
echo " 7. Tag: v${VERSION}"
|
||||
echo " 3. Build backend (cargo build --release -p archipelago)"
|
||||
echo " 4. Build frontend (npm run build)"
|
||||
echo " 5. Generate changelog from git log since v${CURRENT_CARGO_VERSION}"
|
||||
echo " 6. Create release manifest"
|
||||
echo " 7. Commit: 'chore: release v${VERSION}'"
|
||||
echo " 8. Tag: v${VERSION}"
|
||||
echo ""
|
||||
echo "After this script, you would:"
|
||||
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
|
||||
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"
|
||||
npm run build 2>&1 | tail -3
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
echo "[4/7] Validating curated changelog..."
|
||||
echo "[5/8] Validating curated changelog..."
|
||||
|
||||
CHANGELOG_FILE="$PROJECT_ROOT/CHANGELOG.md"
|
||||
RELEASE_DATE=$(date +%Y-%m-%d)
|
||||
@@ -144,12 +151,12 @@ if [ ! -f "$CHANGELOG_FILE" ] || ! grep -q "^## v${VERSION} (" "$CHANGELOG_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[5/7] Creating release manifest..."
|
||||
echo "[6/8] Creating release manifest..."
|
||||
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 "^$"
|
||||
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 \
|
||||
core/archipelago/Cargo.toml \
|
||||
neode-ui/package.json \
|
||||
@@ -161,7 +168,7 @@ git -C "$PROJECT_ROOT" add \
|
||||
|
||||
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}"
|
||||
|
||||
echo ""
|
||||
|
||||
Reference in New Issue
Block a user