docs: trim CLAUDE.md — lean, updated for CI/CD and registry
Removed duplication with rules/ files, updated infrastructure table (git.tx1138.com, app registry, CI runner, ISO debugging), trimmed from 404 lines to ~120. Security rules kept via reference to rules/. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0bef26badd
commit
bc5121b33f
453
CLAUDE.md
453
CLAUDE.md
@ -1,403 +1,130 @@
|
|||||||
# CLAUDE.md — Archipelago (Archy) Project Guide
|
# CLAUDE.md — Archipelago (Archy)
|
||||||
|
|
||||||
## Project Overview
|
## 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.
|
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 (Composition API) + TypeScript (strict) + Vite 7 + Tailwind CSS + Pinia + Podman
|
**Stack**: Rust backend + Vue 3 + TypeScript (strict) + Vite 7 + Tailwind + Pinia + Podman on Debian 12
|
||||||
**Target OS**: Debian 12 (Bookworm) — x86_64 and ARM64
|
**Version**: 0.1.0 | **Target**: x86_64 and ARM64
|
||||||
**Current version**: 0.1.0
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## BETA FREEZE — ACTIVE (2026-03-18)
|
## Beta Freeze (2026-03-18)
|
||||||
|
|
||||||
**Goal: Ship a flawless beta that works perfectly on every machine we install it on.**
|
**Phase 1: Feature Testing (internal) — WE ARE HERE**
|
||||||
|
|
||||||
We are in **beta stabilization mode**. The current feature set is LOCKED. Every session must push toward this goal.
|
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.
|
||||||
|
|
||||||
### Pipeline
|
Track: `docs/BETA-PROGRESS.md` | Checklist: `docs/BETA-RELEASE-CHECKLIST.md`
|
||||||
|
|
||||||
```
|
|
||||||
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
|
## Quick Reference
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Frontend local dev (mock backend on :5959, Vite on :8100)
|
cd neode-ui && npm start # Local dev (mock backend :5959, Vite :8100)
|
||||||
cd neode-ui && npm start
|
cd neode-ui && npm run build # Build (outputs to web/dist/neode-ui/)
|
||||||
|
./scripts/deploy-to-target.sh --live # Deploy to live server (.228)
|
||||||
# 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`)
|
## 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
|
||||||
|
|
||||||
```
|
```
|
||||||
Debian 12 (Bookworm)
|
Debian 12
|
||||||
├── Podman (rootless containers)
|
├── Podman (rootless, user archipelago)
|
||||||
├── Nginx (port 80 → proxies /rpc/, /ws/, /health to backend)
|
├── Nginx (80/443 → backend, app proxies)
|
||||||
├── Rust Backend (core/) — binary on port 5678
|
├── Rust Backend (core/) on 127.0.0.1:5678
|
||||||
│ ├── core/archipelago/ — Main binary, RPC endpoints
|
│ ├── core/archipelago/ — Binary, RPC, auth, sessions
|
||||||
│ ├── core/container/ — PodmanClient, manifest parser, dependency resolver, health monitor
|
│ └── core/container/ — PodmanClient, manifests, health
|
||||||
│ ├── core/security/ — AppArmor profiles, secrets manager, Cosign image verifier
|
|
||||||
│ ├── core/performance/ — Resource manager
|
|
||||||
│ └── core/parmanode/ — Parmanode compatibility layer
|
|
||||||
└── Vue.js UI (neode-ui/)
|
└── Vue.js UI (neode-ui/)
|
||||||
├── src/api/ — RPC client (rpc-client.ts), WebSocket, container client
|
├── src/api/rpc-client.ts — All backend communication
|
||||||
├── src/stores/ — Pinia stores
|
├── src/stores/ — Pinia state
|
||||||
├── src/views/ — Page components
|
├── src/views/ — Pages
|
||||||
├── src/components/ — Reusable components
|
└── src/style.css — ALL styling (global classes only)
|
||||||
├── src/router/ — Vue Router
|
|
||||||
├── src/types/ — TypeScript type definitions
|
|
||||||
└── src/style.css — Global styles + Tailwind utilities
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Data Paths (Server)
|
**Data paths**: `/var/lib/archipelago/{app-id}/` (data), `/opt/archipelago/web-ui/` (frontend), `/usr/local/bin/archipelago` (binary)
|
||||||
|
|
||||||
- App data: `/var/lib/archipelago/{app-id}/`
|
## Critical Rules
|
||||||
- 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** — deploy script handles cross-compilation via rsync + remote build
|
||||||
|
2. **Always deploy after changes** — `./scripts/deploy-to-target.sh --live`
|
||||||
|
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.
|
||||||
|
5. **Type-check before committing** — `cd neode-ui && npx vue-tsc -b --noEmit`
|
||||||
|
|
||||||
### 1. NEVER Build Rust on macOS for Linux
|
## Frontend
|
||||||
|
|
||||||
Always rsync source to the Linux dev server and build there. Building on macOS and copying the binary causes Exec format errors.
|
- `<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
|
```bash
|
||||||
# Deploy does this automatically:
|
# Manual build on .228:
|
||||||
./scripts/deploy-to-target.sh --live
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Always Deploy After Changes
|
**Debugging fresh installs** — SSH in and check:
|
||||||
|
|
||||||
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
|
```bash
|
||||||
sshpass -p 'EwPDR8q45l0Upx@' ssh -o StrictHostKeyChecking=no archipelago@192.168.1.228
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
## Frontend Rules (Vue.js + TypeScript)
|
**Kiosk**: X11 on VT7, console on VT1. `Ctrl+Alt+F1` for terminal, `Ctrl+Alt+F7` for kiosk.
|
||||||
|
Toggle: `sudo archipelago-kiosk enable|disable|toggle`
|
||||||
### 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.
|
|
||||||
|
|
||||||
## Security Standards (Post-Pentest — Mandatory)
|
|
||||||
|
|
||||||
These rules come from a full penetration test (33 findings, all remediated). Follow them for ALL new code.
|
|
||||||
|
|
||||||
### Backend (Rust)
|
|
||||||
|
|
||||||
- **Backend binds to 127.0.0.1 ONLY** — never `0.0.0.0`. All external access goes through nginx.
|
|
||||||
- **Validate ALL user input before path construction** — reject `..`, `/`, `\`, null bytes. Use the existing `validate_app_id()` pattern in `tor.rs`.
|
|
||||||
- **Never pass user input to `sudo` commands** — if unavoidable, validate strictly against an allowlist of characters `[a-zA-Z0-9_-]`.
|
|
||||||
- **Every HTTP endpoint that returns sensitive data MUST check authentication** — use `self.is_authenticated(&headers).await` or be in `UNAUTHENTICATED_METHODS` with justification.
|
|
||||||
- **Rate-limit authentication endpoints** — `extract_client_ip()` must only trust `X-Real-IP` from the loopback interface (127.0.0.1).
|
|
||||||
- **Federation messages require ed25519 signatures** — never accept unsigned peer-joined messages.
|
|
||||||
- **RBAC: use explicit allowlists, not prefix matching** — `method.starts_with("node.")` is BANNED. List exact methods per role.
|
|
||||||
- **Session cookies: `SameSite=Lax; HttpOnly; Path=/`** — `Strict` breaks iframe app fetches. `Lax` still prevents CSRF on POST.
|
|
||||||
- **Destructive operations require password re-verification** — factory reset, onboarding reset, identity export.
|
|
||||||
- **Remember-me secrets: use `OsRng` random bytes** — never derive from `/etc/machine-id` or other public data.
|
|
||||||
- **Rotate session tokens after privilege escalation** — TOTP verification must issue a new token, invalidating the pending one.
|
|
||||||
- **Tar archive extraction: validate every entry path** — never use `archive.unpack()`. Iterate entries and verify no `..` components or paths escaping the target directory.
|
|
||||||
|
|
||||||
### Frontend (Vue/TypeScript)
|
|
||||||
|
|
||||||
- **Validate redirect URLs** — use `isLocalRedirect()` from `router/index.ts` before any `window.location.href` assignment. Reject `javascript:`, protocol-relative (`//`), and external URLs.
|
|
||||||
- **Never use `v-html` with user input** — if unavoidable, always sanitize with `DOMPurify.sanitize()`.
|
|
||||||
- **CSP: no `unsafe-inline` in `script-src`** — Vite builds don't need it. Keep `unsafe-inline` only in `style-src` for Tailwind.
|
|
||||||
|
|
||||||
### Nginx
|
|
||||||
|
|
||||||
- **Session validation: `$cookie_session` (not `$cookie_session_id`)** — cookie name must match the Rust backend's `session=` cookie.
|
|
||||||
- **Prefer `auth_request` over cookie-presence checks** — `if ($cookie_session = "")` only checks presence, not validity. For sensitive endpoints, use nginx `auth_request` to validate against the backend.
|
|
||||||
- **All `/app/*` proxies are unauthenticated at nginx level** — each app must handle its own auth. Never expose apps with default credentials (change Grafana `admin/admin` on first boot, etc.).
|
|
||||||
|
|
||||||
### SSRF Prevention
|
|
||||||
|
|
||||||
- **Validate all user-supplied URLs** — require `https://` scheme, reject private IP ranges (127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16, ::1, fc00::/7).
|
|
||||||
- **Disable redirect following** — use `redirect(Policy::none())` on reqwest clients that fetch user-supplied URLs.
|
|
||||||
- **Onion addresses: validate v3 format** — exactly 56 base32 `[a-z2-7]` chars + `.onion`.
|
|
||||||
- **Webhook URLs: parse with `Url::parse`** — don't split on `:` for host extraction (breaks IPv6).
|
|
||||||
|
|
||||||
### Container Security
|
|
||||||
|
|
||||||
- **Memory limits on every container** — use `--memory=$(mem_limit <name>)` pattern from `first-boot-containers.sh`. Prevents one container from OOM-killing the system.
|
|
||||||
- **Health checks on every container** — define via `--health-cmd` in `podman run`.
|
|
||||||
- **User-stopped tracking** — when a user stops a container via UI, record in `user-stopped.json` so crash recovery and health monitor don't auto-restart it.
|
|
||||||
|
|
||||||
## 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
|
## App Integration Checklist
|
||||||
|
|
||||||
When adding or fixing apps, **every file below must be checked**. Missing any one causes failures on fresh installs.
|
When adding/fixing apps, check ALL of these:
|
||||||
|
- `core/archipelago/src/api/rpc/package/` — config, capabilities, deps
|
||||||
|
- `neode-ui/src/views/marketplace/marketplaceData.ts` — marketplace entry
|
||||||
|
- `image-recipe/configs/nginx-archipelago.conf` — proxy rules (HTTP + HTTPS)
|
||||||
|
- `scripts/image-versions.sh` — pinned image version
|
||||||
|
- `scripts/first-boot-containers.sh` — first boot creation
|
||||||
|
- `scripts/deploy-to-target.sh` — deploy logic
|
||||||
|
|
||||||
### Backend (Rust)
|
## Git
|
||||||
|
|
||||||
- [ ] `core/archipelago/src/api/rpc/package.rs` — `get_app_config()`: ports, volumes, env vars, custom args
|
Commits: `type: description` (`feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `chore:`, `perf:`)
|
||||||
- [ ] `core/archipelago/src/api/rpc/package.rs` — `needs_archy_net`: add if app needs container DNS
|
Push to: `git push tx1138 main`
|
||||||
- [ ] `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
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user