Compare commits

..
7 Commits
Author SHA1 Message Date
DorianandClaude Opus 4.6 9953a99010 fix: v1.3.3 — firmware, fedimint perms, GRUB fallback, data dirs, Rust warnings
- Add firmware-linux-nonfree to ISO (fixes missing Realtek NIC firmware)
- Pre-create nbxplorer/Main and btcpay/Main data directories
- Fix fedimint data dir permissions (chmod 775 for non-root container)
- GRUB GFX fallback: gfxpayload=keep + console fallback for incompatible hardware
- Kill stale Chromium before kiosk restart (prevents duplicate processes)
- Suppress Rust warnings: #[allow(dead_code)] on run_boot_reconciliation,
  #[allow(unused_assignments)] on history_dirty
- Version bump to 1.3.3

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 20:28:53 +01:00
DorianandClaude Opus 4.6 fea256c5a8 fix: CI always syncs from local repo (checkout token unreliable)
The actions/checkout@v4 step fails with stale Gitea token but leaves
a cached .git dir, preventing the fallback from triggering. Now we
always rsync from ~/archy/ which is kept up-to-date via git pull.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 19:17:40 +01:00
DorianandClaude Opus 4.6 aa957d0e87 fix: CI always pulls latest before fallback to local repo
The actions/checkout fails (Gitea token issue) and falls back to
~/archy local copy. But local copy was stale — builds were missing
fixes. Now: always git pull in local repo before rsync fallback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 19:15:54 +01:00
DorianandClaude Opus 4.6 4820995bfb fix: FileBrowser default dirs, login option on onboarding intro
- Pre-create Documents/Photos/Music/Downloads/Builds dirs for FileBrowser
- Add "Already set up? Log in" link on onboarding intro page
- Prevents users from getting stuck in onboarding loop

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 18:33:28 +01:00
DorianandClaude Opus 4.6 f84194d9c6 fix: AIUI proxy graceful error without API key, deploy proxy parity
Claude proxy no longer crashes when ANTHROPIC_API_KEY is not set.
Instead serves a 401 with a helpful message telling users to configure
their API key in Settings. Fixes blank AIUI on fresh installs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 18:30:34 +01:00
DorianandClaude Opus 4.6 843037af47 fix: onboarding persistence, clipboard, install UI, OnlyOffice removal, UI containers
Onboarding:
- Persist current step in localStorage — page refresh resumes where user was
- Router afterEach saves step; guard redirects to saved step, not always intro
- Show npub alongside DID on restore success screen

UI fixes:
- Clipboard polyfill for HTTP contexts (fixes Copy DID crash on non-HTTPS)
- AppCard installing overlay shows for pkg.state=installing (survives refresh)
- Hide uninstall button during installation
- Frontend version bumped to 1.3.2

App store:
- OnlyOffice fully removed from marketplace, curated apps, app config
- Replaced with CryptPad references throughout
- Remove OnlyOffice from ISO capture patterns

