fix: container security hardening, onboarding viewport scaling, boot screen cleanup

Container security:
- Add --cap-drop ALL + --security-opt no-new-privileges:true to 12 containers
  missing hardening in first-boot-containers.sh (mempool-db, electrumx,
  mempool-api, mempool-web, electrs-ui, btcpay-db, nbxplorer, nostr-rs-relay,
  strfry, tailscale, bitcoin-ui, lnd-ui)
- Mirror same hardening in deploy-to-target.sh for consistency
- Add --read-only + tmpfs to nostr-rs-relay
- Fix filebrowser deploy to include security flags
- Remove duplicate UI image definitions in image-versions.sh
- Separate Jellyfin capabilities (needs FOWNER, exec tmpfs for CoreCLR JIT)
- Harden archy-net creation with existence check and error handling

UI fixes:
- Fix onboarding viewport scaling: all 7 screens now use h-full + max-h-full
  pattern so containers never overflow viewport regardless of padding
- Remove path-option-card wrappers from seed verify inputs, left-justify labels
- Remove batteries/barbarian icons from boot screen (keep bitcoin, cloud, github, save)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian 2026-03-31 17:35:34 +01:00
parent f29fa2e729
commit a896ecd431
14 changed files with 160 additions and 51 deletions

View File

@ -59,13 +59,21 @@ pub(super) fn get_app_capabilities(app_id: &str) -> Vec<String> {
"--cap-add=NET_RAW".to_string(),
],
"nextcloud" | "btcpay-server" | "btcpayserver"
| "jellyfin" | "onlyoffice" | "onlyoffice-documentserver" | "portainer" => vec![
| "onlyoffice" | "onlyoffice-documentserver" | "portainer" => vec![
"--cap-add=CHOWN".to_string(),
"--cap-add=SETUID".to_string(),
"--cap-add=SETGID".to_string(),
"--cap-add=DAC_OVERRIDE".to_string(),
"--cap-add=NET_BIND_SERVICE".to_string(),
],
// Jellyfin: CoreCLR needs exec-enabled tmpfs for JIT compilation
"jellyfin" => vec![
"--cap-add=CHOWN".to_string(),
"--cap-add=FOWNER".to_string(),
"--cap-add=SETUID".to_string(),
"--cap-add=SETGID".to_string(),
"--cap-add=DAC_OVERRIDE".to_string(),
],
// Nginx Proxy Manager needs to bind low ports
"nginx-proxy-manager" => vec![
"--cap-add=CHOWN".to_string(),

View File

@ -158,11 +158,37 @@ impl RpcHandler {
run_args.push("--cap-add=NET_RAW");
run_args.push("--device=/dev/net/tun");
} else if needs_archy_net(package_id) {
let _ = tokio::process::Command::new("podman")
// Create archy-net if it doesn't exist (idempotent — "already exists" is fine)
match tokio::process::Command::new("podman")
.args(["network", "create", "archy-net"])
.output()
.await
{
Ok(output) if !output.status.success() => {
let stderr = String::from_utf8_lossy(&output.stderr);
if !stderr.contains("already exists") {
tracing::warn!("Failed to create archy-net: {}", stderr.trim());
}
}
Err(e) => {
tracing::warn!("Failed to run podman network create: {}", e);
}
_ => {}
}
// Verify the network actually exists before using it
let net_check = tokio::process::Command::new("podman")
.args(["network", "exists", "archy-net"])
.status()
.await;
run_args.push("--network=archy-net");
if net_check.map(|s| s.success()).unwrap_or(false) {
run_args.push("--network=archy-net");
} else {
tracing::error!(
"archy-net network does not exist — {} will use default network. \
Inter-container DNS will not work.",
package_id
);
}
}
// Security hardening (skip for privileged containers)
@ -184,6 +210,11 @@ impl RpcHandler {
run_args.push("--tmpfs=/tmp:rw,noexec,nosuid,size=256m");
run_args.push("--tmpfs=/run:rw,noexec,nosuid,size=64m");
}
// Jellyfin: .NET CoreCLR needs exec-enabled /tmp for JIT compilation
if package_id == "jellyfin" {
run_args.push("--tmpfs=/tmp:rw,exec,size=256m");
}
}
// Create data directories

View File

@ -767,9 +767,9 @@ cp /usr/lib/ISOLINUX/isohdpfx.bin /output/isohdpfx.bin
echo " [container] Generating GRUB fonts..."
apt-get install -y -qq fonts-dejavu-core grub-common >/dev/null 2>&1
mkdir -p /output/grub-fonts
grub-mkfont -s 12 -o /output/grub-fonts/dejavu_12.pf2 /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf
grub-mkfont -s 14 -o /output/grub-fonts/dejavu_14.pf2 /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf
grub-mkfont -s 16 -o /output/grub-fonts/dejavu_16.pf2 /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf
grub-mkfont -s 12 -o /output/grub-fonts/dejavu_12.pf2 /usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf
grub-mkfont -s 14 -o /output/grub-fonts/dejavu_14.pf2 /usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf
grub-mkfont -s 16 -o /output/grub-fonts/dejavu_16.pf2 /usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf
grub-mkfont -s 24 -o /output/grub-fonts/dejavu_24.pf2 /usr/share/fonts/truetype/dejavu/DejaVuSansMono-Bold.ttf
echo " [container] Done!"
@ -2817,13 +2817,13 @@ UI vesamenu.c32
PROMPT 0
TIMEOUT 0
MENU TITLE bitcoin node os
MENU TITLE
MENU BACKGROUND splash.png
MENU RESOLUTION 1024 768
MENU VSHIFT 18
MENU HSHIFT 32
MENU WIDTH 18
MENU MARGIN 1
MENU VSHIFT 20
MENU HSHIFT 6
MENU WIDTH 68
MENU MARGIN 2
MENU ROWS 5
MENU TABMSG press tab to edit | archipelago.sh
MENU COLOR screen 37;40 #00000000 #00000000 none

View File

@ -81,8 +81,6 @@ const iconSources = [
'/assets/icon/cloud-done.svg',
'/assets/icon/github.svg',
'/assets/icon/save.svg',
'/assets/icon/batteries.svg',
'/assets/icon/barbarian.svg',
]
interface LogLine { prefix: string; text: string; type: string }

View File

@ -1,9 +1,9 @@
<template>
<div class="min-h-full flex items-center justify-center p-3 sm:p-4 md:p-6">
<div class="h-full flex items-center justify-center p-3 sm:p-4 md:p-6">
<!-- Main Glass Container - Scrollable -->
<div class="max-w-[800px] w-full relative z-10 path-glass-container onb-scroll-container">
<div class="max-w-[800px] w-full max-h-full relative z-10 path-glass-container onb-scroll-container flex flex-col">
<!-- Success Content -->
<div class="text-center space-y-4 sm:space-y-6 px-3 sm:px-4 py-4 sm:py-6">
<div class="flex-1 overflow-y-auto overflow-x-hidden min-h-0 text-center space-y-4 sm:space-y-6 px-3 sm:px-4 py-4 sm:py-6">
<!-- Success Icon -->
<div class="flex justify-center mb-4 sm:mb-6">
<div class="path-option-card cursor-default w-16 h-16 sm:w-20 sm:h-20 rounded-full flex items-center justify-center">

View File

@ -1,6 +1,6 @@
<template>
<div class="min-h-full flex items-center justify-center p-3 sm:p-4 md:p-6">
<div class="max-w-[800px] w-full relative z-10 path-glass-container onb-scroll-container">
<div class="h-full flex items-center justify-center p-3 sm:p-4 md:p-6">
<div class="max-w-[800px] w-full max-h-full relative z-10 path-glass-container onb-scroll-container flex flex-col">
<!-- Header -->
<div class="text-center flex-shrink-0 px-3 sm:px-4 pt-4 sm:pt-6">
<h1 class="text-xl sm:text-2xl md:text-[26px] font-semibold text-white/96 mb-2 sm:mb-4 drop-shadow-[0_2px_6px_rgba(0,0,0,0.4)]">
@ -11,7 +11,8 @@
</p>
</div>
<!-- Content -->
<!-- Scrollable Content -->
<div class="flex-1 overflow-y-auto overflow-x-hidden min-h-0">
<div class="flex flex-col items-center gap-4 sm:gap-6 mb-4 sm:mb-6 px-3 sm:px-4">
<div class="w-full max-w-[600px] space-y-4 sm:space-y-6">
<!-- Name Input -->
@ -59,9 +60,10 @@
<p class="text-orange-400/80 text-sm">Server is still starting up. Your identity will be saved once it's ready.</p>
</div>
<p v-else-if="errorMessage" class="text-red-400 text-sm text-center mb-4">{{ errorMessage }}</p>
</div>
<!-- Action Buttons -->
<div class="flex justify-center max-w-[600px] mx-auto flex-shrink-0 px-3 sm:px-4 pb-4 sm:pb-6">
<div class="flex justify-center max-w-[600px] mx-auto flex-shrink-0 px-3 sm:px-4 pb-4 sm:pb-6 pt-3">
<button
@click="createIdentity"
:disabled="isCreating"

View File

@ -1,6 +1,7 @@
<template>
<div class="min-h-full flex items-center justify-center p-3 sm:p-4 md:p-6">
<div class="max-w-[1200px] w-full relative z-10 path-glass-container onb-scroll-container">
<div class="h-full flex items-center justify-center p-3 sm:p-4 md:p-6">
<div class="max-w-[1200px] w-full max-h-full relative z-10 path-glass-container onb-scroll-container flex flex-col">
<div class="flex-1 overflow-y-auto overflow-x-hidden min-h-0">
<div class="text-center mb-4 sm:mb-6 flex-shrink-0 px-3 sm:px-4 pt-4 sm:pt-6">
<div class="logo-gradient-border inline-block mb-4 sm:mb-6">
<img
@ -68,8 +69,9 @@
<span class="text-xs text-white/50 mt-1 block">(Coming Soon)</span>
</div>
</div>
</div>
<div class="flex justify-center flex-shrink-0 px-3 sm:px-4 pb-4 sm:pb-6 mt-4 sm:mt-8">
<div class="flex justify-center flex-shrink-0 px-3 sm:px-4 pb-4 sm:pb-6 pt-4 sm:pt-6">
<button
@click="proceed"
class="path-action-button path-action-button--continue"

View File

@ -1,7 +1,9 @@
<template>
<div class="min-h-full flex items-center justify-center p-3 sm:p-4 md:p-6 relative">
<div class="h-full flex items-center justify-center p-3 sm:p-4 md:p-6 relative">
<!-- Main Glass Container - Scrollable -->
<div class="max-w-[1200px] w-full relative z-10 path-glass-container onb-scroll-container">
<div class="max-w-[1200px] w-full max-h-full relative z-10 path-glass-container onb-scroll-container flex flex-col">
<!-- Scrollable Content -->
<div class="flex-1 overflow-y-auto overflow-x-hidden min-h-0">
<!-- Header -->
<div class="text-center mb-4 md:mb-6 flex-shrink-0 px-3 sm:px-4 pt-4 sm:pt-6">
<h1 class="text-xl md:text-[26px] font-semibold text-white/96 mb-2 drop-shadow-[0_2px_6px_rgba(0,0,0,0.4)]">Your Node, Your Possibilities</h1>
@ -78,9 +80,10 @@
</p>
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="flex justify-center max-w-[600px] mx-auto flex-shrink-0 px-3 sm:px-4 pb-4 sm:pb-6">
<div class="flex justify-center max-w-[600px] mx-auto flex-shrink-0 px-3 sm:px-4 pb-4 sm:pb-6 pt-4 sm:pt-6">
<button
ref="continueButton"
@click="proceed"

View File

@ -1,6 +1,6 @@
<template>
<div class="min-h-full flex items-center justify-center p-3 sm:p-4 md:p-6">
<div class="max-w-[800px] w-full relative z-10 path-glass-container onb-scroll-container flex flex-col" style="max-height: calc(100dvh - 2rem);">
<div class="h-full flex items-center justify-center p-3 sm:p-4 md:p-6">
<div class="max-w-[800px] w-full max-h-full relative z-10 path-glass-container onb-scroll-container flex flex-col">
<!-- Header -->
<div class="text-center flex-shrink-0 px-3 sm:px-4 pt-4 sm:pt-6 pb-2 sm:pb-3">
<h1 class="text-xl sm:text-2xl md:text-[26px] font-semibold text-white/96 mb-1.5 drop-shadow-[0_2px_6px_rgba(0,0,0,0.4)]">

View File

@ -1,6 +1,6 @@
<template>
<div class="min-h-full flex items-center justify-center p-3 sm:p-4 md:p-6">
<div class="max-w-[800px] w-full relative z-10 path-glass-container onb-scroll-container flex flex-col" style="max-height: calc(100dvh - 2rem);">
<div class="h-full flex items-center justify-center p-3 sm:p-4 md:p-6">
<div class="max-w-[800px] w-full max-h-full relative z-10 path-glass-container onb-scroll-container flex flex-col">
<!-- Header -->
<div class="text-center flex-shrink-0 px-3 sm:px-4 pt-4 sm:pt-6 pb-2 sm:pb-3">
<h1 class="text-xl sm:text-2xl md:text-[26px] font-semibold text-white/96 mb-1.5 drop-shadow-[0_2px_6px_rgba(0,0,0,0.4)]">

View File

@ -1,6 +1,6 @@
<template>
<div class="min-h-full flex items-center justify-center p-3 sm:p-4 md:p-6">
<div class="max-w-[800px] w-full relative z-10 path-glass-container onb-scroll-container flex flex-col" style="max-height: calc(100dvh - 2rem);">
<div class="h-full flex items-center justify-center p-3 sm:p-4 md:p-6">
<div class="max-w-[800px] w-full max-h-full relative z-10 path-glass-container onb-scroll-container flex flex-col">
<!-- Header (hidden after verification) -->
<div v-if="!verified" class="text-center flex-shrink-0 px-3 sm:px-4 pt-4 sm:pt-6 pb-2 sm:pb-3">
<h1 class="text-xl sm:text-2xl md:text-[26px] font-semibold text-white/96 mb-1.5 drop-shadow-[0_2px_6px_rgba(0,0,0,0.4)]">
@ -66,13 +66,12 @@
</div>
<!-- Word Input Fields -->
<div v-if="!verified" class="w-full max-w-[600px] space-y-2 sm:space-y-3">
<div v-if="!verified" class="w-full max-w-[600px] space-y-3 sm:space-y-4">
<div
v-for="(idx, i) in challengeIndices"
:key="idx"
class="path-option-card cursor-default px-3 py-3 sm:px-5 sm:py-4"
>
<label class="block text-xs font-semibold text-white/80 mb-1.5 sm:mb-2 uppercase tracking-wide">
<label class="block text-xs font-semibold text-white/80 mb-1.5 uppercase tracking-wide text-left">
Word #{{ idx + 1 }}
</label>
<input

View File

@ -446,7 +446,10 @@ if [ "$BOTH" = true ]; then
if [ "$RO" = "true" ]; then
$DOCKER stop filebrowser 2>/dev/null; $DOCKER rm filebrowser 2>/dev/null
sudo mkdir -p /var/lib/archipelago/filebrowser
$DOCKER run -d --name filebrowser --restart=unless-stopped --user 0:0 -p 8083:80 -v /var/lib/archipelago/filebrowser:/srv "$FILEBROWSER_IMAGE" 2>/dev/null
$DOCKER run -d --name filebrowser --restart=unless-stopped --user 0:0 \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
-p 8083:80 -v /var/lib/archipelago/filebrowser:/srv "$FILEBROWSER_IMAGE" 2>/dev/null
fi
fi
' 2>/dev/null || true
@ -848,7 +851,10 @@ PYEOF
$DOCKER stop filebrowser 2>/dev/null
$DOCKER rm filebrowser 2>/dev/null
sudo mkdir -p /var/lib/archipelago/filebrowser
$DOCKER run -d --name filebrowser --restart=unless-stopped --user 0:0 -p 8083:80 -v /var/lib/archipelago/filebrowser:/srv "$FILEBROWSER_IMAGE" 2>&1 | tail -1
$DOCKER run -d --name filebrowser --restart=unless-stopped --user 0:0 \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
-p 8083:80 -v /var/lib/archipelago/filebrowser:/srv "$FILEBROWSER_IMAGE" 2>&1 | tail -1
echo " FileBrowser recreated"
else
echo " FileBrowser OK"
@ -856,7 +862,10 @@ PYEOF
else
echo " Creating FileBrowser..."
sudo mkdir -p /var/lib/archipelago/filebrowser
$DOCKER run -d --name filebrowser --restart=unless-stopped --user 0:0 -p 8083:80 -v /var/lib/archipelago/filebrowser:/srv "$FILEBROWSER_IMAGE" 2>&1 | tail -1
$DOCKER run -d --name filebrowser --restart=unless-stopped --user 0:0 \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
-p 8083:80 -v /var/lib/archipelago/filebrowser:/srv "$FILEBROWSER_IMAGE" 2>&1 | tail -1
echo " FileBrowser created"
fi
' 2>/dev/null || true
@ -1091,6 +1100,8 @@ MANIFEST_EOF
echo ' Creating mysql-mempool...'
sudo mkdir -p /var/lib/archipelago/mysql-mempool
\$DOCKER run -d --name archy-mempool-db --restart unless-stopped \$NET_OPT \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \
-v /var/lib/archipelago/mysql-mempool:/var/lib/mysql \
-e MYSQL_DATABASE=mempool \
-e MYSQL_USER=mempool \
@ -1118,6 +1129,8 @@ MANIFEST_EOF
echo ' Creating electrumx (indexer - may take days to sync, do not recreate)...'
sudo mkdir -p /var/lib/archipelago/electrumx
\$DOCKER run -d --name electrumx --restart unless-stopped \$NET_OPT \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
-p 50001:50001 \
-v /var/lib/archipelago/electrumx:/data \
-e DAEMON_URL=http://$BITCOIN_RPC_USER:$BITCOIN_RPC_PASS@bitcoin-knots:8332/ \
@ -1137,6 +1150,8 @@ MANIFEST_EOF
echo ' Creating mempool-api (backend)...'
sudo mkdir -p /var/lib/archipelago/mempool
\$DOCKER run -d --name mempool-api --restart unless-stopped \$NET_OPT \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
-p 8999:8999 \
-v /var/lib/archipelago/mempool:/data \
-e MEMPOOL_BACKEND=electrum \
@ -1164,6 +1179,8 @@ MANIFEST_EOF
if ! \$DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q archy-mempool-web; then
echo ' Creating mempool frontend on 4080...'
\$DOCKER run -d --name archy-mempool-web --restart unless-stopped \$NET_OPT \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
-p 4080:8080 \
-e FRONTEND_HTTP_PORT=8080 \
-e BACKEND_MAINNET_HTTP_HOST=mempool-api \
@ -1187,6 +1204,8 @@ MANIFEST_EOF
echo ' Creating archy-btcpay-db (PostgreSQL)...'
sudo mkdir -p /var/lib/archipelago/postgres-btcpay
\$DOCKER run -d --name archy-btcpay-db --restart unless-stopped \$NET_OPT \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \
-v /var/lib/archipelago/postgres-btcpay:/var/lib/postgresql/data \
-e POSTGRES_DB=btcpay \
-e POSTGRES_USER=btcpay \
@ -1205,6 +1224,8 @@ MANIFEST_EOF
echo ' Creating archy-nbxplorer...'
sudo mkdir -p /var/lib/archipelago/nbxplorer
\$DOCKER run -d --name archy-nbxplorer --restart unless-stopped \$NET_OPT \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
-p 32838:32838 \
-v /var/lib/archipelago/nbxplorer:/data \
-e NBXPLORER_DATADIR=/data \

View File

@ -16,8 +16,6 @@
# DO NOT split until tested on the build server — this is critical infrastructure.
#
LOG="/var/log/archipelago-first-boot.log"
DOCKER=podman
command -v podman >/dev/null 2>&1 || DOCKER=docker
# Source pinned image versions (single source of truth)
source /opt/archipelago/image-versions.sh 2>/dev/null || true
@ -26,9 +24,17 @@ source /opt/archipelago/image-versions.sh 2>/dev/null || true
SCRIPT_DIR_FBC="$(cd "$(dirname "$0")" && pwd)"
[ -f "$SCRIPT_DIR_FBC/lib/common.sh" ] && source "$SCRIPT_DIR_FBC/lib/common.sh" || true
# Must run as root for podman
# Must run as root for system setup (sysctl, loginctl, subuid, chown).
# Podman commands run as the archipelago user (rootless) so the backend
# (which also runs as archipelago) can see and manage the containers.
[ "$(id -u)" -eq 0 ] || { echo "Must run as root" >&2; exit 1; }
# Run podman as the archipelago user (rootless) — NOT as root.
# The backend service runs as User=archipelago and connects to the rootless
# podman socket at /run/user/1000/podman/podman.sock. If we create containers
# as root (rootful podman), the backend can't see them at all.
DOCKER="runuser -u archipelago -- env XDG_RUNTIME_DIR=/run/user/1000 podman"
TARGET_IP=$(hostname -I 2>/dev/null | awk '{print $1}')
[ -z "$TARGET_IP" ] && TARGET_IP="127.0.0.1"
@ -219,6 +225,14 @@ grep -q "^archipelago:" /etc/subuid 2>/dev/null || {
# Ensure /etc/hosts is readable (rootless podman needs it)
chmod 644 /etc/hosts 2>/dev/null
# Ensure XDG_RUNTIME_DIR exists for rootless podman
mkdir -p /run/user/1000
chown archipelago:archipelago /run/user/1000
chmod 700 /run/user/1000
# Start rootless podman socket (required before first podman command)
runuser -u archipelago -- env XDG_RUNTIME_DIR=/run/user/1000 \
systemctl --user start podman.socket 2>/dev/null || true
# Ensure network exists (matches deploy)
$DOCKER network create archy-net 2>/dev/null || true
@ -348,6 +362,8 @@ if ! $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qE 'archy-mempool-d
$DOCKER run -d --name archy-mempool-db --restart unless-stopped \
--health-cmd="mariadb -uroot -e 'SELECT 1' || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit archy-mempool-db) --network archy-net \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \
-v /var/lib/archipelago/mysql-mempool:/var/lib/mysql \
-e MYSQL_DATABASE=mempool -e MYSQL_USER=mempool -e "MYSQL_PASSWORD=$MEMPOOL_DB_PASS" \
-e "MYSQL_ROOT_PASSWORD=$MYSQL_ROOT_PASS" \
@ -368,6 +384,8 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q electrumx; then
$DOCKER run -d --name electrumx --restart unless-stopped \
--health-cmd="curl -sf http://localhost:8000/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit electrumx) --network archy-net \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
-p 50001:50001 -v /var/lib/archipelago/electrumx:/data \
-e "DAEMON_URL=http://$BITCOIN_RPC_USER:$BITCOIN_RPC_PASS@bitcoin-knots:8332/" \
-e COIN=Bitcoin -e DB_DIRECTORY=/data \
@ -383,6 +401,8 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q mempool-api; then
$DOCKER run -d --name mempool-api --restart unless-stopped \
--health-cmd="curl -sf http://localhost:8999/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit mempool-api) --network archy-net \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
-p 8999:8999 -v /var/lib/archipelago/mempool:/data \
-e MEMPOOL_BACKEND=electrum -e ELECTRUM_HOST=electrumx -e ELECTRUM_PORT=50001 \
-e ELECTRUM_TLS_ENABLED=false -e CORE_RPC_HOST="$TARGET_IP" -e CORE_RPC_PORT=8332 \
@ -398,6 +418,8 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -qE 'archy-mempool-web|
$DOCKER run -d --name archy-mempool-web --restart unless-stopped \
--health-cmd="curl -sf http://localhost:8080/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit archy-mempool-web) --network archy-net \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
-p 4080:8080 -e FRONTEND_HTTP_PORT=8080 -e BACKEND_MAINNET_HTTP_HOST=mempool-api \
"$MEMPOOL_WEB_IMAGE" 2>>"$LOG" || true
fi
@ -409,15 +431,21 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q electrs-ui; then
log "Starting ElectrumX UI from pre-built image..."
$DOCKER run -d --name archy-electrs-ui --network host --restart unless-stopped \
--health-cmd="curl -sf http://localhost:80/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
localhost/electrs-ui:local 2>>"$LOG" || \
$DOCKER run -d --name archy-electrs-ui --network host --restart unless-stopped \
--health-cmd="curl -sf http://localhost:80/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
electrs-ui:local 2>>"$LOG" || true
elif [ -d /opt/archipelago/docker/electrs-ui ]; then
log "Building and starting ElectrumX UI from source..."
$DOCKER build -t electrs-ui:local /opt/archipelago/docker/electrs-ui 2>>"$LOG" && \
$DOCKER run -d --name archy-electrs-ui --network host --restart unless-stopped \
--health-cmd="curl -sf http://localhost:80/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
electrs-ui:local 2>>"$LOG" || true
else
log "ElectrumX UI: no image or source found, skipping"
@ -431,6 +459,8 @@ if ! $DOCKER ps -a --format '{{.Names}}' 2>/dev/null | grep -qE 'archy-btcpay-db
$DOCKER run -d --name archy-btcpay-db --restart unless-stopped \
--health-cmd="pg_isready -U postgres || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit archy-btcpay-db) --network archy-net \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \
-v /var/lib/archipelago/postgres-btcpay:/var/lib/postgresql/data \
-e POSTGRES_DB=btcpay -e POSTGRES_USER=btcpay -e "POSTGRES_PASSWORD=$BTCPAY_DB_PASS" \
"$BTCPAY_POSTGRES_IMAGE" 2>>"$LOG" || true
@ -452,6 +482,8 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q archy-nbxplorer; the
$DOCKER run -d --name archy-nbxplorer --restart unless-stopped \
--health-cmd="curl -sf http://localhost:32838/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit archy-nbxplorer) --network archy-net \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
-p 32838:32838 -v /var/lib/archipelago/nbxplorer:/data \
-e NBXPLORER_DATADIR=/data -e NBXPLORER_NETWORK=mainnet -e NBXPLORER_CHAINS=btc \
-e NBXPLORER_BIND=0.0.0.0:32838 -e NBXPLORER_BTCRPCURL=http://bitcoin-knots:8332 \
@ -650,7 +682,9 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q jellyfin; then
$DOCKER run -d --name jellyfin --restart unless-stopped \
--health-cmd="curl -sf http://localhost:8096/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit jellyfin) \
--cap-drop ALL --security-opt no-new-privileges:true \
--cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
--security-opt no-new-privileges:true \
--tmpfs /tmp:rw,exec,size=256m \
-p 8096:8096 \
-v /var/lib/archipelago/jellyfin/config:/config \
-v /var/lib/archipelago/jellyfin/cache:/cache \
@ -782,6 +816,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q tailscale; then
--cap-drop=ALL \
--cap-add=NET_ADMIN \
--cap-add=NET_RAW \
--security-opt no-new-privileges:true \
--device=/dev/net/tun:/dev/net/tun \
--read-only \
--tmpfs /tmp \
@ -802,6 +837,9 @@ if $DOCKER images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -q 'nos
$DOCKER run -d --name nostr-rs-relay --restart unless-stopped \
--health-cmd="curl -sf http://localhost:8080/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit nostr-rs-relay) \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
--read-only --tmpfs /tmp:rw,noexec,nosuid,size=32m \
-p 7047:7047 -v /var/lib/archipelago/nostr-rs-relay:/data \
"${NOSTR_RS_RELAY_IMAGE}" 2>>"$LOG" || true
fi
@ -813,6 +851,8 @@ if $DOCKER images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -q 'str
$DOCKER run -d --name strfry --restart unless-stopped \
--health-cmd="curl -sf http://localhost:7777/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit strfry) \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
-p 7777:7777 -v /var/lib/archipelago/strfry:/data \
"${STRFRY_IMAGE}" 2>>"$LOG" || true
fi
@ -880,16 +920,25 @@ for ui in bitcoin-ui lnd-ui; do
if $DOCKER images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -q "$ui"; then
log "Starting $ui from pre-built image..."
IMG=$($DOCKER images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep "$ui" | head -1)
$DOCKER run -d --name "$CONTAINER_NAME" $PORT_ARG --restart unless-stopped --memory=$(mem_limit "$CONTAINER_NAME") $NET_ARG "$IMG" 2>>"$LOG" || true
$DOCKER run -d --name "$CONTAINER_NAME" $PORT_ARG --restart unless-stopped --memory=$(mem_limit "$CONTAINER_NAME") $NET_ARG \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
"$IMG" 2>>"$LOG" || true
elif [ -d "/opt/archipelago/docker/$ui" ]; then
log "Building $ui from source (/opt/archipelago/docker/$ui)..."
if $DOCKER build -t "$ui:local" "/opt/archipelago/docker/$ui" 2>>"$LOG"; then
$DOCKER run -d --name "$CONTAINER_NAME" $PORT_ARG --restart unless-stopped --memory=$(mem_limit "$CONTAINER_NAME") $NET_ARG "$ui:local" 2>>"$LOG" || true
$DOCKER run -d --name "$CONTAINER_NAME" $PORT_ARG --restart unless-stopped --memory=$(mem_limit "$CONTAINER_NAME") $NET_ARG \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
"$ui:local" 2>>"$LOG" || true
fi
elif [ -d "/home/archipelago/archy/docker/$ui" ]; then
log "Building $ui from source (/home/archipelago/archy/docker/$ui)..."
if $DOCKER build -t "$ui:local" "/home/archipelago/archy/docker/$ui" 2>>"$LOG"; then
$DOCKER run -d --name "$CONTAINER_NAME" $PORT_ARG --restart unless-stopped --memory=$(mem_limit "$CONTAINER_NAME") $NET_ARG "$ui:local" 2>>"$LOG" || true
$DOCKER run -d --name "$CONTAINER_NAME" $PORT_ARG --restart unless-stopped --memory=$(mem_limit "$CONTAINER_NAME") $NET_ARG \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID \
--security-opt no-new-privileges:true \
"$ui:local" 2>>"$LOG" || true
fi
else
log "$ui: no image or source found, skipping"

View File

@ -36,7 +36,7 @@ JELLYFIN_IMAGE="$ARCHY_REGISTRY/jellyfin:10.8.13"
PHOTOPRISM_IMAGE="$ARCHY_REGISTRY/photoprism:240915"
OLLAMA_IMAGE="$ARCHY_REGISTRY/ollama:latest"
VAULTWARDEN_IMAGE="$ARCHY_REGISTRY/vaultwarden:1.30.0-alpine"
NEXTCLOUD_IMAGE="$ARCHY_REGISTRY/nextcloud:28"
NEXTCLOUD_IMAGE="$ARCHY_REGISTRY/nextcloud:29"
SEARXNG_IMAGE="$ARCHY_REGISTRY/searxng:latest"
ONLYOFFICE_IMAGE="$ARCHY_REGISTRY/onlyoffice:latest"
FILEBROWSER_IMAGE="$ARCHY_REGISTRY/filebrowser:v2.27.0"
@ -82,11 +82,7 @@ PENPOT_EXPORTER_IMAGE="$ARCHY_REGISTRY/penpot-exporter:2.4"
PENPOT_FRONTEND_IMAGE="$ARCHY_REGISTRY/penpot-frontend:2.4"
# Custom UI containers (built from docker/ dirs, pushed to registry)
BITCOIN_UI_IMAGE="$ARCHY_REGISTRY/bitcoin-ui:latest"
LND_UI_IMAGE="$ARCHY_REGISTRY/lnd-ui:latest"
ELECTRS_UI_IMAGE="$ARCHY_REGISTRY/electrs-ui:latest"
# Custom UI containers (companion dashboards for headless services)
# These use :latest because they're internally built and pushed — acceptable for self-hosted images
BITCOIN_UI_IMAGE="$ARCHY_REGISTRY/bitcoin-ui:latest"
LND_UI_IMAGE="$ARCHY_REGISTRY/lnd-ui:latest"
ELECTRS_UI_IMAGE="$ARCHY_REGISTRY/electrs-ui:latest"