All checks were successful
Build Archipelago ISO (dev) / build-iso (push) Successful in 12m25s
- Update all references from Debian 12 (Bookworm) to Debian 13 (Trixie) - Enable SystemCallArchitectures, RestrictAddressFamilies, RestrictRealtime in archipelago.service (safe on systemd 256+ which respects NoNewPrivileges=no) - Update GLIBC compatibility checks from 2.36 to 2.40 - ISO filename, build container, and docs updated throughout Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
9.8 KiB
9.8 KiB
Archipelago Developer Guide
Project Structure
archy/
├── core/ # Rust backend
│ └── archipelago/
│ ├── src/
│ │ ├── main.rs # Entry point, module declarations
│ │ ├── api/rpc/ # RPC endpoint handlers
│ │ │ ├── mod.rs # Route dispatcher
│ │ │ ├── auth.rs # Login, session, TOTP
│ │ │ ├── container.rs # Container lifecycle
│ │ │ ├── package.rs # Package install/remove
│ │ │ ├── interfaces.rs # Network interfaces, WiFi, DNS
│ │ │ ├── federation.rs # Federation management
│ │ │ ├── marketplace.rs # Community marketplace
│ │ │ └── ... # Other endpoint groups
│ │ ├── auth.rs # Password hashing, sessions
│ │ ├── config.rs # Configuration loading
│ │ ├── server.rs # HTTP/WS server (axum)
│ │ ├── container/ # Podman integration
│ │ ├── network/ # Network management
│ │ │ ├── dns.rs # DNS configuration
│ │ │ ├── router.rs # UPnP, diagnostics
│ │ │ └── dwn_*.rs # DWN protocol
│ │ ├── federation.rs # Federation protocol
│ │ ├── marketplace.rs # Marketplace discovery
│ │ ├── identity.rs # DID key management
│ │ ├── vpn.rs # VPN (Tailscale/WireGuard)
│ │ ├── mesh.rs # Meshtastic mesh networking
│ │ └── ...
│ ├── Cargo.toml
│ └── tests/ # Integration tests
├── neode-ui/ # Vue 3 frontend
│ ├── src/
│ │ ├── api/ # RPC client, WebSocket, container client
│ │ │ └── rpc-client.ts # Central RPC client (all backend calls)
│ │ ├── views/ # Page components
│ │ │ ├── Home.vue # Dashboard with system stats
│ │ │ ├── Marketplace.vue # App store (curated + community)
│ │ │ ├── Server.vue # Network, VPN, DNS management
│ │ │ ├── Federation.vue # Federation dashboard
│ │ │ ├── Settings.vue # User settings
│ │ │ ├── Web5.vue # DID, DWN, Nostr
│ │ │ └── ...
│ │ ├── stores/ # Pinia state management
│ │ ├── components/ # Reusable UI components
│ │ ├── composables/ # Vue composables
│ │ ├── router/ # Vue Router with guards
│ │ ├── types/ # TypeScript type definitions
│ │ └── style.css # Global styles + Tailwind utilities
│ ├── vite.config.ts
│ └── package.json
├── scripts/ # Deployment and utility scripts
│ ├── deploy-to-target.sh # Main deploy script
│ ├── first-boot-containers.sh # ISO first-boot setup
│ └── run-tests.sh # CI test runner
├── image-recipe/ # ISO build configuration
│ ├── build-auto-installer-iso.sh
│ └── configs/ # Nginx, systemd configs
├── docs/ # Documentation
│ ├── architecture.md
│ ├── app-manifest-spec.md
│ ├── marketplace-protocol.md
│ └── multi-node-architecture.md
├── apps/ # App manifests (YAML)
├── CLAUDE.md # AI development instructions
└── loop/plan.md # Project roadmap
Development Setup
Prerequisites
- macOS (development machine): Node.js 20+, npm
- Linux server (
192.168.1.228): Rust toolchain, Podman, Nginx, Debian 13 - SSH key:
~/.ssh/archipelago-deploy
Local Frontend Development
cd neode-ui
npm install
npm start # Vite dev server on :8100, mock backend on :5959
The dev server at http://localhost:8100 uses a mock backend. Login with password123.
Deploying Changes
Never build Rust on macOS. The deploy script rsyncs source to the Linux server and builds there.
# Deploy to live server (builds backend + frontend, restarts services)
./scripts/deploy-to-target.sh --live
# Deploy to both servers
./scripts/deploy-to-target.sh --both
The deploy script:
- Rsyncs source to the server
- Builds Rust backend on the server (
cargo build --release) - Builds Vue frontend (
npm run build) - Copies artifacts to production paths
- Restarts the
archipelagosystemd service - Runs a health check
Running Tests
# Frontend tests
cd neode-ui && npm test
# Backend tests (on dev server via SSH)
ssh -i ~/.ssh/archipelago-deploy archipelago@192.168.1.228 \
"cd ~/archy/core && cargo test --all-features"
# Both
./scripts/run-tests.sh
Adding a New RPC Endpoint
1. Create the Handler
Add a handler method in the appropriate file under core/archipelago/src/api/rpc/. If no existing file fits, create a new one.
// core/archipelago/src/api/rpc/mymodule.rs
use super::RpcHandler;
use anyhow::Result;
impl RpcHandler {
/// mymodule.action — description of what it does.
pub(super) async fn handle_mymodule_action(
&self,
params: Option<serde_json::Value>,
) -> Result<serde_json::Value> {
let params = params.ok_or_else(|| anyhow::anyhow!("Missing params"))?;
let name = params
.get("name")
.and_then(|v| v.as_str())
.ok_or_else(|| anyhow::anyhow!("Missing required parameter: name"))?;
// Your logic here
let result = do_something(name).await?;
Ok(serde_json::json!({ "ok": true, "result": result }))
}
}
Key patterns:
- Handlers are
pub(super)— visible only to the RPC router - Accept
Option<serde_json::Value>for params (omit for parameterless endpoints) - Return
Result<serde_json::Value> - Use
self.config.data_dirfor data persistence - Use
anyhow::bail!()for error responses
2. Register the Route
Add the module declaration and route in core/archipelago/src/api/rpc/mod.rs:
// At the top:
mod mymodule;
// In the match statement (handle_rpc_call):
"mymodule.action" => self.handle_mymodule_action(params).await,
3. Add Module (if new)
If your logic warrants a separate module:
// core/archipelago/src/main.rs
mod mymodule; // Add to module declarations
4. Frontend Client
Add a convenience method to neode-ui/src/api/rpc-client.ts:
async myAction(params: { name: string }): Promise<{ ok: boolean; result: string }> {
return this.call({
method: 'mymodule.action',
params,
})
}
5. Deploy and Test
./scripts/deploy-to-target.sh --live
curl -X POST http://192.168.1.228/rpc/v1 \
-H "Content-Type: application/json" \
-b "archipelago_session=YOUR_SESSION" \
-d '{"method":"mymodule.action","params":{"name":"test"}}'
Adding a New Vue Page
1. Create the Component
<!-- neode-ui/src/views/MyPage.vue -->
<template>
<div>
<h1 class="text-4xl font-bold text-white mb-2">My Page</h1>
<div class="glass-card p-6">
<!-- Content here -->
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { rpcClient } from '@/api/rpc-client'
// State and logic
</script>
2. Add the Route
In neode-ui/src/router/index.ts, add inside the dashboard children:
{
path: 'my-page',
name: 'my-page',
component: () => import('@/views/MyPage.vue'),
},
3. Standards
- Always use
<script setup lang="ts">— never Options API - Use
glass-cardfor containers,bg-white/5 rounded-lgfor sub-rows - Create global CSS classes in
src/style.cssinstead of inline Tailwind - Use
rpcClientfrom@/api/rpc-client.tsfor all backend calls - Handle loading states and errors for all async operations
Writing Tests
Frontend (Vitest)
// neode-ui/src/api/__tests__/my-test.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest'
describe('MyFeature', () => {
beforeEach(() => {
vi.restoreAllMocks()
})
it('should do something', async () => {
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ result: 'ok' }),
}))
// Test your logic
expect(true).toBe(true)
})
})
Backend (Rust)
#[cfg(test)]
mod tests {
use super::*;
use tempfile::tempdir;
#[tokio::test]
async fn test_my_function() {
let dir = tempdir().unwrap();
let result = my_function(dir.path()).await.unwrap();
assert_eq!(result, expected);
}
}
Code Quality Checklist
- TypeScript strict mode: no
any, useunknownor proper types - No
unwrap()orexpect()in production Rust code — use? - No
console.log— wrap inif (import.meta.env.DEV) - No empty catch blocks — log or handle errors
- Functions under 50 lines
cargo clippyandcargo fmtpassnpx vue-tsc --noEmitpasses- Security: validate all inputs, no command injection
- Container security: readonly_root, no_new_privileges, non-root user
Contributing
- Create a feature branch:
git checkout -b feature/my-feature - Make changes following the standards above
- Test locally:
cd neode-ui && npm test - Deploy to dev server:
./scripts/deploy-to-target.sh --live - Verify at
http://192.168.1.228 - Commit with conventional format:
feat: add my feature