update docs

This commit is contained in:
Harivansh Rathi 2026-04-01 16:43:25 +00:00
parent c5be520772
commit 5bc24fa99d
11 changed files with 591 additions and 632 deletions

110
README.md
View file

@ -1,41 +1,83 @@
# betterNAS # betterNAS
- control-plane owns policy and identity (decides) betterNAS is a self-hostable WebDAV stack for mounting NAS exports in Finder.
- node-agent owns file serving (serves)
- web owns UX (consumer facing)
- nextcloud-app is optional adapter only for cloud storage in s3 n shit
## Monorepo The default product shape is:
- `apps/web`: Next.js control-plane UI - `node-service` serves the real files from the NAS over WebDAV
- `apps/control-plane`: Go control-plane service - `control-server` owns auth, nodes, exports, grants, and mount profile issuance
- `apps/node-agent`: Go NAS runtime / WebDAV node - `web control plane` lets the user manage the NAS and get mount instructions
- `apps/nextcloud-app`: optional Nextcloud adapter - `macOS client` starts as native Finder WebDAV mounting, with a thin helper later
- `packages/contracts`: canonical shared contracts
- `packages/ui`: shared React UI
- `infra/docker`: local Docker runtime
The root planning and delegation guide lives in [skeleton.md](./skeleton.md). For now, the whole stack should be able to run on the user's NAS device.
## Current repo shape
- `apps/node-agent`
- NAS-side Go runtime and WebDAV server
- `apps/control-plane`
- Go backend for auth, registry, and mount profile issuance
- `apps/web`
- Next.js web control plane
- `apps/nextcloud-app`
- optional Nextcloud adapter, not the product center
- `packages/contracts`
- canonical shared contracts
- `infra/docker`
- self-hosted local stack
The main planning docs are:
- [docs/architecture.md](./docs/architecture.md)
- [skeleton.md](./skeleton.md)
- [docs/05-build-plan.md](./docs/05-build-plan.md)
## Default runtime model
```text
self-hosted betterNAS on the user's NAS
+------------------------------+
| web control plane |
| Next.js UI |
+--------------+---------------+
|
v
+------------------------------+
| control-server |
| auth / nodes / exports |
| grants / mount profiles |
+--------------+---------------+
|
v
+------------------------------+
| node-service |
| WebDAV + export runtime |
| real NAS bytes |
+------------------------------+
user Mac
|
+--> browser -> web control plane
|
+--> Finder -> WebDAV mount URL from control-server
```
## Verify ## Verify
Run the repo acceptance loop with: Static verification:
```bash ```bash
pnpm verify pnpm verify
``` ```
## Runtime loop Bootstrap clone-local runtime settings:
Bootstrap clone-local runtime settings with:
```bash ```bash
pnpm agent:bootstrap pnpm agent:bootstrap
``` ```
If `.env.agent` is missing, bootstrap writes clone-local defaults for this checkout. Bring the self-hosted stack up, verify it, and tear it down:
Bring the stack up, verify it, and tear it down with:
```bash ```bash
pnpm stack:up pnpm stack:up
@ -43,16 +85,30 @@ pnpm stack:verify
pnpm stack:down --volumes pnpm stack:down --volumes
``` ```
## Agent loop Run the full loop:
Run the full static and integration loop with:
```bash ```bash
pnpm agent:verify pnpm agent:verify
``` ```
Create or refresh the sibling agent clones with: ## Current end-to-end slice
```bash The first proven slice is:
pnpm clones:setup
``` 1. boot the stack with `pnpm stack:up`
2. verify it with `pnpm stack:verify`
3. get the WebDAV mount URL
4. mount it in Finder
If the stack is running on a remote machine, tunnel the WebDAV port first, then
use Finder `Connect to Server` with the tunneled URL.
## Product boundary
The default betterNAS product is self-hosted and WebDAV-first.
Nextcloud remains optional and secondary:
- useful later for browser/mobile/share surfaces
- not required for the core mount flow
- not the system of record

10
TODO.md
View file

@ -5,7 +5,11 @@
- [x] Add root formatting, verification, and Go formatting rails. - [x] Add root formatting, verification, and Go formatting rails.
- [x] Add hard boundary checks so apps and packages cannot drift across lanes with private imports. - [x] Add hard boundary checks so apps and packages cannot drift across lanes with private imports.
- [x] Make the first contract-backed mount loop real: node registration, export inventory, mount profile issuance, and a Finder-mountable WebDAV export. - [x] Make the first contract-backed mount loop real: node registration, export inventory, mount profile issuance, and a Finder-mountable WebDAV export.
- [ ] Add a manual E2E runbook for remote-host WebDAV testing from a Mac over SSH tunnel. - [x] Prove the first manual remote-host WebDAV mount from a Mac over SSH tunnel.
- [ ] Surface exports and issued mount URLs in the web control plane. - [ ] Surface exports and issued mount URLs in the web control plane.
- [ ] Define the Nix/module shape for installing the node agent and export runtime on a NAS host. - [ ] Add durable control-server storage for nodes, exports, grants, and mount profiles.
- [ ] Decide whether the node agent should self-register or stay control-plane registered by bootstrap tooling. - [ ] Define the self-hosted deployment shape for the full stack on a NAS device.
- [ ] Define the Nix/module shape for installing the node-service on a NAS host.
- [ ] Decide whether the node-service should self-register or stay bootstrap-registered.
- [ ] Decide whether browser file viewing belongs in V1 web control plane or later.
- [ ] Define if and when the optional Nextcloud adapter comes back into scope.

View file

@ -1,51 +1,51 @@
# Control # Control
This clone is the main repo. This repo is the coordination and implementation ground for betterNAS.
Use it for: Use it for:
- shared contracts - shared contracts
- repo guardrails - architecture and planning docs
- runtime scripts - runtime scripts
- integration verification - stack verification
- architecture and coordination - implementation of the self-hosted stack
Planned clone layout: ## Current product focus
```text The default betterNAS product is:
/home/rathi/Documents/GitHub/betterNAS/
betterNAS
betterNAS-runtime
betterNAS-control
betterNAS-node
```
Clone roles: - self-hosted on the user's NAS
- WebDAV-first
- Finder-mountable
- managed through a web control plane
- `betterNAS` The main parts are:
- main coordination repo
- owns contracts, scripts, and shared verification rules
- `betterNAS-runtime`
- owns Docker Compose, stack env, readiness checks, and end-to-end runtime verification
- `betterNAS-control`
- owns the Go control plane and contract-backed API behavior
- `betterNAS-node`
- owns the node agent, WebDAV serving, and NAS-side registration/export behavior
Rules: - `node-service`
- `apps/node-agent`
- `control-server`
- `apps/control-plane`
- `web control plane`
- `apps/web`
- `optional cloud adapter`
- `apps/nextcloud-app`
## Rules
- shared interface changes land in `packages/contracts` first - shared interface changes land in `packages/contracts` first
- runtime verification must stay green in the main repo - `docs/architecture.md` is the canonical architecture contract
- feature agents should stay inside their assigned clone unless a contract change is required - the self-hosted mount flow is the critical path
- optional Nextcloud work must not drive the main architecture
Agent command surface: ## Command surface
- main repo creates or refreshes sibling clones with `pnpm clones:setup` - `pnpm verify`
- each clone bootstraps itself with `pnpm agent:bootstrap` - static verification
- each clone runs the full loop with `pnpm agent:verify` - `pnpm stack:up`
- boot the self-hosted stack
Agent prompts live in: - `pnpm stack:verify`
- verify the working stack
- `docs/agents/runtime-agent.md` - `pnpm stack:down --volumes`
- `docs/agents/control-plane-agent.md` - tear the stack down cleanly
- `docs/agents/node-agent.md` - `pnpm agent:verify`
- bootstrap, verify, boot, and stack-verify in one loop

