fix(demo): never leak the release-server IP in the public demo
Some checks failed
Demo images / Build & push demo images (push) Failing after 5m0s
Some checks failed
Demo images / Build & push demo images (push) Failing after 5m0s
- Companion QR overlay: demo builds encode the demo's own origin (/packages/archipelago-companion.apk ships in the web image) instead of the vps2 raw URL. - Dockerfile.web/.backend: build-time scrub replaces every remaining occurrence of the release-server address with registry.demo.internal and fails the build if any survives. - Mock backend update-mirror list, sideload help text, and changelog prose no longer name the server address. - Copy demo-images workflow into .gitea/workflows/ — Gitea ignores .github/workflows when .gitea/workflows exists, so the CI never ran. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
ad3dae9983
commit
79564486d3
68
.gitea/workflows/demo-images.yml
Normal file
68
.gitea/workflows/demo-images.yml
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
name: Demo images
|
||||||
|
|
||||||
|
# Builds and pushes the public-demo images on every change to the UI / mock
|
||||||
|
# backend, so the separated `archy-demo` Portainer stack auto-tracks the real
|
||||||
|
# code (see demo-deploy/ and docs/demo-deployment-design.md).
|
||||||
|
#
|
||||||
|
# Required repo configuration:
|
||||||
|
# vars.DEMO_REGISTRY e.g. 146.59.87.168:3000/lfg2025
|
||||||
|
# vars.DEMO_REGISTRY_HOST registry host for docker login (no org suffix)
|
||||||
|
# secrets.DEMO_REGISTRY_USER
|
||||||
|
# secrets.DEMO_REGISTRY_TOKEN
|
||||||
|
# Optional:
|
||||||
|
# secrets.PORTAINER_WEBHOOK redeploy hook called after a successful push
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- 'neode-ui/**'
|
||||||
|
- 'docker-compose.demo.yml'
|
||||||
|
- '.github/workflows/demo-images.yml'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build & push demo images
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# Skip cleanly on forks / before registry config is set.
|
||||||
|
if: ${{ vars.DEMO_REGISTRY != '' }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Log in to registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ vars.DEMO_REGISTRY_HOST || vars.DEMO_REGISTRY }}
|
||||||
|
username: ${{ secrets.DEMO_REGISTRY_USER }}
|
||||||
|
password: ${{ secrets.DEMO_REGISTRY_TOKEN }}
|
||||||
|
|
||||||
|
- name: Build & push backend
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: neode-ui/Dockerfile.backend
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ vars.DEMO_REGISTRY }}/archy-demo-backend:demo
|
||||||
|
${{ vars.DEMO_REGISTRY }}/archy-demo-backend:${{ github.sha }}
|
||||||
|
|
||||||
|
- name: Build & push web
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: neode-ui/Dockerfile.web
|
||||||
|
push: true
|
||||||
|
build-args: |
|
||||||
|
VITE_DEMO=1
|
||||||
|
tags: |
|
||||||
|
${{ vars.DEMO_REGISTRY }}/archy-demo-web:demo
|
||||||
|
${{ vars.DEMO_REGISTRY }}/archy-demo-web:${{ github.sha }}
|
||||||
|
|
||||||
|
- name: Trigger Portainer redeploy
|
||||||
|
if: ${{ success() && secrets.PORTAINER_WEBHOOK != '' }}
|
||||||
|
run: curl -fsS -X POST "${{ secrets.PORTAINER_WEBHOOK }}"
|
||||||
@ -24,6 +24,19 @@ COPY docker/fedimint-ui /docker/fedimint-ui
|
|||||||
COPY demo/files /demo/files
|
COPY demo/files /demo/files
|
||||||
COPY demo/content /demo/content
|
COPY demo/content /demo/content
|
||||||
|
|
||||||
|
# This image only ever serves the public demo — scrub the private release/registry
|
||||||
|
# server address from everything it serves (mock data, catalog.json, demo assets)
|
||||||
|
# and fail the build if any occurrence survives.
|
||||||
|
RUN find /app /docker /demo -type f \( -name '*.js' -o -name '*.mjs' -o -name '*.cjs' \
|
||||||
|
-o -name '*.css' -o -name '*.html' -o -name '*.json' -o -name '*.md' -o -name '*.txt' \
|
||||||
|
-o -name '*.yml' -o -name '*.yaml' \) -not -path '*/node_modules/*' \
|
||||||
|
-exec sed -i \
|
||||||
|
-e 's#146\.59\.87\.168:3000/lfg2025#registry.demo.internal/archy#g' \
|
||||||
|
-e 's#146\.59\.87\.168:3000#registry.demo.internal#g' \
|
||||||
|
-e 's#146\.59\.87\.168#registry.demo.internal#g' {} + && \
|
||||||
|
if grep -rq '146\.59\.87\.168' /app/mock-backend.js /app/public /docker /demo; then \
|
||||||
|
echo 'LEAK: release-server IP still in demo image'; exit 1; fi
|
||||||
|
|
||||||
# Expose port
|
# Expose port
|
||||||
EXPOSE 5959
|
EXPOSE 5959
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,20 @@ ENV VITE_DEMO=$VITE_DEMO
|
|||||||
# Use npm script which handles build better
|
# Use npm script which handles build better
|
||||||
RUN npm run build:docker || (echo "Build failed! Listing files:" && ls -la && echo "Checking vite config:" && cat vite.config.ts && exit 1)
|
RUN npm run build:docker || (echo "Build failed! Listing files:" && ls -la && echo "Checking vite config:" && cat vite.config.ts && exit 1)
|
||||||
|
|
||||||
|
# Demo builds must not leak the private release/registry server address anywhere
|
||||||
|
# in the served bundle (JS constants, catalog.json, prose). Replace every
|
||||||
|
# occurrence with a non-routable placeholder, then fail the build if any slipped
|
||||||
|
# through. (The companion-QR URL is handled in source — it needs a URL that works.)
|
||||||
|
RUN if [ "$VITE_DEMO" = "1" ] || [ "$VITE_DEMO" = "true" ]; then \
|
||||||
|
find dist -type f \( -name '*.js' -o -name '*.mjs' -o -name '*.css' -o -name '*.html' \
|
||||||
|
-o -name '*.json' -o -name '*.map' -o -name '*.txt' -o -name '*.webmanifest' \) \
|
||||||
|
-exec sed -i \
|
||||||
|
-e 's#146\.59\.87\.168:3000/lfg2025#registry.demo.internal/archy#g' \
|
||||||
|
-e 's#146\.59\.87\.168:3000#registry.demo.internal#g' \
|
||||||
|
-e 's#146\.59\.87\.168#registry.demo.internal#g' {} + && \
|
||||||
|
if grep -rq '146\.59\.87\.168' dist; then echo 'LEAK: release-server IP still in dist'; exit 1; fi; \
|
||||||
|
fi
|
||||||
|
|
||||||
# Production stage
|
# Production stage
|
||||||
FROM nginx:alpine
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
|||||||
@ -3561,7 +3561,8 @@ app.post('/rpc/v1', (req, res) => {
|
|||||||
|
|
||||||
case 'update.list-mirrors': {
|
case 'update.list-mirrors': {
|
||||||
globalThis.__mockMirrors ||= [
|
globalThis.__mockMirrors ||= [
|
||||||
{ url: 'http://146.59.87.168:3000/lfg2025/archy/raw/branch/main/releases/manifest.json', label: 'Origin (vps2)' },
|
// Neutral placeholder — the public demo must not reveal the real release-server address.
|
||||||
|
{ url: 'https://updates.archipelago.demo/releases/manifest.json', label: 'Origin' },
|
||||||
]
|
]
|
||||||
return res.json({ result: { mirrors: globalThis.__mockMirrors } })
|
return res.json({ result: { mirrors: globalThis.__mockMirrors } })
|
||||||
}
|
}
|
||||||
|
|||||||
@ -77,12 +77,17 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, watch } from 'vue'
|
import { ref, onMounted, watch } from 'vue'
|
||||||
import * as QRCode from 'qrcode'
|
import * as QRCode from 'qrcode'
|
||||||
|
import { IS_DEMO } from '@/composables/useDemoIntro'
|
||||||
|
|
||||||
const STORAGE_KEY = 'neode_companion_intro_seen'
|
const STORAGE_KEY = 'neode_companion_intro_seen'
|
||||||
// Absolute URL so the QR works when scanned by a phone (a relative path has no
|
// Absolute URL so the QR works when scanned by a phone (a relative path has no
|
||||||
// host to resolve). Points at the companion APK hosted on the 146 release server
|
// host to resolve). Points at the companion APK hosted on the 146 release server
|
||||||
// (publicly reachable) rather than the local node's /packages copy.
|
// (publicly reachable) rather than the local node's /packages copy.
|
||||||
const DEFAULT_DOWNLOAD_URL = 'http://146.59.87.168:3000/lfg2025/archy/raw/branch/main/neode-ui/public/packages/archipelago-companion.apk'
|
// The demo serves the APK from its own public origin instead, so the QR never
|
||||||
|
// exposes the release-server address.
|
||||||
|
const DEFAULT_DOWNLOAD_URL = IS_DEMO
|
||||||
|
? `${window.location.origin}/packages/archipelago-companion.apk`
|
||||||
|
: 'http://146.59.87.168:3000/lfg2025/archy/raw/branch/main/neode-ui/public/packages/archipelago-companion.apk'
|
||||||
|
|
||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
const qrDataUrl = ref('')
|
const qrDataUrl = ref('')
|
||||||
|
|||||||
@ -348,7 +348,7 @@
|
|||||||
|
|
||||||
<div class="mt-5 rounded-xl border border-white/10 bg-white/[0.04] p-4 text-sm text-white/65">
|
<div class="mt-5 rounded-xl border border-white/10 bg-white/[0.04] p-4 text-sm text-white/65">
|
||||||
<p class="font-medium text-white/80 mb-2">Easy sources</p>
|
<p class="font-medium text-white/80 mb-2">Easy sources</p>
|
||||||
<p>Use images from Docker Hub, GHCR, the VPS2 Gitea registry (146.59.87.168:3000), or localhost. Good first candidates: Excalidraw, Stirling PDF, FreshRSS, Wallabag, HedgeDoc, CyberChef, Mealie, or PairDrop.</p>
|
<p>Use images from Docker Hub, GHCR, the Archipelago app registry, or localhost. Good first candidates: Excalidraw, Stirling PDF, FreshRSS, Wallabag, HedgeDoc, CyberChef, Mealie, or PairDrop.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-5 flex gap-3">
|
<div class="mt-5 flex gap-3">
|
||||||
|
|||||||
@ -1068,7 +1068,7 @@ init()
|
|||||||
<p>Installing, updating, and removing apps no longer freezes the UI. The backend now spawns the actual work in the background and returns immediately, so the progress bar starts moving right away instead of the whole page locking up for 30+ seconds while podman pulls an image.</p>
|
<p>Installing, updating, and removing apps no longer freezes the UI. The backend now spawns the actual work in the background and returns immediately, so the progress bar starts moving right away instead of the whole page locking up for 30+ seconds while podman pulls an image.</p>
|
||||||
<p>Install progress bar actually reflects reality now. It previously stayed at 0% until the very end because podman doesn't emit parseable progress when run without a TTY. Replaced byte-counting with seven clearly-labelled phases — Preparing, Pulling image, Creating container, Starting, Waiting for health, Finalizing, Done — each mapped to a fixed percentage so the bar only moves forward.</p>
|
<p>Install progress bar actually reflects reality now. It previously stayed at 0% until the very end because podman doesn't emit parseable progress when run without a TTY. Replaced byte-counting with seven clearly-labelled phases — Preparing, Pulling image, Creating container, Starting, Waiting for health, Finalizing, Done — each mapped to a fixed percentage so the bar only moves forward.</p>
|
||||||
<p>Launch button now appears the moment an install finishes, instead of waiting up to 60 seconds for the next container scan. After a successful install or update, the backend kicks the scanner and waits for a fresh manifest to land before flipping the app to Running, so the UI always has real port and UI-route info by the time the card becomes clickable.</p>
|
<p>Launch button now appears the moment an install finishes, instead of waiting up to 60 seconds for the next container scan. After a successful install or update, the backend kicks the scanner and waits for a fresh manifest to land before flipping the app to Running, so the UI always has real port and UI-route info by the time the card becomes clickable.</p>
|
||||||
<p>Retired the decommissioned .23 Hetzner VPS mirror. New nodes default to OVH (146.59.87.168) as Server 1 and tx1138 as Server 2 for both system updates and the app registry. Existing nodes auto-purge any saved .23 entries on next load so they stop paying connection-timeout penalties against a dead host.</p>
|
<p>Retired the decommissioned Hetzner VPS mirror. New nodes default to the OVH mirror as Server 1 and tx1138 as Server 2 for both system updates and the app registry. Existing nodes auto-purge any saved entries for the dead mirror on next load so they stop paying connection-timeout penalties against a dead host.</p>
|
||||||
<p>Update-available badges and version comparisons work again across every app. The backend was looking for its pinned-image catalog at the wrong path and silently getting an empty result on deployed nodes, which meant the UI never showed "update available" even when a newer image was ready. The search path now matches where the image recipe actually installs the file.</p>
|
<p>Update-available badges and version comparisons work again across every app. The backend was looking for its pinned-image catalog at the wrong path and silently getting an empty result on deployed nodes, which meant the UI never showed "update available" even when a newer image was ready. The search path now matches where the image recipe actually installs the file.</p>
|
||||||
<p>Nodes with a 2 TB data drive are no longer silently configured as pruned Bitcoin nodes. The disk-size check that decides whether to enable pruning was measuring the tiny OS partition instead of the large encrypted data partition, so every archy install with a separate data volume was flipping into prune=550 mode on reconcile and deleting its historical blocks on the next bitcoin-knots restart. The check now measures the actual data partition, so full-archive nodes stay full-archive.</p>
|
<p>Nodes with a 2 TB data drive are no longer silently configured as pruned Bitcoin nodes. The disk-size check that decides whether to enable pruning was measuring the tiny OS partition instead of the large encrypted data partition, so every archy install with a separate data volume was flipping into prune=550 mode on reconcile and deleting its historical blocks on the next bitcoin-knots restart. The check now measures the actual data partition, so full-archive nodes stay full-archive.</p>
|
||||||
<p>Recovery from a failed update no longer leaves a container permanently missing. When an app update failed partway through, the rollback path tried to restart the old container by name even though the forward path had already deleted it, leaving a hole in the node that required manual intervention. The reconcile tool now supports a --create-missing flag that rebuilds any registered container from its canonical spec, giving the update flow a safe recovery path.</p>
|
<p>Recovery from a failed update no longer leaves a container permanently missing. When an app update failed partway through, the rollback path tried to restart the old container by name even though the forward path had already deleted it, leaving a hole in the node that required manual intervention. The reconcile tool now supports a --create-missing flag that rebuilds any registered container from its canonical spec, giving the update flow a safe recovery path.</p>
|
||||||
@ -1226,7 +1226,7 @@ init()
|
|||||||
<div class="space-y-3 text-sm text-white/80 pl-3 border-l border-white/10">
|
<div class="space-y-3 text-sm text-white/80 pl-3 border-l border-white/10">
|
||||||
<p>App installs now show a real download progress bar — same accuracy as the system update bar. You'll see "Downloading: 50.5 / 200.0 MB (25%)" with a live percentage instead of a generic spinner. The bar keeps streaming even when the install falls back from one registry to another, so you'll never see a "stuck at 0%" again.</p>
|
<p>App installs now show a real download progress bar — same accuracy as the system update bar. You'll see "Downloading: 50.5 / 200.0 MB (25%)" with a live percentage instead of a generic spinner. The bar keeps streaming even when the install falls back from one registry to another, so you'll never see a "stuck at 0%" again.</p>
|
||||||
<p>Uninstalls now show what's actually happening: "Stopping containers (2/5)", "Cleaning up volumes", "Removing app data" — labelled per app so you can fire off multiple uninstalls in parallel and watch each one's stage on its own card.</p>
|
<p>Uninstalls now show what's actually happening: "Stopping containers (2/5)", "Cleaning up volumes", "Removing app data" — labelled per app so you can fire off multiple uninstalls in parallel and watch each one's stage on its own card.</p>
|
||||||
<p>OVH (146.59.87.168) is now baked in as Server 3 by default for both updates and the app registry — extra mirror, completely independent network path so a single-provider outage can't take everything down.</p>
|
<p>The OVH mirror is now baked in as Server 3 by default for both updates and the app registry — extra mirror, completely independent network path so a single-provider outage can't take everything down.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- v1.7.29-alpha -->
|
<!-- v1.7.29-alpha -->
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user