feat: standalone WireGuard from first install, fix networking stack
Standalone WireGuard (wg0:51820): - New archipelago-wg.service creates wg0 independent of NostrVPN - Keypair generated on first-boot, persisted on LUKS partition - vpn.create-peer uses wg genkey/pubkey (no nvpn dependency) - wg-address service depends on archipelago-wg, not nostr-vpn Networking fixes: - Remove nos.lol from default relays (requires PoW, events rejected) - Add Tor hidden service for private relay (port 7777) — NAT'd peers can reach relay over Tor for NostrVPN signaling - Fix Tor hostname sync race: wait loop before copying hostname files - Add tor-hostnames + wireguard dirs to LUKS partition setup - Include relay in hostname sync loops (setup-tor.sh + first-boot) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
a029a4c948
commit
b30f41f3d7
95
CLAUDE.md
95
CLAUDE.md
@ -1,25 +1,16 @@
|
|||||||
# CLAUDE.md — Archipelago (Archy)
|
# CLAUDE.md — Archipelago (Archy)
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Archipelago is a **Bitcoin Node OS** — bootable, self-sovereign personal server. Flash to USB, install on hardware, manage via web UI.
|
Archipelago is a **Bitcoin Node OS** — bootable, self-sovereign personal server. Flash to USB, install on hardware, manage via web UI.
|
||||||
|
|
||||||
**Stack**: Rust backend + Vue 3 + TypeScript (strict) + Vite 7 + Tailwind + Pinia + Podman on Debian 12
|
**Stack**: Rust backend + Vue 3 + TypeScript (strict) + Vite 7 + Tailwind + Pinia + Podman on Debian 12
|
||||||
**Version**: 1.3.0 | **Target**: x86_64 and ARM64
|
**Version**: 1.3.0 | **Target**: x86_64 and ARM64
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Beta Freeze (2026-03-18)
|
## Beta Freeze (2026-03-18)
|
||||||
|
|
||||||
**Phase 1: Feature Testing (internal) — WE ARE HERE**
|
Phase 1: Feature Testing (internal). Feature set is locked.
|
||||||
|
Only: bug fixes, security hardening, ISO build fixes, UI polish, testing.
|
||||||
Feature set is LOCKED. Only: bug fixes, security hardening, ISO build fixes, UI polish, testing.
|
|
||||||
No new features, no new apps, no new deps, no scope creep.
|
|
||||||
|
|
||||||
Track: `docs/BETA-PROGRESS.md` | Checklist: `docs/BETA-RELEASE-CHECKLIST.md`
|
Track: `docs/BETA-PROGRESS.md` | Checklist: `docs/BETA-RELEASE-CHECKLIST.md`
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Quick Reference
|
## Quick Reference
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -28,19 +19,6 @@ cd neode-ui && npm run build # Build (outputs to web/dist/neode-ui/)
|
|||||||
./scripts/deploy-to-target.sh --live # Deploy to live server (.228)
|
./scripts/deploy-to-target.sh --live # Deploy to live server (.228)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Infrastructure
|
|
||||||
|
|
||||||
| What | Where |
|
|
||||||
|------|-------|
|
|
||||||
| Dev server | `192.168.1.228` (SSH key: `~/.ssh/archipelago-deploy`) |
|
|
||||||
| Secondary | `192.168.1.198` |
|
|
||||||
| Git remote | `git.tx1138.com` (remote name: `tx1138`) |
|
|
||||||
| App registry | `80.71.235.15:3000/archipelago/` (HTTP, insecure) |
|
|
||||||
| CI runner | act_runner on .228, workflow: `.gitea/workflows/build-iso.yml` |
|
|
||||||
| ISO builds | FileBrowser at `http://192.168.1.228:8083` → Builds/ |
|
|
||||||
| SSH creds | Gitignored `scripts/deploy-config.sh` |
|
|
||||||
| Web password | `password123` |
|
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -48,75 +26,22 @@ Debian 12
|
|||||||
├── Podman (rootless, user archipelago)
|
├── Podman (rootless, user archipelago)
|
||||||
├── Nginx (80/443 → backend, app proxies)
|
├── Nginx (80/443 → backend, app proxies)
|
||||||
├── Rust Backend (core/) on 127.0.0.1:5678
|
├── Rust Backend (core/) on 127.0.0.1:5678
|
||||||
│ ├── core/archipelago/ — Binary, RPC, auth, sessions
|
|
||||||
│ └── core/container/ — PodmanClient, manifests, health
|
|
||||||
└── Vue.js UI (neode-ui/)
|
└── Vue.js UI (neode-ui/)
|
||||||
├── src/api/rpc-client.ts — All backend communication
|
|
||||||
├── src/stores/ — Pinia state
|
|
||||||
├── src/views/ — Pages
|
|
||||||
└── src/style.css — ALL styling (global classes only)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Data paths**: `/var/lib/archipelago/{app-id}/` (data), `/opt/archipelago/web-ui/` (frontend), `/usr/local/bin/archipelago` (binary)
|
**Data paths**: `/var/lib/archipelago/{app-id}/` (data), `/opt/archipelago/web-ui/` (frontend), `/usr/local/bin/archipelago` (binary)
|
||||||
|
|
||||||
## Critical Rules
|
## Critical Rules
|
||||||
|
|
||||||
1. **Never build Rust on macOS** — deploy script handles cross-compilation via rsync + remote build
|
1. Do not build Rust on macOS — deploy script handles cross-compilation via rsync + remote build.
|
||||||
2. **Always deploy after changes** — `./scripts/deploy-to-target.sh --live`
|
2. Always deploy after changes — `./scripts/deploy-to-target.sh --live`
|
||||||
3. **Frontend builds to `web/dist/neode-ui/`** — not `neode-ui/dist/`
|
3. Frontend builds to `web/dist/neode-ui/` — not `neode-ui/dist/`
|
||||||
4. **Container images**: `scripts/image-versions.sh` is the single source of truth. All scripts use `$*_IMAGE` variables, never hardcoded registry paths.
|
4. Container images: `scripts/image-versions.sh` is the single source of truth. All scripts use `$*_IMAGE` variables, not hardcoded registry paths.
|
||||||
5. **Type-check before committing** — `cd neode-ui && npx vue-tsc -b --noEmit`
|
5. Type-check before committing — `cd neode-ui && npx vue-tsc -b --noEmit`
|
||||||
|
|
||||||
## Frontend
|
|
||||||
|
|
||||||
- `<script setup lang="ts">` always — no Options API
|
|
||||||
- Global CSS in `style.css` — **never inline Tailwind**
|
|
||||||
- `.glass-button` for ALL buttons — `.gradient-button` is BANNED
|
|
||||||
- `.glass-card` for containers, `.path-option-card` for interactive cards
|
|
||||||
- `translateZ(0)` + `isolation: isolate` on glass elements (Chromium compositor fix)
|
|
||||||
- Pinia for state, typed RPC client, handle loading/error/empty states
|
|
||||||
|
|
||||||
## Backend (Rust)
|
|
||||||
|
|
||||||
- No `unwrap()`/`expect()` — use `?` with `.context()`
|
|
||||||
- `tracing` for logging, never `println!` or log secrets
|
|
||||||
- Backend binds `127.0.0.1` only — nginx handles external access
|
|
||||||
- Validate all input before path construction — reject `..`, `/`, null bytes
|
|
||||||
- `tokio` runtime, timeouts on all external ops
|
|
||||||
|
|
||||||
## Security (Post-Pentest)
|
|
||||||
|
|
||||||
- RBAC: explicit method allowlists, never prefix matching
|
|
||||||
- Session cookies: `SameSite=Lax; HttpOnly; Path=/`
|
|
||||||
- Rate-limit auth endpoints, rotate tokens after privilege escalation
|
|
||||||
- Validate redirect URLs with `isLocalRedirect()`, never `v-html` with user input
|
|
||||||
- Container security: drop ALL caps, add only required, `no-new-privileges`, memory limits, health checks
|
|
||||||
- See `.claude/rules/` for detailed crypto, API, container, and Bitcoin rules
|
|
||||||
|
|
||||||
## ISO Build & CI
|
|
||||||
|
|
||||||
CI builds on every push to `main` via git.tx1138.com Actions.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Manual build on .228:
|
|
||||||
ssh archipelago@192.168.1.228
|
|
||||||
cd ~/archy/image-recipe
|
|
||||||
sudo UNBUNDLED=1 DEV_SERVER=localhost BUILD_FROM_SOURCE=0 ./build-auto-installer-iso.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
**Debugging fresh installs** — SSH in and check:
|
|
||||||
```bash
|
|
||||||
cat /var/log/archipelago-install.log # Full installer output
|
|
||||||
cat /var/log/archipelago-first-boot-diagnostics.log # Service status, nginx, LUKS, etc.
|
|
||||||
sudo archipelago-diagnostics # Re-run diagnostics anytime
|
|
||||||
```
|
|
||||||
|
|
||||||
**Kiosk**: X11 on VT7, console on VT1. `Ctrl+Alt+F1` for terminal, `Ctrl+Alt+F7` for kiosk.
|
|
||||||
Toggle: `sudo archipelago-kiosk enable|disable|toggle`
|
|
||||||
|
|
||||||
## App Integration Checklist
|
## App Integration Checklist
|
||||||
|
|
||||||
When adding/fixing apps, check ALL of these:
|
When adding/fixing apps, check all of these:
|
||||||
- `core/archipelago/src/api/rpc/package/` — config, capabilities, deps
|
- `core/archipelago/src/api/rpc/package/` — config, capabilities, deps
|
||||||
- `neode-ui/src/views/marketplace/marketplaceData.ts` — marketplace entry
|
- `neode-ui/src/views/marketplace/marketplaceData.ts` — marketplace entry
|
||||||
- `image-recipe/configs/nginx-archipelago.conf` — proxy rules (HTTP + HTTPS)
|
- `image-recipe/configs/nginx-archipelago.conf` — proxy rules (HTTP + HTTPS)
|
||||||
@ -128,3 +53,7 @@ When adding/fixing apps, check ALL of these:
|
|||||||
|
|
||||||
Commits: `type: description` (`feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `chore:`, `perf:`)
|
Commits: `type: description` (`feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `chore:`, `perf:`)
|
||||||
Push to: `git push tx1138 main`
|
Push to: `git push tx1138 main`
|
||||||
|
|
||||||
|
## Compact Instructions
|
||||||
|
|
||||||
|
When compacting, preserve: list of modified files, test results, deploy target state, current branch, infrastructure IPs.
|
||||||
|
|||||||
@ -41,8 +41,12 @@ impl RpcHandler {
|
|||||||
// Prefer onion (always works), fall back to direct IP
|
// Prefer onion (always works), fall back to direct IP
|
||||||
let relay_url = relay_onion.clone().or(relay_direct.clone());
|
let relay_url = relay_onion.clone().or(relay_direct.clone());
|
||||||
|
|
||||||
|
// Standalone WireGuard public key
|
||||||
|
let wg_pubkey = tokio::fs::read_to_string("/var/lib/archipelago/wireguard/public.key")
|
||||||
|
.await.ok().map(|s| s.trim().to_string());
|
||||||
|
|
||||||
Ok(serde_json::json!({
|
Ok(serde_json::json!({
|
||||||
"connected": status.connected,
|
"connected": status.connected || wg_ip.is_some(),
|
||||||
"provider": status.provider,
|
"provider": status.provider,
|
||||||
"interface": status.interface,
|
"interface": status.interface,
|
||||||
"ip_address": status.ip_address,
|
"ip_address": status.ip_address,
|
||||||
@ -53,6 +57,7 @@ impl RpcHandler {
|
|||||||
"configured": config.enabled,
|
"configured": config.enabled,
|
||||||
"configured_provider": format!("{:?}", config.provider).to_lowercase(),
|
"configured_provider": format!("{:?}", config.provider).to_lowercase(),
|
||||||
"wg_ip": wg_ip,
|
"wg_ip": wg_ip,
|
||||||
|
"wg_pubkey": wg_pubkey,
|
||||||
"node_npub": node_npub,
|
"node_npub": node_npub,
|
||||||
"relay_url": relay_url,
|
"relay_url": relay_url,
|
||||||
"relay_onion": relay_onion,
|
"relay_onion": relay_onion,
|
||||||
@ -373,44 +378,48 @@ impl RpcHandler {
|
|||||||
let params = params.unwrap_or(serde_json::json!({}));
|
let params = params.unwrap_or(serde_json::json!({}));
|
||||||
let name = params.get("name").and_then(|v| v.as_str()).unwrap_or("Mobile");
|
let name = params.get("name").and_then(|v| v.as_str()).unwrap_or("Mobile");
|
||||||
|
|
||||||
// Get server status for endpoint info
|
// Check that wg0 is up (standalone WireGuard)
|
||||||
let status = vpn::get_status().await;
|
let wg0_up = tokio::process::Command::new("ip")
|
||||||
if !status.connected {
|
.args(["link", "show", "wg0"])
|
||||||
anyhow::bail!("NostrVPN is not running. Start VPN first.");
|
.output().await
|
||||||
|
.map(|o| o.status.success())
|
||||||
|
.unwrap_or(false);
|
||||||
|
if !wg0_up {
|
||||||
|
anyhow::bail!("WireGuard (wg0) is not running. Wait for first-boot to complete.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a keypair for the new peer via nvpn keygen
|
// Generate a keypair for the new peer using wg genkey/pubkey
|
||||||
let keygen = tokio::process::Command::new("nvpn")
|
let genkey = tokio::process::Command::new("wg")
|
||||||
.arg("keygen")
|
.arg("genkey")
|
||||||
.output()
|
.output().await
|
||||||
.await
|
.map_err(|e| anyhow::anyhow!("wg genkey failed: {}", e))?;
|
||||||
.map_err(|e| anyhow::anyhow!("nvpn keygen failed: {}", e))?;
|
if !genkey.status.success() {
|
||||||
|
anyhow::bail!("wg genkey failed: {}", String::from_utf8_lossy(&genkey.stderr));
|
||||||
if !keygen.status.success() {
|
|
||||||
anyhow::bail!("nvpn keygen failed: {}", String::from_utf8_lossy(&keygen.stderr));
|
|
||||||
}
|
}
|
||||||
|
let peer_private = String::from_utf8_lossy(&genkey.stdout).trim().to_string();
|
||||||
|
|
||||||
let keygen_output = String::from_utf8_lossy(&keygen.stdout);
|
let mut pubkey_cmd = tokio::process::Command::new("wg");
|
||||||
let lines: Vec<&str> = keygen_output.lines().collect();
|
pubkey_cmd.arg("pubkey");
|
||||||
|
pubkey_cmd.stdin(std::process::Stdio::piped());
|
||||||
|
pubkey_cmd.stdout(std::process::Stdio::piped());
|
||||||
|
let mut pubkey_child = pubkey_cmd.spawn()
|
||||||
|
.map_err(|e| anyhow::anyhow!("wg pubkey spawn failed: {}", e))?;
|
||||||
|
if let Some(ref mut stdin) = pubkey_child.stdin {
|
||||||
|
use tokio::io::AsyncWriteExt;
|
||||||
|
stdin.write_all(peer_private.as_bytes()).await?;
|
||||||
|
stdin.shutdown().await?;
|
||||||
|
}
|
||||||
|
let pubkey_out = pubkey_child.wait_with_output().await?;
|
||||||
|
let peer_public = String::from_utf8_lossy(&pubkey_out.stdout).trim().to_string();
|
||||||
|
|
||||||
// Parse private and public keys from keygen output (format: "private_key=<key>\npublic_key=<key>")
|
// Read server's WireGuard public key (standalone WG key, then fall back to nvpn)
|
||||||
let parse_key = |line: &str| -> String {
|
let server_pubkey = if let Ok(key) = tokio::fs::read_to_string("/var/lib/archipelago/wireguard/public.key").await {
|
||||||
if let Some(pos) = line.find('=') {
|
key.trim().to_string()
|
||||||
line[pos + 1..].trim().to_string()
|
|
||||||
} else {
|
|
||||||
line.trim().to_string()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let (peer_private, peer_public) = if lines.len() >= 2 {
|
|
||||||
(parse_key(lines[0]), parse_key(lines[1]))
|
|
||||||
} else {
|
} else {
|
||||||
anyhow::bail!("Unexpected keygen output: {}", keygen_output);
|
vpn::read_nvpn_config_value("node", "public_key").await
|
||||||
|
.ok_or_else(|| anyhow::anyhow!("Cannot read server public key"))?
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get server's WireGuard public key from nvpn config
|
|
||||||
let server_pubkey = vpn::read_nvpn_config_value("node", "public_key").await
|
|
||||||
.ok_or_else(|| anyhow::anyhow!("Cannot read server public key from nvpn config"))?;
|
|
||||||
|
|
||||||
// Detect host IP — prefer config, then nvpn, then system detection
|
// Detect host IP — prefer config, then nvpn, then system detection
|
||||||
let host_ip = if self.config.host_ip != "127.0.0.1" {
|
let host_ip = if self.config.host_ip != "127.0.0.1" {
|
||||||
self.config.host_ip.clone()
|
self.config.host_ip.clone()
|
||||||
|
|||||||
@ -38,7 +38,7 @@ pub struct RelayStats {
|
|||||||
/// Default relays seeded on first use.
|
/// Default relays seeded on first use.
|
||||||
const DEFAULT_RELAYS: &[&str] = &[
|
const DEFAULT_RELAYS: &[&str] = &[
|
||||||
"wss://relay.damus.io",
|
"wss://relay.damus.io",
|
||||||
"wss://nos.lol",
|
"wss://relay.primal.net",
|
||||||
"wss://relay.nostr.band",
|
"wss://relay.nostr.band",
|
||||||
"wss://relay.snort.social",
|
"wss://relay.snort.social",
|
||||||
"wss://nostr.wine",
|
"wss://nostr.wine",
|
||||||
@ -451,7 +451,7 @@ mod tests {
|
|||||||
let _ = load_relays(tmp.path()).await.unwrap();
|
let _ = load_relays(tmp.path()).await.unwrap();
|
||||||
|
|
||||||
toggle_relay(tmp.path(), "wss://relay.damus.io", false).await.unwrap();
|
toggle_relay(tmp.path(), "wss://relay.damus.io", false).await.unwrap();
|
||||||
toggle_relay(tmp.path(), "wss://nos.lol", false).await.unwrap();
|
toggle_relay(tmp.path(), "wss://relay.primal.net", false).await.unwrap();
|
||||||
|
|
||||||
let stats = get_stats(tmp.path()).await.unwrap();
|
let stats = get_stats(tmp.path()).await.unwrap();
|
||||||
assert_eq!(stats.total_relays, DEFAULT_RELAYS.len());
|
assert_eq!(stats.total_relays, DEFAULT_RELAYS.len());
|
||||||
|
|||||||
@ -369,6 +369,7 @@ COPY archipelago-reconcile.timer /etc/systemd/system/archipelago-reconcile.timer
|
|||||||
COPY archipelago-tor-helper.service /etc/systemd/system/archipelago-tor-helper.service
|
COPY archipelago-tor-helper.service /etc/systemd/system/archipelago-tor-helper.service
|
||||||
COPY archipelago-tor-helper.path /etc/systemd/system/archipelago-tor-helper.path
|
COPY archipelago-tor-helper.path /etc/systemd/system/archipelago-tor-helper.path
|
||||||
COPY nostr-vpn.service /etc/systemd/system/nostr-vpn.service
|
COPY nostr-vpn.service /etc/systemd/system/nostr-vpn.service
|
||||||
|
COPY archipelago-wg.service /etc/systemd/system/archipelago-wg.service
|
||||||
COPY archipelago-wg-address.service /etc/systemd/system/archipelago-wg-address.service
|
COPY archipelago-wg-address.service /etc/systemd/system/archipelago-wg-address.service
|
||||||
COPY nostr-relay.service /etc/systemd/system/nostr-relay.service
|
COPY nostr-relay.service /etc/systemd/system/nostr-relay.service
|
||||||
COPY nostr-relay-config.toml /etc/archipelago/nostr-relay-config.toml
|
COPY nostr-relay-config.toml /etc/archipelago/nostr-relay-config.toml
|
||||||
@ -399,7 +400,8 @@ RUN systemctl enable NetworkManager || true && \
|
|||||||
systemctl enable archipelago-reconcile.timer || true && \
|
systemctl enable archipelago-reconcile.timer || true && \
|
||||||
systemctl enable archipelago-tor-helper.path || true && \
|
systemctl enable archipelago-tor-helper.path || true && \
|
||||||
systemctl enable nostr-relay || true
|
systemctl enable nostr-relay || true
|
||||||
# nostr-vpn and wg-address are enabled by first-boot after Nostr identity is generated
|
# archipelago-wg + wg-address: enabled by first-boot after WG key is generated
|
||||||
|
# nostr-vpn: enabled by first-boot after Nostr identity is generated
|
||||||
# (env file doesn't exist until onboarding, so pre-enabling causes crash-loop)
|
# (env file doesn't exist until onboarding, so pre-enabling causes crash-loop)
|
||||||
|
|
||||||
# Remove policy-rc.d so services can start on first boot
|
# Remove policy-rc.d so services can start on first boot
|
||||||
@ -494,6 +496,10 @@ NGINXCONF
|
|||||||
echo " Using nostr-vpn.service from configs/"
|
echo " Using nostr-vpn.service from configs/"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
if [ -f "$SCRIPT_DIR/configs/archipelago-wg.service" ]; then
|
||||||
|
cp "$SCRIPT_DIR/configs/archipelago-wg.service" "$WORK_DIR/archipelago-wg.service"
|
||||||
|
echo " Using archipelago-wg.service from configs/"
|
||||||
|
fi
|
||||||
if [ -f "$SCRIPT_DIR/configs/archipelago-wg-address.service" ]; then
|
if [ -f "$SCRIPT_DIR/configs/archipelago-wg-address.service" ]; then
|
||||||
cp "$SCRIPT_DIR/configs/archipelago-wg-address.service" "$WORK_DIR/archipelago-wg-address.service"
|
cp "$SCRIPT_DIR/configs/archipelago-wg-address.service" "$WORK_DIR/archipelago-wg-address.service"
|
||||||
echo " Using archipelago-wg-address.service from configs/"
|
echo " Using archipelago-wg-address.service from configs/"
|
||||||
@ -1357,11 +1363,14 @@ HiddenServicePort 4080 127.0.0.1:4080
|
|||||||
|
|
||||||
HiddenServiceDir $TOR_DIR/hidden_service_fedimint
|
HiddenServiceDir $TOR_DIR/hidden_service_fedimint
|
||||||
HiddenServicePort 8175 127.0.0.1:8175
|
HiddenServicePort 8175 127.0.0.1:8175
|
||||||
|
|
||||||
|
HiddenServiceDir $TOR_DIR/hidden_service_relay
|
||||||
|
HiddenServicePort 7777 127.0.0.1:7777
|
||||||
TORRC
|
TORRC
|
||||||
|
|
||||||
# Create hidden service dirs with correct ownership and permissions (700, not 750)
|
# Create hidden service dirs with correct ownership and permissions (700, not 750)
|
||||||
# Tor refuses to start if permissions are too permissive
|
# Tor refuses to start if permissions are too permissive
|
||||||
for svc in archipelago bitcoin electrumx lnd btcpay mempool fedimint; do
|
for svc in archipelago bitcoin electrumx lnd btcpay mempool fedimint relay; do
|
||||||
mkdir -p "$TOR_DIR/hidden_service_$svc"
|
mkdir -p "$TOR_DIR/hidden_service_$svc"
|
||||||
chown debian-tor:debian-tor "$TOR_DIR/hidden_service_$svc"
|
chown debian-tor:debian-tor "$TOR_DIR/hidden_service_$svc"
|
||||||
chmod 700 "$TOR_DIR/hidden_service_$svc"
|
chmod 700 "$TOR_DIR/hidden_service_$svc"
|
||||||
@ -1406,7 +1415,7 @@ done
|
|||||||
# Sync hostnames to backend-readable directory
|
# Sync hostnames to backend-readable directory
|
||||||
HOSTNAMES_DIR="/var/lib/archipelago/tor-hostnames"
|
HOSTNAMES_DIR="/var/lib/archipelago/tor-hostnames"
|
||||||
mkdir -p "$HOSTNAMES_DIR"
|
mkdir -p "$HOSTNAMES_DIR"
|
||||||
for svc in archipelago bitcoin electrumx lnd btcpay mempool fedimint; do
|
for svc in archipelago bitcoin electrumx lnd btcpay mempool fedimint relay; do
|
||||||
if [ -f "$TOR_DIR/hidden_service_${svc}/hostname" ]; then
|
if [ -f "$TOR_DIR/hidden_service_${svc}/hostname" ]; then
|
||||||
cp "$TOR_DIR/hidden_service_${svc}/hostname" "$HOSTNAMES_DIR/$svc"
|
cp "$TOR_DIR/hidden_service_${svc}/hostname" "$HOSTNAMES_DIR/$svc"
|
||||||
echo "$(date): Synced hostname: $svc" >> "$LOG"
|
echo "$(date): Synced hostname: $svc" >> "$LOG"
|
||||||
@ -1965,7 +1974,7 @@ mkdir -p /mnt/target/var/lib/archipelago
|
|||||||
mount /dev/mapper/archipelago-data /mnt/target/var/lib/archipelago
|
mount /dev/mapper/archipelago-data /mnt/target/var/lib/archipelago
|
||||||
|
|
||||||
# Recreate directory structure on encrypted partition
|
# Recreate directory structure on encrypted partition
|
||||||
mkdir -p /mnt/target/var/lib/archipelago/{data,config,containers,secrets,tor,identities,lnd,nostr-relay,nostr-vpn}
|
mkdir -p /mnt/target/var/lib/archipelago/{data,config,containers,secrets,tor,identities,lnd,nostr-relay,nostr-vpn,tor-hostnames,wireguard}
|
||||||
mkdir -p /mnt/target/var/lib/archipelago/containers/storage
|
mkdir -p /mnt/target/var/lib/archipelago/containers/storage
|
||||||
mkdir -p /mnt/target/var/lib/archipelago/data/cloud/{Documents,Photos,Music,Videos,Downloads}
|
mkdir -p /mnt/target/var/lib/archipelago/data/cloud/{Documents,Photos,Music,Videos,Downloads}
|
||||||
# Copy relay config from rootfs (LUKS mount hides what the Dockerfile put there)
|
# Copy relay config from rootfs (LUKS mount hides what the Dockerfile put there)
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=Assign WireGuard server address to wg0
|
Description=Assign WireGuard server address to wg0
|
||||||
After=nostr-vpn.service
|
After=archipelago-wg.service
|
||||||
Wants=nostr-vpn.service
|
Wants=archipelago-wg.service
|
||||||
ConditionPathExists=/sys/class/net/wg0
|
ConditionPathExists=/sys/class/net/wg0
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
|
|||||||
14
image-recipe/configs/archipelago-wg.service
Normal file
14
image-recipe/configs/archipelago-wg.service
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Archipelago Standalone WireGuard (wg0)
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.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
|
||||||
@ -82,7 +82,7 @@ define(['./workbox-21a80088'], (function (workbox) { 'use strict';
|
|||||||
"revision": "3ca0b8505b4bec776b69afdba2768812"
|
"revision": "3ca0b8505b4bec776b69afdba2768812"
|
||||||
}, {
|
}, {
|
||||||
"url": "index.html",
|
"url": "index.html",
|
||||||
"revision": "0.huo00jkc7v4"
|
"revision": "0.nnkdothias"
|
||||||
}], {});
|
}], {});
|
||||||
workbox.cleanupOutdatedCaches();
|
workbox.cleanupOutdatedCaches();
|
||||||
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
|
workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), {
|
||||||
|
|||||||
@ -76,7 +76,13 @@ fi
|
|||||||
# The backend reads from /var/lib/archipelago/tor-hostnames/{service} at startup
|
# The backend reads from /var/lib/archipelago/tor-hostnames/{service} at startup
|
||||||
TOR_HOSTNAMES="/var/lib/archipelago/tor-hostnames"
|
TOR_HOSTNAMES="/var/lib/archipelago/tor-hostnames"
|
||||||
mkdir -p "$TOR_HOSTNAMES"
|
mkdir -p "$TOR_HOSTNAMES"
|
||||||
for svc in archipelago bitcoin lnd electrumx btcpay mempool fedimint; do
|
# Wait for Tor to generate hostname files (setup-tor.sh may still be running)
|
||||||
|
for attempt in $(seq 1 10); do
|
||||||
|
[ -f /var/lib/tor/hidden_service_archipelago/hostname ] && break
|
||||||
|
log "Waiting for Tor hostnames (attempt $attempt/10)..."
|
||||||
|
sleep 3
|
||||||
|
done
|
||||||
|
for svc in archipelago bitcoin lnd electrumx btcpay mempool fedimint relay; do
|
||||||
for dir in /var/lib/tor/hidden_service_${svc}; do
|
for dir in /var/lib/tor/hidden_service_${svc}; do
|
||||||
if [ -f "$dir/hostname" ]; then
|
if [ -f "$dir/hostname" ]; then
|
||||||
cp "$dir/hostname" "$TOR_HOSTNAMES/$svc" 2>/dev/null
|
cp "$dir/hostname" "$TOR_HOSTNAMES/$svc" 2>/dev/null
|
||||||
@ -86,6 +92,25 @@ done
|
|||||||
chown -R archipelago:archipelago "$TOR_HOSTNAMES" 2>/dev/null
|
chown -R archipelago:archipelago "$TOR_HOSTNAMES" 2>/dev/null
|
||||||
log "Tor hostnames populated: $(ls $TOR_HOSTNAMES 2>/dev/null | tr '\n' ' ')"
|
log "Tor hostnames populated: $(ls $TOR_HOSTNAMES 2>/dev/null | tr '\n' ' ')"
|
||||||
|
|
||||||
|
# ── Standalone WireGuard: generate keypair and start wg0 ──────────────
|
||||||
|
WG_DIR="/var/lib/archipelago/wireguard"
|
||||||
|
mkdir -p "$WG_DIR"
|
||||||
|
if [ ! -f "$WG_DIR/private.key" ]; then
|
||||||
|
wg genkey > "$WG_DIR/private.key" 2>/dev/null
|
||||||
|
chmod 600 "$WG_DIR/private.key"
|
||||||
|
wg pubkey < "$WG_DIR/private.key" > "$WG_DIR/public.key"
|
||||||
|
chown -R archipelago:archipelago "$WG_DIR"
|
||||||
|
log "WireGuard keypair generated"
|
||||||
|
fi
|
||||||
|
modprobe wireguard 2>/dev/null || true
|
||||||
|
systemctl enable --now archipelago-wg 2>/dev/null || true
|
||||||
|
systemctl enable --now archipelago-wg-address 2>/dev/null || true
|
||||||
|
# Open firewall port for standalone WG
|
||||||
|
if command -v ufw >/dev/null 2>&1 && ufw status | grep -q "Status: active"; then
|
||||||
|
ufw allow 51820/udp >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
log "Standalone WireGuard (wg0:51820) started"
|
||||||
|
|
||||||
# ── Private Nostr Relay: start for VPN signaling and general use ──────
|
# ── Private Nostr Relay: start for VPN signaling and general use ──────
|
||||||
if command -v nostr-rs-relay >/dev/null 2>&1; then
|
if command -v nostr-rs-relay >/dev/null 2>&1; then
|
||||||
# Relay config is pre-installed by ISO at /var/lib/archipelago/nostr-relay/config.toml
|
# Relay config is pre-installed by ISO at /var/lib/archipelago/nostr-relay/config.toml
|
||||||
@ -153,12 +178,9 @@ NOSTR_PUBKEY=${NOSTR_PUBKEY}
|
|||||||
NVPNENV
|
NVPNENV
|
||||||
chmod 600 /var/lib/archipelago/nostr-vpn/env
|
chmod 600 /var/lib/archipelago/nostr-vpn/env
|
||||||
|
|
||||||
# Load WireGuard kernel module
|
# Start NostrVPN mesh service (standalone WG already started above)
|
||||||
modprobe wireguard 2>/dev/null || true
|
systemctl reset-failed nostr-vpn 2>/dev/null || true
|
||||||
|
|
||||||
# Start NostrVPN and WireGuard address services
|
|
||||||
systemctl enable --now nostr-vpn 2>/dev/null || true
|
systemctl enable --now nostr-vpn 2>/dev/null || true
|
||||||
systemctl enable --now archipelago-wg-address 2>/dev/null || true
|
|
||||||
log "NostrVPN configured with node identity and started"
|
log "NostrVPN configured with node identity and started"
|
||||||
else
|
else
|
||||||
log "NostrVPN: no Nostr identity yet — will configure after onboarding"
|
log "NostrVPN: no Nostr identity yet — will configure after onboarding"
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user