View file

@ -1,6 +1,7 @@
# betterNAS Part 1: NAS Node # betterNAS Part 1: NAS Node
This document describes the software that runs on the actual NAS machine, VM, or workstation that owns the files. This document describes the software that runs on the actual NAS machine, VM,
or workstation that owns the files.
## What it is ## What it is
@ -8,10 +9,11 @@ The NAS node is the machine that actually has the storage.
It should run: It should run:
- a WebDAV server - the `node-service`
- a small betterNAS node agent - a WebDAV server surface
- declarative config via Nix - export configuration
- optional tunnel or relay connection if the machine is not directly reachable - optional enrollment or heartbeat back to `control-server`
- later, a reproducible install path such as Docker or Nix
It should expose one or more storage exports such as: It should expose one or more storage exports such as:
@ -24,47 +26,38 @@ It should expose one or more storage exports such as:
- serves the real file bytes - serves the real file bytes
- exposes chosen directories over WebDAV - exposes chosen directories over WebDAV
- registers itself with the control plane - reports identity, health, and exports to `control-server`
- reports health, identity, and available exports - stays simple enough to self-host on a single NAS box
- optionally keeps an outbound connection alive for remote access
## What it should not do ## What it should not do
- own user-facing product logic - own product policy
- decide permissions by itself - decide user access rules by itself
- become the system of record for shares, devices, or policies - become the system of record for users, grants, or shares
## Diagram ## Diagram
```text ```text
betterNAS system self-hosted betterNAS stack
local device <-------> control plane <-------> cloud/web layer web control plane ---> control-server ---> [THIS DOC] node-service
| | | ^ |
| | | | |
+-------------------------+--------------------------+ +---------------- user browser ----------+
|
v local Mac ---------------- Finder mount ----------+
+---------------------------+
| [THIS DOC] NAS node |
|---------------------------|
| WebDAV server |
| node agent |
| exported directories |
| optional tunnel/relay |
+---------------------------+
``` ```
## Core decisions ## Core decisions
- The NAS node should be where WebDAV is served from whenever possible. - The NAS node should be where WebDAV is served from whenever possible.
- The control plane should configure access, but file bytes should flow from the node to the user device as directly as possible. - The node should be installable as one boring runtime on the user's machine.
- The node should be installable with a Nix module or flake so setup is reproducible. - The node should expose exports, not product semantics.
## TODO ## TODO
- Choose the WebDAV server we will standardize on for the node. - Define the self-hosted install shape: Docker first, Nix second, or both.
- Define the node agent responsibilities and API back to the control plane. - Define the node identity and enrollment model.
- Define the storage export model: path, label, capacity, tags, protocol support. - Define the storage export model: path, label, tags, permissions, capacity.
- Define direct-access vs relayed-access behavior. - Define when the node self-registers vs when bootstrap tooling registers it.
- Define how the node connects to the cloud/web layer for optional Nextcloud integration. - Define direct-access vs relay-access behavior for remote use.

View file

@ -1,10 +1,11 @@
# betterNAS Part 2: Control Plane # betterNAS Part 2: Control Server
This document describes the main backend that owns product semantics and coordinates the rest of the system. This document describes the main backend that owns product semantics and
coordinates the rest of the system.
## What it is ## What it is
The control plane is the source of truth for betterNAS. `control-server` is the source of truth for betterNAS.
It should own: It should own:
@ -14,8 +15,7 @@ It should own:
- storage exports - storage exports
- access grants - access grants
- mount profiles - mount profiles
- cloud access profiles - later, share flows and audit events
- audit events
## What it does ## What it does
@ -23,35 +23,32 @@ It should own:
- tracks which NAS nodes exist - tracks which NAS nodes exist
- decides who can access which export - decides who can access which export
- issues mount instructions to local devices - issues mount instructions to local devices
- coordinates optional cloud/web access - drives the web control plane
- stores the operational model of the whole product - stores the operational model of the product
## What it should not do ## What it should not do
- proxy file bytes unless absolutely necessary - proxy file bytes by default
- become a bottleneck in the data path - become the only data path between the Mac and the NAS
- depend on Nextcloud as its system of record - depend on Nextcloud as its source of truth
## Diagram ## Diagram
```text ```text
betterNAS system self-hosted betterNAS stack
NAS node <---------> [THIS DOC] control plane <---------> local device node-service <--------> [THIS DOC] control-server <--------> web control plane
| | | ^ |
| | | | |
+---------------------------+-----------------------+-----------+ +----------- Finder mount flow -+
|
v
cloud/web layer
``` ```
## Core decisions ## Core decisions
- The control plane is the product brain. - `control-server` is the product brain.
- It should own policy and registry, not storage bytes. - It owns policy and registry, not storage bytes.
- It should stay standalone even if it integrates with Nextcloud. - It should stay deployable on the user's NAS in the default product shape.
- It should issue access decisions, not act like a file server. - The web UI should remain a consumer of this service, not a second backend.
## Suggested first entities ## Suggested first entities
@ -61,13 +58,12 @@ It should own:
- `StorageExport` - `StorageExport`
- `AccessGrant` - `AccessGrant`
- `MountProfile` - `MountProfile`
- `CloudProfile`
- `AuditEvent` - `AuditEvent`
## TODO ## TODO
- Define the first real domain model and database schema. - Define the first durable database schema.
- Define auth between user device, NAS node, and control plane. - Define auth between user browser, user device, NAS node, and control-server.
- Define the API for mount profiles and access grants. - Define the API for node registration, export inventory, and mount issuance.
- Define how the control plane tells the cloud/web layer what to expose. - Define how mount tokens or credentials are issued and rotated.
- Define direct-access vs relay behavior for unreachable NAS nodes. - Define what optional cloud/share integration looks like later.

View file

