Compare commits

...

4 Commits

Author SHA1 Message Date
archipelago
a322b04021 fix(iso): avoid polkit in live debootstrap seed 2026-05-15 18:32:14 -04:00
archipelago
645cf69ed7 chore(release): refresh v1.7.56-alpha manifest after wifi fix 2026-05-15 18:26:17 -04:00
archipelago
01ec0565a6 fix: restore wifi setup and ssh password updates 2026-05-15 18:15:06 -04:00
archipelago
30505f41ff chore(release): refresh v1.7.56-alpha notes and artifacts 2026-05-15 17:54:32 -04:00
11 changed files with 188 additions and 21 deletions

View File

@ -1,11 +1,25 @@
# Changelog # Changelog
## v1.7.56-alpha (2026-05-14) ## v1.7.56-alpha (2026-05-15)
- Health notifications now clear when an app is no longer unhealthy, including stale alerts for removed containers such as Portainer. - Health notifications now clear when an app is no longer unhealthy, including stale alerts for removed containers such as Portainer.
- Fresh installs now include the full Wi-Fi userspace stack (`wpasupplicant`, `wireless-regdb`, `iw`, `rfkill`, `polkitd`, `pciutils`, and `usbutils`) so NetworkManager can scan and connect with Intel Wi-Fi cards out of the box.
- The installed system now grants the `archipelago` service user explicit NetworkManager PolicyKit access for web-triggered Wi-Fi scans and connection changes.
- Wi-Fi connect now replaces stale/partial NetworkManager profiles and creates an explicit WPA-PSK profile with the supplied password, avoiding no-secret retry failures after a failed attempt.
- Settings password changes now update the Linux/SSH password through non-interactive sudo, so the web password and SSH password stay in sync when the checkbox is enabled.
- Quadlet environment values with spaces or shell metacharacters are quoted consistently, preventing env drift recreate loops for apps like nostr-rs-relay and Grafana. - Quadlet environment values with spaces or shell metacharacters are quoted consistently, preventing env drift recreate loops for apps like nostr-rs-relay and Grafana.
- Boot/bootstrap reconcile avoids restarting running Bitcoin containers while repairing RPC config, preserving IBD progress on active nodes. - Boot/bootstrap reconcile avoids restarting running Bitcoin containers while repairing RPC config, preserving IBD progress on active nodes.
- Exit code 137 is labeled as SIGKILL instead of assuming OOM, avoiding false OOM alerts for orchestrator-managed recreates. - Exit code 137 is labeled as SIGKILL instead of assuming OOM, avoiding false OOM alerts for orchestrator-managed recreates.
- Container reconcile force-recreates Podman records stuck in `Stopping`, preserving bind-mounted app data while recovering wedged containers automatically.
- Container health reporting is honest for running containers: Archipelago surfaces Podman's actual health state instead of marking every running container healthy.
- Quadlet reconciliation restarts services when stale health gates, port bindings, network aliases, exec commands, or healthchecks drift from the current manifest.
- Bitcoin Knots sync performance improves on fresh installs and updates with 8Gi container memory, a 4Gi dbcache, and full CPU parallelism.
- ElectrumX initial indexing gets more headroom: CPU caps are removed, memory is raised to 4Gi, cache is raised to 3Gi, and oversized sends are allowed for heavier wallet/indexing workloads.
- Mempool/ElectrumX lifecycle qualification respects pruned/non-archival Bitcoin nodes instead of installing a half-running stack with unhealthy dependencies.
- LND wallet/RPC helpers are more tolerant of container-owned files and updated REST port metadata, improving LND lifecycle and wallet-connect flows.
- Marketplace/catalog metadata carries richer container config so remote lifecycle tests install apps using the same settings users get from the UI.
- The app screensaver no longer activates during media-heavy app sessions such as IndeeHub, Jellyfin, Immich, PhotoPrism, and File Browser; apps can also pause/resume it with media playback messages.
- A fresh `1.7.56-alpha` unbundled installer ISO is built from the same primary VPS2 release line for easy download and USB flashing.
## v1.7.55-alpha (2026-05-13) ## v1.7.55-alpha (2026-05-13)

