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
## 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.
- 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.
- 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.
- 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)

View File

@ -307,14 +307,62 @@ async fn scan_wifi() -> Result<Vec<serde_json::Value>> {
/// Connect to a WiFi network using nmcli.
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")
.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()
.await
.context("Failed to run nmcli wifi connect")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
if !activate.status.success() {
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);
}

View File

@ -381,14 +381,14 @@ async fn change_ssh_password(new_password: &str) -> Result<()> {
// usermod -p writes directly to /etc/shadow, bypassing PAM
// Use /usr/sbin/usermod - not always in systemd's PATH
let status = tokio::process::Command::new("/usr/sbin/usermod")
.args(["-p", &hash, &ssh_user])
let status = tokio::process::Command::new("/usr/bin/sudo")
.args(["-n", "/usr/sbin/usermod", "-p", &hash, &ssh_user])
.output()
.await?;
if !status.status.success() {
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);

View File

@ -316,6 +316,11 @@ RUN apt-get update && apt-get -y full-upgrade && apt-get install -y --no-install
dbus \
sudo \
network-manager \
wpasupplicant \
wireless-regdb \
iw \
rfkill \
polkitd \
openssh-server \
nginx \
podman \
@ -455,8 +460,21 @@ RUN mkdir -p /etc/systemd/system/user@.service.d && \
# Allow unprivileged ping inside rootless containers
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
RUN systemctl enable NetworkManager || true && \
systemctl enable polkit || systemctl enable polkit.service || true && \
systemctl enable ssh || true && \
systemctl enable nginx || true && \
systemctl enable archipelago || true && \
@ -696,6 +714,7 @@ kmod,procps,iproute2,ca-certificates,gdisk,\
cryptsetup,cryptsetup-initramfs,parted,dosfstools,e2fsprogs,\
linux-image-${DEB_ARCH},grub-efi-${DEB_ARCH},grub-pc-bin,\
ifupdown,isc-dhcp-client,\
wpasupplicant,wireless-regdb,iw,rfkill,\
pciutils,usbutils,less,nano \
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..."
chroot /mnt/archipelago apt-get install -y \
sudo \
networkmanager \
network-manager \
wpasupplicant \
wireless-regdb \
iw \
rfkill \
pciutils \
usbutils \
polkitd \
openssh-server \
curl \
wget \
@ -198,9 +205,20 @@ echo "archipelago:archipelago" | chroot /mnt/archipelago chpasswd
echo "⚙️ Enabling services..."
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 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
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)
if (e.key === 's' || e.key === 'S') {
if (!isInput && appStore.isAuthenticated && !screensaverStore.isActive) {
if (!isInput && appStore.isAuthenticated && !screensaverStore.isActive && !screensaverStore.isSuppressed) {
e.preventDefault()
screensaverStore.activate()
}

View File

@ -68,6 +68,24 @@ describe('useScreensaverStore', () => {
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', () => {
const store = useScreensaverStore()
store.deactivate()

View File

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

View File

@ -91,6 +91,7 @@
import { ref, computed, onMounted, onBeforeUnmount, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useAppLauncherStore } from '@/stores/appLauncher'
import { useScreensaverStore } from '@/stores/screensaver'
import NostrIdentityPicker from '@/components/NostrIdentityPicker.vue'
import AppSessionHeader from './appSession/AppSessionHeader.vue'
import AppSessionFrame from './appSession/AppSessionFrame.vue'
@ -115,6 +116,7 @@ const isInlinePanel = computed(() => !!props.appIdProp)
const route = useRoute()
const router = useRouter()
const screensaverStore = useScreensaverStore()
const sessionRef = ref<HTMLElement | null>(null)
const frameRef = ref<InstanceType<typeof AppSessionFrame> | null>(null)
@ -145,6 +147,14 @@ const appId = computed(() => {
const appTitle = computed(() => resolveAppTitle(appId.value))
const isMobile = typeof window !== 'undefined' && window.innerWidth < 768
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(() => {
return resolveAppUrl(appId.value, route.query.path as string | undefined)
@ -306,6 +316,8 @@ function onFullscreenChange() {
function onMessage(e: MessageEvent) {
if (e.data?.type === 'nostr-request') nostrBridge.handleNostrRequest(e)
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
@ -338,6 +350,9 @@ onMounted(() => {
sessionRef.value?.requestFullscreen().catch(() => {})
})
}
if (screensaverSuppressedApps.has(appId.value)) {
screensaverStore.suppress(screensaverReason.value)
}
})
onBeforeUnmount(() => {
@ -347,6 +362,7 @@ onBeforeUnmount(() => {
window.removeEventListener('keydown', onKeyDown, true)
window.removeEventListener('message', onMessage)
document.removeEventListener('fullscreenchange', onFullscreenChange)
screensaverStore.resume(screensaverReason.value)
if (document.fullscreenElement) document.exitFullscreen().catch(() => {})
})
</script>

View File

@ -1,11 +1,17 @@
{
"version": "1.7.56-alpha",
"release_date": "2026-05-14",
"release_date": "2026-05-15",
"changelog": [
"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.",
"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": [
{
@ -13,16 +19,16 @@
"current_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",
"sha256": "11217a0e40c1704ee9f5bbcdeb59e38c9efc5e00ea7c673c5c3f0f69de720109",
"size_bytes": 42647960
"sha256": "f6c54cc7fbaac3dde97b1a719a6d380ce734a4d52366d4579770effadef92c9c",
"size_bytes": 42862944
},
{
"name": "archipelago-frontend-1.7.56-alpha.tar.gz",
"current_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",
"sha256": "9cee503e260891267fed33e4c97f067a4a592b10695ecea4cbf730d517ff6c8b",
"size_bytes": 166493666
"sha256": "91bfa10085696921c929ab7d216a8e617eb02e7105f21f32cdc3a2ba15158cd4",
"size_bytes": 166465069
}
]
}

View File

@ -1,11 +1,17 @@
{
"version": "1.7.56-alpha",
"release_date": "2026-05-14",
"release_date": "2026-05-15",
"changelog": [
"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.",
"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": [
{
@ -13,16 +19,16 @@
"current_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",
"sha256": "11217a0e40c1704ee9f5bbcdeb59e38c9efc5e00ea7c673c5c3f0f69de720109",
"size_bytes": 42647960
"sha256": "f6c54cc7fbaac3dde97b1a719a6d380ce734a4d52366d4579770effadef92c9c",
"size_bytes": 42862944
},
{
"name": "archipelago-frontend-1.7.56-alpha.tar.gz",
"current_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",
"sha256": "9cee503e260891267fed33e4c97f067a4a592b10695ecea4cbf730d517ff6c8b",
"size_bytes": 166493666
"sha256": "91bfa10085696921c929ab7d216a8e617eb02e7105f21f32cdc3a2ba15158cd4",
"size_bytes": 166465069
}
]
}