@ -1,19 +1,21 @@
# betterNAS Part 3: Local Device # betterNAS Part 3: Local Device
This document describes the software and user experience on the user's Mac or other local device. This document describes the software and user experience on the user's Mac or
other local device.
## What it is ## What it is
The local device layer is how a user actually mounts and uses their NAS. The local device layer is how a user actually mounts and uses their NAS.
It can start simple: It should start simple:
- Finder + WebDAV mount - browser opens the web control plane
- manual `Connect to Server` - user gets a WebDAV mount URL
- Finder mounts the export
It can later grow into: It can later grow into:
- a small desktop helper - a small helper app
- one-click mount flows - one-click mount flows
- auto-mount at login - auto-mount at login
- status and reconnect behavior - status and reconnect behavior
@ -21,52 +23,50 @@ It can later grow into:
## What it does ## What it does
- authenticates the user to betterNAS - authenticates the user to betterNAS
- fetches allowed mount profiles from the control plane - fetches allowed mount profiles from `control-server`
- mounts approved storage exports locally - mounts approved storage exports locally
- gives the user a native-feeling way to browse files - gives the user a native-feeling way to browse files
## What it should not do ## What it should not do
- invent its own permissions model - invent its own permissions model
- hardcode NAS endpoints outside the control plane - hardcode node endpoints outside the control-server
- become tightly coupled to Nextcloud - depend on the optional cloud adapter for the core mount flow
## Diagram ## Diagram
```text ```text
betterNAS system self-hosted betterNAS stack
NAS node <---------> control plane <---------> [THIS DOC] local device node-service <--------> control-server <--------> web control plane
| | | ^ ^
| | | | |
+---------------------------+-----------------------+-----------+ +------------- [THIS DOC] local device ---------+
| browser + Finder
v
cloud/web layer
``` ```
## Core decisions ## Core decisions
- V1 can rely on native Finder WebDAV mounting. - V1 relies on native Finder WebDAV mounting.
- A lightweight helper app is likely enough before a full custom client. - The web UI should be enough to get the user to a mountable URL.
- The local device should consume mount profiles, not raw infrastructure details. - A lightweight helper app is likely enough before a full native client.
## User modes ## User modes
### Mount mode ### Mount mode
- user mounts a NAS export into Finder - user mounts a NAS export in Finder
- files are browsed as a mounted remote disk - files are browsed as a mounted remote disk
### Cloud mode ### Browser mode
- user accesses the same storage through browser/mobile/cloud surfaces - user manages the NAS and exports in the web control plane
- this is not the same as a mounted filesystem - optional later: browse files in the browser
## TODO ## TODO
- Define the mount profile format the control plane returns. - Define the mount profile format returned by `control-server`.
- Decide what the first local UX is: manual Finder flow, helper app, or both. - Decide whether the first UX is manual Finder flow, helper app, or both.
- Define credential storage and Keychain behavior. - Define credential handling and Keychain behavior.
- Define auto-mount, reconnect, and offline expectations. - Define reconnect and auto-mount expectations.
- Define how the local device hands off to the cloud/web layer when mount mode is not enough. - Define what later native client work is actually worth doing.

View file

@ -1,69 +1,71 @@
# betterNAS Part 4: Cloud / Web Layer # betterNAS Part 4: Web Control Plane and Optional Cloud Layer
This document describes the optional browser, mobile, and cloud-drive style access layer. This document describes the browser UI that users interact with, plus the
optional cloud adapter layer that may exist later.
## What it is ## What it is
The cloud/web layer is the part of betterNAS that makes storage accessible beyond local mounts. The web control plane is part of the core product.
This is where we can reuse Nextcloud heavily for: It should provide:
- browser file UI - onboarding
- uploads and downloads - node and export management
- sharing links - mount instructions
- WebDAV-based cloud access - sharing and browser file access later
- mobile reference behavior
An optional cloud adapter may later provide:
- Nextcloud-backed browser file UI
- mobile-friendly access
- share and link workflows
## What it does ## What it does
- gives users a browser-based file experience - gives users a browser-based entry point into betterNAS
- supports sharing and link-based access - talks only to `control-server`
- gives us a cloud mode in addition to mount mode - exposes the mount flow cleanly
- can act as a reference surface while the main betterNAS product grows - optionally layers on cloud/mobile/share behavior later
## What it should not do ## What it should not do
- own the product system of record - own product state separately from `control-server`
- become the only way users access storage - become the only way users access their storage
- swallow control-plane logic that should stay in betterNAS - make the optional cloud adapter part of the core mount path
## Diagram ## Diagram
```text ```text
betterNAS system self-hosted betterNAS stack
NAS node <---------> control plane <---------> local device node-service <--------> control-server <--------> [THIS DOC] web control plane
| | | ^ |
| | | | |
+---------------------------+-----------------------+-----------+ +----------- Finder mount flow -+
|
v optional later:
+----------------------+ Nextcloud adapter / cloud/mobile/share surface
| [THIS DOC] cloud/web |
|----------------------|
| Nextcloud adapter |
| browser UI |
| sharing / mobile |
+----------------------+
``` ```
## Core decisions ## Core decisions
- The cloud/web layer is optional but very high leverage. - The web control plane is part of the core product now.
- Nextcloud is a strong fit here because it already gives us file UI and sharing primitives. - Nextcloud is optional and secondary.
- It should sit beside mount mode, not replace it. - The first user value is managing exports and getting a mount URL, not a full
browser file manager.
## Likely role of Nextcloud ## Likely near-term role of the web control plane
- browser-based file UI - sign in
- share and link management - see available NAS nodes
- optional mobile and cloud-drive style access - see available exports
- adapter over the same storage exports the control plane knows about - request mount instructions
- copy or launch the WebDAV mount flow
## TODO ## TODO
- Decide whether Nextcloud is directly user-facing in v1 or mostly an adapter behind betterNAS. - Define the first user-facing screens for nodes, exports, and mount actions.
- Define how storage exports from the NAS node appear in the cloud/web layer. - Define how auth/session works in the web UI.
- Define how shares in this layer map back to control-plane access grants. - Decide whether browser file viewing is part of V1 or follows later.
- Define what mobile access looks like in v1. - Decide whether Nextcloud remains an internal adapter or becomes user-facing.
- Define branding and how much of the cloud/web layer stays stock vs customized. - Define what sharing means before adding any cloud/mobile layer.

View file