View File

@ -307,14 +307,62 @@ async fn scan_wifi() -> Result<Vec<serde_json::Value>> {
/// Connect to a WiFi network using nmcli. /// Connect to a WiFi network using nmcli.
async fn connect_wifi(ssid: &str, password: &str) -> Result<()> { async fn connect_wifi(ssid: &str, password: &str) -> Result<()> {
let conn_name = format!("archipelago-wifi-{ssid}");
// Delete prior profiles for this SSID/name first. Failed attempts can leave
// a profile with key-mgmt but no saved PSK, causing future retries to fail
// with "no secrets" before the supplied password is used.
let _ = tokio::process::Command::new("nmcli")
.args(["connection", "delete", &conn_name])
.output()
.await;
let _ = tokio::process::Command::new("nmcli")
.args(["connection", "delete", ssid])
.output()
.await;
let output = tokio::process::Command::new("nmcli") let output = tokio::process::Command::new("nmcli")
.args(["device", "wifi", "connect", ssid, "password", password]) .args([
"connection",
"add",
"type",
"wifi",
"con-name",
&conn_name,
"ifname",
"*",
"ssid",
ssid,
"wifi-sec.key-mgmt",
"wpa-psk",
"wifi-sec.psk",
password,
"ipv4.method",
"auto",
"ipv6.method",
"auto",
])
.output()
.await
.context("Failed to run nmcli wifi profile create")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
anyhow::bail!("WiFi profile create failed: {}", stderr);
}
let activate = tokio::process::Command::new("nmcli")
.args(["connection", "up", &conn_name])
.output() .output()
.await .await
.context("Failed to run nmcli wifi connect")?; .context("Failed to run nmcli wifi connect")?;
if !output.status.success() { if !activate.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr); let stderr = String::from_utf8_lossy(&activate.stderr);
let _ = tokio::process::Command::new("nmcli")
.args(["connection", "delete", &conn_name])
.output()
.await;
anyhow::bail!("WiFi connection failed: {}", stderr); anyhow::bail!("WiFi connection failed: {}", stderr);
} }

View File

