Compare commits
2 Commits
v1.7.18-al
...
v1.7.20-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d8a9e66e3 | ||
|
|
9fc9696dbd |
@@ -74,12 +74,38 @@ jobs:
|
||||
|
||||
- name: Include AIUI if available
|
||||
run: |
|
||||
if [ -d "/opt/archipelago/web-ui/aiui" ] && [ -f "/opt/archipelago/web-ui/aiui/index.html" ]; then
|
||||
mkdir -p web/dist/neode-ui/aiui
|
||||
cp -r /opt/archipelago/web-ui/aiui/* web/dist/neode-ui/aiui/
|
||||
echo "AIUI included from /opt/archipelago/web-ui/aiui/"
|
||||
# AIUI (the Claude chat sidebar) lives outside the Vue build
|
||||
# and must be copied into the frontend dist BEFORE packaging,
|
||||
# otherwise OTA-tarball upgrades silently strip it from nodes
|
||||
# in the field. Try in order: cached on runner, then the
|
||||
# newest release tarball in this repo's releases/ dir as a
|
||||
# fallback so a freshly-provisioned runner still gets AIUI.
|
||||
AIUI_SRC=""
|
||||
if [ -f "/opt/archipelago/web-ui/aiui/index.html" ]; then
|
||||
AIUI_SRC="/opt/archipelago/web-ui/aiui"
|
||||
elif [ -f "$HOME/archy/web/dist/neode-ui/aiui/index.html" ]; then
|
||||
AIUI_SRC="$HOME/archy/web/dist/neode-ui/aiui"
|
||||
else
|
||||
echo "WARNING: AIUI not found on build server — ISO will not include AIUI"
|
||||
LATEST_FRONTEND=$(ls -t releases/v*/archipelago-frontend-*.tar.gz 2>/dev/null | head -1)
|
||||
if [ -n "$LATEST_FRONTEND" ]; then
|
||||
echo "Extracting AIUI from $LATEST_FRONTEND (runner cache miss)"
|
||||
TMP=$(mktemp -d)
|
||||
tar xzf "$LATEST_FRONTEND" -C "$TMP" ./aiui 2>/dev/null || true
|
||||
if [ -f "$TMP/aiui/index.html" ]; then
|
||||
AIUI_SRC="$TMP/aiui"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [ -n "$AIUI_SRC" ]; then
|
||||
mkdir -p web/dist/neode-ui/aiui
|
||||
cp -r "$AIUI_SRC/"* web/dist/neode-ui/aiui/
|
||||
echo "AIUI included from $AIUI_SRC ($(du -sh web/dist/neode-ui/aiui | cut -f1))"
|
||||
else
|
||||
echo "FAIL: AIUI not found anywhere (runner cache + release tarballs)"
|
||||
echo " checked: /opt/archipelago/web-ui/aiui"
|
||||
echo " \$HOME/archy/web/dist/neode-ui/aiui"
|
||||
echo " releases/v*/archipelago-frontend-*.tar.gz"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Configure root podman for insecure registry
|
||||
@@ -93,7 +119,15 @@ jobs:
|
||||
run: |
|
||||
cd image-recipe
|
||||
export ARCHIPELAGO_BIN="$(pwd)/../core/target/release/archipelago"
|
||||
ls -la "$ARCHIPELAGO_BIN" || echo "WARNING: binary not found"
|
||||
if [ ! -x "$ARCHIPELAGO_BIN" ]; then
|
||||
echo "FAIL: backend binary missing or not executable at $ARCHIPELAGO_BIN"
|
||||
exit 1
|
||||
fi
|
||||
BIN_VERSION=$(strings "$ARCHIPELAGO_BIN" | grep -oE 'archipelago [0-9]+\.[0-9]+\.[0-9]+(-[a-z]+)?' | head -1 || true)
|
||||
EXPECTED=$(grep '^version' ../core/archipelago/Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
|
||||
echo "Binary: $ARCHIPELAGO_BIN ($(du -h "$ARCHIPELAGO_BIN" | cut -f1))"
|
||||
echo "Embedded version string: ${BIN_VERSION:-unknown}"
|
||||
echo "Expected version (Cargo.toml): $EXPECTED"
|
||||
sudo -E UNBUNDLED=1 DEV_SERVER=localhost BUILD_FROM_SOURCE=0 \
|
||||
ARCHIPELAGO_BIN="$ARCHIPELAGO_BIN" \
|
||||
./build-auto-installer-iso.sh
|
||||
|
||||
2
core/Cargo.lock
generated
2
core/Cargo.lock
generated
@@ -80,7 +80,7 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
||||
|
||||
[[package]]
|
||||
name = "archipelago"
|
||||
version = "1.7.18-alpha"
|
||||
version = "1.7.20-alpha"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"archipelago-container",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "archipelago"
|
||||
version = "1.7.18-alpha"
|
||||
version = "1.7.20-alpha"
|
||||
edition = "2021"
|
||||
description = "Archipelago Bitcoin Node OS - Native backend"
|
||||
authors = ["Archipelago Team"]
|
||||
|
||||
@@ -342,6 +342,7 @@ mod tests {
|
||||
"local.onion",
|
||||
"localpub",
|
||||
None,
|
||||
None,
|
||||
|_| "test-sig".to_string(),
|
||||
)
|
||||
.await
|
||||
@@ -376,6 +377,7 @@ mod tests {
|
||||
"local.onion",
|
||||
"localpub",
|
||||
None,
|
||||
None,
|
||||
|_| "test-sig".to_string(),
|
||||
)
|
||||
.await
|
||||
@@ -409,6 +411,7 @@ mod tests {
|
||||
"local.onion",
|
||||
"localpub",
|
||||
None,
|
||||
None,
|
||||
|_| "test-sig".to_string(),
|
||||
)
|
||||
.await
|
||||
@@ -421,6 +424,7 @@ mod tests {
|
||||
"local.onion",
|
||||
"localpub",
|
||||
None,
|
||||
None,
|
||||
|_| "test-sig".to_string(),
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -36,6 +36,31 @@ fn is_canceled() -> bool {
|
||||
DOWNLOAD_CANCEL.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
/// Parse "MAJOR.MINOR.PATCH[-suffix]" into a tuple; suffix is ignored.
|
||||
/// Returns None if the numeric portion can't be parsed — callers should
|
||||
/// fall back to string comparison in that case so we don't silently
|
||||
/// mis-rank versions we don't understand.
|
||||
fn parse_version_triple(v: &str) -> Option<(u32, u32, u32)> {
|
||||
let core = v.split('-').next().unwrap_or(v);
|
||||
let mut parts = core.split('.');
|
||||
let major: u32 = parts.next()?.parse().ok()?;
|
||||
let minor: u32 = parts.next()?.parse().ok()?;
|
||||
let patch: u32 = parts.next()?.parse().ok()?;
|
||||
Some((major, minor, patch))
|
||||
}
|
||||
|
||||
/// Is `candidate` strictly newer than `current`? Used to guard against
|
||||
/// the manifest offering a version we've already passed (e.g. a stale
|
||||
/// cached manifest or a node that sideloaded past the manifest's
|
||||
/// latest). Falls back to string inequality if either version doesn't
|
||||
/// parse, preserving the old behaviour for unusual version strings.
|
||||
fn is_newer(candidate: &str, current: &str) -> bool {
|
||||
match (parse_version_triple(candidate), parse_version_triple(current)) {
|
||||
(Some(a), Some(b)) => a > b,
|
||||
_ => candidate != current,
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_UPDATE_MANIFEST_URL: &str =
|
||||
"https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/manifest.json";
|
||||
const UPDATE_STATE_FILE: &str = "update_state.json";
|
||||
@@ -117,13 +142,13 @@ pub async fn load_state(data_dir: &Path) -> Result<UpdateState> {
|
||||
let running = env!("CARGO_PKG_VERSION");
|
||||
if state.current_version != running {
|
||||
state.current_version = running.to_string();
|
||||
// Clear any stale "available_update" that matched the old
|
||||
// current_version — the new binary will re-check on its own.
|
||||
if let Some(ref avail) = state.available_update {
|
||||
if avail.version == running {
|
||||
state.available_update = None;
|
||||
}
|
||||
}
|
||||
// Binary version changed (sideload or apply). Any stored
|
||||
// `available_update` is either redundant (points at the running
|
||||
// version) or stale (points at a version we've already passed —
|
||||
// which would surface as a "downgrade" offer in the UI). Clear
|
||||
// it unconditionally; the next check_for_updates will repopulate
|
||||
// if there's genuinely something newer.
|
||||
state.available_update = None;
|
||||
save_state(data_dir, &state).await?;
|
||||
}
|
||||
Ok(state)
|
||||
@@ -161,7 +186,7 @@ pub async fn check_for_updates(data_dir: &Path) -> Result<UpdateState> {
|
||||
Ok(resp) if resp.status().is_success() => {
|
||||
match resp.json::<UpdateManifest>().await {
|
||||
Ok(manifest) => {
|
||||
if manifest.version != state.current_version {
|
||||
if is_newer(&manifest.version, &state.current_version) {
|
||||
info!(
|
||||
current = %state.current_version,
|
||||
available = %manifest.version,
|
||||
@@ -169,7 +194,16 @@ pub async fn check_for_updates(data_dir: &Path) -> Result<UpdateState> {
|
||||
);
|
||||
state.available_update = Some(manifest);
|
||||
} else {
|
||||
debug!("Already on latest version: {}", state.current_version);
|
||||
// Manifest version matches us or is behind
|
||||
// us — either we're current, or the remote
|
||||
// manifest is stale. Either way don't offer
|
||||
// it as an "update" (that would be a
|
||||
// downgrade prompt).
|
||||
debug!(
|
||||
current = %state.current_version,
|
||||
manifest = %manifest.version,
|
||||
"No newer version in manifest"
|
||||
);
|
||||
state.available_update = None;
|
||||
}
|
||||
handled = true;
|
||||
@@ -848,9 +882,15 @@ pub async fn run_update_scheduler(data_dir: std::path::PathBuf) {
|
||||
debug!("Update scheduler: apply failed: {}", e);
|
||||
continue;
|
||||
}
|
||||
info!("Update scheduler: update applied, restart needed");
|
||||
// Signal for service restart (systemd will handle via exit code)
|
||||
std::process::exit(0);
|
||||
info!("Update scheduler: update applied, restart scheduled by apply_update");
|
||||
// apply_update has already spawned a 2s-delayed
|
||||
// `systemctl restart archipelago`. Don't call
|
||||
// std::process::exit here — that kills the runtime
|
||||
// before the spawned restart task runs, and since
|
||||
// the unit is Restart=on-failure a clean exit(0)
|
||||
// leaves the service dead. Fall through; the
|
||||
// scheduled restart will bring us back cleanly.
|
||||
return;
|
||||
}
|
||||
Ok(_) => {
|
||||
debug!("Update scheduler: no update available");
|
||||
@@ -926,6 +966,52 @@ mod tests {
|
||||
assert_eq!(state.schedule, UpdateSchedule::DailyCheck);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_version_triple() {
|
||||
assert_eq!(parse_version_triple("1.7.18"), Some((1, 7, 18)));
|
||||
assert_eq!(parse_version_triple("1.7.18-alpha"), Some((1, 7, 18)));
|
||||
assert_eq!(parse_version_triple("0.0.1"), Some((0, 0, 1)));
|
||||
assert_eq!(parse_version_triple("garbage"), None);
|
||||
assert_eq!(parse_version_triple("1.2"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_newer() {
|
||||
assert!(is_newer("1.7.19-alpha", "1.7.18-alpha"));
|
||||
assert!(is_newer("1.8.0-alpha", "1.7.99-alpha"));
|
||||
assert!(is_newer("1.7.10-alpha", "1.7.9-alpha")); // numeric, not lexical
|
||||
assert!(!is_newer("1.7.18-alpha", "1.7.18-alpha"));
|
||||
assert!(!is_newer("1.7.17-alpha", "1.7.18-alpha")); // would-be downgrade
|
||||
assert!(!is_newer("1.7.9-alpha", "1.7.10-alpha"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_load_state_clears_stale_available_on_version_bump() {
|
||||
// Simulates a sideload: state file on disk says we're on
|
||||
// 1.7.16-alpha with 1.7.17-alpha staged as the pending update,
|
||||
// but the running binary is 1.7.18-alpha (skipped a version).
|
||||
// load_state must drop the stale available_update so the UI
|
||||
// doesn't offer a downgrade.
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let stale = UpdateState {
|
||||
current_version: "1.7.16-alpha".to_string(),
|
||||
available_update: Some(UpdateManifest {
|
||||
version: "1.7.17-alpha".to_string(),
|
||||
release_date: "2026-04-20".to_string(),
|
||||
changelog: vec![],
|
||||
components: vec![],
|
||||
}),
|
||||
..UpdateState::default()
|
||||
};
|
||||
save_state(dir.path(), &stale).await.unwrap();
|
||||
let loaded = load_state(dir.path()).await.unwrap();
|
||||
assert_eq!(loaded.current_version, env!("CARGO_PKG_VERSION"));
|
||||
assert!(
|
||||
loaded.available_update.is_none(),
|
||||
"stale available_update must be cleared after version bump"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_load_state_creates_default_when_missing() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
@@ -961,13 +1047,14 @@ mod tests {
|
||||
};
|
||||
save_state(dir.path(), &state).await.unwrap();
|
||||
let loaded = load_state(dir.path()).await.unwrap();
|
||||
assert_eq!(loaded.current_version, "1.0.0");
|
||||
// load_state rewrites current_version to match the running
|
||||
// binary (sideload self-heal), so don't assert on the saved
|
||||
// value. The migration also clears available_update when the
|
||||
// version changes — check the other fields survived.
|
||||
assert_eq!(loaded.current_version, env!("CARGO_PKG_VERSION"));
|
||||
assert!(loaded.update_in_progress);
|
||||
assert_eq!(loaded.schedule, UpdateSchedule::Manual);
|
||||
let manifest = loaded.available_update.unwrap();
|
||||
assert_eq!(manifest.version, "1.1.0");
|
||||
assert_eq!(manifest.components.len(), 1);
|
||||
assert_eq!(manifest.components[0].size_bytes, 5000);
|
||||
assert!(loaded.available_update.is_none());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -1017,7 +1104,9 @@ mod tests {
|
||||
};
|
||||
save_state(dir.path(), &state).await.unwrap();
|
||||
let status = get_status(dir.path()).await.unwrap();
|
||||
assert_eq!(status.current_version, "3.0.0");
|
||||
// get_status → load_state, which rewrites current_version to
|
||||
// match the running binary (see the sideload-self-heal path).
|
||||
assert_eq!(status.current_version, env!("CARGO_PKG_VERSION"));
|
||||
assert!(status.rollback_available);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,6 +180,29 @@ init()
|
||||
</button>
|
||||
</div>
|
||||
<div class="overflow-y-auto flex-1 min-h-0 space-y-6 pr-1">
|
||||
<!-- v1.7.20-alpha -->
|
||||
<div>
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<span class="text-xs font-mono px-2 py-0.5 rounded bg-orange-500/20 text-orange-300">v1.7.20-alpha</span>
|
||||
<span class="text-xs text-white/40">Apr 21, 2026</span>
|
||||
</div>
|
||||
<div class="space-y-3 text-sm text-white/80 pl-3 border-l border-white/10">
|
||||
<p>Fixed a critical bug where nodes on the automatic daily-update schedule could end up offline after their nightly update. The scheduler was killing the service a moment too early, before the built-in restart handler had a chance to bring the new version back up — leaving the node dead until someone SSH'd in and started it manually. The scheduler now hands off cleanly to the same restart path the 'Install Update' button uses, so auto-applied updates come back online on their own.</p>
|
||||
<p>Applies to any node configured for 'Check & Apply Daily' — no change required on your end, the fix ships with this update.</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- v1.7.19-alpha -->
|
||||
<div>
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
<span class="text-xs font-mono px-2 py-0.5 rounded bg-orange-500/20 text-orange-300">v1.7.19-alpha</span>
|
||||
<span class="text-xs text-white/40">Apr 21, 2026</span>
|
||||
</div>
|
||||
<div class="space-y-3 text-sm text-white/80 pl-3 border-l border-white/10">
|
||||
<p>Your node no longer offers a version you've already passed as an "available update". If you sideload or skip a release, any stored pointer to an earlier version is dropped on next restart, and the System Update page offers only the genuinely newer release — no more seeing an older version listed as something to install.</p>
|
||||
<p>Version comparison is now numeric, not alphabetic. 1.7.10 correctly outranks 1.7.9 (earlier naive string-order would have got this backwards once the patch number hits double digits), so update prompts and "up to date" checks stay accurate past the nines.</p>
|
||||
<p>A stale manifest from a slow cache or proxy can no longer downgrade your node. If the manifest reports a version equal to or behind what's running, your node treats that as "up to date" rather than offering the older version as an update.</p>
|
||||
</div>
|
||||
</div>
|
||||
<!-- v1.7.18-alpha -->
|
||||
<div>
|
||||
<div class="flex items-center gap-2 mb-3">
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
{
|
||||
"version": "1.7.18-alpha",
|
||||
"release_date": "2026-04-20",
|
||||
"version": "1.7.20-alpha",
|
||||
"release_date": "2026-04-21",
|
||||
"changelog": [
|
||||
"Nodes discovered through a trusted peer now land as Trusted instead of Observer. When your federated peer shares its own peer list with you, those nodes get the same trust level as a direct invite — the link they came through is already one you vetted, so you no longer need to promote them by hand before they can be used normally.",
|
||||
"The update flow now writes clearer logs at every step. Start of download, cancel, and apply each emit a one-line entry to the system journal with the staging path and the affected files, so if a download misbehaves on your node it's easy to see exactly where it got to."
|
||||
"Fixed a critical bug where nodes on 'Check & Apply Daily' could end up offline after their nightly update. The scheduler was killing the service a moment too early, before the built-in restart handler could bring the new version back up — leaving the node dead until someone SSH'd in. The scheduler now uses the same restart path as the 'Install Update' button, so auto-applied updates come back online on their own.",
|
||||
"Applies automatically — no action needed on your end beyond taking this update."
|
||||
],
|
||||
"components": [
|
||||
{
|
||||
"name": "archipelago",
|
||||
"current_version": "1.7.17-alpha",
|
||||
"new_version": "1.7.18-alpha",
|
||||
"download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.18-alpha/archipelago",
|
||||
"sha256": "a025110247f49290f0005dc78af106b3205f676bbf5dc883531b3c5a14f8d663",
|
||||
"size_bytes": 40661320
|
||||
"current_version": "1.7.19-alpha",
|
||||
"new_version": "1.7.20-alpha",
|
||||
"download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.20-alpha/archipelago",
|
||||
"sha256": "bf4f8b91b021cad445a868f454707e0fa005446f755604f8c3e072bb7a059e6f",
|
||||
"size_bytes": 40640016
|
||||
},
|
||||
{
|
||||
"name": "archipelago-frontend-1.7.18-alpha.tar.gz",
|
||||
"current_version": "1.7.17-alpha",
|
||||
"new_version": "1.7.18-alpha",
|
||||
"download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.18-alpha/archipelago-frontend-1.7.18-alpha.tar.gz",
|
||||
"sha256": "e6bfee15ff8cb3aad7cef381fc3b13a937630ad6fafd1ff4955883f69cc3b9b0",
|
||||
"size_bytes": 162065873
|
||||
"name": "archipelago-frontend-1.7.20-alpha.tar.gz",
|
||||
"current_version": "1.7.19-alpha",
|
||||
"new_version": "1.7.20-alpha",
|
||||
"download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.20-alpha/archipelago-frontend-1.7.20-alpha.tar.gz",
|
||||
"sha256": "a82f187b597c51e5f3d8753529914651ab2d8e8bb3ad9c36d287b335e4d386a9",
|
||||
"size_bytes": 162082209
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
BIN
releases/v1.7.19-alpha/archipelago
Executable file
BIN
releases/v1.7.19-alpha/archipelago
Executable file
Binary file not shown.
BIN
releases/v1.7.19-alpha/archipelago-frontend-1.7.19-alpha.tar.gz
Normal file
BIN
releases/v1.7.19-alpha/archipelago-frontend-1.7.19-alpha.tar.gz
Normal file
Binary file not shown.
BIN
releases/v1.7.20-alpha/archipelago
Executable file
BIN
releases/v1.7.20-alpha/archipelago
Executable file
Binary file not shown.
BIN
releases/v1.7.20-alpha/archipelago-frontend-1.7.20-alpha.tar.gz
Normal file
BIN
releases/v1.7.20-alpha/archipelago-frontend-1.7.20-alpha.tar.gz
Normal file
Binary file not shown.
Reference in New Issue
Block a user