@ -12,240 +12,151 @@ It answers four questions:
## The full system ## The full system
```text ```text
betterNAS build plan self-hosted betterNAS
[2] control plane [3] web control plane
+--------------------------------+ +--------------------------------+
| API + policy + registry + UI | | onboarding / management / UX |
+--------+---------------+-------+ +---------------+----------------+
| | |
control/API | | cloud adapter v
v v [2] control-server
[1] NAS node [4] cloud/web layer +--------------------------------+
+------------------+ +-------------------+ | auth / nodes / exports |
| WebDAV + agent | | Nextcloud adapter | | grants / mount profiles |
| real storage | | browser/mobile | +---------------+----------------+
+---------+--------+ +---------+---------+ |
| ^ v
| mount profile | [1] node-service
v | +--------------------------------+
[3] local device ---------------+ | WebDAV + export runtime |
+----------------------+ | real storage |
| Finder mount/helper | +---------------+----------------+
| native user entry | ^
+----------------------+ |
[4] local device
+--------------------------------+
| browser + Finder mount |
+--------------------------------+
optional later:
- Nextcloud adapter
- hosted control plane
- hosted web UI
``` ```
## The core rule ## The core rule
The control plane owns product semantics. `control-server` owns product semantics.
The other three parts are execution surfaces: The other three parts are execution surfaces:
- the NAS node serves storage - `node-service` serves storage
- the local device mounts and uses storage - `web control plane` exposes management and mount UX
- the cloud/web layer exposes storage through browser and mobile-friendly flows - `local device` consumes the issued mount flow
## What we steal vs write ## What we steal vs write
| Part | Steal first | Write ourselves | | Part | Steal first | Write ourselves |
| --------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | | ----------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------- |
| NAS node | NixOS/Nix module patterns, existing WebDAV servers | node agent, export model, node registration flow | | node-service | Go WebDAV primitives, Docker packaging, later Nix module patterns | node runtime, export model, node enrollment |
| Control plane | Go stdlib routing, pgx/sqlc, go-redis/asynq, OpenAPI codegen | product domain model, policy engine, mount/cloud APIs, registry | | control-server | Go stdlib routing, pgx/sqlc, Redis helpers, OpenAPI codegen | product domain model, policy engine, mount APIs, registry |
| Local device | Finder WebDAV mount, macOS Keychain, later maybe launch agent patterns | helper app, mount profile handling, auto-mount UX | | web control plane | Next.js app conventions, shared UI primitives | product UI, onboarding, node/export flows, mount UX |
| Cloud/web layer | Nextcloud server, Nextcloud shell app, Nextcloud share/file UI, Nextcloud mobile references | betterNAS integration layer, mapping between product model and Nextcloud, later branded UI | | local device | Finder WebDAV mount flow, macOS Keychain later | helper app or mount launcher later |
| optional adapter | Nextcloud server and app template | betterNAS mapping layer if we decide to keep a cloud/mobile surface |
## Where each part should start ## Where each part should start
## 1. NAS node ## 1. node-service
Start from: Start from:
- Nix flake / module - one Go binary
- a standard WebDAV server - one export root
- a very small agent process - one WebDAV surface
- one deployable self-hosted runtime
Do not start by writing: Do not start by writing:
- custom storage protocol - a custom storage protocol
- custom file server - a custom sync engine
- custom sync engine - a complex relay stack
The NAS node should be boring and reproducible. ## 2. control-server
## 2. Control plane
Start from: Start from:
- Go - Go
- standard library routing first - one API
- Postgres via `pgx` and `sqlc` - one durable data model
- Redis via `go-redis` - node registration and mount profile issuance
- OpenAPI-driven contracts
- standalone API mindset
Do not start by writing: Do not start by writing:
- microservices - microservices
- custom file transport - file proxying by default
- a proxy that sits in the middle of every file transfer - hosted-only assumptions
This is the first real thing we should build. ## 3. web control plane
## 3. Local device
Start from: Start from:
- native Finder `Connect to Server` - sign in
- WebDAV mount URLs issued by the control plane - list nodes and exports
- show mount URL and mount instructions
Do not start by writing:
- a large browser file manager
- a second backend hidden inside Next.js
## 4. local device
Start from:
- Finder `Connect to Server`
- WebDAV mount URL issued by `control-server`
Then later add: Then later add:
- a lightweight helper app - one-click helper
- Keychain integration - Keychain integration
- auto-mount at login - auto-mount at login
Do not start by writing:
- a full custom desktop sync client
- a Finder extension
- a new filesystem driver
## 4. Cloud / web layer
Start from:
- stock Nextcloud
- current shell app
- Nextcloud browser/share/mobile primitives
Then later add:
- betterNAS-specific integration pages
- standalone control-plane web UI
- custom branding or replacement UI where justified
Do not start by writing:
- a full custom browser file manager
- a custom mobile client
- a custom sharing stack
## Recommended build order ## Recommended build order
### Phase A: make the storage path real ### Phase A: make the self-hosted mount path real
1. NAS node can expose a directory over WebDAV 1. node-service exposes a directory over WebDAV
2. control plane can register the node and its exports 2. control-server registers the node and its exports
3. local device can mount that export in Finder 3. web control plane shows the export and mount action
4. local device mounts the export in Finder
This is the shortest path to a real product loop. ### Phase B: make the product real
### Phase B: make the product model real 1. add durable users, nodes, exports, grants, mount profiles
2. add auth and token lifecycle
3. add a proper web UI for admin and user control flows
1. add users, devices, NAS nodes, exports, grants, mount profiles ### Phase C: make deployment real
2. add auth and policy
3. add a simple standalone web UI for admin/control use
This is where betterNAS becomes its own product. 1. define Docker self-hosting shape
2. define Nix-based NAS host install shape
3. define remote access story for non-local usage
### Phase C: add cloud mode ### Phase D: add optional adapter surfaces
1. connect the same storage into Nextcloud 1. add Nextcloud only if browser/share/mobile value justifies it
2. expose browser/mobile/share flows 2. keep it out of the critical mount path
3. map Nextcloud behavior back to betterNAS product semantics
This is high leverage, but should not block Phase A.
## External parts we should deliberately reuse
### NAS node
- WebDAV server implementation
- Nix module patterns
### Control plane
- Go API service scaffold
- Postgres
- Redis
### Local device
- Finder's native WebDAV mounting
- macOS credential storage
### Cloud/web layer
- Nextcloud server
- Nextcloud app shell
- Nextcloud share/browser behavior
- Nextcloud mobile and desktop references
## From-scratch parts we should deliberately own
### NAS node
- node enrollment
- export registration
- machine identity and health reporting
### Control plane
- full backend domain model
- access and policy model
- mount profile generation
- cloud profile generation
- audit and registry
### Local device
- user-friendly mounting workflow
- helper app if needed
- local mount orchestration
### Cloud/web layer
- betterNAS-to-Nextcloud mapping layer
- standalone betterNAS product UI over time
## First scaffolds to use
| Part | First scaffold |
| --------------- | ------------------------------------------------------------- |
| NAS node | Nix flake/module + WebDAV server service config |
| Control plane | Go service + OpenAPI contract + Postgres/Redis adapters later |
| Local device | documented Finder mount flow, then lightweight helper app |
| Cloud/web layer | current Nextcloud scaffold and shell app |
## What not to overbuild early
- custom sync engine
- custom desktop client
- custom mobile app
- many backend services
- control-plane-in-the-data-path file proxy
Those can come later if the simpler stack proves insufficient.
## Build goal for V1 ## Build goal for V1
V1 should prove one clean loop: V1 should prove one clean loop:
```text ```text
user picks NAS export in betterNAS UI user opens betterNAS web UI
-> control plane issues mount profile -> sees a registered export
-> local device mounts WebDAV export -> requests mount instructions
-> user sees and uses files in Finder -> Finder mounts the WebDAV export
-> optional Nextcloud surface exposes the same storage in cloud mode -> user sees and uses files from the NAS
``` ```
If that loop works, the architecture is sound.
## TODO
- Choose the exact WebDAV server for the NAS node.
- Decide the first Nix module layout for node installation.
- Define the first database-backed control-plane entities.
- Decide whether the local device starts as documentation-only or a helper app.
- Decide when the Nextcloud cloud/web layer becomes user-facing in v1.

View file

