release(v1.7.0-alpha): bump + fix git-method update + reconciler creates

Two fixes bundled into the OTA:

1. update.download hard-fail on git-path nodes. handle_update_check's git
   branch reported update_available=true + update_method="git" but never
   populated state.available_update, so update.download returned "No update
   available to download" even though the UI showed one. SystemUpdate.vue
   now routes update_method=="git" through update.git-apply (pull+rebuild+
   restart via self-update.sh); manifest-path nodes keep the download→apply
   flow. i18n strings + confirm modal added for the git path.

2. Reconciler creating containers behind the user's back. On fresh
   unbundled installs (.198, .253) archy-mempool-db and archy-btcpay-db
   materialised ~10 min after first boot because reconcile-containers.sh
   walked container-specs.sh's canonical tier list and created any
   "missing" container. reset_spec() now defaults SPEC_OPTIONAL="true",
   so reconcile is strictly a repair tool — baseline comes from
   first-boot-containers.sh (filebrowser on unbundled), everything else
   from the install RPC.

Also forces OTA trigger for nodes on 1.6.0-alpha that otherwise saw
"I'm at manifest.version, nothing to do" and skipped the refreshed 1.6
artifacts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-04-20 06:22:29 -04:00
parent 6b9b7a5a9c
commit 6a4d48b49f
6 changed files with 78 additions and 17 deletions

2
core/Cargo.lock generated
View File

