Rootless podman migration (TASK-11): - Remove sudo from all podman calls in PodmanClient + 8 backend files - Remove sudo from all podman/docker calls in deploy script - Restore full systemd security hardening: NoNewPrivileges, RestrictAddressFamilies, MemoryDenyWriteExecute, RestrictRealtime, RestrictNamespaces, RestrictSUIDSGID, SystemCallFilter, ProtectSystem=strict - Enable loginctl linger for rootless container persistence - Remove Ollama from auto-deploy (marketplace-only) Session & auth hardening: - Increase MAX_CONCURRENT_SESSIONS 20→50 (prevents eviction storms) - Debounced 401 redirect in rpc-client.ts (prevents redirect storms) Boot stability: - optimize-debian.sh: adds chrony, swap, removes policy-rc.d - deploy script: pre-restart chrony + swap setup - ISO build: chrony package, swap file creation - BootScreen: no longer clears localStorage (prevents splash replay) - RootRedirect: sole owner of localStorage clearing on server ready UI fixes: - Sidebar opacity default changed from 0→visible (fixes missing sidebar after page-persistence login without entrance animation) - Console.log/error wrapped in import.meta.env.DEV guards - Remove unused route import from RootRedirect Beta tracking: - CLAUDE.md: beta freeze protocol added - MASTER_PLAN.md: TASK-11, TASK-17, phase structure - BETA-PROGRESS.md: initial tracking doc - Tagged v1.2.0-alpha.1 as pre-rootless baseline Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
360 lines
14 KiB
Markdown
360 lines
14 KiB
Markdown
# CLAUDE.md — Archipelago (Archy) Project Guide
|
|
|
|
## Project Overview
|
|
|
|
Archipelago is a **Bitcoin Node OS** — a bootable, self-sovereign personal server you flash to USB, install on hardware, and manage via a web UI. Similar to Umbrel/Start9/RaspiBlitz but custom-built with production-grade security.
|
|
|
|
**Stack**: Rust backend + Vue 3 (Composition API) + TypeScript (strict) + Vite 7 + Tailwind CSS + Pinia + Podman
|
|
**Target OS**: Debian 12 (Bookworm) — x86_64 and ARM64
|
|
**Current version**: 0.1.0
|
|
|
|
---
|
|
|
|
## BETA FREEZE — ACTIVE (2026-03-18)
|
|
|
|
**Goal: Ship a flawless beta that works perfectly on every machine we install it on.**
|
|
|
|
We are in **beta stabilization mode**. The current feature set is LOCKED. Every session must push toward this goal.
|
|
|
|
### Pipeline
|
|
|
|
```
|
|
PHASE 1: Feature Testing (internal) ← WE ARE HERE
|
|
↓ Gate: every feature works, bugs fixed, security hardened, ISO verified
|
|
PHASE 2: User Testing (real users on real hardware we don't control)
|
|
↓ Gate: user-reported issues resolved, telemetry shows stable fleet
|
|
PHASE 3: Beta Live (public release)
|
|
```
|
|
|
|
### What IS allowed
|
|
- Bug fixes for existing features
|
|
- Security hardening and testing
|
|
- Beta telemetry / node reporting (TASK-12 — needed for user testing)
|
|
- UI/layout rearrangements (moving things around, improving flow)
|
|
- Boot screen completion (FEATURE-4 — already in progress)
|
|
- Testing all features end-to-end on fresh installs
|
|
- Performance and reliability improvements to existing code
|
|
- ISO build hardening
|
|
|
|
### What is NOT allowed
|
|
- New features (watch-only wallet, mesh balance check, etc. are POST-BETA)
|
|
- New app integrations
|
|
- New backend modules or RPC endpoints (unless fixing existing bugs or beta telemetry)
|
|
- New dependencies (unless required for beta infrastructure)
|
|
- Scope creep of any kind
|
|
|
|
### Status tracking
|
|
- **Progress tracker**: `docs/BETA-PROGRESS.md` — updated every session
|
|
- **Beta checklist**: `docs/BETA-RELEASE-CHECKLIST.md` — the acceptance criteria
|
|
- **Master plan**: `docs/MASTER_PLAN.md` — phased roadmap (Phase 1/2/3)
|
|
|
|
### Session protocol
|
|
1. Read `docs/BETA-PROGRESS.md` at start of every session
|
|
2. Report current phase and status before starting work
|
|
3. Work only on current-phase items
|
|
4. Update `docs/BETA-PROGRESS.md` at end of every session with what changed
|
|
|
|
---
|
|
|
|
## Quick Reference
|
|
|
|
```bash
|
|
# Frontend local dev (mock backend on :5959, Vite on :8100)
|
|
cd neode-ui && npm start
|
|
|
|
# Deploy to live server (frontend + backend + restart services)
|
|
./scripts/deploy-to-target.sh --live
|
|
|
|
# Deploy to both servers
|
|
./scripts/deploy-to-target.sh --both
|
|
|
|
# Frontend build (outputs to web/dist/neode-ui/)
|
|
cd neode-ui && npm run build
|
|
|
|
# Type-check frontend
|
|
cd neode-ui && npm run type-check
|
|
|
|
# Rust checks (run on dev server, NOT macOS)
|
|
cargo clippy --all-targets --all-features
|
|
cargo fmt --all
|
|
cargo test --all-features
|
|
```
|
|
|
|
Dev server: `http://192.168.1.228` | Local frontend: `http://localhost:8100` (password: `password123`)
|
|
|
|
## Architecture
|
|
|
|
```
|
|
Debian 12 (Bookworm)
|
|
├── Podman (rootless containers)
|
|
├── Nginx (port 80 → proxies /rpc/, /ws/, /health to backend)
|
|
├── Rust Backend (core/) — binary on port 5678
|
|
│ ├── core/archipelago/ — Main binary, RPC endpoints
|
|
│ ├── core/container/ — PodmanClient, manifest parser, dependency resolver, health monitor
|
|
│ ├── core/security/ — AppArmor profiles, secrets manager, Cosign image verifier
|
|
│ ├── core/performance/ — Resource manager
|
|
│ └── core/parmanode/ — Parmanode compatibility layer
|
|
└── Vue.js UI (neode-ui/)
|
|
├── src/api/ — RPC client (rpc-client.ts), WebSocket, container client
|
|
├── src/stores/ — Pinia stores
|
|
├── src/views/ — Page components
|
|
├── src/components/ — Reusable components
|
|
├── src/router/ — Vue Router
|
|
├── src/types/ — TypeScript type definitions
|
|
└── src/style.css — Global styles + Tailwind utilities
|
|
```
|
|
|
|
### Data Paths (Server)
|
|
|
|
- App data: `/var/lib/archipelago/{app-id}/`
|
|
- Secrets: `/var/lib/archipelago/secrets/{app-id}/` (encrypted)
|
|
- Frontend: `/opt/archipelago/web-ui/`
|
|
- Backend binary: `/usr/local/bin/archipelago`
|
|
- Systemd service: `/etc/systemd/system/archipelago.service`
|
|
- Nginx config: `/etc/nginx/sites-available/archipelago`
|
|
|
|
## CRITICAL Workflow Rules
|
|
|
|
### 1. NEVER Build Rust on macOS for Linux
|
|
|
|
Always rsync source to the Linux dev server and build there. Building on macOS and copying the binary causes Exec format errors.
|
|
|
|
```bash
|
|
# Deploy does this automatically:
|
|
./scripts/deploy-to-target.sh --live
|
|
```
|
|
|
|
### 2. Always Deploy After Changes
|
|
|
|
After editing code (frontend, backend, scripts, or configs), deploy to the live server. Do not leave deployment to the user.
|
|
|
|
### 3. Frontend Build Output Path
|
|
|
|
Frontend builds to `web/dist/neode-ui/` — NOT `neode-ui/dist/`.
|
|
|
|
### 4. Deploy-Test-Fix Loop
|
|
|
|
1. Make the change
|
|
2. Deploy with `./scripts/deploy-to-target.sh --live`
|
|
3. Test at http://192.168.1.228
|
|
4. If broken, fix and redeploy — repeat until working
|
|
5. End loop only when everything works
|
|
|
|
### 5. SSH Access
|
|
|
|
- **Primary**: `archipelago@192.168.1.228` — password: `EwPDR8q45l0Upx@`
|
|
- **Secondary**: `archipelago@192.168.1.198`
|
|
- Credentials stored in gitignored `scripts/deploy-config.sh`
|
|
|
|
```bash
|
|
sshpass -p 'EwPDR8q45l0Upx@' ssh -o StrictHostKeyChecking=no archipelago@192.168.1.228
|
|
```
|
|
|
|
## Frontend Rules (Vue.js + TypeScript)
|
|
|
|
### Component Standards
|
|
|
|
- **Always** `<script setup lang="ts">` — never Options API, never plain JS
|
|
- **Pinia** for all state management — focused single-purpose stores
|
|
- **TypeScript strict mode** — no `any`, use `unknown` or proper types
|
|
- Export types from dedicated `.types.ts` files
|
|
- Use type guards for runtime type checking
|
|
|
|
### Styling — Global Classes Only
|
|
|
|
- **ALWAYS** create global utility classes in `neode-ui/src/style.css`
|
|
- **NEVER** use inline Tailwind classes directly in components
|
|
- Use semantic class names: `.glass-card`, `.glass-button`, `.gradient-button`, `.path-option-card`
|
|
|
|
### API Client Rules
|
|
|
|
- Use `@/api/rpc-client.ts` for RPC calls, `@/api/container-client.ts` for containers
|
|
- **NEVER** hardcode API endpoints — use environment variables
|
|
- Handle loading states, error states, retry logic for all async operations
|
|
|
|
### CSS Class Hierarchy
|
|
|
|
| Class | Use | Hover |
|
|
|-------|-----|-------|
|
|
| `.path-option-card` | Section containers, interactive cards (Settings-style) | Lifts -2px |
|
|
| `.glass-card` | Content containers, modals, panels | No |
|
|
| `.info-card` | Status badges, metric displays | No |
|
|
| `.info-card-button` | Action buttons inside info sections | Lifts, brightens |
|
|
| `bg-black/20 rounded-xl border border-white/10` | Info sub-cards inside sections | No |
|
|
| `bg-white/5` | Simple read-only info rows | No |
|
|
| `.glass-button` | ALL buttons (primary and secondary) | Subtle brighten |
|
|
| `.path-action-button` | Large action buttons (Logout, Continue) | Lifts -2px |
|
|
|
|
### BANNED Classes — Do NOT Use
|
|
- **`.gradient-button`** — REMOVED. Use `.glass-button` instead. The gradient style breaks the clean glass aesthetic.
|
|
- **`.gradient-card`** / **`.gradient-card-dark`** — REMOVED. Use `.glass-card` or `.path-option-card` instead.
|
|
|
|
### Design Tokens
|
|
|
|
- **Font**: Avenir Next (primary), Montserrat (`font-archipelago`)
|
|
- **Spacing**: 4px grid system, 16px default padding
|
|
- **Glassmorphism**: `background: rgba(0,0,0,0.60)`, `backdrop-filter: blur(24px)`, `inset 0 1px 0 rgba(255,255,255,0.22)`
|
|
- **Transitions**: `all 0.3s ease` standard, `translateY(-2px)` hover, `translateY(1px)` active
|
|
- **Accent orange** (Bitcoin): `#fb923c` — `#f59e0b`
|
|
- **Green** (success): `#4ade80` | **Red** (danger): `#ef4444` | **Blue** (info): `#3b82f6`
|
|
- **Text**: `rgba(255,255,255,0.9)` primary, `rgba(255,255,255,0.6-0.7)` muted
|
|
|
|
### Tailwind Custom Values
|
|
|
|
- Blur: `backdrop-blur-glass` (18px), `backdrop-blur-glass-strong` (24px)
|
|
- Colors: `glass-dark` (0,0,0,0.35), `glass-darker` (0,0,0,0.6), `glass-border` (255,255,255,0.18)
|
|
- Shadows: `shadow-glass`, `shadow-glass-inset`
|
|
|
|
## Backend Rules (Rust)
|
|
|
|
### Error Handling
|
|
|
|
- **No `unwrap()` or `expect()` in production code** — use `?` operator
|
|
- `thiserror` for library error types, `anyhow` for application errors
|
|
- Custom error types per module: `{module}::Error`
|
|
- Include context: `.context("What failed and why")`
|
|
|
|
### RPC Endpoints
|
|
|
|
- Use `rpc_toolkit::command` macro for all endpoints
|
|
- Use `#[context] ctx: RpcContext` for context
|
|
- Return `Result<T, Error>` — validate all inputs before processing
|
|
|
|
### Async & Runtime
|
|
|
|
- `tokio` runtime only — never mix with other async runtimes
|
|
- Set timeouts on all external operations
|
|
- Use `select!` for racing futures with timeouts
|
|
- Handle shutdown gracefully with cancellation tokens
|
|
|
|
### Code Organization
|
|
|
|
- New modules in `core/{module-name}/`, add to `core/Cargo.toml` members
|
|
- `snake_case` for all modules/files
|
|
- Run `cargo clippy --all-targets --all-features` and `cargo fmt --all` before commits
|
|
|
|
### Logging
|
|
|
|
- Use `tracing` for structured logging — never `println!`
|
|
- Never log secrets, passwords, keys, or tokens
|
|
- Include context: `tracing::info!(user_id = %id, "Action")`
|
|
|
|
## Container & Security
|
|
|
|
### App Manifests
|
|
|
|
- All manifests in `apps/{app-id}/manifest.yml`
|
|
- Follow spec in `docs/app-manifest-spec.md`
|
|
- Use `archipelago_container::PodmanClient` — **NEVER** call Docker directly
|
|
|
|
### Security Requirements (Non-Negotiable)
|
|
|
|
- **ALWAYS** `readonly_root: true` unless explicitly needed
|
|
- **ALWAYS** drop all capabilities, add only required ones
|
|
- **ALWAYS** run as non-root user (UID > 1000)
|
|
- **ALWAYS** `no-new-privileges: true`
|
|
- **NEVER** use `latest` tag — pin specific image versions
|
|
- **NEVER** hardcode secrets — use `core/security/secrets_manager.rs`
|
|
|
|
### App Icons
|
|
|
|
Single source of truth: `neode-ui/public/assets/img/app-icons/`
|
|
Naming: `{app-id}.{png|webp|svg}` — do not duplicate elsewhere.
|
|
|
|
## Code Quality
|
|
|
|
- Zero compiler warnings (Rust and TypeScript)
|
|
- Zero linter errors (clippy, eslint)
|
|
- Functions under 50 lines, single responsibility
|
|
- Comment WHY not WHAT — code should be self-documenting
|
|
- Remove dead code entirely — never comment it out
|
|
- No `TODO`/`FIXME` in commits — fix now or create issues
|
|
- Workspace-relative paths only — **NEVER** hardcode `/Users/dorian/...`
|
|
|
|
## Git Conventions
|
|
|
|
### Commit Format
|
|
|
|
```
|
|
type: description
|
|
```
|
|
|
|
**Types**: `feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `chore:`, `perf:`
|
|
|
|
### Rules
|
|
|
|
- Atomic commits — one logical change per commit
|
|
- `main` branch always production-ready
|
|
- Feature branches: `feature/description`, bug fixes: `fix/description`
|
|
- Never commit secrets, `.env` files, or credentials
|
|
- Tag releases: `v1.2.3` (SemVer)
|
|
|
|
## App Integration Checklist
|
|
|
|
When adding or fixing apps, **every file below must be checked**. Missing any one causes failures on fresh installs.
|
|
|
|
### Backend (Rust)
|
|
|
|
- [ ] `core/archipelago/src/api/rpc/package.rs` — `get_app_config()`: ports, volumes, env vars, custom args
|
|
- [ ] `core/archipelago/src/api/rpc/package.rs` — `needs_archy_net`: add if app needs container DNS
|
|
- [ ] `core/archipelago/src/api/rpc/package.rs` — `get_app_capabilities()`: add required caps (CHOWN, etc.)
|
|
- [ ] `core/archipelago/src/api/rpc/package.rs` — dependency checks (e.g., electrs requires bitcoin)
|
|
- [ ] `core/archipelago/src/container/docker_packages.rs` — `get_app_metadata()`: title, description, icon, repo
|
|
- [ ] `core/archipelago/src/container/docker_packages.rs` — UI address mapping (e.g., `http://localhost:50002`)
|
|
|
|
### Frontend (Vue)
|
|
|
|
- [ ] `neode-ui/src/views/Marketplace.vue` — `getCuratedAppList()`: marketplace entry with dockerImage
|
|
- [ ] `neode-ui/src/stores/appLauncher.ts` — port-to-proxy mapping (if app has custom UI port)
|
|
- [ ] `neode-ui/src/views/AppDetails.vue` — route ID mapping (if app ID differs from container name)
|
|
|
|
### Nginx
|
|
|
|
- [ ] `image-recipe/configs/nginx-archipelago.conf` — `/app/{id}/` proxy in HTTP block
|
|
- [ ] `image-recipe/configs/snippets/archipelago-https-app-proxies.conf` — `/app/{id}/` proxy in HTTPS block
|
|
- [ ] Any custom status endpoints (e.g., `/electrs-status`) proxied before the SPA catch-all
|
|
|
|
### Deploy & First Boot
|
|
|
|
- [ ] `scripts/deploy-to-target.sh` — container creation/update logic
|
|
- [ ] `scripts/first-boot-containers.sh` — container created on fresh ISO install
|
|
- [ ] Custom UI containers (e.g., electrs-ui): built and started in both deploy and first-boot
|
|
|
|
### ISO Build
|
|
|
|
- [ ] `image-recipe/build-auto-installer-iso.sh` — `CAPTURE_PATTERNS`: image captured from live server
|
|
- [ ] `image-recipe/build-auto-installer-iso.sh` — `CONTAINER_IMAGES`: fallback image pulled from registry
|
|
- [ ] `image-recipe/build-auto-installer-iso.sh` — docker UI source files bundled for build fallback
|
|
- [ ] `image-recipe/build-auto-installer-iso.sh` — installer copies files to target disk
|
|
|
|
### Runtime Verification
|
|
|
|
- [ ] Test the app UI loads on its configured port
|
|
- [ ] Auto-connect dependencies (Bitcoin RPC, LND, etc.) — apps must work out of the box
|
|
- [ ] Most apps launch in iframe; BTCPay (23000) and Home Assistant (8123) open in new tab (X-Frame-Options)
|
|
|
|
## ISO Build
|
|
|
|
Build on the target server (has all dependencies):
|
|
|
|
```bash
|
|
ssh archipelago@192.168.1.228
|
|
cd ~/archy/image-recipe
|
|
sudo ./build-auto-installer-iso.sh
|
|
# Result: results/archipelago-auto-installer-*.iso
|
|
```
|
|
|
|
After testing on live server, always update ISO build to include changes. Sync system configs:
|
|
- `archipelago.service` → `image-recipe/configs/`
|
|
- `nginx-archipelago.conf` → `image-recipe/configs/`
|
|
|
|
## Key Documentation
|
|
|
|
- `docs/architecture.md` — System architecture
|
|
- `docs/current-state.md` — Current development phase
|
|
- `docs/development-setup.md` — Local dev setup
|
|
- `docs/app-manifest-spec.md` — YAML manifest spec
|
|
- `BUILD-GUIDE.md` — ISO build guide
|
|
- `DEPLOYMENT.md` — Deployment details
|
|
- `CHANGELOG.md` — Version history
|