Container stability:
- UI containers (bitcoin-ui, lnd-ui, electrs-ui) pull from registry first
- Added --cap-add FOWNER for rootless Podman compatibility
- electrs-ui now included in first-boot loop alongside bitcoin-ui and lnd-ui

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 18:20:52 +01:00
DorianandClaude Opus 4.6 bf73ef7299 chore: bump version to 1.3.2
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 17:08:52 +01:00
20 changed files with 159 additions and 62 deletions
+12
View File
@@ -15,6 +15,18 @@ jobs:
with:
fetch-depth: 1
clean: true
continue-on-error: true
- name: Sync from local repo (always — checkout token is unreliable)
run: |
# Pull latest into local repo
cd /home/archipelago/archy && git pull origin main 2>/dev/null || true
# Always sync from local repo to ensure latest code
echo "Syncing workspace from local repo..."
rsync -a --delete --exclude='.git' --exclude='target/' --exclude='node_modules/' \
/home/archipelago/archy/ ./ 2>/dev/null || cp -a /home/archipelago/archy/* . 2>/dev/null || true
echo "Source version: $(grep '^version' core/archipelago/Cargo.toml 2>/dev/null)"
echo "Latest commit: $(cd /home/archipelago/archy && git log --oneline -1 2>/dev/null)"
- name: Build backend
run: |
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "archipelago"
version = "1.3.1"
version = "1.3.3"
edition = "2021"
description = "Archipelago Bitcoin Node OS - Native backend"
authors = ["Archipelago Team"]
+1
View File
@@ -409,6 +409,7 @@ fn container_boot_tier(name: &str) -> u8 {
/// Run the reconciliation script after boot to fix any config drift.
/// Ensures all containers match their canonical specs from container-specs.sh.
#[allow(dead_code)]
pub async fn run_boot_reconciliation() {
let script = "/home/archipelago/archy/scripts/reconcile-containers.sh";
if !std::path::Path::new(script).exists() {
+1
View File
@@ -491,6 +491,7 @@ pub fn spawn_health_monitor(state: Arc<StateManager>, data_dir: PathBuf) {
// Load persistent restart history and seed the in-memory tracker
let mut restart_history = RestartHistory::load(&data_dir).await;
restart_history.seed_tracker(&mut tracker);
#[allow(unused_assignments)]
let mut history_dirty = false;
loop {
+14 -3
View File
@@ -289,6 +289,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
firmware-realtek \
firmware-iwlwifi \
firmware-misc-nonfree \
firmware-linux-nonfree \
intel-microcode \
amd64-microcode \
xorg \
@@ -1055,7 +1056,7 @@ IMAGES_CAPTURED_FROM_SERVER=0
if [ -n "$DEV_SERVER" ] && [ "$DEV_SERVER" != "localhost" ] && [ "$DEV_SERVER" != "127.0.0.1" ]; then
echo " Capturing container images from live server ($DEV_SERVER)..."
# Patterns match against `podman images` repository names (not container names)
CAPTURE_PATTERNS="bitcoin-ui bitcoinknots lnd lnd-ui electrs-ui filebrowser mempool backend frontend electrs tailscale homeassistant home-assistant btcpayserver nbxplorer postgres alpine-tor nostr-rs-relay strfry fedimintd gatewayd dwn-server grafana uptime-kuma jellyfin vaultwarden searxng mariadb valkey nginx-alpine portainer photoprism nextcloud nginx-proxy-manager onlyoffice adguard"
CAPTURE_PATTERNS="bitcoin-ui bitcoinknots lnd lnd-ui electrs-ui filebrowser mempool backend frontend electrs tailscale homeassistant home-assistant btcpayserver nbxplorer postgres alpine-tor nostr-rs-relay strfry fedimintd gatewayd dwn-server vaultwarden searxng mariadb valkey nginx-alpine portainer nginx-proxy-manager adguard"
REMOTE_TMP="/tmp/archipelago-image-capture-$$"
SAVED_LIST=$(ssh "$DEV_SERVER" "mkdir -p $REMOTE_TMP && for p in $CAPTURE_PATTERNS; do img=\$(podman images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -i \"\$p\" | head -1); [ -n \"\$img\" ] && podman save -o \"$REMOTE_TMP/\$p.tar\" \"\$img\" 2>/dev/null && echo \"\$p\"; done" 2>/dev/null) || true
for p in $SAVED_LIST; do
@@ -2181,6 +2182,9 @@ class Handler(http.server.BaseHTTPRequestHandler):
m = data.get("model", "")
if m in MODEL_MAP: data["model"] = MODEL_MAP[m]
body = json.dumps(data).encode()
if not API_KEY:
err = json.dumps({"type":"error","error":{"type":"auth_error","message":"AIUI not configured. Set your Anthropic API key in Settings > AIUI to enable AI chat."}}).encode()
self.send_response(401); self.send_header("Content-Type","application/json"); self.send_header("Content-Length",str(len(err))); self.end_headers(); self.wfile.write(err); return
headers = {"Content-Type":"application/json","x-api-key":API_KEY,"anthropic-version":"2023-06-01","anthropic-dangerous-direct-browser-access":"true"}
for h in ["anthropic-version","anthropic-beta"]:
if self.headers.get(h): headers[h] = self.headers[h]
@@ -2210,7 +2214,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
self.send_response(200); self.send_header("Content-Type","application/json"); self.end_headers(); self.wfile.write(b'{"status":"ok"}')
else: self.send_response(404); self.end_headers()
def log_message(self, fmt, *args): pass
if not API_KEY: print("ERROR: ANTHROPIC_API_KEY not set — configure via setup-aiui-server.sh"); sys.exit(1)
if not API_KEY: print("WARNING: ANTHROPIC_API_KEY not set — AIUI will return setup instructions")
server = http.server.HTTPServer(("127.0.0.1", PORT), Handler)
print(f"Claude API proxy on port {PORT}")
server.serve_forever()
@@ -2474,6 +2478,10 @@ rm -f /mnt/target/usr/share/initramfs-tools/hooks/live* 2>/dev/null || true
# Suppress os-prober warning in GRUB
echo "GRUB_DISABLE_OS_PROBER=true" >> /mnt/target/etc/default/grub
# GFX fallback for hardware without graphical GRUB support
echo 'GRUB_GFXMODE=auto' >> /mnt/target/etc/default/grub
echo 'GRUB_GFXPAYLOAD_LINUX=keep' >> /mnt/target/etc/default/grub
echo 'GRUB_TERMINAL_OUTPUT=gfxterm' >> /mnt/target/etc/default/grub
# Install Archipelago GRUB theme on target system
if [ -d "$BOOT_MEDIA/boot/grub/themes/archipelago" ]; then
@@ -2944,12 +2952,15 @@ fi
set timeout=5
set default=0
# Load font for graphical menu
# Load font for graphical menu — fallback to text mode on hardware without gfxterm
if loadfont ($root)/boot/grub/font.pf2; then
set gfxmode=auto
set gfxpayload=keep
insmod gfxterm
insmod png
terminal_output gfxterm
else
terminal_output console
fi
# Archipelago GRUB theme
@@ -24,6 +24,10 @@ xset s noblank 2>/dev/null
# Hide cursor
unclutter -idle 3 -root &
# Kill any stale Chromium instances before starting
pkill -u archipelago -f 'chromium.*kiosk' 2>/dev/null
sleep 1
# Run Chromium as archipelago user in a restart loop
while true; do
sudo -u archipelago env DISPLAY=:0 HOME=/home/archipelago chromium --kiosk \
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "neode-ui",
"private": true,
"version": "1.2.0-alpha",
"version": "1.3.3",
"type": "module",
"scripts": {
"start": "./start-dev.sh",
+11
View File
@@ -29,4 +29,15 @@ export async function isOnboardingComplete(): Promise<boolean> {
export async function completeOnboarding(): Promise<void> {
await callWithRetry(() => rpcClient.completeOnboarding(), 3)
localStorage.setItem('neode_onboarding_complete', '1')
localStorage.removeItem('neode_onboarding_step')
}
/** Save current onboarding step so refresh resumes where user left off */
export function saveOnboardingStep(step: string): void {
localStorage.setItem('neode_onboarding_step', step)
}
/** Get the last saved onboarding step, or 'intro' if none */
export function getSavedOnboardingStep(): string {
return localStorage.getItem('neode_onboarding_step') || 'intro'
}
+18
View File
@@ -4,6 +4,24 @@ import './style.css'
import App from './App.vue'
import router from './router'
import i18n from './i18n'
// Clipboard polyfill for HTTP (non-secure) contexts where navigator.clipboard is unavailable
if (!navigator.clipboard) {
Object.defineProperty(navigator, 'clipboard', {
value: {
async writeText(text: string) {
const ta = document.createElement('textarea')
ta.value = text
ta.style.cssText = 'position:fixed;opacity:0'
document.body.appendChild(ta)
ta.select()
document.execCommand('copy')
document.body.removeChild(ta)
},
async readText() { return '' },
},
})
}
import { useToast } from '@/composables/useToast'
const app = createApp(App)
+11 -2
View File
@@ -303,10 +303,11 @@ router.beforeEach(async (to, _from, next) => {
}
// Check if this is a fresh install that needs onboarding
try {
const { isOnboardingComplete } = await import('@/composables/useOnboarding')
const { isOnboardingComplete, getSavedOnboardingStep } = await import('@/composables/useOnboarding')
const setupDone = await isOnboardingComplete()
if (!setupDone) {
next('/onboarding/intro')
const step = getSavedOnboardingStep()
next(`/onboarding/${step}`)
return
}
} catch {
@@ -328,6 +329,14 @@ router.beforeEach(async (to, _from, next) => {
next()
})
// Persist onboarding step so page refresh resumes where user left off
router.afterEach((to) => {
const match = to.path.match(/^\/onboarding\/(.+)/)
if (match && match[1] !== 'intro') {
localStorage.setItem('neode_onboarding_step', match[1]!)
}
})
// Stop all login/splash audio when entering the dashboard
router.afterEach((to, from) => {
if (to.path.startsWith('/dashboard') && !from.path.startsWith('/dashboard')) {
+15
View File
@@ -34,6 +34,15 @@
>
Restore from seed phrase
</a>
<a
tabindex="0"
role="button"
class="text-white/50 hover:text-white/80 underline text-sm cursor-pointer mt-2 block text-center onb-cta"
@click="goToLogin"
@keydown.enter="goToLogin"
>
Already set up? Log in
</a>
</div>
</div>
</div>
@@ -64,6 +73,12 @@ function goToRestore() {
playNavSound('action')
router.push('/onboarding/seed-restore').catch(() => {})
}
function goToLogin() {
playNavSound('action')
localStorage.setItem('neode_onboarding_complete', '1')
router.push('/login').catch(() => {})
}
</script>
<style scoped>
@@ -39,6 +39,15 @@
</div>
</div>
</div>
<div v-if="restoredNpub" class="path-option-card cursor-default px-4 py-4 sm:px-6 sm:py-5">
<div class="text-left">
<h3 class="text-xs sm:text-sm font-semibold text-white/80 mb-2 uppercase tracking-wide">Your Nostr ID</h3>
<div class="bg-black/40 rounded-lg p-3 sm:p-4 backdrop-blur-sm border border-white/10">
<p class="text-white/95 font-mono text-xs sm:text-sm break-all leading-relaxed">{{ restoredNpub }}</p>
</div>
<p class="text-xs text-white/50 mt-2">For Nostr social apps and NIP-07 signing</p>
</div>
</div>
</div>
<!-- Word Input Grid -->
@@ -120,6 +129,7 @@ const isRestoring = ref(false)
const errorMessage = ref('')
const serverStarting = ref(false)
const restoredDid = ref('')
const restoredNpub = ref('')
const allFilled = computed(() => seedWords.value.every(w => w.trim().length > 0))
@@ -167,6 +177,7 @@ async function restore() {
if (res.restored) {
restored.value = true
restoredDid.value = res.did
restoredNpub.value = res.nostr_npub || ''
if (res.did) localStorage.setItem('neode_did', res.did)
if (res.nostr_npub) localStorage.setItem('neode_nostr_npub', res.nostr_npub)
+6 -6
View File
@@ -10,17 +10,17 @@
@click="$emit('goToApp', id)"
@keydown.enter="handleEnter"
>
<!-- Installing overlay -->
<!-- Installing overlay shown for both client-tracked installs and backend 'installing' state -->
<div
v-if="isInstalling"
v-if="isInstalling || pkg.state === 'installing'"
class="absolute inset-0 z-20 flex items-center justify-center bg-black/70 backdrop-blur-sm rounded-xl"
>
<div class="flex items-center gap-3 text-white/90">
<svg class="animate-spin h-5 w-5" fill="none" viewBox="0 0 24 24">
<div class="flex flex-col items-center gap-3 text-white/90">
<svg class="animate-spin h-6 w-6" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
<span class="text-sm font-medium">Installing</span>
<span class="text-sm font-medium">{{ installProgress?.message || 'Installing...' }}</span>
</div>
</div>
@@ -40,7 +40,7 @@
<!-- Uninstall Icon (not for web-only apps) -->
<button
v-if="!isWebOnly && !isUninstalling"
v-if="!isWebOnly && !isUninstalling && !isInstalling && pkg.state !== 'installing'"
@click.stop="$emit('showUninstall', id, pkg)"
class="absolute top-4 right-4 p-2 rounded-lg text-white/60 hover:text-red-400 hover:bg-red-500/20 transition-colors z-10"
:aria-label="`${t('common.uninstall')} ${pkg.manifest?.title || id}`"
+2 -2
View File
@@ -35,7 +35,7 @@ export const APP_CATEGORY_MAP: Record<string, string> = {
'lnd': 'money', 'mempool': 'money', 'mempool-web': 'money', 'btcpay-server': 'commerce',
'fedimint': 'money', 'fedimint-gateway': 'money',
'indeedhub': 'media', 'jellyfin': 'media', 'photoprism': 'media', 'immich': 'media',
'nextcloud': 'data', 'vaultwarden': 'data', 'filebrowser': 'data', 'onlyoffice': 'data',
'nextcloud': 'data', 'vaultwarden': 'data', 'filebrowser': 'data', 'cryptpad': 'data',
'homeassistant': 'home', 'lorabell': 'home', 'endurain': 'home',
'searxng': 'community', 'ollama': 'community', 'grafana': 'data',
'nostr-rs-relay': 'nostr', 'nostrudel': 'nostr',
@@ -103,7 +103,7 @@ export const WEB_ONLY_APPS: Record<string, PackageDataEntry> = {
export const TAB_LAUNCH_APPS = new Set([
'btcpay-server', 'grafana', 'photoprism', 'homeassistant',
'vaultwarden', 'nextcloud', 'uptime-kuma', 'portainer',
'onlyoffice', 'nginx-proxy-manager', 'tailscale',
'cryptpad', 'nginx-proxy-manager', 'tailscale',
])
export function opensInTab(id: string): boolean {
+1 -1
View File
@@ -12,7 +12,7 @@ export function getCuratedAppList(): MarketplaceApp[] {
{ id: 'grafana', title: 'Grafana', version: '10.2.0', description: 'Analytics and monitoring platform. Dashboards for your node metrics and system health.', icon: '/assets/img/app-icons/grafana.png', author: 'Grafana Labs', dockerImage: `${R}/grafana:10.2.0`, repoUrl: 'https://github.com/grafana/grafana' },
{ id: 'searxng', title: 'SearXNG', version: '2024.1.0', description: 'Privacy-respecting metasearch engine. Search the internet without being tracked or profiled.', icon: '/assets/img/app-icons/searxng.png', author: 'SearXNG', dockerImage: `${R}/searxng:latest`, repoUrl: 'https://github.com/searxng/searxng' },
{ id: 'ollama', title: 'Ollama', version: '0.5.4', description: 'Run AI models locally. Llama, Mistral, and more — on your hardware, completely private.', icon: '/assets/img/app-icons/ollama.png', author: 'Ollama', dockerImage: `${R}/ollama:latest`, repoUrl: 'https://github.com/ollama/ollama' },
{ id: 'onlyoffice', title: 'OnlyOffice', version: '7.5.1', description: 'Self-hosted office suite. Documents, spreadsheets, and presentations without the cloud.', icon: '/assets/img/app-icons/onlyoffice.webp', author: 'Ascensio System SIA', dockerImage: `${R}/onlyoffice:latest`, repoUrl: 'https://github.com/ONLYOFFICE/DocumentServer' },
{ id: 'cryptpad', title: 'CryptPad', version: '2024.12.0', description: 'End-to-end encrypted documents, spreadsheets, and presentations. Zero-knowledge collaboration.', icon: '/assets/img/app-icons/cryptpad.webp', author: 'XWiki SAS', dockerImage: `${R}/cryptpad:2024.12.0`, repoUrl: 'https://github.com/cryptpad/cryptpad' },
{ id: 'penpot', title: 'Penpot', version: '2.4', description: 'Open-source design platform. Self-hosted alternative to Figma for design and prototyping.', icon: '/assets/img/app-icons/penpot.webp', author: 'Penpot', dockerImage: `${R}/penpot-frontend:2.4`, repoUrl: 'https://github.com/penpot/penpot' },
{ id: 'nextcloud', title: 'Nextcloud', version: '28', description: 'Your own private cloud. File sync, calendars, contacts — all on your hardware.', icon: '/assets/img/app-icons/nextcloud.webp', author: 'Nextcloud', dockerImage: `${R}/nextcloud:28`, repoUrl: 'https://github.com/nextcloud/server' },
{ id: 'vaultwarden', title: 'Vaultwarden', version: '1.30.0', description: 'Self-hosted password vault. Bitwarden-compatible with zero-knowledge encryption.', icon: '/assets/img/app-icons/vaultwarden.webp', author: 'Vaultwarden', dockerImage: `${R}/vaultwarden:1.30.0-alpine`, repoUrl: 'https://github.com/dani-garcia/vaultwarden' },
@@ -42,6 +42,7 @@
import { ref, computed } from 'vue'
import { useRouter } from 'vue-router'
import { shortDid } from './utils'
import { safeClipboardWrite } from '../web5/utils'
const props = defineProps<{
selfDid: string
@@ -59,7 +60,7 @@ const shortDidDisplay = computed(() => shortDid(props.selfDid))
function handleCopy() {
if (props.selfDid) {
navigator.clipboard.writeText(props.selfDid).catch(() => {})
safeClipboardWrite(props.selfDid)
didCopied.value = true
setTimeout(() => { didCopied.value = false }, 2000)
}
@@ -227,15 +227,15 @@ export function getCuratedAppList(): MarketplaceApp[] {
repoUrl: 'https://github.com/ollama/ollama'
},
{
id: 'onlyoffice',
title: 'OnlyOffice',
version: '7.5.1',
description: 'Office suite for document collaboration. Edit docs, spreadsheets, and presentations.',
icon: '/assets/img/app-icons/onlyoffice.webp',
author: 'Ascensio System SIA',
dockerImage: `${REGISTRY}/onlyoffice:latest`,
id: 'cryptpad',
title: 'CryptPad',
version: '2024.12.0',
description: 'End-to-end encrypted documents, spreadsheets, and presentations. Zero-knowledge collaboration.',
icon: '/assets/img/app-icons/cryptpad.webp',
author: 'XWiki SAS',
dockerImage: `${REGISTRY}/cryptpad:2024.12.0`,
manifestUrl: undefined,
repoUrl: 'https://github.com/ONLYOFFICE/DocumentServer'
repoUrl: 'https://github.com/cryptpad/cryptpad'
},
{
id: 'penpot',
+15 -12
View File
@@ -37,18 +37,21 @@ export function formatMessageTime(ts: string): string {
}
export async function safeClipboardWrite(text: string): Promise<void> {
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(text)
} else {
const ta = document.createElement('textarea')
ta.value = text
ta.style.position = 'fixed'
ta.style.opacity = '0'
document.body.appendChild(ta)
ta.select()
document.execCommand('copy')
document.body.removeChild(ta)
}
// navigator.clipboard is unavailable on HTTP (non-secure contexts)
try {
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(text)
return
}
} catch { /* fall through to textarea fallback */ }
const ta = document.createElement('textarea')
ta.value = text
ta.style.position = 'fixed'
ta.style.opacity = '0'
document.body.appendChild(ta)
ta.select()
document.execCommand('copy')
document.body.removeChild(ta)
}
export function isMediaType(mime: string): boolean {
+4 -1
View File
@@ -687,6 +687,9 @@ class Handler(http.server.BaseHTTPRequestHandler):
m = data.get("model", "")
if m in MODEL_MAP: data["model"] = MODEL_MAP[m]
body = json.dumps(data).encode()
if not API_KEY:
err = json.dumps({"type":"error","error":{"type":"auth_error","message":"AIUI not configured. Set your Anthropic API key in Settings > AIUI to enable AI chat."}}).encode()
self.send_response(401); self.send_header("Content-Type","application/json"); self.send_header("Content-Length",str(len(err))); self.end_headers(); self.wfile.write(err); return
headers = {"Content-Type":"application/json","x-api-key":API_KEY,"anthropic-version":"2023-06-01","anthropic-dangerous-direct-browser-access":"true"}
for h in ["anthropic-version","anthropic-beta"]:
if self.headers.get(h): headers[h] = self.headers[h]
@@ -716,7 +719,7 @@ class Handler(http.server.BaseHTTPRequestHandler):
self.send_response(200); self.send_header("Content-Type","application/json"); self.end_headers(); self.wfile.write(b"{\"status\":\"ok\"}")
else: self.send_response(404); self.end_headers()
def log_message(self, fmt, *args): pass
if not API_KEY: print("ERROR: ANTHROPIC_API_KEY not set"); sys.exit(1)
if not API_KEY: print("WARNING: ANTHROPIC_API_KEY not set — AIUI will return setup instructions")
server = http.server.HTTPServer(("127.0.0.1", PORT), Handler)
print(f"Claude API proxy on port {PORT}")
server.serve_forever()
+21 -24
View File
@@ -649,7 +649,7 @@ if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q archy-nbxplorer; the
$DOCKER start archy-nbxplorer 2>/dev/null || true
else
log "Creating NBXplorer..."
mkdir -p /var/lib/archipelago/nbxplorer
mkdir -p /var/lib/archipelago/nbxplorer/Main
$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 --network-alias archy-nbxplorer \
@@ -667,7 +667,7 @@ track_container "archy-nbxplorer"
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q btcpay-server; then
log "Creating BTCPay Server..."
mkdir -p /var/lib/archipelago/btcpay
mkdir -p /var/lib/archipelago/btcpay/Main
$DOCKER run -d --name btcpay-server --restart unless-stopped \
--health-cmd="curl -sf http://localhost:49392/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit btcpay-server) --network archy-net --network-alias btcpay-server \
@@ -739,6 +739,7 @@ track_container "lnd"
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q fedimint; then
log "Creating Fedimint..."
mkdir -p /var/lib/archipelago/fedimint
chmod 775 /var/lib/archipelago/fedimint # fedimint container runs as non-root
$DOCKER run -d --name fedimint --restart unless-stopped \
--health-cmd="curl -sf http://localhost:8174/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit fedimint) --network archy-net --network-alias fedimint \
@@ -949,6 +950,8 @@ track_container "searxng"
if ! $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q filebrowser; then
log "Creating File Browser..."
mkdir -p /var/lib/archipelago/filebrowser /var/lib/archipelago/filebrowser-data
# Pre-create default directories so FileBrowser doesn't 404 on first load
mkdir -p /var/lib/archipelago/filebrowser/{Documents,Photos,Music,Downloads,Builds}
$DOCKER run -d --name filebrowser --restart unless-stopped \
--health-cmd="curl -sf http://localhost:80/ || exit 1" --health-interval=120s --health-timeout=5s --health-retries=3 \
--memory=$(mem_limit filebrowser) \
@@ -1092,41 +1095,35 @@ if [ -f "$RPC_PASS_FILE" ]; then
done
fi
for ui in bitcoin-ui lnd-ui; do
for ui in bitcoin-ui lnd-ui electrs-ui; do
if $DOCKER ps --format '{{.Names}}' 2>/dev/null | grep -q "$ui"; then
continue
fi
case $ui in
# UI containers use --network host so they can proxy to localhost services
# (Bitcoin RPC at 127.0.0.1:8332, backend at 127.0.0.1:5678)
# Internal nginx ports: bitcoin-ui=8334, electrs-ui=50002, lnd-ui=8080 (host 8081)
bitcoin-ui) PORT_ARG=""; NET_ARG="--network host" ;;
lnd-ui) PORT_ARG="-p 8081:8080"; NET_ARG="" ;; # nginx inside listens on 8080 (no NET_BIND_SERVICE needed)
electrs-ui) PORT_ARG=""; NET_ARG="--network host" ;;
bitcoin-ui) PORT_ARG=""; NET_ARG="--network host"; REG_IMG="${BITCOIN_UI_IMAGE}" ;;
lnd-ui) PORT_ARG="-p 8081:8080"; NET_ARG=""; REG_IMG="${LND_UI_IMAGE}" ;;
electrs-ui) PORT_ARG=""; NET_ARG="--network host"; REG_IMG="${ELECTRS_UI_IMAGE}" ;;
esac
CONTAINER_NAME="archy-$ui"
if $DOCKER images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -q "$ui"; then
log "Starting $ui from pre-built image..."
UI_CAPS="--user 0:0 --cap-drop ALL --cap-add CHOWN --cap-add FOWNER --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE"
# Try registry image first, then local image, then build from source
if [ -n "$REG_IMG" ] && $DOCKER pull --tls-verify=false "$REG_IMG" 2>>"$LOG"; then
log "Starting $ui from registry ($REG_IMG)..."
$DOCKER run -d --name "$CONTAINER_NAME" $PORT_ARG --restart unless-stopped --memory=$(mem_limit "$CONTAINER_NAME") $NET_ARG \
$UI_CAPS "$REG_IMG" 2>>"$LOG" || true
elif $DOCKER images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -q "$ui"; then
log "Starting $ui from local 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 \
--user 0:0 \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
"$IMG" 2>>"$LOG" || true
$UI_CAPS "$IMG" 2>>"$LOG" || true
elif [ -d "/opt/archipelago/docker/$ui" ]; then
log "Building $ui from source (/opt/archipelago/docker/$ui)..."
log "Building $ui from source..."
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 \
--user 0:0 \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
"$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 \
--user 0:0 \
--cap-drop ALL --cap-add CHOWN --cap-add SETUID --cap-add SETGID --cap-add DAC_OVERRIDE \
"$ui:local" 2>>"$LOG" || true
$UI_CAPS "$ui:local" 2>>"$LOG" || true
fi
else
log "$ui: no image or source found, skipping"