# 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

## 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