@ -3,61 +3,76 @@
This file is the canonical contract for the repository. This file is the canonical contract for the repository.
If the planning docs, scaffold code, or future tasks disagree, this file and If the planning docs, scaffold code, or future tasks disagree, this file and
[`packages/contracts`](../packages/contracts) [`packages/contracts`](../packages/contracts) win.
win.
## The single first task ## Product default
Before splitting work across agents, do one foundation task: betterNAS is self-hosted first.
- scaffold the four product parts For the current product shape, the user should be able to run the whole stack on
- lock the shared contracts their NAS machine:
- define one end-to-end verification loop
- enforce clear ownership boundaries
That first task should leave the repo in a state where later work can be - `node-service` serves the real files over WebDAV
parallelized without interface drift. - `control-server` owns auth, nodes, exports, grants, and mount profiles
- `web control plane` is the browser UI over the control-server
- the local device mounts an issued WebDAV URL in Finder
## The four parts Optional hosted deployments can come later. Optional Nextcloud integration can
come later.
## The core system
```text ```text
betterNAS canonical contract betterNAS canonical contract
[2] control plane self-hosted on user's NAS
+-----------------------------------+
| system of record | +--------------------------------------+
| users / devices / nodes / grants | | [2] control-server |
| mount profiles / cloud profiles | | system of record |
+---------+---------------+---------+ | auth / nodes / exports / grants |
| | | mount sessions / audit |
control/API | | cloud adapter +------------------+-------------------+
v v |
[1] NAS node [4] cloud / web layer v
+-------------------+ +----------------------+ +--------------------------------------+
| WebDAV + node | | Nextcloud adapter | | [1] node-service |
| real file bytes | | browser / mobile | | WebDAV export runtime |
+---------+---------+ +----------+-----------+ | real file bytes |
| ^ +------------------+-------------------+
| mount profile | ^
v | |
[3] local device --------------+ +------------------+-------------------+
+----------------------+ | [3] web control plane |
| Finder mount/helper | | onboarding / management / mount UX |
| native user entry | +------------------+-------------------+
+----------------------+ ^
|
user browser
user local device
|
+-----------------------------------------------> Finder mount
via issued WebDAV URL
[4] optional cloud adapter
|
+--> secondary browser/mobile/share layer
not part of the core mount path
``` ```
## Non-negotiable rules ## Non-negotiable rules
1. The control plane is the system of record. 1. `control-server` is the system of record.
2. File bytes should flow as directly as possible between the NAS node and the 2. `node-service` serves the bytes.
local device. 3. `web control plane` is a UI over `control-server`, not a second policy
3. The control plane should issue policy, grants, and profiles. It should not backend.
become the default file proxy. 4. The main data path should be `local device <-> node-service` whenever
4. The NAS node should serve WebDAV directly whenever possible. possible.
5. The local device consumes mount profiles. It does not hardcode infra details. 5. `control-server` should issue access, grants, and mount profiles. It should
6. The cloud/web layer is optional and secondary. Nextcloud is an adapter, not not become the default file proxy.
the product center. 6. The self-hosted stack should work without Nextcloud.
7. Nextcloud, if used, is an optional adapter and secondary surface.
## Canonical sources of truth ## Canonical sources of truth
@ -67,7 +82,7 @@ Use these in this order:
for boundaries, ownership, and delivery rules for boundaries, ownership, and delivery rules
2. [`packages/contracts`](../packages/contracts) 2. [`packages/contracts`](../packages/contracts)
for machine-readable types, schemas, and route constants for machine-readable types, schemas, and route constants
3. the part docs for local detail: 3. the part docs:
- [`docs/01-nas-node.md`](./01-nas-node.md) - [`docs/01-nas-node.md`](./01-nas-node.md)
- [`docs/02-control-plane.md`](./02-control-plane.md) - [`docs/02-control-plane.md`](./02-control-plane.md)
- [`docs/03-local-device.md`](./03-local-device.md) - [`docs/03-local-device.md`](./03-local-device.md)
@ -84,31 +99,33 @@ The monorepo is split into these primary implementation lanes:
- [`apps/nextcloud-app`](../apps/nextcloud-app) - [`apps/nextcloud-app`](../apps/nextcloud-app)
- [`packages/contracts`](../packages/contracts) - [`packages/contracts`](../packages/contracts)
Every parallel task should primarily stay inside one of those lanes unless it is The first three are core. `apps/nextcloud-app` is optional and should not drive
an explicit contract task. the main architecture.
## The contract surface we need first ## The contract surface we need first
The first shared contract set should cover only the seams that let all four The first shared contract set should cover only the seams needed for the
parts exist at once. self-hosted mount flow.
### NAS node -> control plane ### Node-service -> control-server
- node registration - node registration
- node heartbeat - node heartbeat
- export inventory - export inventory
### Local device -> control plane ### Web control plane -> control-server
- list allowed exports - auth/session bootstrapping
- list nodes and exports
- issue mount profile - issue mount profile
- issue share or cloud profile later
### Cloud/web layer -> control plane ### Local device -> control-server
- issue cloud profile - fetch mount instructions
- read export metadata - receive issued WebDAV URL and credentials or token material
### Control plane internal ### Control-server internal
- health - health
- version - version
@ -117,57 +134,61 @@ parts exist at once.
- `StorageExport` - `StorageExport`
- `AccessGrant` - `AccessGrant`
- `MountProfile` - `MountProfile`
- `CloudProfile` - `AuditEvent`
## Parallel work boundaries ## Parallel work boundaries
Each area gets an owner and a narrow write surface. | Part | Owns | May read | Must not own |
| ----------------- | ------------------------------------------------ | ------------------------------ | ------------------------------ |
| node-service | NAS runtime, WebDAV serving, export reporting | contracts, control-server docs | product policy |
| control-server | domain model, grants, profile issuance, registry | everything | direct file serving by default |
| web control plane | onboarding, node/export management, mount UX | contracts, control-server docs | source of truth |
| optional adapter | Nextcloud mapping and cloud surfaces | contracts, control-server docs | core mount path |
| Part | Owns | May read | Must not own | The shared write surface across parts should stay narrow:
| --------------- | ------------------------------------------------ | ----------------------------- | ------------------------------ |
| NAS node | node runtime, export reporting, WebDAV config | contracts, control-plane docs | product policy |
| Control plane | domain model, grants, profile issuance, registry | everything | direct file serving by default |
| Local device | mount UX, helper flows, credential handling | contracts, control-plane docs | access policy |
| Cloud/web layer | Nextcloud adapter, browser/mobile integration | contracts, control-plane docs | source of truth |
The only shared write surface across teams should be:
- [`packages/contracts`](../packages/contracts) - [`packages/contracts`](../packages/contracts)
- this file when the architecture contract changes - this file when architecture changes
## Verification loop ## Verification loop
This is the first loop every scaffold and agent should target. This is the main loop every near-term task should support.
```text ```text
[1] mock or real NAS node exposes a WebDAV export [node-service]
-> [2] control plane registers the node and export serves a WebDAV export
-> [3] local device asks for a mount profile |
-> [3] local device receives a WebDAV mount URL v
-> user can mount the export in Finder [control-server]
-> [4] optional cloud/web layer can expose the same export in cloud mode registers the node and export
issues a mount profile
|
v
[web control plane]
shows the export and mount action
|
v
[local device]
mounts the issued WebDAV URL in Finder
``` ```
If a task does not help one of those steps become real, it is probably too If a task does not make one of those steps more real, it is probably too early.
early.
## Definition of done for the foundation scaffold ## Definition of done for the current foundation
The initial scaffold is complete when: The current foundation is in good shape when:
- all four parts have a documented entry point - the self-hosted stack boots locally
- the control plane can represent nodes, exports, grants, and profiles - the control-server can represent nodes, exports, grants, and mount profiles
- the contracts package exports the first shared shapes and schemas - the node-service serves a real WebDAV export
- local verification can prove the mount-profile loop end to end - the web control plane can expose the mount flow
- future agents can work inside one part without inventing new interfaces - a local Mac can mount the export in Finder
## Rules for future tasks and agents ## Rules for future tasks and agents
1. No part may invent private request or response shapes for shared flows. 1. No part may invent private request or response shapes for shared flows.
2. Contract changes must update 2. Contract changes must update [`packages/contracts`](../packages/contracts)
[`packages/contracts`](../packages/contracts)
first. first.
3. Architecture changes must update this file in the same change. 3. Architecture changes must update this file in the same change.
4. Additive contract changes are preferred over breaking ones. 4. Additive contract changes are preferred over breaking ones.
5. New tasks should target one part at a time unless they are explicitly 5. Prioritize the self-hosted mount loop before optional cloud/mobile work.
contract tasks.

