Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,2 @@
.git
.gitmodules
node_modules
javascript
*.s9pk
keep/target
keep/keep-web/ui/node_modules
keep/keep-web/ui/dist
.gitmodules
1 change: 1 addition & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ jobs:
release:
uses: start9labs/shared-workflows/.github/workflows/release.yml@master
with:
FREE_DISK_SPACE: true # keep-web compiles Rust + the Vite UI from source
RELEASE_REGISTRY: ${{ vars.RELEASE_REGISTRY }}
S3_S9PKS_BASE_URL: ${{ vars.S3_S9PKS_BASE_URL }}
secrets:
Expand Down
25 changes: 25 additions & 0 deletions .github/workflows/tagAndRelease.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Tag and Release

on:
push:
branches: ['main']
paths-ignore: ['*.md']

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
tag:
uses: start9labs/shared-workflows/.github/workflows/tagAndRelease.yml@master
with:
REFERENCE_REGISTRY: ${{ vars.REFERENCE_REGISTRY }}
FREE_DISK_SPACE: true # keep-web compiles Rust + the Vite UI from source
RELEASE_REGISTRY: ${{ vars.RELEASE_REGISTRY }}
S3_S9PKS_BASE_URL: ${{ vars.S3_S9PKS_BASE_URL }}
secrets:
DEV_KEY: ${{ secrets.DEV_KEY }}
S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }}
S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }}
permissions:
contents: write
8 changes: 6 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
node_modules/
javascript/
*.s9pk
startos/*.js
node_modules/
.DS_Store
.vscode/
docker-images
javascript
ncc-cache
9 changes: 9 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# AGENTS.md

This is a StartOS service-package repository — it builds a `.s9pk` for StartOS.

Develop it inside a StartOS packaging workspace created by `start-cli s9pk init-workspace`,
which provides the packaging guide and agent context one level up. If you're reading this in a
bare clone with no workspace, the full guide is at <https://docs.start9.com/packaging>.

Work this package's `TODO.md` from top to bottom. Keep `README.md` (architecture, for developers and LLMs) and `instructions.md` (end-user docs) in sync with your changes.
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@AGENTS.md
15 changes: 9 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@

- **[`README.md`](./README.md)** — what this package is and how it's built (image, volumes, interfaces, CI). Technical reference for developers and AI assistants.
- **[`instructions.md`](./instructions.md)** — the user-facing instructions packed into the `.s9pk` and shown on the **Instructions** tab in StartOS, for the person running the service.
- **[`TODO.md`](./TODO.md)** — pending work on this package.

Any code change that affects user-visible behavior must update `README.md` and `instructions.md` in the same change. Content rules: [Writing READMEs](https://docs.start9.com/packaging/writing-readmes.html), [Writing Instructions](https://docs.start9.com/packaging/writing-instructions.html).
**Read all three before starting any work.** Any code change that affects user-visible behavior must update `README.md` and `instructions.md` in the same change; add to `TODO.md` when you defer work, and remove items when complete. Content rules: [Writing READMEs](https://docs.start9.com/packaging/writing-readmes.html), [Writing Instructions](https://docs.start9.com/packaging/writing-instructions.html).

## Environment setup

Expand All @@ -17,27 +18,29 @@ See [Environment Setup](https://docs.start9.com/packaging/environment-setup.html
git clone --recurse-submodules https://github.com/privkeyio/keep-startos
cd keep-startos
npm ci # install the TypeScript SDK
make x86 # build keep_x86_64.s9pk (x86_64 only — see Makefile / manifest)
make x86 # build keep_x86_64.s9pk (x86_64 only — see Makefile / manifest)
make install # sideload to the host in ~/.startos/config.yaml
```

The `keep/` submodule is the upstream source; the root `Dockerfile` compiles `keep-web` (Rust, release, wss-only) and builds the Svelte SPA from it. See [Makefile options](https://docs.start9.com/packaging/makefile.html).

## Updating the upstream version

See [UPDATING.md](./UPDATING.md) to bump the `keep` submodule, then update `version` and `releaseNotes` in the file under `startos/versions/`, renaming it to the new version string (a new version file is only needed for a migration or to preserve old release notes — see [Versions](https://docs.start9.com/packaging/versions.html)).
1. Apply the upstream bump per [UPDATING.md](./UPDATING.md) (fast-forward the `keep` submodule and stage the new pointer).
2. Update `version` and `releaseNotes` in `startos/versions/current.ts` — the latest version always lives in that file, so an in-place edit is all most bumps need. A new file is spun off only when the bump requires a migration — see [Versions](https://docs.start9.com/packaging/versions.html).

## CI/CD

Two workflows under `.github/workflows/` wrap reusable workflows in [`start9labs/shared-workflows`](https://github.com/Start9Labs/shared-workflows):
Three workflows under `.github/workflows/` wrap reusable workflows in [`start9labs/shared-workflows`](https://github.com/Start9Labs/shared-workflows):

- **`build.yml`** — on PR to `main` and manual dispatch, builds the `.s9pk` to verify it packs. Requires repo secret `DEV_KEY` (a StartOS developer key from `start-cli init-key`).
- **`release.yml`** — on a `v*.*` tag, builds and publishes. Requires `DEV_KEY` plus registry/S3 vars (`RELEASE_REGISTRY`, `S3_S9PKS_BASE_URL`) and secrets (`S3_ACCESS_KEY`, `S3_SECRET_KEY`) when publishing to your own registry.
- **`tagAndRelease.yml`** — on push to `main`, tags `v<version>` and runs the release, skipping if that version is already in production.

### Cutting a release

1. Update `startos/versions/` (version + release notes) and bump the `keep` submodule if needed.
2. `git tag v0.3.0 && git push --tags` → `release.yml` builds and publishes.
1. Update `startos/versions/current.ts` (version + release notes) and bump the `keep` submodule if needed.
2. `git tag v0.4.0 && git push --tags` → `release.yml` builds and publishes (or merge to `main` and let `tagAndRelease.yml` tag and publish automatically).

## How to contribute

Expand Down
238 changes: 217 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,229 @@
# keep-startos
<p align="center">
<img src="icon.png" alt="Keep Logo" width="21%">
</p>

StartOS package for [Keep](https://github.com/privkeyio/keep) — an always-on FROST threshold co-signer for Nostr and Bitcoin.
# Keep on StartOS

The server holds one share of a multi-device FROST key and coordinates with your other devices over Nostr relays to produce signatures, so no single device ever holds the whole key. It exposes a NIP-46 bunker for Nostr clients and a web admin UI for importing your share and watching signing activity.
> **Upstream docs:** <https://github.com/privkeyio/keep>
>
> Everything not listed in this document should behave the same as upstream
> Keep. If a feature, setting, or behavior is not mentioned here, the upstream
> documentation is accurate and fully applicable.

Built on the [`keep-web`](https://github.com/privkeyio/keep/tree/main/keep-web) daemon (Rust axum API + embedded FROST node + Svelte admin SPA).
[Keep](https://github.com/privkeyio/keep) is a self-custodial key manager for Nostr and Bitcoin. This package runs its [`keep-web`](https://github.com/privkeyio/keep/tree/main/keep-web) daemon as an always-on FROST threshold co-signer: it holds one share of a multi-device key and coordinates signatures with your other devices over Nostr relays, so no single device ever holds the whole key.

## Building
---

This package targets **StartOS 0.4.x** and uses the StartOS TypeScript SDK.
## Table of Contents

```sh
git clone --recurse-submodules https://github.com/privkeyio/keep-startos
cd keep-startos
make # produces keep_x86_64.s9pk (x86_64 only)
make install # installs to the host in ~/.startos/config.yaml
```
- [Image and Container Runtime](#image-and-container-runtime)
- [Volume and Data Layout](#volume-and-data-layout)
- [Installation and First-Run Flow](#installation-and-first-run-flow)
- [Configuration Management](#configuration-management)
- [Network Access and Interfaces](#network-access-and-interfaces)
- [Actions (StartOS UI)](#actions-startos-ui)
- [Dependencies](#dependencies)
- [Backups and Restore](#backups-and-restore)
- [Health Checks](#health-checks)
- [Limitations and Differences](#limitations-and-differences)
- [What Is Unchanged from Upstream](#what-is-unchanged-from-upstream)
- [Contributing](#contributing)
- [Quick Reference for AI Consumers](#quick-reference-for-ai-consumers)

---

## Image and Container Runtime

| Property | Value |
|----------|-------|
| Image | `keep` — built from source via the root `Dockerfile` |
| Source | `keep` git submodule (upstream `keep-web` crate + Svelte admin SPA) |
| Build | Rust release (`keep-web`, default features = wss-only) + Vite/Svelte UI, on a slim Debian runtime |
| Architectures | x86_64 |
| Entrypoint | `/app/keep-web` |

---

## Volume and Data Layout

| Volume | Mount Point | Purpose |
|--------|-------------|---------|
| `main` | `/data` | Encrypted vault and StartOS settings |

**Key paths on the `main` volume:**

- `/data/vault` — the encrypted Keep vault: your FROST share(s), signing state, and the persisted co-signing kill-switch flag
- `/data/start9/store.json` — StartOS persistent settings: vault password, Web Admin token, bunker/FROST relays, optional group

---

## Installation and First-Run Flow

| Step | Upstream | StartOS |
|------|----------|---------|
| Vault creation | Manual (`keep` CLI) | Auto-created at `/data/vault` on first start |
| Vault password | User-supplied | Auto-generated internal secret (never shown) |
| Web Admin login | Token logged at startup | Generated and rotated via the Set Web Admin Password action |
| Share import | CLI / app | Pasted into the Web Admin in setup mode |

**First-run steps:**

1. A critical task prompts you to run **Set Web Admin Password**; copy the generated password and sign in (username `admin`).
2. Open the **Web Admin** and import your FROST share (a `kshare1…` export from the device that created the group).
3. Restart the service so `keep-web` loads the share and starts the co-signer.
4. Flip the co-signing kill switch in the Web Admin (ships off, fail-closed).

See [instructions.md](instructions.md) for the user-facing walkthrough.

---

## Configuration Management

| StartOS-Managed | Upstream-Managed (Web Admin) |
|-----------------|------------------------------|
| Vault path, listen address, UI dir (env) | FROST share import |
| Vault password (auto-generated internal secret) | Co-signing kill switch |
| Web Admin token (Set Web Admin Password action) | Signing policy / per-request approvals |
| Bunker & FROST relays, optional group (Configure action) | Active-group selection (with shares for multiple groups) |

**Environment variables set by StartOS** (`startos/main.ts`):

| Variable | Value | Purpose |
|----------|-------|---------|
| `KEEP_PATH` | `/data/vault` | Vault location (on the backed-up `main` volume) |
| `KEEP_WEB_LISTEN` | `0.0.0.0:8080` | Web Admin bind address |
| `KEEP_WEB_UI_DIR` | `/app/ui` | Built Svelte admin SPA |
| `KEEP_PASSWORD` | (auto-generated) | Vault encryption password |
| `KEEP_WEB_AUTH_TOKEN` | (set by action) | Web Admin bearer token; omitted until set |
| `KEEP_BUNKER_RELAY` | (Configure) | NIP-46 bunker relays, comma-separated |
| `KEEP_FROST_RELAY` | (Configure) | FROST coordination relays, comma-separated |
| `KEEP_FROST_GROUP` | (Configure, optional) | Pins a specific group npub; omitted → auto-select (switch the active group in the Web Admin) |

Single-key mode (`KEEP_ALLOW_SINGLE_KEY`) and env-level auto-approve (`KEEP_FROST_AUTO_APPROVE`) are intentionally left unset — Keep runs as a fail-closed FROST co-signer.

Relays default to a single `wss://bucket.coracle.social` (upstream keep-core's default): it reliably delivers the rapid ephemeral kind-24242 events FROST coordination needs, which most general-purpose relays drop. The Configure action requires at least one relay (the bunker will not start with none) and accepts up to 10 — add more known-good relays there for redundancy.

---

## Network Access and Interfaces

| Interface | Port | Protocol | Purpose |
|-----------|------|----------|---------|
| Web Admin (`ui`) | 8080 | HTTP | Admin SPA + bearer-authenticated `/api` |

The NIP-46 bunker and FROST signing are **not** bound ports: `keep-web` reaches Nostr relays over outbound WebSocket connections, and clients reach the bunker through those relays using the connection string shown in the Web Admin.

**Access methods:**

- LAN IP with unique port
- `<hostname>.local` with unique port
- Tor `.onion` address (if added)
- Custom domains (if configured)

The `keep` git submodule pins the upstream source built into the image (`Dockerfile` builds `keep-web` + the SPA).
---

## Structure
## Actions (StartOS UI)

- `startos/` — package definition (manifest, main daemon, interfaces, config store, actions, i18n).
- `Dockerfile` — multi-stage build: Rust (`keep-web`, release, wss-only) + Node (SPA) → slim runtime image.
- `keep/` — upstream source as a git submodule.
### Set Web Admin Password

## CI
Generate or rotate the Web Admin bearer token. On install a critical task prompts you to run it before signing in; re-run any time to rotate.

- **Build** (`.github/workflows/build.yml`): on PRs to `main` and manual dispatch, builds the `.s9pk` via Start9's shared workflow to verify it packs. Requires repo secret **`DEV_KEY`** (a StartOS developer key, `start-cli init-key`).
- **Release** (`.github/workflows/release.yml`): on `v*.*` tags, builds and publishes. Requires `DEV_KEY` plus the registry/S3 vars (`RELEASE_REGISTRY`, `S3_S9PKS_BASE_URL`) and secrets (`S3_ACCESS_KEY`, `S3_SECRET_KEY`) when publishing to your own registry.
| Property | Value |
|----------|-------|
| Availability | Any status |
| Visibility | Always visible (also a critical task on install) |
| Inputs | None |
| Outputs | Username (`admin`) and the generated password |

See [`instructions.md`](instructions.md) for setup and usage.
### Configure

Set the relays and group Keep uses, then restart to apply.

| Property | Value |
|----------|-------|
| Availability | Any status |
| Visibility | Always visible |
| Inputs | Bunker Relays, FROST Relays, Group (npub, optional) |
| Outputs | Confirmation; restarts the service |

---

## Dependencies

None.

---

## Backups and Restore

**Included in backup:**

- `main` volume — the encrypted vault (`/data/vault`) and `store.json`

**Restore behavior:**

- The vault and its password are restored together, so your share, kill-switch state, relays, and Web Admin token come back as-is — no re-import or reconfiguration. Init regenerates the vault password only on a fresh install, never on restore.

---

## Health Checks

| Check | Display Name | Method | Messages |
|-------|--------------|--------|----------|
| `primary` | Web Admin | Port-listening check on 8080 | "The Keep admin UI is ready" / "The Keep admin UI is not responding" |

---

## Limitations and Differences

1. **x86_64 only** — `keep-web` is built for x86_64; there is no aarch64 or riscv64 image.
2. **Requires an existing FROST group** — this package imports shares; it does not run key generation. Create the group and export a share elsewhere (e.g. Keep Android) first.
3. **Co-signing is off by default** — fail-closed; enable it with the Web Admin kill switch.
4. **Share import is Web-Admin-only** — the upstream CLI and enclave paths are not exposed; import in setup mode, then restart so the co-signer starts.

---

## What Is Unchanged from Upstream

- FROST threshold signing and distributed key generation coordinated over Nostr relays
- NIP-46 bunker remote signing for any compatible Nostr client
- The `keep-web` admin SPA and its bearer-authenticated `/api` surface
- Argon2id + XChaCha20-Poly1305 vault encryption with keys zeroized in RAM
- The co-signing kill switch and live signing-activity feed

---

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md) for environment setup, the submodule and `make x86` build, and the release workflow.

---

## Quick Reference for AI Consumers

```yaml
package_id: keep
architectures: [x86_64]
image: keep (built from source; keep-web crate + Svelte SPA)
volumes:
main: /data
ports:
ui: 8080
dependencies: none
startos_managed_env_vars:
- KEEP_PATH
- KEEP_WEB_LISTEN
- KEEP_WEB_UI_DIR
- KEEP_PASSWORD
- KEEP_WEB_AUTH_TOKEN
- KEEP_BUNKER_RELAY
- KEEP_FROST_RELAY
- KEEP_FROST_GROUP
actions:
- set-web-admin-password
- configure
health_checks:
- primary: port_check 8080
backup_volumes:
- main
```
1 change: 1 addition & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# TODO
2 changes: 1 addition & 1 deletion UPDATING.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ cd keep && awk -F'"' '/^version/ {print $2; exit}' Cargo.toml
cd .. && git add keep
```

2. Update `startos/versions/` — set `version` (and `releaseNotes`) to match. Rename the version file to the new version string if you want the old notes preserved in git history.
2. Update `startos/versions/current.ts` — set `version` (and `releaseNotes`) to match. The latest version always lives in `current.ts`, so an in-place edit is all most bumps need; spin off a new version file only when the bump carries a migration (see [Versions](https://docs.start9.com/packaging/versions.html)).

3. Rebuild and sideload to smoke-test:

Expand Down
Loading
Loading