@@ -80,7 +80,7 @@ checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61"
[[package]]
name = "archipelago"
version = "1.6.0-alpha"
version = "1.7.0-alpha"
dependencies = [
"anyhow",
"archipelago-container",

View File

@@ -1,6 +1,6 @@
[package]
name = "archipelago"
version = "1.6.0-alpha"
version = "1.7.0-alpha"
edition = "2021"
description = "Archipelago Bitcoin Node OS - Native backend"
authors = ["Archipelago Team"]

View File

@@ -683,7 +683,12 @@
"applySuccess": "Update applied. The service will restart momentarily.",
"applyFailed": "Failed to apply update. You can try again or rollback.",
"rollbackSuccess": "Rolled back to previous version. Service will restart.",
"rollbackFailed": "Rollback failed."
"rollbackFailed": "Rollback failed.",
"pullAndRebuild": "Pull & Rebuild",
"gitMethodHint": "This node builds from source. Update will git-pull, rebuild the backend and UI, then restart — takes a few minutes.",
"gitApplyTitle": "Pull & Rebuild?",
"gitApplyMessage": "Archipelago will pull the latest code, rebuild, and restart. This can take several minutes and the UI will be briefly unavailable.",
"gitApplyStarted": "Update started. The backend will rebuild and restart — this can take a few minutes."
},
"kiosk": {
"pressEsc": "Press ESC to exit",

View File

@@ -682,7 +682,12 @@
"applySuccess": "Actualizaci\u00f3n aplicada. El servicio se reiniciar\u00e1 en un momento.",
"applyFailed": "Error al aplicar la actualizaci\u00f3n. Puede intentar de nuevo o revertir.",
"rollbackSuccess": "Se revirti\u00f3 a la versi\u00f3n anterior. El servicio se reiniciar\u00e1.",
"rollbackFailed": "Error al revertir."
"rollbackFailed": "Error al revertir.",
"pullAndRebuild": "Pull y Recompilar",
"gitMethodHint": "Este nodo compila desde el c\u00f3digo fuente. La actualizaci\u00f3n har\u00e1 git-pull, recompilar\u00e1 y reiniciar\u00e1 — tarda unos minutos.",
"gitApplyTitle": "\u00bfPull y Recompilar?",
"gitApplyMessage": "Archipelago descargar\u00e1 el c\u00f3digo m\u00e1s reciente, lo compilar\u00e1 y reiniciar\u00e1. Puede tardar varios minutos y la UI estar\u00e1 brevemente no disponible.",
"gitApplyStarted": "Actualizaci\u00f3n iniciada. El backend se recompilar\u00e1 y reiniciar\u00e1 — puede tardar unos minutos."
},
"kiosk": {
"pressEsc": "Presione ESC para salir",

View File

@@ -64,21 +64,33 @@
<!-- Actions -->
<div class="flex gap-3">
<!-- Git path: one-shot pull+rebuild+restart -->
<button
v-if="!downloading && !applying"
v-if="updateMethod === 'git' && !applying"
@click="requestGitApply"
class="glass-button rounded-lg px-6 py-2 text-sm font-medium bg-orange-500/20 border-orange-400/30"
>
{{ t('systemUpdate.pullAndRebuild') }}
</button>
<!-- Manifest path: download then apply -->
<button
v-if="updateMethod !== 'git' && !downloading && !applying && !downloaded"
@click="downloadUpdate"
class="glass-button rounded-lg px-6 py-2 text-sm font-medium"
>
{{ t('systemUpdate.downloadUpdate') }}
</button>
<button
v-if="downloaded && !applying"
v-if="updateMethod !== 'git' && downloaded && !applying"
@click="requestApply"
class="glass-button rounded-lg px-6 py-2 text-sm font-medium bg-orange-500/20 border-orange-400/30"
>
{{ t('systemUpdate.applyUpdate') }}
</button>
</div>
<p v-if="updateMethod === 'git'" class="text-xs text-white/40 mt-3">
{{ t('systemUpdate.gitMethodHint') }}
</p>
</div>
<!-- No update available -->
@@ -169,12 +181,18 @@
<div v-if="confirmAction" class="fixed inset-0 z-50 flex items-center justify-center bg-black/10 backdrop-blur-md" @click.self="cancelConfirm">
<div class="glass-card p-6 max-w-sm w-full mx-4">
<h3 class="text-lg font-semibold text-white mb-3">
{{ confirmAction === 'apply' ? t('systemUpdate.applyTitle') : t('systemUpdate.rollbackTitle') }}
{{ confirmAction === 'rollback'
? t('systemUpdate.rollbackTitle')
: confirmAction === 'git-apply'
? t('systemUpdate.gitApplyTitle')
: t('systemUpdate.applyTitle') }}
</h3>
<p class="text-sm text-white/70 mb-6">
{{ confirmAction === 'apply'
? t('systemUpdate.applyMessage')
: t('systemUpdate.rollbackMessage') }}
{{ confirmAction === 'rollback'
? t('systemUpdate.rollbackMessage')
: confirmAction === 'git-apply'
? t('systemUpdate.gitApplyMessage')
: t('systemUpdate.applyMessage') }}
</p>
<div class="flex gap-3 justify-end">
<button @click="cancelConfirm" class="glass-button rounded-lg px-4 py-2 text-sm font-medium">
@@ -185,7 +203,11 @@
class="glass-button rounded-lg px-4 py-2 text-sm font-medium"
:class="confirmAction === 'rollback' ? 'bg-red-500/20 border-red-400/30' : 'bg-orange-500/20 border-orange-400/30'"
>
{{ confirmAction === 'apply' ? t('systemUpdate.applyNow') : t('systemUpdate.rollbackButton') }}
{{ confirmAction === 'rollback'
? t('systemUpdate.rollbackButton')
: confirmAction === 'git-apply'
? t('systemUpdate.pullAndRebuild')
: t('systemUpdate.applyNow') }}
</button>
</div>
</div>
@@ -222,10 +244,11 @@ const loading = ref(false)
const downloading = ref(false)
const downloaded = ref(false)
const applying = ref(false)
const confirmAction = ref<'apply' | 'rollback' | null>(null)
const confirmAction = ref<'apply' | 'git-apply' | 'rollback' | null>(null)
const currentVersion = ref('0.0.0')
const lastCheck = ref<string | null>(null)
const updateInfo = ref<UpdateDetail | null>(null)
const updateMethod = ref<'git' | 'manifest' | null>(null)
const rollbackAvailable = ref(false)
const updateInProgress = ref(false)
const statusMessage = ref('')
@@ -299,10 +322,12 @@ async function checkForUpdates() {
last_check: string | null
update_available: boolean
update: UpdateDetail | null
update_method?: string
}>({ method: 'update.check' })
currentVersion.value = res.current_version
lastCheck.value = res.last_check
updateInfo.value = res.update
updateMethod.value = res.update_method === 'git' ? 'git' : 'manifest'
if (!res.update_available) {
showStatus(t('systemUpdate.upToDateMessage'))
}
@@ -349,6 +374,10 @@ function requestApply() {
confirmAction.value = 'apply'
}
function requestGitApply() {
confirmAction.value = 'git-apply'
}
function requestRollback() {
confirmAction.value = 'rollback'
}
@@ -358,15 +387,32 @@ function cancelConfirm() {
}
async function executeConfirm() {
if (confirmAction.value === 'apply') {
confirmAction.value = null
const action = confirmAction.value
confirmAction.value = null
if (action === 'apply') {
await applyUpdate()
} else if (confirmAction.value === 'rollback') {
confirmAction.value = null
} else if (action === 'git-apply') {
await applyUpdateGit()
} else if (action === 'rollback') {
await rollbackUpdate()
}
}
async function applyUpdateGit() {
applying.value = true
statusMessage.value = ''
try {
await rpcClient.call({ method: 'update.git-apply' })
showStatus(t('systemUpdate.gitApplyStarted'))
updateInfo.value = null
} catch (e) {
showStatus(t('systemUpdate.applyFailed'), true)
if (import.meta.env.DEV) console.warn('Git apply failed', e)
} finally {
applying.value = false
}
}
async function applyUpdate() {
applying.value = true
statusMessage.value = ''

View File

@@ -73,7 +73,12 @@ reset_spec() {
SPEC_SECURITY="no-new-privileges:true" SPEC_RESTART="unless-stopped"
SPEC_HEALTH_CMD="" SPEC_ENV="" SPEC_CUSTOM_ARGS="" SPEC_READONLY="false"
SPEC_TMPFS="" SPEC_TIER="3" SPEC_DATA_DIR="" SPEC_DATA_UID="100000:100000"
SPEC_DEPENDS="" SPEC_LOCAL_IMAGE="false" SPEC_OPTIONAL="false"
# SPEC_OPTIONAL defaults true: reconcile-containers.sh only REPAIRS existing
# containers — it never creates missing ones. Baseline (filebrowser) is
# bootstrapped by first-boot-containers.sh; all other apps come from the
# install RPC. Per-spec `SPEC_OPTIONAL="true"` lines below are now redundant
# but kept for readability.
SPEC_DEPENDS="" SPEC_LOCAL_IMAGE="false" SPEC_OPTIONAL="true"
SPEC_ENTRYPOINT=""
}