Archipelago — open-source initial import
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
# Stable symlink for USB serial adapters used as mesh radios.
|
||||
# Creates /dev/mesh-radio pointing to the underlying ttyUSB device.
|
||||
# Supports MeshCore and Meshtastic radios using CP2102 (Heltec V3),
|
||||
# CH340 (T-Beam), FTDI (RAK WisBlock), and known USB CDC ACM radios.
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="10c4", ATTRS{idProduct}=="ea60", SYMLINK+="mesh-radio", MODE="0660", GROUP="dialout"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", SYMLINK+="mesh-radio", MODE="0660", GROUP="dialout"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", SYMLINK+="mesh-radio", MODE="0660", GROUP="dialout"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="239a", KERNEL=="ttyACM[0-9]*", SYMLINK+="mesh-radio", MODE="0660", GROUP="dialout"
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="2e8a", KERNEL=="ttyACM[0-9]*", SYMLINK+="mesh-radio", MODE="0660", GROUP="dialout"
|
||||
@@ -0,0 +1,12 @@
|
||||
[Unit]
|
||||
Description=Archipelago Container Doctor
|
||||
After=archipelago.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
# Runs as root: needs to kill orphaned conmon processes, fix permissions
|
||||
User=root
|
||||
ExecStart=/home/archipelago/archy/scripts/container-doctor.sh --local
|
||||
TimeoutStartSec=300
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
@@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=Archipelago container doctor (periodic)
|
||||
|
||||
[Timer]
|
||||
# First run 2 minutes after boot, then every 5 minutes. The doctor is
|
||||
# idempotent and exits quickly when no drift exists; this keeps vanished
|
||||
# rootless port listeners and stopped containers from remaining broken.
|
||||
OnBootSec=2min
|
||||
OnUnitActiveSec=5min
|
||||
# Jitter to avoid load spikes
|
||||
RandomizedDelaySec=60
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
@@ -0,0 +1,21 @@
|
||||
[Unit]
|
||||
Description=Archipelago FIPS mesh transport (wraps upstream fips daemon)
|
||||
# Stay dark until onboarding materialises the seed-derived key. Archipelago
|
||||
# backend unmasks + starts this unit via `sudo systemctl` once the key is
|
||||
# present; pre-onboarding the unit must be masked so no traffic is sent
|
||||
# from an ephemeral identity.
|
||||
ConditionPathExists=/var/lib/archipelago/identity/fips_key
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStartPre=/bin/sh -c 'test -x /usr/bin/fips || { echo "fips daemon not installed — run fips.install from dashboard" >&2; exit 1; }'
|
||||
ExecStart=/usr/bin/fips --config /etc/fips/fips.yaml
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
# UDP 8668 is reachable on all interfaces by default; the daemon does its
|
||||
# own Noise authentication so no firewall gate is added here.
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,143 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Start a dedicated X server for the attached kiosk display.
|
||||
/usr/bin/Xorg :0 vt1 -nolisten tcp -keeptty &
|
||||
XPID=$!
|
||||
|
||||
export DISPLAY=:0
|
||||
export HOME=/home/archipelago
|
||||
|
||||
X_READY=false
|
||||
for _ in $(seq 1 30); do
|
||||
if kill -0 "$XPID" 2>/dev/null && xrandr --query >/tmp/archipelago-kiosk-xrandr.txt 2>/dev/null; then
|
||||
X_READY=true
|
||||
break
|
||||
fi
|
||||
sleep 0.5
|
||||
done
|
||||
|
||||
if [ "$X_READY" != "true" ]; then
|
||||
echo 'ERROR: Xorg failed to become ready'
|
||||
kill "$XPID" 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
KIOSK_SAFE_AREA_X_PX=${ARCHIPELAGO_KIOSK_SAFE_AREA_X_PX:-}
|
||||
KIOSK_SAFE_AREA_Y_PX=${ARCHIPELAGO_KIOSK_SAFE_AREA_Y_PX:-}
|
||||
|
||||
# Actual mode of the kiosk output — configure_display overwrites these so
|
||||
# Chromium's window matches the panel instead of assuming 1080p (a 1366x768
|
||||
# laptop panel otherwise gets a clipped oversized window).
|
||||
KIOSK_MODE_W=1920
|
||||
KIOSK_MODE_H=1080
|
||||
|
||||
configure_display() {
|
||||
command -v xrandr >/dev/null 2>&1 || return 0
|
||||
|
||||
local output mode internal width height
|
||||
output=$(awk '/ connected/ && $1 !~ /^eDP|^LVDS/{print $1; exit}' /tmp/archipelago-kiosk-xrandr.txt)
|
||||
[ -n "$output" ] || output=$(awk '/ connected/{print $1; exit}' /tmp/archipelago-kiosk-xrandr.txt)
|
||||
[ -n "$output" ] || return 0
|
||||
|
||||
# Pick the EDID-preferred ("+") mode, falling back to the first-listed
|
||||
# mode (EDID lists native first). Deliberately ignore "*" (currently
|
||||
# active) — trusting "active" lets a bad clone/mirror state from a
|
||||
# previous boot perpetuate itself forever instead of self-healing.
|
||||
mode=$(awk -v out="$output" '
|
||||
$1 == out { active = 1; next }
|
||||
active && /^[[:space:]]+[0-9]+x[0-9]+/ {
|
||||
if ($0 ~ /\+/ && !preferred) { preferred = $1 }
|
||||
if (!first) first = $1
|
||||
}
|
||||
active && /^[^[:space:]]/ { active = 0 }
|
||||
END { if (preferred) print preferred; else if (first) print first }
|
||||
' /tmp/archipelago-kiosk-xrandr.txt)
|
||||
[ -n "$mode" ] || mode=1920x1080
|
||||
|
||||
# Kiosk should use one native output. A spanning desktop makes Chromium land
|
||||
# on the laptop panel or stretch across both outputs.
|
||||
for internal in $(awk '/ connected/ && $1 ~ /^eDP|^LVDS/{print $1}' /tmp/archipelago-kiosk-xrandr.txt); do
|
||||
[ "$internal" = "$output" ] || xrandr --output "$internal" --off 2>/dev/null || true
|
||||
done
|
||||
|
||||
xrandr --output "$output" \
|
||||
--primary \
|
||||
--mode "$mode" \
|
||||
--pos 0x0 \
|
||||
--scale 1x1 \
|
||||
--panning 0x0 \
|
||||
--transform none 2>/dev/null || true
|
||||
|
||||
width=${mode%x*}
|
||||
height=${mode#*x}
|
||||
case "$width:$height" in *[!0-9:]*|:*) width=1920; height=1080 ;; esac
|
||||
KIOSK_MODE_W=$width
|
||||
KIOSK_MODE_H=$height
|
||||
|
||||
# Browser safe-area fallback for TVs that crop edges. Driver underscan is
|
||||
# preferable, but many Intel HDMI outputs do not expose that property.
|
||||
KIOSK_SAFE_AREA_X_PX=${KIOSK_SAFE_AREA_X_PX:-$((width * 3 / 100))}
|
||||
KIOSK_SAFE_AREA_Y_PX=${KIOSK_SAFE_AREA_Y_PX:-$((height * 3 / 100))}
|
||||
}
|
||||
|
||||
configure_display
|
||||
|
||||
xhost +SI:localuser:archipelago 2>/dev/null || true
|
||||
xsetroot -solid black 2>/dev/null || true
|
||||
xset s off 2>/dev/null || true
|
||||
xset -dpms 2>/dev/null || true
|
||||
xset s noblank 2>/dev/null || true
|
||||
|
||||
pkill -u archipelago -f 'chromium.*localhost' 2>/dev/null || true
|
||||
sleep 1
|
||||
|
||||
# GPU vs headless (#36, choppy-audio incident 2026-06-28). --enable-gpu-rasterization
|
||||
# spins a dedicated GPU process at 55-92% CPU even on real GPU hardware (Intel HD 5500)
|
||||
# because under X11 it falls back to software compositing anyway — that CPU
|
||||
# starvation is what caused choppy HDMI audio. --in-process-gpu avoids the
|
||||
# separate process; GpuRasterization is also disabled via --disable-features below.
|
||||
# On a GPU-less / headless server (no /dev/dri), disable GPU entirely instead.
|
||||
if [ -e /dev/dri/card0 ] || [ -e /dev/dri/renderD128 ]; then
|
||||
GPU_FLAGS="--in-process-gpu --num-raster-threads=1"
|
||||
else
|
||||
GPU_FLAGS="--disable-gpu --num-raster-threads=1"
|
||||
fi
|
||||
|
||||
ARCHIPELAGO_UID=$(id -u archipelago)
|
||||
|
||||
while true; do
|
||||
# XDG_RUNTIME_DIR must be passed explicitly — without it Chromium's audio
|
||||
# backend can't find PipeWire-Pulse's socket at /run/user/<uid>/pulse/native,
|
||||
# falls back to raw ALSA "default", fails to connect, and produces no audio
|
||||
# at all with no visible error (--noerrdialogs suppresses it).
|
||||
sudo -u archipelago env DISPLAY=:0 HOME=/home/archipelago XDG_RUNTIME_DIR=/run/user/$ARCHIPELAGO_UID chromium --kiosk \
|
||||
--app=http://localhost/kiosk?safe_area_x=${KIOSK_SAFE_AREA_X_PX:-0}\&safe_area_y=${KIOSK_SAFE_AREA_Y_PX:-0} \
|
||||
--noerrdialogs \
|
||||
--disable-infobars \
|
||||
--disable-translate \
|
||||
--no-first-run \
|
||||
--check-for-update-interval=31536000 \
|
||||
--disable-features=TranslateUI,MetricsReporting,AutofillServerCommunication,PasswordManagerEnabled,GpuRasterization \
|
||||
--disable-session-crashed-bubble \
|
||||
--disable-save-password-bubble \
|
||||
--disable-suggestions-service \
|
||||
--disable-component-update \
|
||||
$GPU_FLAGS \
|
||||
--renderer-process-limit=2 \
|
||||
--window-size=${KIOSK_MODE_W},${KIOSK_MODE_H} \
|
||||
--window-position=0,0 \
|
||||
--start-fullscreen \
|
||||
--force-device-scale-factor=1 \
|
||||
--disable-background-networking \
|
||||
--disable-background-timer-throttling \
|
||||
--disable-backgrounding-occluded-windows \
|
||||
--disable-breakpad \
|
||||
--disable-metrics \
|
||||
--disable-metrics-reporting \
|
||||
--disable-domain-reliability \
|
||||
--js-flags="--max-old-space-size=128" \
|
||||
--user-data-dir=/var/lib/archipelago/chromium-kiosk
|
||||
sleep 3
|
||||
done
|
||||
|
||||
kill "$XPID" 2>/dev/null || true
|
||||
@@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=Archipelago Kiosk Watchdog
|
||||
After=archipelago.service
|
||||
Wants=archipelago.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
ExecStart=/usr/local/bin/archipelago-kiosk-watchdog
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,48 @@
|
||||
[Unit]
|
||||
Description=Archipelago Kiosk (X11 + Chromium)
|
||||
After=archipelago.service systemd-user-sessions.service network-online.target
|
||||
Wants=archipelago.service network-online.target
|
||||
ConditionPathExists=/usr/local/bin/archipelago-kiosk-launcher
|
||||
Conflicts=getty@tty1.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
# Wait up to 5 min for archipelago to serve /health. On slow hardware
|
||||
# first-boot is dominated by the FileBrowser pull (unbundled ISO),
|
||||
# initial archipelago state sync, and frontend settle — .198 took
|
||||
# longer than 120s and chromium launched against an empty backend,
|
||||
# producing a white window that only recovered on reboot. 300s gives
|
||||
# slow-but-functional hardware enough headroom; TimeoutStartSec is
|
||||
# bumped in lockstep so systemd doesn't kill us mid-wait.
|
||||
ExecStartPre=/bin/bash -c 'for i in $(seq 1 150); do curl -sf http://localhost/health >/dev/null 2>&1 && break; sleep 2; done'
|
||||
# Also wait for the web-ui asset swap to finish. The first-boot rsync into
|
||||
# /opt/archipelago/web-ui is non-atomic and writes the large bg-*.webp images
|
||||
# last — a kiosk launched mid-swap rendered the UI with blank backgrounds
|
||||
# (CSS background-image 404s are never refetched). A representative large
|
||||
# asset answering 200 means the swap is effectively done. Bounded 60s so a
|
||||
# renamed asset can never block kiosk startup.
|
||||
ExecStartPre=/bin/bash -c 'for i in $(seq 1 30); do curl -sf -o /dev/null http://localhost/assets/img/bg-home.webp && break; sleep 2; done; exit 0'
|
||||
ExecStart=/usr/local/bin/archipelago-kiosk-launcher
|
||||
TimeoutStartSec=360
|
||||
Restart=always
|
||||
RestartSec=5
|
||||
|
||||
# Resource guardrail (#36). On GPU-less / headless hardware chromium could spin
|
||||
# software compositing at ~92% of a core, saturating the node and starving the
|
||||
# backend (it caused the .198 receive timeout + deploy storms). Cap CPU + memory
|
||||
# so a runaway kiosk can never take the whole machine down; Delegate so the cap
|
||||
# also binds the chromium/Xorg children in this unit's cgroup.
|
||||
# CPUQuota=75% (0.75 cores) was too tight even for normal playback — the kiosk
|
||||
# was throttled ~40% of the time, which is what caused choppy HDMI audio on
|
||||
# archy-x250-exp (2026-06-28 incident). 200% (2 cores) gives enough headroom.
|
||||
Delegate=yes
|
||||
CPUQuota=200%
|
||||
# Raised from 1500M/1200M: a Framework (Tiger Lake) kiosk sat at 806M used /
|
||||
# 1.1G peak, riding the old MemoryHigh reclaim-throttle line — the throttling
|
||||
# itself was the perceived UI lag. Keep Max well above real peaks; High stays
|
||||
# the soft reclaim line so a runaway kiosk still can't take the machine down.
|
||||
MemoryMax=2800M
|
||||
MemoryHigh=2200M
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,10 @@
|
||||
# Archipelago persistent log directory and files
|
||||
# Runtime log destination. Backend runs as `archipelago`, but /var/log/
|
||||
# is root-owned, so we pre-create the directory and log files with the
|
||||
# right ownership at boot / install-time.
|
||||
#
|
||||
# Logrotate (image-recipe/configs/logrotate.conf) rotates files in this
|
||||
# directory daily, keeping 30 compressed copies.
|
||||
|
||||
d /var/log/archipelago 0755 archipelago archipelago - -
|
||||
f /var/log/archipelago/container-installs.log 0644 archipelago archipelago - -
|
||||
@@ -0,0 +1,9 @@
|
||||
[Unit]
|
||||
Description=Watch for Archipelago Tor management actions
|
||||
|
||||
[Path]
|
||||
PathExists=/var/lib/archipelago/tor-config/tor-action
|
||||
MakeDirectory=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,9 @@
|
||||
[Unit]
|
||||
Description=Process Archipelago Tor management action
|
||||
After=tor.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/opt/archipelago/scripts/tor-helper.sh
|
||||
# Runs as root — needs to write /etc/tor/torrc and restart tor.service
|
||||
User=root
|
||||
@@ -0,0 +1,19 @@
|
||||
[Unit]
|
||||
Description=Archipelago Self-Update
|
||||
After=network-online.target archipelago.service
|
||||
Wants=network-online.target
|
||||
ConditionPathExists=/home/archipelago/archy/.git
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
User=archipelago
|
||||
ExecStart=/home/archipelago/archy/scripts/self-update.sh
|
||||
TimeoutStartSec=600
|
||||
Environment="HOME=/home/archipelago"
|
||||
Environment="PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/archipelago/.cargo/bin"
|
||||
|
||||
# Allow sudo for service restart and file install
|
||||
# Requires archipelago user in sudoers for specific commands
|
||||
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
@@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=Check for Archipelago updates daily
|
||||
ConditionPathExists=/home/archipelago/archy/.git
|
||||
|
||||
[Timer]
|
||||
# Check at 3 AM daily (low-activity window)
|
||||
OnCalendar=*-*-* 03:00:00
|
||||
# Randomize within 30 min window to avoid thundering herd
|
||||
RandomizedDelaySec=1800
|
||||
# Run once on boot if last check was missed
|
||||
Persistent=true
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
@@ -0,0 +1,14 @@
|
||||
[Unit]
|
||||
Description=Assign WireGuard server address to wg0
|
||||
After=archipelago-wg.service
|
||||
Wants=archipelago-wg.service
|
||||
ConditionPathExists=/sys/class/net/wg0
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStart=/bin/bash -c 'ip address show dev wg0 | grep -q "10.44.0.1" || ip address add 10.44.0.1/16 dev wg0'
|
||||
ExecStart=/bin/bash -c 'iptables -t nat -C POSTROUTING -s 10.44.0.0/16 ! -o wg0 -j MASQUERADE 2>/dev/null || iptables -t nat -A POSTROUTING -s 10.44.0.0/16 ! -o wg0 -j MASQUERADE'
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=Archipelago Standalone WireGuard (wg0)
|
||||
After=network.target
|
||||
ConditionPathExists=/var/lib/archipelago/wireguard/private.key
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStart=/usr/local/bin/archipelago-wg setup /var/lib/archipelago/wireguard/private.key
|
||||
ExecStop=/bin/bash -c 'ip link del wg0 2>/dev/null || true'
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,74 @@
|
||||
[Unit]
|
||||
Description=Archipelago Backend
|
||||
After=network-online.target archipelago-setup-tor.service
|
||||
Wants=network-online.target
|
||||
# The data dir AND podman's graphroot (containers/storage) both live on the
|
||||
# separate /var/lib/archipelago volume. Without this, on a cold boot the service
|
||||
# (and its ExecStartPre) can start BEFORE var-lib-archipelago.mount, write to the
|
||||
# bare mountpoint on rootfs, fail every podman call, exit, and get restarted every
|
||||
# 5s until the volume mounts (~5 min of "[FAILED] Failed to start" on boot — B17).
|
||||
# RequiresMountsFor adds both Requires= and After= on the mount unit so we never
|
||||
# start until the data volume is mounted.
|
||||
RequiresMountsFor=/var/lib/archipelago
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
User=archipelago
|
||||
Environment="ARCHIPELAGO_BIND=127.0.0.1:5678"
|
||||
Environment="ARCHIPELAGO_USE_QUADLET_BACKENDS=true"
|
||||
EnvironmentFile=-/var/lib/archipelago/telemetry.env
|
||||
# DEV_MODE disabled in production — enabled via override.conf on dev servers
|
||||
Environment="XDG_RUNTIME_DIR=/run/user/1000"
|
||||
# + prefix runs these as root (needed for chown/mkdir outside ReadWritePaths)
|
||||
ExecStartPre=+/bin/bash -c 'mkdir -p /run/user/1000 /var/lib/containers && chown archipelago:archipelago /run/user/1000 && chmod 700 /run/user/1000'
|
||||
ExecStartPre=+/bin/bash -c 'mkdir -p /var/lib/archipelago && chown archipelago:archipelago /var/lib/archipelago && echo "ARCHIPELAGO_HOST_IP=$(hostname -I 2>/dev/null | awk "{print $$1}")" > /var/lib/archipelago/host-ip.env && chown archipelago:archipelago /var/lib/archipelago/host-ip.env'
|
||||
ExecStart=/usr/local/bin/archipelago
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
WatchdogSec=300
|
||||
TimeoutStartSec=300
|
||||
# Backend shuts down in <1s; 15s is generous for any cleanup
|
||||
TimeoutStopSec=15
|
||||
|
||||
# Filesystem protection
|
||||
ProtectSystem=strict
|
||||
# ProtectHome=no: rootless podman needs writable ~/.local/share/containers
|
||||
ProtectHome=no
|
||||
# PrivateTmp disabled: rootless podman runtime lives in /tmp/podman-run-UID/
|
||||
# and must be shared between the service and SSH-created containers
|
||||
ReadWritePaths=/var/lib/archipelago /etc/containers /var/lib/containers /run/user /tmp /home/archipelago/.local/share/containers /home/archipelago/.config/containers /etc
|
||||
|
||||
# Privilege restriction — NoNewPrivileges=no required for sudo archipelago-wg
|
||||
# (WireGuard peer management). Scoped via sudoers to only archipelago-wg.
|
||||
NoNewPrivileges=no
|
||||
PrivateDevices=no
|
||||
SupplementaryGroups=dialout debian-tor fips
|
||||
|
||||
# Syscall and network restrictions — safe on Debian 13 (systemd 256+)
|
||||
# which respects NoNewPrivileges=no as an explicit override for seccomp filters
|
||||
SystemCallArchitectures=native
|
||||
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
|
||||
RestrictRealtime=yes
|
||||
|
||||
# MemoryDenyWriteExecute removed: ring (rustls) and secp256k1 (bitcoin/nostr)
|
||||
# use assembly code that requires executable memory mappings on some platforms
|
||||
|
||||
# Resource limits
|
||||
MemoryMax=4G
|
||||
LimitNOFILE=65535
|
||||
TasksMax=2048
|
||||
|
||||
# Delegate cgroup controllers so rootless podman (run from this system service
|
||||
# as user=archipelago, not user@1000.service) can create transient libpod-*.scope
|
||||
# units with --memory / --cpus / --pids-limit. Without this, podman create fails
|
||||
# at start time with: "MemoryMax is out of range" because systemd rejects resource
|
||||
# limits on undelegated cgroup subtrees. Required for the ProdContainerOrchestrator
|
||||
# code path (see core/archipelago/src/container/prod_orchestrator.rs).
|
||||
Delegate=memory pids cpu io
|
||||
|
||||
# Logging
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,7 @@
|
||||
pcm.!default {
|
||||
type pulse
|
||||
hint.description "Default ALSA Device (via PulseAudio)"
|
||||
}
|
||||
ctl.!default {
|
||||
type pulse
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
[Journal]
|
||||
Storage=persistent
|
||||
SystemMaxUse=500M
|
||||
RuntimeMaxUse=100M
|
||||
ForwardToSyslog=no
|
||||
RateLimitIntervalSec=30s
|
||||
RateLimitBurst=10000
|
||||
@@ -0,0 +1,24 @@
|
||||
# Log rotation configuration for Archipelago
|
||||
/var/log/archipelago/*.log {
|
||||
daily
|
||||
rotate 30
|
||||
compress
|
||||
delaycompress
|
||||
missingok
|
||||
notifempty
|
||||
create 0644 root root
|
||||
sharedscripts
|
||||
postrotate
|
||||
/usr/bin/systemctl reload archipelago > /dev/null 2>&1 || true
|
||||
endscript
|
||||
}
|
||||
|
||||
/var/lib/archipelago/logs/*.log {
|
||||
daily
|
||||
rotate 14
|
||||
compress
|
||||
delaycompress
|
||||
missingok
|
||||
notifempty
|
||||
create 0644 archipelago archipelago
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
# Gitea iframe proxy — strips X-Frame-Options so Gitea works in Archipelago iframe.
|
||||
# Gitea container binds to port 3001, this proxy listens on port 3000 (the public port).
|
||||
# Deployed to /etc/nginx/conf.d/gitea-iframe.conf
|
||||
server {
|
||||
listen 3000;
|
||||
server_name _;
|
||||
client_max_body_size 1G;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3001;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_hide_header X-Frame-Options;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
[info]
|
||||
relay_url = "ws://0.0.0.0:7777/"
|
||||
name = "Archipelago Private Relay"
|
||||
description = "Private Nostr relay for Archipelago mesh VPN peer discovery"
|
||||
|
||||
[database]
|
||||
data_directory = "/var/lib/archipelago/nostr-relay"
|
||||
in_memory = true
|
||||
|
||||
[network]
|
||||
address = "0.0.0.0"
|
||||
port = 7777
|
||||
ping_interval = 120
|
||||
|
||||
[limits]
|
||||
messages_per_sec = 50
|
||||
max_event_bytes = 65536
|
||||
max_ws_message_bytes = 65536
|
||||
max_ws_frame_bytes = 65536
|
||||
@@ -0,0 +1,24 @@
|
||||
[Unit]
|
||||
Description=Archipelago Private Nostr Relay
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
Before=nostr-vpn.service
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=archipelago
|
||||
ExecStartPre=/bin/bash -c 'mkdir -p /var/lib/archipelago/nostr-relay'
|
||||
ExecStart=/usr/local/bin/nostr-rs-relay --config /var/lib/archipelago/nostr-relay/config.toml
|
||||
Restart=always
|
||||
RestartSec=3
|
||||
TimeoutStopSec=10
|
||||
|
||||
# Resource limits — relay is lightweight (in-memory mode)
|
||||
MemoryMax=512M
|
||||
LimitNOFILE=4096
|
||||
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,32 @@
|
||||
[Unit]
|
||||
Description=Nostr VPN - Mesh VPN with Nostr identity
|
||||
After=network-online.target tor.service archipelago.service
|
||||
Wants=network-online.target
|
||||
StartLimitIntervalSec=300
|
||||
StartLimitBurst=10
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=root
|
||||
Environment=HOME=/var/lib/archipelago/nostr-vpn
|
||||
EnvironmentFile=-/var/lib/archipelago/nostr-vpn/env
|
||||
ExecStartPre=+/bin/bash -c 'mkdir -p /run/nostr-vpn /var/lib/archipelago/nostr-vpn/.config/nvpn'
|
||||
ExecStartPre=/bin/bash -c 'test -f /var/lib/archipelago/nostr-vpn/env || { echo "NostrVPN not configured — waiting for onboarding"; exit 1; }'
|
||||
ExecStart=/usr/local/bin/nvpn daemon
|
||||
Restart=on-failure
|
||||
RestartSec=30
|
||||
TimeoutStartSec=30
|
||||
TimeoutStopSec=10
|
||||
|
||||
# No sandbox — runs as root for TUN/WireGuard, needs unrestricted filesystem
|
||||
|
||||
# Resource limits
|
||||
MemoryMax=256M
|
||||
TasksMax=64
|
||||
|
||||
# Logging
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,366 @@
|
||||
# App proxies for HTTPS - avoids mixed content when embedding apps from HTTPS page
|
||||
# Complete list for all apps that may be launched from the UI
|
||||
location /app/grafana/ {
|
||||
proxy_pass http://127.0.0.1:3000/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_set_header Accept-Encoding "";
|
||||
sub_filter_once on;
|
||||
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||
}
|
||||
location = /app/uptime-kuma/ {
|
||||
return 302 /app/uptime-kuma/dashboard;
|
||||
}
|
||||
location /app/uptime-kuma/ {
|
||||
proxy_pass http://127.0.0.1:3002/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Prefix /app/uptime-kuma;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_redirect / /app/uptime-kuma/;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_set_header Accept-Encoding "";
|
||||
sub_filter_once on;
|
||||
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||
}
|
||||
location /app/gitea/ {
|
||||
proxy_pass http://127.0.0.1:3001/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
}
|
||||
location /app/searxng/ {
|
||||
proxy_pass http://127.0.0.1:8888/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_set_header Accept-Encoding "";
|
||||
sub_filter_once on;
|
||||
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||
}
|
||||
location /app/portainer/ {
|
||||
proxy_pass http://127.0.0.1:9000/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_set_header Accept-Encoding "";
|
||||
sub_filter_once on;
|
||||
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||
}
|
||||
location /app/filebrowser/ {
|
||||
client_max_body_size 10G;
|
||||
proxy_pass http://127.0.0.1:8083/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_request_buffering off;
|
||||
proxy_set_header Accept-Encoding "";
|
||||
sub_filter_once on;
|
||||
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||
}
|
||||
location /app/endurain/ {
|
||||
proxy_pass http://127.0.0.1:8080/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_set_header Accept-Encoding "";
|
||||
sub_filter_once on;
|
||||
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||
}
|
||||
location /app/lnd/ {
|
||||
proxy_pass http://127.0.0.1:18083/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
proxy_set_header Accept-Encoding "";
|
||||
sub_filter_once on;
|
||||
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||
}
|
||||
location /app/jellyfin/ {
|
||||
proxy_pass http://127.0.0.1:8096/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_set_header Accept-Encoding "";
|
||||
sub_filter_once on;
|
||||
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||
}
|
||||
location /app/photoprism/ {
|
||||
proxy_pass http://127.0.0.1:2342/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_set_header Accept-Encoding "";
|
||||
sub_filter_once on;
|
||||
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||
}
|
||||
location /app/mempool/ {
|
||||
proxy_pass http://127.0.0.1:4080/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
proxy_set_header Accept-Encoding "";
|
||||
sub_filter_once on;
|
||||
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||
}
|
||||
location /app/fedimint/ {
|
||||
proxy_pass http://127.0.0.1:8175/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
proxy_set_header Accept-Encoding "";
|
||||
sub_filter_types text/css application/javascript application/json;
|
||||
sub_filter_once off;
|
||||
sub_filter 'href="/' 'href="/app/fedimint/';
|
||||
sub_filter 'src="/' 'src="/app/fedimint/';
|
||||
sub_filter "href='/" "href='/app/fedimint/";
|
||||
sub_filter "src='/" "src='/app/fedimint/";
|
||||
sub_filter 'url("/' 'url("/app/fedimint/';
|
||||
sub_filter "url('/" "url('/app/fedimint/";
|
||||
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||
}
|
||||
location /app/fedimint-gateway/ {
|
||||
proxy_pass http://127.0.0.1:8176/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
proxy_set_header Accept-Encoding "";
|
||||
sub_filter_once on;
|
||||
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||
}
|
||||
location /app/tailscale/ {
|
||||
# Tailscale has no web UI — managed via CLI/Tailscale app
|
||||
default_type application/json;
|
||||
return 503 '{"error":{"code":"NO_WEB_UI","message":"Tailscale is managed via CLI"}}';
|
||||
}
|
||||
location /app/routstr/ {
|
||||
proxy_pass http://127.0.0.1:8200/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_set_header Accept-Encoding "";
|
||||
sub_filter_once on;
|
||||
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||
}
|
||||
location /app/nostr-vpn/ {
|
||||
proxy_pass http://127.0.0.1:8201/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
}
|
||||
location /app/fips/ {
|
||||
proxy_pass http://127.0.0.1:8202/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
}
|
||||
location /app/ollama/ {
|
||||
proxy_pass http://127.0.0.1:11434/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_set_header Accept-Encoding "";
|
||||
sub_filter_once on;
|
||||
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||
}
|
||||
location /app/bitcoin-ui/ {
|
||||
proxy_pass http://127.0.0.1:8334/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_set_header Accept-Encoding "";
|
||||
sub_filter_once on;
|
||||
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||
}
|
||||
location /app/botfights/api/ {
|
||||
proxy_pass http://127.0.0.1:9100/api/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_read_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
}
|
||||
location /app/botfights/ {
|
||||
proxy_pass http://127.0.0.1:9100/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_hide_header Cross-Origin-Embedder-Policy;
|
||||
proxy_hide_header Cross-Origin-Opener-Policy;
|
||||
proxy_hide_header Cross-Origin-Resource-Policy;
|
||||
proxy_set_header Accept-Encoding "";
|
||||
sub_filter_types text/css application/javascript application/json;
|
||||
sub_filter_once off;
|
||||
sub_filter 'href="/' 'href="/app/botfights/';
|
||||
sub_filter 'src="/' 'src="/app/botfights/';
|
||||
sub_filter "href='/" "href='/app/botfights/";
|
||||
sub_filter "src='/" "src='/app/botfights/";
|
||||
sub_filter '</head>' '<script src="/nostr-provider.js"></script><script>window.addEventListener("message",function(e){var d=e.data;if(d&&d.type==="arcade-input"&&d.key){var t=d.action==="up"?"keyup":"keydown";document.dispatchEvent(new KeyboardEvent(t,{key:d.key,bubbles:true}))}})</script></head>';
|
||||
}
|
||||
location /app/electrumx/ {
|
||||
proxy_pass http://127.0.0.1:50002/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_set_header Accept-Encoding "";
|
||||
sub_filter_once on;
|
||||
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||
}
|
||||
location /app/indeedhub/_next/ {
|
||||
proxy_pass http://127.0.0.1:7777/_next/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_cache_valid 200 30d;
|
||||
add_header Cache-Control "public, max-age=2592000, immutable";
|
||||
}
|
||||
location /app/indeedhub/ws/ {
|
||||
proxy_pass http://127.0.0.1:7777/ws/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_read_timeout 86400s;
|
||||
}
|
||||
location /app/indeedhub/ {
|
||||
proxy_pass http://127.0.0.1:7777/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_set_header Accept-Encoding "";
|
||||
sub_filter_types text/css application/javascript application/json;
|
||||
sub_filter_once off;
|
||||
sub_filter 'href="/' 'href="/app/indeedhub/';
|
||||
sub_filter 'src="/' 'src="/app/indeedhub/';
|
||||
sub_filter "href='/" "href='/app/indeedhub/";
|
||||
sub_filter "src='/" "src='/app/indeedhub/";
|
||||
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||
}
|
||||
location /app/nginx-proxy-manager/ {
|
||||
proxy_pass http://127.0.0.1:8081/;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_hide_header X-Frame-Options;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
proxy_hide_header Content-Security-Policy;
|
||||
proxy_set_header Accept-Encoding "";
|
||||
sub_filter_once on;
|
||||
sub_filter '</head>' '<script src="/nostr-provider.js"></script></head>';
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
# PWA installability - required for Install (not just Add to Home Screen) on Android
|
||||
# Manifest MUST be served with application/manifest+json - Chrome rejects otherwise
|
||||
location = /manifest.webmanifest {
|
||||
default_type application/manifest+json;
|
||||
add_header Cache-Control "public, max-age=0, must-revalidate";
|
||||
}
|
||||
# Service worker - no cache so updates apply
|
||||
location ~ ^/(sw\.js|workbox-.*\.js|registerSW\.js)$ {
|
||||
add_header Content-Type application/javascript;
|
||||
add_header Service-Worker-Allowed /;
|
||||
add_header Cache-Control "no-cache, no-store, must-revalidate";
|
||||
}
|
||||
# index.html - avoid aggressive cache for PWA updates
|
||||
location = /index.html {
|
||||
add_header Cache-Control "public, max-age=0, must-revalidate";
|
||||
}
|
||||
Reference in New Issue
Block a user