fix: release v1.7.50-alpha OTA runtime repair
This commit is contained in:
parent
7ab788d178
commit
be9f9528c3
2
core/Cargo.lock
generated
2
core/Cargo.lock
generated
@ -80,7 +80,7 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "archipelago"
|
name = "archipelago"
|
||||||
version = "1.7.49-alpha"
|
version = "1.7.50-alpha"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"archipelago-container",
|
"archipelago-container",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "archipelago"
|
name = "archipelago"
|
||||||
version = "1.7.49-alpha"
|
version = "1.7.50-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"]
|
||||||
|
|||||||
@ -15,7 +15,7 @@
|
|||||||
//! best-effort — failures are logged but never abort the backend.
|
//! best-effort — failures are logged but never abort the backend.
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
use tracing::{debug, info, warn};
|
use tracing::{debug, info, warn};
|
||||||
|
|
||||||
@ -31,6 +31,7 @@ const DOCTOR_SERVICE_PATH: &str = "/etc/systemd/system/archipelago-doctor.servic
|
|||||||
const DOCTOR_TIMER_PATH: &str = "/etc/systemd/system/archipelago-doctor.timer";
|
const DOCTOR_TIMER_PATH: &str = "/etc/systemd/system/archipelago-doctor.timer";
|
||||||
|
|
||||||
const NGINX_CONF_PATH: &str = "/etc/nginx/sites-available/archipelago";
|
const NGINX_CONF_PATH: &str = "/etc/nginx/sites-available/archipelago";
|
||||||
|
const RUNTIME_ASSETS_DIR: &str = "/opt/archipelago/web-ui/archipelago-runtime";
|
||||||
|
|
||||||
/// Inserted into every server block of the nginx config that lacks the
|
/// Inserted into every server block of the nginx config that lacks the
|
||||||
/// `/api/app-catalog` proxy. Kept in sync with the canonical block in
|
/// `/api/app-catalog` proxy. Kept in sync with the canonical block in
|
||||||
@ -40,6 +41,11 @@ const NGINX_APP_CATALOG_BLOCK: &str = "\n # App Store catalog proxy — backe
|
|||||||
/// Entry point called from main startup. Never returns an error to the caller —
|
/// Entry point called from main startup. Never returns an error to the caller —
|
||||||
/// failing to bootstrap host artifacts must not prevent the backend from serving.
|
/// failing to bootstrap host artifacts must not prevent the backend from serving.
|
||||||
pub async fn ensure_doctor_installed() {
|
pub async fn ensure_doctor_installed() {
|
||||||
|
match run_runtime_assets().await {
|
||||||
|
Ok(changed) if changed => info!("Runtime assets synchronized from OTA payload"),
|
||||||
|
Ok(_) => debug!("No OTA runtime payload to synchronize"),
|
||||||
|
Err(e) => warn!("Runtime asset bootstrap failed (non-fatal): {:#}", e),
|
||||||
|
}
|
||||||
match run().await {
|
match run().await {
|
||||||
Ok(changed) if changed => info!("Doctor artifacts synchronized with binary"),
|
Ok(changed) if changed => info!("Doctor artifacts synchronized with binary"),
|
||||||
Ok(_) => debug!("Doctor artifacts already in sync"),
|
Ok(_) => debug!("Doctor artifacts already in sync"),
|
||||||
@ -52,6 +58,117 @@ pub async fn ensure_doctor_installed() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn run_runtime_assets() -> Result<bool> {
|
||||||
|
// The v1.7.50 OTA bridge puts scripts/apps/docker assets inside the
|
||||||
|
// frontend tarball because older binaries only know how to apply the
|
||||||
|
// backend binary and frontend archive. Once the new backend starts, it
|
||||||
|
// promotes that payload into /opt so app installs use the matching specs.
|
||||||
|
let runtime_dir = Path::new(RUNTIME_ASSETS_DIR);
|
||||||
|
if !runtime_dir.exists() {
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut changed = false;
|
||||||
|
for (relative, dest) in [
|
||||||
|
("apps", "/opt/archipelago/apps"),
|
||||||
|
("scripts", "/opt/archipelago/scripts"),
|
||||||
|
("docker", "/opt/archipelago/docker"),
|
||||||
|
] {
|
||||||
|
let src = runtime_dir.join(relative);
|
||||||
|
if src.exists() {
|
||||||
|
replace_dir_from_runtime(&src, dest).await?;
|
||||||
|
if relative == "scripts" {
|
||||||
|
let _ = host_sudo(&[
|
||||||
|
"find", dest, "-type", "f", "-name", "*.sh", "-exec", "chmod", "755", "{}", "+",
|
||||||
|
])
|
||||||
|
.await;
|
||||||
|
let image_versions = format!("{}/image-versions.sh", dest);
|
||||||
|
if Path::new(&image_versions).exists() {
|
||||||
|
let _ =
|
||||||
|
host_sudo(&["cp", &image_versions, "/opt/archipelago/image-versions.sh"])
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let configs = runtime_dir.join("image-recipe/configs");
|
||||||
|
for unit in ["archipelago-doctor.service", "archipelago-doctor.timer"] {
|
||||||
|
let src = configs.join(unit);
|
||||||
|
if src.exists() {
|
||||||
|
let src_s = src.to_string_lossy().to_string();
|
||||||
|
let dest = format!("/etc/systemd/system/{}", unit);
|
||||||
|
let status = host_sudo(&["install", "-m", "644", &src_s, &dest])
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("install {}", unit))?;
|
||||||
|
if !status.success() {
|
||||||
|
anyhow::bail!("install {} exited with {}", unit, status);
|
||||||
|
}
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if changed {
|
||||||
|
let _ = host_sudo(&["systemctl", "daemon-reload"]).await;
|
||||||
|
let _ = host_sudo(&["systemctl", "enable", "--now", "archipelago-doctor.timer"]).await;
|
||||||
|
}
|
||||||
|
Ok(changed)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn replace_dir_from_runtime(src: &Path, dest: &str) -> Result<()> {
|
||||||
|
let tmp = format!("{}.new.{}", dest, chrono::Utc::now().timestamp_millis());
|
||||||
|
let src_dot = path_dot(src);
|
||||||
|
let mkdir = host_sudo(&["mkdir", "-p", &tmp])
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("mkdir {}", tmp))?;
|
||||||
|
if !mkdir.success() {
|
||||||
|
anyhow::bail!("mkdir {} exited with {}", tmp, mkdir);
|
||||||
|
}
|
||||||
|
let copy = host_sudo(&["cp", "-a", &src_dot, &tmp])
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("copy runtime {} -> {}", src.display(), tmp))?;
|
||||||
|
if !copy.success() {
|
||||||
|
let _ = host_sudo(&["rm", "-rf", &tmp]).await;
|
||||||
|
anyhow::bail!("copy runtime {} exited with {}", src.display(), copy);
|
||||||
|
}
|
||||||
|
let _ = host_sudo(&["mkdir", "-p", dest]).await;
|
||||||
|
let cleanup = host_sudo(&[
|
||||||
|
"find",
|
||||||
|
dest,
|
||||||
|
"-mindepth",
|
||||||
|
"1",
|
||||||
|
"-maxdepth",
|
||||||
|
"1",
|
||||||
|
"-exec",
|
||||||
|
"rm",
|
||||||
|
"-rf",
|
||||||
|
"{}",
|
||||||
|
"+",
|
||||||
|
])
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("clean {}", dest))?;
|
||||||
|
if !cleanup.success() {
|
||||||
|
let _ = host_sudo(&["rm", "-rf", &tmp]).await;
|
||||||
|
anyhow::bail!("clean {} exited with {}", dest, cleanup);
|
||||||
|
}
|
||||||
|
let tmp_dot = format!("{}/.", tmp);
|
||||||
|
let promote = host_sudo(&["cp", "-a", &tmp_dot, dest])
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("promote {} -> {}", tmp, dest))?;
|
||||||
|
let _ = host_sudo(&["rm", "-rf", &tmp]).await;
|
||||||
|
if !promote.success() {
|
||||||
|
anyhow::bail!("promote {} exited with {}", dest, promote);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path_dot(path: &Path) -> String {
|
||||||
|
let mut p = PathBuf::from(path);
|
||||||
|
p.push(".");
|
||||||
|
p.to_string_lossy().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
async fn run() -> Result<bool> {
|
async fn run() -> Result<bool> {
|
||||||
// Dev-box guard: on contributors' laptops `/home/archipelago/archy` is
|
// Dev-box guard: on contributors' laptops `/home/archipelago/archy` is
|
||||||
// typically a symlink into the git checkout, and writing through it
|
// typically a symlink into the git checkout, and writing through it
|
||||||
|
|||||||
@ -571,15 +571,10 @@ pub async fn check_for_updates(data_dir: &Path) -> Result<UpdateState> {
|
|||||||
mirror = %manifest_url,
|
mirror = %manifest_url,
|
||||||
"No newer version in manifest"
|
"No newer version in manifest"
|
||||||
);
|
);
|
||||||
if state.available_update.is_some() {
|
|
||||||
// A later mirror might still have a
|
|
||||||
// newer version — don't clobber what an
|
|
||||||
// earlier mirror told us. But also don't
|
|
||||||
// break: another mirror could be ahead.
|
|
||||||
continue 'mirrors;
|
|
||||||
}
|
|
||||||
state.manifest_mirror = None;
|
state.manifest_mirror = None;
|
||||||
state.available_update = None;
|
state.available_update = None;
|
||||||
|
handled = true;
|
||||||
|
continue 'mirrors;
|
||||||
}
|
}
|
||||||
handled = true;
|
handled = true;
|
||||||
break 'mirrors;
|
break 'mirrors;
|
||||||
@ -1173,6 +1168,125 @@ pub async fn apply_update(data_dir: &Path) -> Result<()> {
|
|||||||
}
|
}
|
||||||
info!(name = %name, "Frontend archive extracted to /opt/archipelago/web-ui");
|
info!(name = %name, "Frontend archive extracted to /opt/archipelago/web-ui");
|
||||||
}
|
}
|
||||||
|
_ if name.contains("runtime") && name.ends_with(".tar.gz") => {
|
||||||
|
let ts = chrono::Utc::now().timestamp_millis();
|
||||||
|
let staging_new = format!("/opt/archipelago/runtime.new.{}", ts);
|
||||||
|
let archive = src.to_string_lossy().to_string();
|
||||||
|
|
||||||
|
let mk = host_sudo(&["mkdir", "-p", &staging_new])
|
||||||
|
.await
|
||||||
|
.context("Failed to create runtime staging dir")?;
|
||||||
|
if !mk.success() {
|
||||||
|
anyhow::bail!("mkdir {} failed", staging_new);
|
||||||
|
}
|
||||||
|
|
||||||
|
let extract = host_sudo(&["tar", "-xzf", &archive, "-C", &staging_new])
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("Failed to extract {}", name))?;
|
||||||
|
if !extract.success() {
|
||||||
|
let _ = host_sudo(&["rm", "-rf", &staging_new]).await;
|
||||||
|
anyhow::bail!("tar extraction failed for {}", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
let runtime_paths = [
|
||||||
|
("apps", "apps"),
|
||||||
|
("scripts", "scripts"),
|
||||||
|
("docker", "docker"),
|
||||||
|
(
|
||||||
|
"image-recipe/configs/archipelago-doctor.service",
|
||||||
|
"archipelago-doctor.service",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"image-recipe/configs/archipelago-doctor.timer",
|
||||||
|
"archipelago-doctor.timer",
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (relative, label) in runtime_paths {
|
||||||
|
let staged_path = format!("{}/{}", staging_new, relative);
|
||||||
|
if !Path::new(&staged_path).exists() {
|
||||||
|
tracing::debug!(path = %relative, "Runtime artifact path absent, skipping");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
match label {
|
||||||
|
"apps" | "scripts" | "docker" => {
|
||||||
|
let dest = format!("/opt/archipelago/{}", label);
|
||||||
|
let tmp_dest =
|
||||||
|
format!("{}.new.{}", dest, chrono::Utc::now().timestamp_millis());
|
||||||
|
let _ = host_sudo(&["mkdir", "-p", &tmp_dest]).await;
|
||||||
|
let staged_dot = format!("{}/.", staged_path);
|
||||||
|
let copy = host_sudo(&["cp", "-a", &staged_dot, &tmp_dest])
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("Failed to copy runtime {}", label))?;
|
||||||
|
if !copy.success() {
|
||||||
|
let _ = host_sudo(&["rm", "-rf", &tmp_dest]).await;
|
||||||
|
anyhow::bail!("runtime copy failed for {}", label);
|
||||||
|
}
|
||||||
|
let _ = host_sudo(&["mkdir", "-p", &dest]).await;
|
||||||
|
let clean = host_sudo(&[
|
||||||
|
"find",
|
||||||
|
&dest,
|
||||||
|
"-mindepth",
|
||||||
|
"1",
|
||||||
|
"-maxdepth",
|
||||||
|
"1",
|
||||||
|
"-exec",
|
||||||
|
"rm",
|
||||||
|
"-rf",
|
||||||
|
"{}",
|
||||||
|
"+",
|
||||||
|
])
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("Failed to clean runtime {}", label))?;
|
||||||
|
if !clean.success() {
|
||||||
|
let _ = host_sudo(&["rm", "-rf", &tmp_dest]).await;
|
||||||
|
anyhow::bail!("runtime clean failed for {}", label);
|
||||||
|
}
|
||||||
|
let tmp_dot = format!("{}/.", tmp_dest);
|
||||||
|
let promote = host_sudo(&["cp", "-a", &tmp_dot, &dest])
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("Failed to promote runtime {}", label))?;
|
||||||
|
let _ = host_sudo(&["rm", "-rf", &tmp_dest]).await;
|
||||||
|
if !promote.success() {
|
||||||
|
anyhow::bail!("runtime promote failed for {}", label);
|
||||||
|
}
|
||||||
|
if label == "scripts" {
|
||||||
|
let _ = host_sudo(&[
|
||||||
|
"find", &dest, "-type", "f", "-name", "*.sh", "-exec", "chmod",
|
||||||
|
"755", "{}", "+",
|
||||||
|
])
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"archipelago-doctor.service" | "archipelago-doctor.timer" => {
|
||||||
|
let dest = format!("/etc/systemd/system/{}", label);
|
||||||
|
let install = host_sudo(&["install", "-m", "644", &staged_path, &dest])
|
||||||
|
.await
|
||||||
|
.with_context(|| format!("Failed to install {}", label))?;
|
||||||
|
if !install.success() {
|
||||||
|
anyhow::bail!("runtime unit install failed for {}", label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if Path::new(&format!("{}/scripts/image-versions.sh", staging_new)).exists() {
|
||||||
|
let _ = host_sudo(&[
|
||||||
|
"cp",
|
||||||
|
&format!("{}/scripts/image-versions.sh", staging_new),
|
||||||
|
"/opt/archipelago/image-versions.sh",
|
||||||
|
])
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _ = host_sudo(&["systemctl", "daemon-reload"]).await;
|
||||||
|
let _ =
|
||||||
|
host_sudo(&["systemctl", "enable", "--now", "archipelago-doctor.timer"]).await;
|
||||||
|
let _ = host_sudo(&["rm", "-rf", &staging_new]).await;
|
||||||
|
info!(name = %name, "Runtime assets applied to /opt/archipelago");
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
debug!(name = %name, "Unknown component, skipping");
|
debug!(name = %name, "Unknown component, skipping");
|
||||||
}
|
}
|
||||||
|
|||||||
4
neode-ui/package-lock.json
generated
4
neode-ui/package-lock.json
generated
@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "neode-ui",
|
"name": "neode-ui",
|
||||||
"version": "1.7.49-alpha",
|
"version": "1.7.50-alpha",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "neode-ui",
|
"name": "neode-ui",
|
||||||
"version": "1.7.49-alpha",
|
"version": "1.7.50-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,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "neode-ui",
|
"name": "neode-ui",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.7.49-alpha",
|
"version": "1.7.50-alpha",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "./start-dev.sh",
|
"start": "./start-dev.sh",
|
||||||
|
|||||||
@ -1,31 +1,27 @@
|
|||||||
{
|
{
|
||||||
"version": "1.7.44-alpha",
|
"version": "1.7.50-alpha",
|
||||||
"release_date": "2026-04-28",
|
"release_date": "2026-05-01",
|
||||||
"changelog": [
|
"changelog": [
|
||||||
"43de3b73 feat(orchestrator): complete container migration and release hardening",
|
"OTA now carries app manifests, scripts, Docker contexts, and doctor units inside the frontend payload so older nodes restore /opt/archipelago runtime assets after updating.",
|
||||||
"ce39430b feat(self-update): sync and rebuild UI containers on OTA",
|
"Startup bootstrap promotes the embedded runtime payload into /opt/archipelago and reloads the doctor timer without requiring rsync.",
|
||||||
"72dec5aa fix(lnd-ui): align container port across all specs",
|
"Update checks now continue past stale mirrors so nodes can discover a newer manifest from the next configured mirror."
|
||||||
"83aacdf2 chore(release): archive ISO build recipes, tarball-only releases",
|
|
||||||
"All notable changes to Archipelago will be documented in this file.",
|
|
||||||
"The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),",
|
|
||||||
"and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html)."
|
|
||||||
],
|
],
|
||||||
"components": [
|
"components": [
|
||||||
{
|
{
|
||||||
"name": "archipelago",
|
"name": "archipelago",
|
||||||
"current_version": "1.7.44-alpha",
|
"current_version": "1.7.49-alpha",
|
||||||
"new_version": "1.7.44-alpha",
|
"new_version": "1.7.50-alpha",
|
||||||
"download_url": "https://github.com/archipelago-os/releases/releases/download/v1.7.44-alpha/archipelago",
|
"download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.50-alpha/archipelago",
|
||||||
"sha256": "ea9167d376210bdc416dd0dcf3c1737e2fa2b1bdb9f192e473f2edaaf0d046a7",
|
"sha256": "494f71d58608787a4f485ec6e295057e70045e905d745837d1cd6089e484197d",
|
||||||
"size_bytes": 41373032
|
"size_bytes": 41779280
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "archipelago-frontend-1.7.44-alpha.tar.gz",
|
"name": "archipelago-frontend-1.7.50-alpha.tar.gz",
|
||||||
"current_version": "1.7.44-alpha",
|
"current_version": "1.7.49-alpha",
|
||||||
"new_version": "1.7.44-alpha",
|
"new_version": "1.7.50-alpha",
|
||||||
"download_url": "https://github.com/archipelago-os/releases/releases/download/v1.7.44-alpha/archipelago-frontend-1.7.44-alpha.tar.gz",
|
"download_url": "https://git.tx1138.com/lfg2025/archy/raw/branch/main/releases/v1.7.50-alpha/archipelago-frontend-1.7.50-alpha.tar.gz",
|
||||||
"sha256": "28bdbccd8151bfa7834761adb5363862ef54b76c6010d1446fb8cca4ed4fe18c",
|
"sha256": "6684d5f1083fd2d1db236fcc4a94efee7986ab54602b3231b031f3e9f7dec361",
|
||||||
"size_bytes": 162089899
|
"size_bytes": 165153241
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -87,6 +87,30 @@ if [ -z "$FRONTEND_ARCHIVE" ]; then
|
|||||||
echo " Including AIUI from demo/aiui/"
|
echo " Including AIUI from demo/aiui/"
|
||||||
cp -r "$PROJECT_ROOT/demo/aiui" "$STAGING_DIR/aiui"
|
cp -r "$PROJECT_ROOT/demo/aiui" "$STAGING_DIR/aiui"
|
||||||
fi
|
fi
|
||||||
|
# OTA bridge for nodes running older updaters: they only know how to
|
||||||
|
# apply the backend binary and frontend archive. Carry host runtime
|
||||||
|
# assets inside the frontend tarball; the new backend promotes them
|
||||||
|
# from /opt/archipelago/web-ui/archipelago-runtime on first startup.
|
||||||
|
RUNTIME_DIR="$STAGING_DIR/archipelago-runtime"
|
||||||
|
mkdir -p "$RUNTIME_DIR"
|
||||||
|
for runtime_path in apps scripts docker; do
|
||||||
|
if [ -d "$PROJECT_ROOT/$runtime_path" ]; then
|
||||||
|
echo " Including runtime $runtime_path/"
|
||||||
|
cp -r "$PROJECT_ROOT/$runtime_path" "$RUNTIME_DIR/$runtime_path"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ -f "$PROJECT_ROOT/image-recipe/configs/archipelago-doctor.service" ] || \
|
||||||
|
[ -f "$PROJECT_ROOT/image-recipe/configs/archipelago-doctor.timer" ]; then
|
||||||
|
mkdir -p "$RUNTIME_DIR/image-recipe/configs"
|
||||||
|
for unit in archipelago-doctor.service archipelago-doctor.timer; do
|
||||||
|
if [ -f "$PROJECT_ROOT/image-recipe/configs/$unit" ]; then
|
||||||
|
echo " Including runtime unit $unit"
|
||||||
|
cp "$PROJECT_ROOT/image-recipe/configs/$unit" "$RUNTIME_DIR/image-recipe/configs/$unit"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
rm -rf "$RUNTIME_DIR/scripts/resilience/reports"
|
||||||
|
find "$RUNTIME_DIR" -type f \( -name '*.bak' -o -name '._*' -o -name '*.log' \) -delete
|
||||||
# Force world-readable perms on every entry BEFORE tar, so the
|
# Force world-readable perms on every entry BEFORE tar, so the
|
||||||
# archive's internal mode bits are 755/644 regardless of what
|
# archive's internal mode bits are 755/644 regardless of what
|
||||||
# the staging dir's umask gave us. Without this, mktemp -d
|
# the staging dir's umask gave us. Without this, mktemp -d
|
||||||
|
|||||||
@ -200,6 +200,19 @@ if [ -f "$REPO_DIR/scripts/image-versions.sh" ]; then
|
|||||||
sudo cp "$REPO_DIR/scripts/image-versions.sh" /opt/archipelago/image-versions.sh
|
sudo cp "$REPO_DIR/scripts/image-versions.sh" /opt/archipelago/image-versions.sh
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Sync app manifests and app-local build contexts into the canonical
|
||||||
|
# production manifest root. The backend orchestrator loads install specs from
|
||||||
|
# /opt/archipelago/apps; updating only the binary/frontend can leave a node
|
||||||
|
# with new installer logic but stale or missing app manifests.
|
||||||
|
APPS_DEST="/opt/archipelago/apps"
|
||||||
|
if [ -d "$REPO_DIR/apps" ]; then
|
||||||
|
sudo mkdir -p "$APPS_DEST"
|
||||||
|
sudo rsync -a --delete "$REPO_DIR/apps/" "$APPS_DEST/"
|
||||||
|
ok "App manifests synced"
|
||||||
|
else
|
||||||
|
warn "Apps directory not found at $REPO_DIR/apps — install manifests may be stale"
|
||||||
|
fi
|
||||||
|
|
||||||
# Update first-boot-containers.sh too (the canonical first-boot orchestrator).
|
# Update first-boot-containers.sh too (the canonical first-boot orchestrator).
|
||||||
# Nodes run it once on install, but keeping a fresh copy on disk means any
|
# Nodes run it once on install, but keeping a fresh copy on disk means any
|
||||||
# future boot or reconciler invocation uses current port specs and caps.
|
# future boot or reconciler invocation uses current port specs and caps.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user