View file

@ -1,46 +1,55 @@
# betterNAS References # betterNAS References
This file tracks the upstream repos, tools, and docs we are likely to reuse, reference, fork from, or borrow ideas from as betterNAS evolves. This file tracks the upstream repos, tools, and docs we are likely to reuse,
reference, fork from, or borrow ideas from as betterNAS evolves.
The goal is simple: do not lose the external pieces that give us leverage. The ordering matters:
## NAS node 1. self-hosted WebDAV stack first
2. control-server and web control plane second
3. optional cloud adapter later
### WebDAV server candidates ## Primary now: self-hosted DAV stack
- `rclone serve webdav` ### Node-service and WebDAV
- repo: https://github.com/rclone/rclone
- why: fast way to stand up a WebDAV layer over existing storage - Go WebDAV package
- docs: https://pkg.go.dev/golang.org/x/net/webdav
- why: embeddable WebDAV implementation for the NAS runtime
- `hacdias/webdav` - `hacdias/webdav`
- repo: https://github.com/hacdias/webdav - repo: https://github.com/hacdias/webdav
- why: small standalone WebDAV server, easy to reason about - why: small standalone WebDAV reference
- Apache `mod_dav` - `rclone serve webdav`
- docs: https://httpd.apache.org/docs/current/mod/mod_dav.html - repo: https://github.com/rclone/rclone
- why: standard WebDAV implementation if we want conventional infra - why: useful reference for standing up WebDAV over existing storage
### Nix / host configuration ### Self-hosting and NAS configuration
- NixOS manual - NixOS manual
- docs: https://nixos.org/manual/nixos/stable/ - docs: https://nixos.org/manual/nixos/stable/
- why: host module design, service config, declarative machine setup - why: host module design and declarative machine setup
- Nixpkgs - Nixpkgs
- repo: https://github.com/NixOS/nixpkgs - repo: https://github.com/NixOS/nixpkgs
- why: reference for packaging and service modules - why: service module and packaging reference
## Control plane - Docker Compose docs
- docs: https://docs.docker.com/compose/
- why: current self-hosted runtime packaging baseline
## Primary now: control-server
### Backend and infra references ### Backend and infra references
- Go routing enhancements - Go routing enhancements
- docs: https://go.dev/blog/routing-enhancements - docs: https://go.dev/blog/routing-enhancements
- why: best low-dependency baseline if we stay with the standard library - why: low-dependency baseline for the API
- `chi` - `chi`
- repo: https://github.com/go-chi/chi - repo: https://github.com/go-chi/chi
- why: thin stdlib-friendly router if we want middleware and route groups - why: thin router if stdlib becomes too bare
- PostgreSQL - PostgreSQL
- docs: https://www.postgresql.org/docs/ - docs: https://www.postgresql.org/docs/
@ -48,7 +57,7 @@ The goal is simple: do not lose the external pieces that give us leverage.
- `pgx` - `pgx`
- repo: https://github.com/jackc/pgx - repo: https://github.com/jackc/pgx
- why: Postgres-first Go driver and toolkit - why: Postgres-first Go driver
- `sqlc` - `sqlc`
- repo: https://github.com/sqlc-dev/sqlc - repo: https://github.com/sqlc-dev/sqlc
@ -56,96 +65,70 @@ The goal is simple: do not lose the external pieces that give us leverage.
- Redis - Redis
- docs: https://redis.io/docs/latest/ - docs: https://redis.io/docs/latest/
- why: cache, jobs, ephemeral coordination - why: cache, jobs, and ephemeral coordination
- `go-redis` - `go-redis`
- repo: https://github.com/redis/go-redis - repo: https://github.com/redis/go-redis
- why: primary Redis client for Go - why: primary Redis client
- `asynq` - `asynq`
- repo: https://github.com/hibiken/asynq - repo: https://github.com/hibiken/asynq
- why: practical Redis-backed background jobs - why: practical Redis-backed background jobs
- `koanf`
- repo: https://github.com/knadh/koanf
- why: layered config if the control plane grows beyond env-only config
- `envconfig`
- repo: https://github.com/kelseyhightower/envconfig
- why: small env-only config loader
- `log/slog`
- docs: https://pkg.go.dev/log/slog
- why: structured logging without extra dependencies
- `oapi-codegen` - `oapi-codegen`
- repo: https://github.com/oapi-codegen/oapi-codegen - repo: https://github.com/oapi-codegen/oapi-codegen
- why: generate Go and TS surfaces from OpenAPI with less drift - why: generate Go and TS surfaces from OpenAPI with less drift
### SSH access / gateway reference ## Primary now: web control plane and local device
- `sshpiper` ### Web control plane
- repo: https://github.com/tg123/sshpiper
- why: SSH proxy/gateway reference if we add SSH-brokered access later
## Local device - Next.js
- repo: https://github.com/vercel/next.js
- why: control-plane web UI
### macOS native mount references - Turborepo
- docs: https://turborepo.dev/repo/docs/crafting-your-repository/structuring-a-repository
- why: monorepo boundaries and task graph rules
### macOS mount UX
- Apple Finder `Connect to Server` - Apple Finder `Connect to Server`
- docs: https://support.apple.com/en-lamr/guide/mac-help/mchlp3015/mac - docs: https://support.apple.com/en-lamr/guide/mac-help/mchlp3015/mac
- why: baseline native mounting UX on macOS - why: baseline native mount UX on macOS
- Apple Finder WebDAV mounting - Apple Finder WebDAV mounting
- docs: https://support.apple.com/is-is/guide/mac-help/mchlp1546/mac - docs: https://support.apple.com/is-is/guide/mac-help/mchlp1546/mac
- why: direct WebDAV mount behavior in Finder - why: direct WebDAV mount behavior in Finder
### macOS integration references
- Apple developer docs - Apple developer docs
- docs: https://developer.apple.com/documentation/ - docs: https://developer.apple.com/documentation/
- why: Keychain, launch agents, desktop helpers, future native integration - why: Keychain, helper apps, launch agents, and later native integration
- Keychain data protection - Keychain data protection
- docs: https://support.apple.com/guide/security/keychain-data-protection-secb0694df1a/web - docs: https://support.apple.com/guide/security/keychain-data-protection-secb0694df1a/web
- why: baseline secret-storage model for device credentials - why: baseline secret-storage model for device credentials
- Finder Sync extensions
- docs: https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/Finder.html
- why: future helper-app integration pattern if Finder UX grows
- WebDAV RFC 4918 - WebDAV RFC 4918
- docs: https://www.rfc-editor.org/rfc/rfc4918 - docs: https://www.rfc-editor.org/rfc/rfc4918
- why: protocol semantics and caveats - why: protocol semantics and caveats
## Cloud / web layer ## Optional later: cloud adapter
### Nextcloud server and app references ### Nextcloud server and app references
- Nextcloud server - Nextcloud server
- repo: https://github.com/nextcloud/server - repo: https://github.com/nextcloud/server
- why: cloud/web/share substrate - why: optional browser/share/mobile substrate
- Nextcloud app template - Nextcloud app template
- repo: https://github.com/nextcloud/app_template - repo: https://github.com/nextcloud/app_template
- why: official starting point for the thin shell app - why: official starting point for the thin adapter app
- Nextcloud AppAPI / ExApps - Nextcloud AppAPI / ExApps
- docs: https://docs.nextcloud.com/server/latest/admin_manual/exapps_management/AppAPIAndExternalApps.html - docs: https://docs.nextcloud.com/server/latest/admin_manual/exapps_management/AppAPIAndExternalApps.html
- why: external app integration model - why: external app integration model
### Nextcloud client references
- Nextcloud desktop
- repo: https://github.com/nextcloud/desktop
- why: Finder/cloud-drive style reference behavior
- Nextcloud iOS
- repo: https://github.com/nextcloud/ios
- why: mobile reference implementation
### Nextcloud storage and protocol references
- Nextcloud WebDAV access - Nextcloud WebDAV access
- docs: https://docs.nextcloud.com/server/latest/user_manual/en/files/access_webdav.html - docs: https://docs.nextcloud.com/server/latest/user_manual/en/files/access_webdav.html
- why: protocol and client behavior reference - why: protocol and client behavior reference
@ -154,20 +137,10 @@ The goal is simple: do not lose the external pieces that give us leverage.
- docs: https://docs.nextcloud.com/server/latest/user_manual/en/external_storage/external_storage.html - docs: https://docs.nextcloud.com/server/latest/user_manual/en/external_storage/external_storage.html
- why: storage aggregation reference - why: storage aggregation reference
- Nextcloud theming / branded clients
- docs: https://docs.nextcloud.com/server/latest/admin_manual/configuration_server/theming.html
- why: future branding path if Nextcloud stays user-facing
## Frontend
- Next.js
- repo: https://github.com/vercel/next.js
- why: likely standalone control-plane web UI
## Working rule ## Working rule
Use these references in this order: Use these references in this order:
1. steal primitives that already solve boring problems 1. steal primitives that solve the self-hosted DAV problem first
2. adapt them at the control-plane boundary 2. adapt them at the control-server boundary
3. only fork or replace when the product meaningfully diverges 3. only pull in optional cloud layers when the core mount product is solid

