From bf7bc7f104118e3557e05232176b337aa3cea9e4 Mon Sep 17 00:00:00 2001 From: Dorian Date: Sun, 12 Apr 2026 09:06:12 -0400 Subject: [PATCH] fix: ISO install - fallback registry, filebrowser noauth, registries 1. registries.conf includes docker.io search + fallback 23.182.128.160 2. First-boot pull_with_fallback() tries primary then fallback registry 3. FileBrowser created with noauth config on persistent volume 4. Backend dynamic registries.json pre-created in ISO 5. Filebrowser password secret created for token flow Fixes: apps stuck at 0% download, filebrowser not working, dynamic catalog not loading on fresh installs. Co-Authored-By: Claude Opus 4.6 (1M context) --- Android/app/build.gradle.kts | 4 +- .../app/ui/components/NESController.kt | 52 +++++++++++------ .../ui/components/NESPortraitController.kt | 30 +++------- image-recipe/build-auto-installer-iso.sh | 20 ++++++- .../src/views/appSession/MobileGamepad.vue | 58 ++++++++++--------- scripts/first-boot-containers.sh | 58 ++++++++++++++++--- 6 files changed, 144 insertions(+), 78 deletions(-) diff --git a/Android/app/build.gradle.kts b/Android/app/build.gradle.kts index e761858c..01a4c393 100644 --- a/Android/app/build.gradle.kts +++ b/Android/app/build.gradle.kts @@ -11,8 +11,8 @@ android { applicationId = "com.archipelago.app" minSdk = 26 targetSdk = 35 - versionCode = 4 - versionName = "0.4.0" + versionCode = 6 + versionName = "0.4.2" vectorDrawables { useSupportLibrary = true diff --git a/Android/app/src/main/java/com/archipelago/app/ui/components/NESController.kt b/Android/app/src/main/java/com/archipelago/app/ui/components/NESController.kt index 07cac98e..4b18b23c 100644 --- a/Android/app/src/main/java/com/archipelago/app/ui/components/NESController.kt +++ b/Android/app/src/main/java/com/archipelago/app/ui/components/NESController.kt @@ -186,28 +186,20 @@ fun NESController( } } - // A/B/C Buttons in inlay (same size as D-pad inlay, more right margin) + // A/B/C Buttons in inlay — triangle: C top, B+A bottom Inlay(c, Modifier.align(Alignment.CenterEnd).padding(end = 48.dp).size(140.dp)) { - Row( + Column( Modifier.fillMaxSize(), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, ) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - RoundBtn(c, 42.dp) { onKey("c") } - Text("C", color = c.labelMuted, fontSize = 8.sp, fontWeight = FontWeight.Bold) - } - Spacer(Modifier.width(10.dp)) - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Spacer(Modifier.height(8.dp)) - RoundBtn(c, 42.dp) { onKey("b") } - Text("B", color = c.labelMuted, fontSize = 8.sp, fontWeight = FontWeight.Bold) - } - Spacer(Modifier.width(10.dp)) - Column(horizontalAlignment = Alignment.CenterHorizontally) { - RoundBtn(c, 42.dp) { onKey("a") } - Text("A", color = c.labelMuted, fontSize = 8.sp, fontWeight = FontWeight.Bold) - Spacer(Modifier.height(8.dp)) + // C on top (white) + ColorBtn(Color(0xFF888888), Color(0xFFAAAAAA), 44.dp) { onKey("c") } + Spacer(Modifier.height(6.dp)) + // B + A on bottom row + Row(horizontalArrangement = Arrangement.spacedBy(12.dp)) { + ColorBtn(Color(0xFF3B82F6), Color(0xFF60A5FA), 44.dp) { onKey("b") } + ColorBtn(Color(0xFFEA580C), Color(0xFFFB923C), 44.dp) { onKey("a") } } } } @@ -361,6 +353,28 @@ fun RoundBtn(c: NESPalette, sz: Dp = 52.dp, onClick: () -> Unit) { } } +/** Colored round button — custom color instead of palette */ +@Composable +fun ColorBtn(color: Color, pressColor: Color, sz: Dp = 48.dp, onClick: () -> Unit) { + var p by remember { mutableStateOf(false) } + Box( + Modifier + .size(sz) + .shadow(if (p) 1.dp else 4.dp, CircleShape) + .clip(CircleShape) + .background(Brush.verticalGradient( + if (p) listOf(pressColor, color.copy(alpha = 0.85f)) + else listOf(color, color.copy(alpha = 0.8f)) + )) + .pointerInput(Unit) { detectTapGestures(onPress = { p = true; onClick(); tryAwaitRelease(); p = false }) }, + contentAlignment = Alignment.Center, + ) { + if (!p) Box(Modifier.fillMaxSize().clip(CircleShape).background( + Brush.verticalGradient(listOf(Color.White.copy(alpha = 0.18f), Color.Transparent)) + )) + } +} + /** START/SELECT capsule */ @Composable fun CapsuleBtn(label: String, c: NESPalette, w: Dp = 64.dp, h: Dp = 28.dp, onClick: () -> Unit) { diff --git a/Android/app/src/main/java/com/archipelago/app/ui/components/NESPortraitController.kt b/Android/app/src/main/java/com/archipelago/app/ui/components/NESPortraitController.kt index e825921a..8894c8c8 100644 --- a/Android/app/src/main/java/com/archipelago/app/ui/components/NESPortraitController.kt +++ b/Android/app/src/main/java/com/archipelago/app/ui/components/NESPortraitController.kt @@ -15,7 +15,6 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -25,9 +24,7 @@ import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.res.painterResource -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import com.archipelago.app.R import com.archipelago.app.ui.theme.ControllerStyle import com.archipelago.app.ui.theme.NES @@ -116,26 +113,17 @@ fun NESPortraitController( Spacer(Modifier.height(12.dp)) - // A/B/C Buttons + // A/B/C Buttons — triangle: C top, B+A bottom Inlay(c, Modifier.fillMaxWidth()) { - Row( - Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 10.dp), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, + Column( + Modifier.fillMaxWidth().padding(horizontal = 12.dp, vertical = 8.dp), + horizontalAlignment = Alignment.CenterHorizontally, ) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { - RoundBtn(c, 46.dp) { onKey("c") } - Text("C", color = c.labelMuted, fontSize = 8.sp, fontWeight = FontWeight.Bold) - } - Spacer(Modifier.width(16.dp)) - Column(horizontalAlignment = Alignment.CenterHorizontally) { - RoundBtn(c, 46.dp) { onKey("b") } - Text("B", color = c.labelMuted, fontSize = 8.sp, fontWeight = FontWeight.Bold) - } - Spacer(Modifier.width(16.dp)) - Column(horizontalAlignment = Alignment.CenterHorizontally) { - RoundBtn(c, 46.dp) { onKey("a") } - Text("A", color = c.labelMuted, fontSize = 8.sp, fontWeight = FontWeight.Bold) + ColorBtn(Color(0xFF888888), Color(0xFFAAAAAA), 46.dp) { onKey("c") } + Spacer(Modifier.height(6.dp)) + Row(horizontalArrangement = Arrangement.spacedBy(14.dp)) { + ColorBtn(Color(0xFF3B82F6), Color(0xFF60A5FA), 46.dp) { onKey("b") } + ColorBtn(Color(0xFFEA580C), Color(0xFFFB923C), 46.dp) { onKey("a") } } } } diff --git a/image-recipe/build-auto-installer-iso.sh b/image-recipe/build-auto-installer-iso.sh index 9371378a..614a5065 100755 --- a/image-recipe/build-auto-installer-iso.sh +++ b/image-recipe/build-auto-installer-iso.sh @@ -2113,14 +2113,32 @@ STORAGECONF # Symlink for backward compat (some tools look in ~/.local/share/containers) ln -sf /var/lib/archipelago/containers/storage /mnt/target/home/archipelago/.local/share/containers/storage 2>/dev/null || true -# Configure Archipelago app registry (HTTP, insecure) +# Configure Archipelago app registries (primary + fallback) cat > /mnt/target/home/archipelago/.config/containers/registries.conf <<'REGCONF' +unqualified-search-registries = ["docker.io"] + [[registry]] location = "git.tx1138.com" insecure = true + +[[registry]] +location = "23.182.128.160:3000" +insecure = true REGCONF chown -R 1000:1000 /mnt/target/home/archipelago/.config +# Pre-create dynamic registry config for the backend (fallback registries) +mkdir -p /mnt/target/var/lib/archipelago/config +cat > /mnt/target/var/lib/archipelago/config/registries.json <<'DYNREG' +{ + "registries": [ + {"url": "git.tx1138.com/lfg2025", "name": "Archipelago Primary", "tls_verify": true, "enabled": true, "priority": 0}, + {"url": "23.182.128.160:3000/lfg2025", "name": "Archipelago Fallback", "tls_verify": false, "enabled": true, "priority": 10} + ] +} +DYNREG +chown -R 1000:1000 /mnt/target/var/lib/archipelago/config + # Configure podman to use netavark backend (enables container DNS on archy-net). # netavark + aardvark-dns binaries come from the rootfs (Debian 13 apt packages). if [ -f /mnt/target/usr/lib/podman/netavark ]; then diff --git a/neode-ui/src/views/appSession/MobileGamepad.vue b/neode-ui/src/views/appSession/MobileGamepad.vue index f4901729..1eca5523 100644 --- a/neode-ui/src/views/appSession/MobileGamepad.vue +++ b/neode-ui/src/views/appSession/MobileGamepad.vue @@ -76,7 +76,7 @@ - +
- - + > +
+ + +
@@ -233,20 +235,24 @@ function tap(key: string) { send(key, 'down'); setTimeout(() => send(key, 'up'), color: rgba(255, 255, 255, 0.8); } -/* ── Action buttons (A / B / C) ── */ +/* ── Action buttons — triangle layout ── */ .gamepad-actions { display: flex; - gap: 10px; + flex-direction: column; align-items: center; flex-shrink: 0; + gap: 6px; +} + +.action-row { + display: flex; + gap: 10px; } .action-btn { - width: 54px; - height: 54px; + width: 50px; + height: 50px; border-radius: 50%; - font-size: 18px; - font-weight: 800; display: flex; align-items: center; justify-content: center; @@ -276,11 +282,11 @@ function tap(key: string) { send(key, 'down'); setTimeout(() => send(key, 'up'), } .action-c { - background: rgba(74, 222, 128, 0.2); - border-color: rgba(74, 222, 128, 0.5); - color: #4ade80; + background: rgba(255, 255, 255, 0.12); + border-color: rgba(255, 255, 255, 0.4); + color: #ffffff; } .action-c:active { - background: rgba(74, 222, 128, 0.45); + background: rgba(255, 255, 255, 0.3); } diff --git a/scripts/first-boot-containers.sh b/scripts/first-boot-containers.sh index b5c13ed5..d85b6abc 100644 --- a/scripts/first-boot-containers.sh +++ b/scripts/first-boot-containers.sh @@ -78,27 +78,67 @@ if [ -f "$UNBUNDLED_MARKER" ]; then # Ensure archy-net exists $DOCKER network create archy-net 2>/dev/null || true - # Create FileBrowser only + # Helper: pull image with fallback registry + pull_with_fallback() { + local img="$1" + log " Pulling $img..." + if $DOCKER pull "$img" 2>>"$LOG"; then + return 0 + fi + # Try fallback registry + local fallback_img + fallback_img=$(echo "$img" | sed "s|${ARCHY_REGISTRY}|${ARCHY_REGISTRY_FALLBACK}|") + if [ "$fallback_img" != "$img" ] && [ -n "$ARCHY_REGISTRY_FALLBACK" ]; then + log " Primary failed, trying fallback: $fallback_img" + if $DOCKER pull "$fallback_img" --tls-verify=false 2>>"$LOG"; then + $DOCKER tag "$fallback_img" "$img" 2>/dev/null + return 0 + fi + fi + # Try docker.io as last resort for common images + local short_name + short_name=$(echo "$img" | sed 's|.*/||') + local dockerhub="docker.io/library/$short_name" + log " Fallback failed, trying docker.io: $dockerhub" + $DOCKER pull "$dockerhub" 2>>"$LOG" && $DOCKER tag "$dockerhub" "$img" 2>/dev/null && return 0 + return 1 + } + + # Create FileBrowser (noauth — behind Archipelago login) if ! $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -q filebrowser; then - log "Creating FileBrowser..." + log "Creating FileBrowser (noauth)..." mkdir -p /var/lib/archipelago/filebrowser /var/lib/archipelago/filebrowser-data - mkdir -p /var/lib/archipelago/data/cloud/{Documents,Photos,Music,Videos,Downloads} - sudo chown -R 100000:100000 /var/lib/archipelago/filebrowser - sudo chown -R 100000:100000 /var/lib/archipelago/filebrowser-data - sudo chown -R 100000:100000 /var/lib/archipelago/data + mkdir -p /var/lib/archipelago/filebrowser/{Documents,Photos,Music,Videos,Downloads} + chown -R 1000:1000 /var/lib/archipelago/filebrowser + chown -R 1000:1000 /var/lib/archipelago/filebrowser-data + # Write config with database on persistent volume + cat > /var/lib/archipelago/filebrowser-data/.filebrowser.json <<'FBEOF' +{"port":80,"baseURL":"","address":"0.0.0.0","database":"/data/filebrowser.db","root":"/srv","log":"stdout"} +FBEOF + chown 1000:1000 /var/lib/archipelago/filebrowser-data/.filebrowser.json + pull_with_fallback "${FILEBROWSER_IMAGE}" $DOCKER run -d --name filebrowser --restart unless-stopped \ + --network archy-net \ --cap-drop=ALL --cap-add=DAC_OVERRIDE --cap-add=NET_BIND_SERVICE \ --security-opt=no-new-privileges:true \ - --health-cmd='curl -sf http://localhost:80/ || exit 1' \ + --health-cmd='wget -q --spider http://localhost:80/health || exit 1' \ --health-interval=30s --health-timeout=5s --health-retries=3 \ --memory=256m \ -p 8083:80 \ -v /var/lib/archipelago/filebrowser:/srv \ -v /var/lib/archipelago/filebrowser-data:/data \ - -v /var/lib/archipelago/data/cloud:/srv/cloud \ ${FILEBROWSER_IMAGE} \ - --database=/data/database.db --root=/srv --address=0.0.0.0 --port=80 2>>"$LOG" && \ + --config /data/.filebrowser.json 2>>"$LOG" && \ log " FileBrowser created" || log " WARNING: FileBrowser creation failed" + # Set noauth after first start + sleep 3 + $DOCKER exec filebrowser /filebrowser config set --auth.method=noauth --database /data/filebrowser.db 2>>"$LOG" || true + $DOCKER exec filebrowser /filebrowser users add admin admin --perm.admin --database /data/filebrowser.db 2>>"$LOG" || true + $DOCKER restart filebrowser 2>>"$LOG" || true + # Create filebrowser password for backend token flow + mkdir -p /var/lib/archipelago/secrets/filebrowser + echo -n "admin" > /var/lib/archipelago/secrets/filebrowser/password + chown -R 1000:1000 /var/lib/archipelago/secrets fi log "Unbundled first-boot complete"