Compare commits
4 Commits
5818541721
...
a322b04021
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a322b04021 | ||
|
|
645cf69ed7 | ||
|
|
01ec0565a6 | ||
|
|
30505f41ff |
16
CHANGELOG.md
16
CHANGELOG.md
@ -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)
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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,
|
||||
}
|
||||
})
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user