@ -381,14 +381,14 @@ async fn change_ssh_password(new_password: &str) -> Result<()> {
// usermod -p writes directly to /etc/shadow, bypassing PAM // usermod -p writes directly to /etc/shadow, bypassing PAM
// Use /usr/sbin/usermod - not always in systemd's PATH // Use /usr/sbin/usermod - not always in systemd's PATH
let status = tokio::process::Command::new("/usr/sbin/usermod") let status = tokio::process::Command::new("/usr/bin/sudo")
.args(["-p", &hash, &ssh_user]) .args(["-n", "/usr/sbin/usermod", "-p", &hash, &ssh_user])
.output() .output()
.await?; .await?;
if !status.status.success() { if !status.status.success() {
let stderr = String::from_utf8_lossy(&status.stderr); let stderr = String::from_utf8_lossy(&status.stderr);
anyhow::bail!("usermod failed: {}", stderr); anyhow::bail!("sudo usermod failed: {}", stderr);
} }
tracing::info!("SSH password updated for user {}", ssh_user); tracing::info!("SSH password updated for user {}", ssh_user);

View File

@ -316,6 +316,11 @@ RUN apt-get update && apt-get -y full-upgrade && apt-get install -y --no-install
dbus \ dbus \
sudo \ sudo \
network-manager \ network-manager \
wpasupplicant \
wireless-regdb \
iw \
rfkill \
polkitd \
openssh-server \ openssh-server \
nginx \ nginx \
podman \ podman \
@ -455,8 +460,21 @@ RUN mkdir -p /etc/systemd/system/user@.service.d && \
# Allow unprivileged ping inside rootless containers # Allow unprivileged ping inside rootless containers
RUN printf 'net.ipv4.ping_group_range=0 2147483647\n' > /etc/sysctl.d/90-podman-ping.conf RUN printf 'net.ipv4.ping_group_range=0 2147483647\n' > /etc/sysctl.d/90-podman-ping.conf
# Archipelago's web UI manages Wi-Fi through the backend service, not a local
# desktop seat. Allow the dedicated system user to control NetworkManager.
RUN mkdir -p /etc/polkit-1/rules.d && \
printf '%s\n' \
'polkit.addRule(function(action, subject) {' \
' if (subject.user == "archipelago" && action.id.indexOf("org.freedesktop.NetworkManager.") == 0) {' \
' return polkit.Result.YES;' \
' }' \
'});' \
> /etc/polkit-1/rules.d/49-archipelago-networkmanager.rules && \
chmod 644 /etc/polkit-1/rules.d/49-archipelago-networkmanager.rules
# Enable services # Enable services
RUN systemctl enable NetworkManager || true && \ RUN systemctl enable NetworkManager || true && \
systemctl enable polkit || systemctl enable polkit.service || true && \
systemctl enable ssh || true && \ systemctl enable ssh || true && \
systemctl enable nginx || true && \ systemctl enable nginx || true && \
systemctl enable archipelago || true && \ systemctl enable archipelago || true && \
@ -696,6 +714,7 @@ kmod,procps,iproute2,ca-certificates,gdisk,\
cryptsetup,cryptsetup-initramfs,parted,dosfstools,e2fsprogs,\ cryptsetup,cryptsetup-initramfs,parted,dosfstools,e2fsprogs,\
linux-image-${DEB_ARCH},grub-efi-${DEB_ARCH},grub-pc-bin,\ linux-image-${DEB_ARCH},grub-efi-${DEB_ARCH},grub-pc-bin,\
ifupdown,isc-dhcp-client,\ ifupdown,isc-dhcp-client,\
wpasupplicant,wireless-regdb,iw,rfkill,\
pciutils,usbutils,less,nano \ pciutils,usbutils,less,nano \
trixie /installer http://deb.debian.org/debian trixie /installer http://deb.debian.org/debian

View File

@ -166,7 +166,14 @@ chroot /mnt/archipelago apt-get install -y linux-image-amd64 grub-efi-amd64 grub
echo "📦 Installing essential packages..." echo "📦 Installing essential packages..."
chroot /mnt/archipelago apt-get install -y \ chroot /mnt/archipelago apt-get install -y \
sudo \ sudo \
networkmanager \ network-manager \
wpasupplicant \
wireless-regdb \
iw \
rfkill \
pciutils \
usbutils \
polkitd \
openssh-server \ openssh-server \
curl \ curl \
wget \ wget \
@ -198,9 +205,20 @@ echo "archipelago:archipelago" | chroot /mnt/archipelago chpasswd
echo "⚙️ Enabling services..." echo "⚙️ Enabling services..."
chroot /mnt/archipelago systemctl enable NetworkManager || true chroot /mnt/archipelago systemctl enable NetworkManager || true
chroot /mnt/archipelago systemctl enable polkit || chroot /mnt/archipelago systemctl enable polkit.service || true
chroot /mnt/archipelago systemctl enable ssh || chroot /mnt/archipelago systemctl enable sshd || true chroot /mnt/archipelago systemctl enable ssh || chroot /mnt/archipelago systemctl enable sshd || true
chroot /mnt/archipelago systemctl enable chrony || true chroot /mnt/archipelago systemctl enable chrony || true
mkdir -p /mnt/archipelago/etc/polkit-1/rules.d
cat > /mnt/archipelago/etc/polkit-1/rules.d/49-archipelago-networkmanager.rules <<'EOF'
polkit.addRule(function(action, subject) {
if (subject.user == "archipelago" && action.id.indexOf("org.freedesktop.NetworkManager.") == 0) {
return polkit.Result.YES;
}
});
EOF
chmod 644 /mnt/archipelago/etc/polkit-1/rules.d/49-archipelago-networkmanager.rules
# Remove policy-rc.d so services can start on first boot # Remove policy-rc.d so services can start on first boot
rm -f /mnt/archipelago/usr/sbin/policy-rc.d rm -f /mnt/archipelago/usr/sbin/policy-rc.d

View File

@ -161,7 +161,7 @@ function onKeyDown(e: KeyboardEvent) {
} }
// 's' key activates screensaver when authenticated (skip if typing in input) // 's' key activates screensaver when authenticated (skip if typing in input)
if (e.key === 's' || e.key === 'S') { if (e.key === 's' || e.key === 'S') {
if (!isInput && appStore.isAuthenticated && !screensaverStore.isActive) { if (!isInput && appStore.isAuthenticated && !screensaverStore.isActive && !screensaverStore.isSuppressed) {
e.preventDefault() e.preventDefault()
screensaverStore.activate() screensaverStore.activate()
} }

View File

@ -68,6 +68,24 @@ describe('useScreensaverStore', () => {
expect(store.isActive).toBe(false) expect(store.isActive).toBe(false)
}) })
it('suppression prevents automatic and manual activation until resumed', () => {
const store = useScreensaverStore()
store.suppress('video')
expect(store.isSuppressed).toBe(true)
store.resetInactivityTimer()
vi.advanceTimersByTime(5 * 60 * 1000)
expect(store.isActive).toBe(false)
store.activate()
expect(store.isActive).toBe(false)
store.resume('video')
expect(store.isSuppressed).toBe(false)
vi.advanceTimersByTime(3 * 60 * 1000)
expect(store.isActive).toBe(true)
})
it('activate clears any pending timer', () => { it('activate clears any pending timer', () => {
const store = useScreensaverStore() const store = useScreensaverStore()
store.deactivate() store.deactivate()

View File

@ -6,12 +6,15 @@ const INACTIVITY_MS = 3 * 60 * 1000 // 3 minutes
export const useScreensaverStore = defineStore('screensaver', () => { export const useScreensaverStore = defineStore('screensaver', () => {
const isActive = ref(false) const isActive = ref(false)
const activationCount = ref(0) const activationCount = ref(0)
const suppressionReasons = ref<Set<string>>(new Set())
let inactivityTimer: ReturnType<typeof setTimeout> | null = null let inactivityTimer: ReturnType<typeof setTimeout> | null = null
/** True when the current activation is the ASCII variant (every 3rd time) */ /** True when the current activation is the ASCII variant (every 3rd time) */
const isAsciiMode = computed(() => activationCount.value > 0 && activationCount.value % 3 === 0) const isAsciiMode = computed(() => activationCount.value > 0 && activationCount.value % 3 === 0)
const isSuppressed = computed(() => suppressionReasons.value.size > 0)
function activate() { function activate() {
if (isSuppressed.value) return
activationCount.value++ activationCount.value++
isActive.value = true isActive.value = true
clearInactivityTimer() clearInactivityTimer()
@ -24,8 +27,10 @@ export const useScreensaverStore = defineStore('screensaver', () => {
function resetInactivityTimer() { function resetInactivityTimer() {
clearInactivityTimer() clearInactivityTimer()
if (isSuppressed.value) return
inactivityTimer = setTimeout(() => { inactivityTimer = setTimeout(() => {
inactivityTimer = null inactivityTimer = null
if (isSuppressed.value) return
isActive.value = true isActive.value = true
}, INACTIVITY_MS) }, INACTIVITY_MS)
} }
@ -37,13 +42,30 @@ export const useScreensaverStore = defineStore('screensaver', () => {
} }
} }
function suppress(reason: string) {
suppressionReasons.value = new Set(suppressionReasons.value).add(reason)
clearInactivityTimer()
isActive.value = false
}
function resume(reason: string) {
if (!suppressionReasons.value.has(reason)) return
const next = new Set(suppressionReasons.value)
next.delete(reason)
suppressionReasons.value = next
if (next.size === 0) resetInactivityTimer()
}
return { return {
isActive, isActive,
isAsciiMode, isAsciiMode,
isSuppressed,
activationCount, activationCount,
activate, activate,
deactivate, deactivate,
resetInactivityTimer, resetInactivityTimer,
clearInactivityTimer, clearInactivityTimer,
suppress,
resume,
} }
}) })

View File

@ -91,6 +91,7 @@
import { ref, computed, onMounted, onBeforeUnmount, watch } from 'vue' import { ref, computed, onMounted, onBeforeUnmount, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { useAppLauncherStore } from '@/stores/appLauncher' import { useAppLauncherStore } from '@/stores/appLauncher'
import { useScreensaverStore } from '@/stores/screensaver'
import NostrIdentityPicker from '@/components/NostrIdentityPicker.vue' import NostrIdentityPicker from '@/components/NostrIdentityPicker.vue'
import AppSessionHeader from './appSession/AppSessionHeader.vue' import AppSessionHeader from './appSession/AppSessionHeader.vue'
import AppSessionFrame from './appSession/AppSessionFrame.vue' import AppSessionFrame from './appSession/AppSessionFrame.vue'
@ -115,6 +116,7 @@ const isInlinePanel = computed(() => !!props.appIdProp)
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const screensaverStore = useScreensaverStore()
const sessionRef = ref<HTMLElement | null>(null) const sessionRef = ref<HTMLElement | null>(null)
const frameRef = ref<InstanceType<typeof AppSessionFrame> | null>(null) const frameRef = ref<InstanceType<typeof AppSessionFrame> | null>(null)
@ -145,6 +147,14 @@ const appId = computed(() => {
const appTitle = computed(() => resolveAppTitle(appId.value)) const appTitle = computed(() => resolveAppTitle(appId.value))
const isMobile = typeof window !== 'undefined' && window.innerWidth < 768 const isMobile = typeof window !== 'undefined' && window.innerWidth < 768
const mustOpenNewTab = computed(() => NEW_TAB_APPS.has(appId.value)) const mustOpenNewTab = computed(() => NEW_TAB_APPS.has(appId.value))
const screensaverReason = computed(() => `app-session:${appId.value}`)
const screensaverSuppressedApps = new Set([
'indeedhub',
'jellyfin',
'immich',
'photoprism',
'filebrowser',
])
const appUrl = computed(() => { const appUrl = computed(() => {
return resolveAppUrl(appId.value, route.query.path as string | undefined) return resolveAppUrl(appId.value, route.query.path as string | undefined)
@ -306,6 +316,8 @@ function onFullscreenChange() {
function onMessage(e: MessageEvent) { function onMessage(e: MessageEvent) {
if (e.data?.type === 'nostr-request') nostrBridge.handleNostrRequest(e) if (e.data?.type === 'nostr-request') nostrBridge.handleNostrRequest(e)
if (e.data?.type === 'archipelago:identity:request') identity.handleIdentityRequest() if (e.data?.type === 'archipelago:identity:request') identity.handleIdentityRequest()
if (e.data?.type === 'archipelago:media:playing') screensaverStore.suppress(screensaverReason.value)
if (e.data?.type === 'archipelago:media:idle') screensaverStore.resume(screensaverReason.value)
} }
// Enter fullscreen on mount if mode is fullscreen // Enter fullscreen on mount if mode is fullscreen
@ -338,6 +350,9 @@ onMounted(() => {
sessionRef.value?.requestFullscreen().catch(() => {}) sessionRef.value?.requestFullscreen().catch(() => {})
}) })
} }
if (screensaverSuppressedApps.has(appId.value)) {
screensaverStore.suppress(screensaverReason.value)
}
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
@ -347,6 +362,7 @@ onBeforeUnmount(() => {
window.removeEventListener('keydown', onKeyDown, true) window.removeEventListener('keydown', onKeyDown, true)
window.removeEventListener('message', onMessage) window.removeEventListener('message', onMessage)
document.removeEventListener('fullscreenchange', onFullscreenChange) document.removeEventListener('fullscreenchange', onFullscreenChange)
screensaverStore.resume(screensaverReason.value)
if (document.fullscreenElement) document.exitFullscreen().catch(() => {}) if (document.fullscreenElement) document.exitFullscreen().catch(() => {})
}) })
</script> </script>

View File

@ -1,11 +1,17 @@
{ {
"version": "1.7.56-alpha", "version": "1.7.56-alpha",
"release_date": "2026-05-14", "release_date": "2026-05-15",
"changelog": [ "changelog": [
"Health notifications now clear when an app is no longer unhealthy, including stale alerts for removed containers such as Portainer.", "Health notifications now clear when an app is no longer unhealthy, including stale alerts for removed containers such as Portainer.",
"Fresh installs now include the full Wi-Fi userspace stack (`wpasupplicant`, `wireless-regdb`, `iw`, `rfkill`, `polkitd`, `pciutils`, and `usbutils`) so NetworkManager can scan and connect with Intel Wi-Fi cards out of the box.",
"The installed system now grants the `archipelago` service user explicit NetworkManager PolicyKit access for web-triggered Wi-Fi scans and connection changes.",
"Wi-Fi connect now replaces stale/partial NetworkManager profiles and creates an explicit WPA-PSK profile with the supplied password, avoiding no-secret retry failures after a failed attempt.",
"Settings password changes now update the Linux/SSH password through non-interactive sudo, so the web password and SSH password stay in sync when the checkbox is enabled.",
"Quadlet environment values with spaces or shell metacharacters are quoted consistently, preventing env drift recreate loops for apps like nostr-rs-relay and Grafana.", "Quadlet environment values with spaces or shell metacharacters are quoted consistently, preventing env drift recreate loops for apps like nostr-rs-relay and Grafana.",
"Boot/bootstrap reconcile avoids restarting running Bitcoin containers while repairing RPC config, preserving IBD progress on active nodes.", "Boot/bootstrap reconcile avoids restarting running Bitcoin containers while repairing RPC config, preserving IBD progress on active nodes.",
"Exit code 137 is labeled as SIGKILL instead of assuming OOM, avoiding false OOM alerts for orchestrator-managed recreates." "Exit code 137 is labeled as SIGKILL instead of assuming OOM, avoiding false OOM alerts for orchestrator-managed recreates.",
"Container reconcile force-recreates Podman records stuck in `Stopping`, preserving bind-mounted app data while recovering wedged containers automatically.",
"Container health reporting is honest for running containers: Archipelago surfaces Podman's actual health state instead of marking every running container healthy."
], ],
"components": [ "components": [
{ {
@ -13,16 +19,16 @@
"current_version": "1.7.56-alpha", "current_version": "1.7.56-alpha",
"new_version": "1.7.56-alpha", "new_version": "1.7.56-alpha",
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.56-alpha/archipelago", "download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.56-alpha/archipelago",
"sha256": "11217a0e40c1704ee9f5bbcdeb59e38c9efc5e00ea7c673c5c3f0f69de720109", "sha256": "f6c54cc7fbaac3dde97b1a719a6d380ce734a4d52366d4579770effadef92c9c",
"size_bytes": 42647960 "size_bytes": 42862944
}, },
{ {
"name": "archipelago-frontend-1.7.56-alpha.tar.gz", "name": "archipelago-frontend-1.7.56-alpha.tar.gz",
"current_version": "1.7.56-alpha", "current_version": "1.7.56-alpha",
"new_version": "1.7.56-alpha", "new_version": "1.7.56-alpha",
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.56-alpha/archipelago-frontend-1.7.56-alpha.tar.gz", "download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.56-alpha/archipelago-frontend-1.7.56-alpha.tar.gz",
"sha256": "9cee503e260891267fed33e4c97f067a4a592b10695ecea4cbf730d517ff6c8b", "sha256": "91bfa10085696921c929ab7d216a8e617eb02e7105f21f32cdc3a2ba15158cd4",
"size_bytes": 166493666 "size_bytes": 166465069
} }
] ]
} }

View File

@ -1,11 +1,17 @@
{ {
"version": "1.7.56-alpha", "version": "1.7.56-alpha",
"release_date": "2026-05-14", "release_date": "2026-05-15",
"changelog": [ "changelog": [
"Health notifications now clear when an app is no longer unhealthy, including stale alerts for removed containers such as Portainer.", "Health notifications now clear when an app is no longer unhealthy, including stale alerts for removed containers such as Portainer.",
"Fresh installs now include the full Wi-Fi userspace stack (`wpasupplicant`, `wireless-regdb`, `iw`, `rfkill`, `polkitd`, `pciutils`, and `usbutils`) so NetworkManager can scan and connect with Intel Wi-Fi cards out of the box.",
"The installed system now grants the `archipelago` service user explicit NetworkManager PolicyKit access for web-triggered Wi-Fi scans and connection changes.",
"Wi-Fi connect now replaces stale/partial NetworkManager profiles and creates an explicit WPA-PSK profile with the supplied password, avoiding no-secret retry failures after a failed attempt.",
"Settings password changes now update the Linux/SSH password through non-interactive sudo, so the web password and SSH password stay in sync when the checkbox is enabled.",
"Quadlet environment values with spaces or shell metacharacters are quoted consistently, preventing env drift recreate loops for apps like nostr-rs-relay and Grafana.", "Quadlet environment values with spaces or shell metacharacters are quoted consistently, preventing env drift recreate loops for apps like nostr-rs-relay and Grafana.",
"Boot/bootstrap reconcile avoids restarting running Bitcoin containers while repairing RPC config, preserving IBD progress on active nodes.", "Boot/bootstrap reconcile avoids restarting running Bitcoin containers while repairing RPC config, preserving IBD progress on active nodes.",
"Exit code 137 is labeled as SIGKILL instead of assuming OOM, avoiding false OOM alerts for orchestrator-managed recreates." "Exit code 137 is labeled as SIGKILL instead of assuming OOM, avoiding false OOM alerts for orchestrator-managed recreates.",
"Container reconcile force-recreates Podman records stuck in `Stopping`, preserving bind-mounted app data while recovering wedged containers automatically.",
"Container health reporting is honest for running containers: Archipelago surfaces Podman's actual health state instead of marking every running container healthy."
], ],
"components": [ "components": [
{ {
@ -13,16 +19,16 @@
"current_version": "1.7.56-alpha", "current_version": "1.7.56-alpha",
"new_version": "1.7.56-alpha", "new_version": "1.7.56-alpha",
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.56-alpha/archipelago", "download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.56-alpha/archipelago",
"sha256": "11217a0e40c1704ee9f5bbcdeb59e38c9efc5e00ea7c673c5c3f0f69de720109", "sha256": "f6c54cc7fbaac3dde97b1a719a6d380ce734a4d52366d4579770effadef92c9c",
"size_bytes": 42647960 "size_bytes": 42862944
}, },
{ {
"name": "archipelago-frontend-1.7.56-alpha.tar.gz", "name": "archipelago-frontend-1.7.56-alpha.tar.gz",
"current_version": "1.7.56-alpha", "current_version": "1.7.56-alpha",
"new_version": "1.7.56-alpha", "new_version": "1.7.56-alpha",
"download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.56-alpha/archipelago-frontend-1.7.56-alpha.tar.gz", "download_url": "http://146.59.87.168:3000/lfg2025/archy/releases/download/v1.7.56-alpha/archipelago-frontend-1.7.56-alpha.tar.gz",
"sha256": "9cee503e260891267fed33e4c97f067a4a592b10695ecea4cbf730d517ff6c8b", "sha256": "91bfa10085696921c929ab7d216a8e617eb02e7105f21f32cdc3a2ba15158cd4",
"size_bytes": 166493666 "size_bytes": 166465069
} }
] ]
} }