View file

@ -7,17 +7,17 @@ Its job is simple:
- lock the repo shape - lock the repo shape
- lock the language per runtime - lock the language per runtime
- lock the first shared contract surface - lock the first shared contract surface
- give agents a safe place to work in parallel - keep the self-hosted stack clear
- keep the list of upstream references we are stealing from - make later scoped execution runs easier
## Repo shape ## Repo shape
```text ```text
betterNAS/ betterNAS/
├── apps/ ├── apps/
│ ├── web/ # Next.js control-plane UI │ ├── web/ # Next.js web control plane
│ ├── control-plane/ # Go control-plane API │ ├── control-plane/ # Go control-server
│ ├── node-agent/ # Go NAS runtime + WebDAV surface │ ├── node-agent/ # Go node-service and WebDAV runtime
│ └── nextcloud-app/ # optional Nextcloud adapter │ └── nextcloud-app/ # optional Nextcloud adapter
├── packages/ ├── packages/
│ ├── contracts/ # canonical OpenAPI, schemas, TS types │ ├── contracts/ # canonical OpenAPI, schemas, TS types
@ -25,23 +25,64 @@ betterNAS/
│ ├── eslint-config/ # shared lint config │ ├── eslint-config/ # shared lint config
│ └── typescript-config/ # shared TS config │ └── typescript-config/ # shared TS config
├── infra/ ├── infra/
│ └── docker/ # local runtime stack │ └── docker/ # self-hosted stack for local proof
├── docs/ # architecture and part docs ├── docs/ # architecture and build docs
├── scripts/ # local helper scripts ├── scripts/ # bootstrap, verify, and stack helpers
├── go.work # Go workspace ├── go.work # Go workspace
├── turbo.json # Turborepo task graph ├── turbo.json # Turborepo task graph
└── skeleton.md # this file └── skeleton.md # this file
``` ```
## Runtime and language choices ## Runtime and language choices
| Part | Language | Why | | Part | Language | Why |
| -------------------- | ---------------------------------- | -------------------------------------------------------------------- | | -------------------- | ---------------------------------- | ------------------------------------------------------------------- |
| `apps/web` | TypeScript + Next.js | best UI velocity, best admin/control-plane UX | | `apps/web` | TypeScript + Next.js | fastest way to build the control-plane UI |
| `apps/control-plane` | Go | strong concurrency, static binaries, operationally simple | | `apps/control-plane` | Go | strong backend baseline, static binaries, simple self-hosting |
| `apps/node-agent` | Go | best fit for host runtime, WebDAV service, and future Nix deployment | | `apps/node-agent` | Go | best fit for NAS runtime, WebDAV serving, and future Nix deployment |
| `apps/nextcloud-app` | PHP | native language for the Nextcloud adapter surface | | `apps/nextcloud-app` | PHP | native language for an optional Nextcloud adapter |
| `packages/contracts` | OpenAPI + JSON Schema + TypeScript | language-neutral source of truth with practical TS ergonomics | | `packages/contracts` | OpenAPI + JSON Schema + TypeScript | language-neutral source of truth with practical frontend ergonomics |
## Default deployment model
The default product story is self-hosted:
```text
self-hosted betterNAS stack on user's NAS
+--------------------------------------------+
| web control plane |
| user opens this in browser |
+-------------------+------------------------+
|
v
+--------------------------------------------+
| control-server |
| auth / nodes / exports / grants |
| mount profile issuance |
+-------------------+------------------------+
|
v
+--------------------------------------------+
| node-service |
| WebDAV export runtime |
| real NAS files |
+--------------------------------------------+
user Mac
|
+--> browser -> web control plane
|
+--> Finder -> issued WebDAV mount URL
```
Optional later shape:
- hosted control-server
- hosted web control plane
- optional Nextcloud adapter for cloud/mobile/share surfaces
Those are not required for the core betterNAS product loop.
## Canonical contract rule ## Canonical contract rule
@ -52,39 +93,38 @@ The source of truth for shared interfaces is:
3. [`packages/contracts/schemas`](./packages/contracts/schemas) 3. [`packages/contracts/schemas`](./packages/contracts/schemas)
4. [`packages/contracts/src`](./packages/contracts/src) 4. [`packages/contracts/src`](./packages/contracts/src)
Agents must not invent private shared request or response shapes outside those Agents must not invent shared request or response shapes outside those
locations. locations.
## Parallel lanes ## Implementation lanes
```text ```text
shared write surface shared write surface
+-------------------------------------------+ +-------------------------------------------+
| docs/architecture.md | | docs/architecture.md |
| packages/contracts/ | | packages/contracts/ |
+----------------+--------------------------+ +----------------+--------------------------+
| |
+-----------------+-----------------+-----------------+ +---------------+----------------+----------------+
| | | | | | |
v v v v v v v
NAS node control plane local device cloud layer node-service control-server web control plane
lane lane lane lane
optional later:
nextcloud adapter
``` ```
Allowed ownership: Allowed ownership:
- NAS node lane - node-service lane
- `apps/node-agent` - `apps/node-agent`
- future `infra/nix/node-*` - future `infra/nix` host module work
- control-plane lane - control-server lane
- `apps/control-plane` - `apps/control-plane`
- DB and queue integration code later - web control plane lane
- local-device lane - `apps/web`
- mount docs first - optional adapter lane
- future helper app
- cloud layer lane
- `apps/nextcloud-app` - `apps/nextcloud-app`
- Nextcloud mapping logic
- shared contract lane - shared contract lane
- `packages/contracts` - `packages/contracts`
- `docs/architecture.md` - `docs/architecture.md`
@ -92,24 +132,24 @@ Allowed ownership:
## The first verification loop ## The first verification loop
```text ```text
[node-agent] [node-service]
serves WebDAV export serves WebDAV export
| |
v v
[control-plane] [control-server]
registers node + export registers node + export
issues mount profile issues mount profile
| |
v v
[local device] [web control plane]
mounts WebDAV in Finder shows export and mount action
| |
v v
[cloud layer] [local device]
optionally exposes same export in Nextcloud mounts in Finder
``` ```
If a task does not make one of those steps more real, it is probably too early. This is the main product loop.
## Upstream references to steal from ## Upstream references to steal from
@ -125,18 +165,14 @@ If a task does not make one of those steps more real, it is probably too early.
- Next.js backend-for-frontend guide - Next.js backend-for-frontend guide
- https://nextjs.org/docs/app/guides/backend-for-frontend - https://nextjs.org/docs/app/guides/backend-for-frontend
- why: keep Next.js as UI/BFF, not the system-of-record backend - why: keep Next.js as UI and orchestration surface, not the source-of-truth backend
### Go control plane ### Go control-server
- Go routing enhancements - Go routing enhancements
- https://go.dev/blog/routing-enhancements - https://go.dev/blog/routing-enhancements
- why: stdlib-first routing baseline - why: stdlib-first routing baseline
- `chi`
- https://github.com/go-chi/chi
- why: minimal router if stdlib patterns become too bare
- `pgx` - `pgx`
- https://github.com/jackc/pgx - https://github.com/jackc/pgx
- why: Postgres-first Go driver - why: Postgres-first Go driver
@ -153,23 +189,11 @@ If a task does not make one of those steps more real, it is probably too early.
- https://github.com/hibiken/asynq - https://github.com/hibiken/asynq
- why: practical Redis-backed job system - why: practical Redis-backed job system
- `koanf`
- https://github.com/knadh/koanf
- why: layered config if env-only config becomes too small
- `envconfig`
- https://github.com/kelseyhightower/envconfig
- why: tiny env-only config option
- `log/slog`
- https://pkg.go.dev/log/slog
- why: structured logging without adding a logging framework first
- `oapi-codegen` - `oapi-codegen`
- https://github.com/oapi-codegen/oapi-codegen - https://github.com/oapi-codegen/oapi-codegen
- why: generate Go and TS surfaces from OpenAPI - why: generate surfaces from OpenAPI with less drift
### NAS node and WebDAV ### Node-service and WebDAV
- Go WebDAV package - Go WebDAV package
- https://pkg.go.dev/golang.org/x/net/webdav - https://pkg.go.dev/golang.org/x/net/webdav
@ -183,11 +207,7 @@ If a task does not make one of those steps more real, it is probably too early.
- https://nixos.org/manual/nixos/stable/ - https://nixos.org/manual/nixos/stable/
- why: declarative host setup and service wiring - why: declarative host setup and service wiring
- Nixpkgs ### Local mount UX
- https://github.com/NixOS/nixpkgs
- why: service module and packaging reference
### Local device and mount UX
- Finder `Connect to Server` - Finder `Connect to Server`
- https://support.apple.com/en-lamr/guide/mac-help/mchlp3015/mac - https://support.apple.com/en-lamr/guide/mac-help/mchlp3015/mac
@ -201,36 +221,20 @@ If a task does not make one of those steps more real, it is probably too early.
- https://support.apple.com/guide/security/keychain-data-protection-secb0694df1a/web - https://support.apple.com/guide/security/keychain-data-protection-secb0694df1a/web
- why: local credential storage model - why: local credential storage model
- Finder Sync extensions
- https://developer.apple.com/library/archive/documentation/General/Conceptual/ExtensibilityPG/Finder.html
- why: future helper app / Finder integration reference
- WebDAV RFC 4918 - WebDAV RFC 4918
- https://www.rfc-editor.org/rfc/rfc4918 - https://www.rfc-editor.org/rfc/rfc4918
- why: protocol semantics and edge cases - why: protocol semantics and edge cases
### Cloud and adapter layer ### Optional cloud adapter
- Nextcloud app template - Nextcloud app template
- https://github.com/nextcloud/app_template - https://github.com/nextcloud/app_template
- why: thin adapter app reference - why: thin adapter app reference
- AppAPI / External Apps
- https://docs.nextcloud.com/server/latest/admin_manual/exapps_management/AppAPIAndExternalApps.html
- why: official external-app integration path
- Nextcloud WebDAV docs - Nextcloud WebDAV docs
- https://docs.nextcloud.com/server/latest/user_manual/en/files/access_webdav.html - https://docs.nextcloud.com/server/latest/user_manual/en/files/access_webdav.html
- why: protocol/client behavior reference - why: protocol/client behavior reference
- Nextcloud external storage
- https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/external_storage_configuration_gui.html
- why: storage aggregation behavior
- Nextcloud file sharing config
- https://docs.nextcloud.com/server/latest/admin_manual/configuration_files/file_sharing_configuration.html
- why: share semantics reference
## What we steal vs what we own ## What we steal vs what we own
### Steal ### Steal
@ -240,22 +244,21 @@ If a task does not make one of those steps more real, it is probably too early.
- Go stdlib and proven Go infra libraries - Go stdlib and proven Go infra libraries
- Go WebDAV implementation - Go WebDAV implementation
- Finder native WebDAV mount UX - Finder native WebDAV mount UX
- Nextcloud shell-app and cloud/web primitives - optional Nextcloud adapter primitives later
### Own ### Own
- the betterNAS domain model - the betterNAS domain model
- the control-plane API - the control-server API
- the node registration and export model - the node registration and export model
- the mount profile model - the mount profile model
- the mapping between cloud mode and mount mode - the self-hosted stack wiring
- the repo contract and shared schemas - the repo contract and shared schemas
- the root `pnpm verify` loop - the root `pnpm verify` loop
## The first implementation slices after this scaffold ## The next implementation slices
1. make `apps/node-agent` serve a real configurable WebDAV export 1. make `apps/web` expose the real mount flow to a user
2. make `apps/control-plane` store real node/export records 2. add durable control-server storage for nodes, exports, and grants
3. issue real mount profiles from the control plane 3. define the self-hosted NAS install shape for `apps/node-agent`
4. make `apps/web` let a user pick an export and request a profile 4. keep the optional cloud adapter out of the critical path
5. keep `apps/nextcloud-app` thin and optional