Skip to content

Commit 6bda8ca

Browse files
committed
add automated Docker image tests, Makefile, agent skills, and updated docs
- tests/test-image.sh: 9-test TAP suite (build, binaries, env validation, healthcheck, shutdown) - .env.test: minimal unquoted env for isolated test runs - Makefile: dev/test/release targets - CI: test job gates push/sign in docker-publish.yml - AGENTS.md + .agent/skills/: agent reference and skill docs - README: added Development, Testing, CI/CD, Contributing sections - compose.yml: renamed from docker-compose.yml, added healthcheck - start.sh: added env validation and signal handling
1 parent 3c1af38 commit 6bda8ca

File tree

16 files changed

+905
-41
lines changed

16 files changed

+905
-41
lines changed

.agent/skills/ci-cd/SKILL.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
---
2+
name: ci-cd
3+
description: CI/CD pipeline for haven-docker including the GitHub Actions workflow for GHCR publishing, the manual DockerHub release script, cosign image signing, and version management. Use when modifying build automation, release processes, or image signing.
4+
---
5+
6+
# CI/CD
7+
8+
## GitHub Actions — `docker-publish.yml`
9+
10+
Located at `.github/workflows/docker-publish.yml`.
11+
12+
### Triggers
13+
14+
- **Schedule**: daily at 12:28 UTC
15+
- **Push**: to `main` branch or semver tags (`v*.*.*`)
16+
- **Pull request**: against `main`
17+
18+
### Pipeline steps
19+
20+
1. Checkout repo
21+
2. Install cosign (skipped on PRs)
22+
3. Set up Docker Buildx
23+
4. Log into GHCR (`ghcr.io`) using `GITHUB_TOKEN`
24+
5. Extract Docker metadata (tags, labels)
25+
6. Build and push image (push skipped on PRs, uses GHA cache)
26+
7. Sign image with cosign (skipped on PRs, uses Sigstore/Fulcio)
27+
28+
### Registry
29+
30+
- **GHCR**: `ghcr.io/sudocarlos/haven-docker` (automated via Actions)
31+
- **DockerHub**: `sudocarlos/haven` (manual via `release-to-dockerhub.sh`)
32+
33+
## Manual DockerHub Release — `release-to-dockerhub.sh`
34+
35+
```bash
36+
./release-to-dockerhub.sh
37+
```
38+
39+
This script:
40+
1. Extracts version from the `TAG=` line in `Dockerfile`
41+
2. Builds with `docker buildx` (no cache), pushes `latest` + version tags
42+
3. Commits all changes, creates an annotated git tag `dockerhub-<version>`
43+
4. Pushes commits and tags
44+
45+
### Prerequisites
46+
- Logged into DockerHub (`docker login`)
47+
- Docker Buildx configured
48+
49+
## Version Management
50+
51+
The Haven version is pinned in `Dockerfile`:
52+
53+
```dockerfile
54+
ARG TAG=v1.2.0-rc2
55+
ARG COMMIT=986c21b79c93779a449a52f6414ea267c83428bb
56+
```
57+
58+
The release script extracts the version with:
59+
```bash
60+
VERSION=`awk -F "=" '/TAG=/{print $NF}' Dockerfile`
61+
```
62+
63+
**To bump**: update `TAG` and `COMMIT` in `Dockerfile`, then run the release workflow.
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
---
2+
name: docker-packaging
3+
description: Dockerfile multi-stage build, compose.yml service config, image tagging, volume mounts, and resource limits for the haven-docker project. Use when modifying the Docker build, changing volumes, updating compose settings, or troubleshooting container issues.
4+
---
5+
6+
# Docker Packaging
7+
8+
## Dockerfile
9+
10+
Multi-stage build in `Dockerfile`:
11+
12+
1. **Builder stage** (`golang` base):
13+
- Clones `bitvora/haven`, checks out the `TAG` arg
14+
- Runs `go install -v`
15+
16+
2. **Runtime stage** (`debian:stable-slim`):
17+
- Installs curl, gpg, ca-certificates, build-essential, Tor
18+
- Copies built Go binary and Haven source from builder
19+
- Copies `start.sh` as entrypoint
20+
- Exposes port `3355`
21+
22+
### Version pinning
23+
24+
```dockerfile
25+
ARG TAG=v1.2.0-rc2
26+
ARG COMMIT=986c21b79c93779a449a52f6414ea267c83428bb
27+
```
28+
29+
To update Haven version: change `TAG` and `COMMIT` at the top of the `Dockerfile`.
30+
31+
## compose.yml
32+
33+
Single service `haven`:
34+
35+
```yaml
36+
services:
37+
haven:
38+
container_name: haven
39+
image: sudocarlos/haven:main
40+
build: .
41+
env_file: .env
42+
ports:
43+
- 3355:3355
44+
volumes:
45+
- ./data/blossom:/haven/blossom # Media storage
46+
- ./data/db:/haven/db # Database files
47+
- ./relays_blastr.json:/haven/relays_blastr.json
48+
- ./relays_import.json:/haven/relays_import.json
49+
- ./data/tor:/var/lib/tor # Tor hidden service state
50+
restart: unless-stopped
51+
init: true
52+
deploy:
53+
resources:
54+
limits:
55+
memory: 2GB
56+
```
57+
58+
### Key details
59+
60+
- `init: true` ensures proper PID 1 signal handling
61+
- Memory limit is 2 GB — increase if using lmdb with large datasets
62+
- All persistent data lives under `./data/` on the host
63+
- Relay JSON files are bind-mounted directly (not inside `data/`)
64+
65+
## Common Tasks
66+
67+
### Rebuild after Dockerfile changes
68+
69+
```bash
70+
docker compose build --no-cache
71+
docker compose up -d
72+
```
73+
74+
### View container resource usage
75+
76+
```bash
77+
docker stats haven
78+
```
79+
80+
## Testing
81+
82+
Run the full image test suite:
83+
84+
```bash
85+
make test
86+
```
87+
88+
Tests are in `tests/test-image.sh` (TAP output). They validate:
89+
- Image builds successfully
90+
- Required binaries exist (`haven`, `curl`, `tor`, `bash`)
91+
- `start.sh` entrypoint is executable
92+
- Env validation rejects missing `OWNER_NPUB` / `RELAY_URL`
93+
- Haven process starts and port 3355 responds
94+
- Docker healthcheck passes
95+
- Container shuts down gracefully
96+
97+
Tests use `.env.test` (minimal config) and clean up containers on exit.
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
---
2+
name: haven-config
3+
description: Configuration reference for Haven relay including .env variables, relay JSON files, database engine selection, backup providers, and rate limiter tuning. Use when modifying relay settings, adding new config variables, or debugging configuration issues.
4+
---
5+
6+
# Haven Configuration
7+
8+
All configuration is in `.env` (copy from `.env.example`). No config files are baked into the image.
9+
10+
## Core Settings
11+
12+
| Variable | Default | Description |
13+
|----------|---------|-------------|
14+
| `OWNER_NPUB` || Owner's Nostr public key (npub format) |
15+
| `RELAY_URL` || Public-facing relay hostname (no `wss://`) |
16+
| `RELAY_PORT` | `3355` | Port Haven listens on |
17+
| `RELAY_BIND_ADDRESS` | `0.0.0.0` | Bind address (empty string = all interfaces) |
18+
| `DB_ENGINE` | `badger` | `badger` or `lmdb` (lmdb needs NVMe for stability) |
19+
| `LMDB_MAPSIZE` | `0` | 0 = default (~273 GB), or size in bytes |
20+
| `BLOSSOM_PATH` | `blossom/` | Media storage directory inside container |
21+
| `TOR_ENABLED` | `0` | Set to `1` to start a Tor hidden service |
22+
23+
## Relay Sections
24+
25+
Haven runs four relay types, each with its own settings block:
26+
27+
| Relay | Prefix | Purpose |
28+
|-------|--------|---------|
29+
| Private | `PRIVATE_RELAY_*` | Owner drafts and ecash |
30+
| Chat | `CHAT_RELAY_*` | Private messages, WoT-gated |
31+
| Outbox | `OUTBOX_RELAY_*` | Public notes + Blossom media |
32+
| Inbox | `INBOX_RELAY_*` | Interactions with owner's notes |
33+
34+
Each relay section has: `NAME`, `NPUB`, `DESCRIPTION`, `ICON`, and rate limiter vars.
35+
36+
### Rate Limiter Variables (per relay)
37+
38+
| Suffix | Description |
39+
|--------|-------------|
40+
| `EVENT_IP_LIMITER_TOKENS_PER_INTERVAL` | Tokens added per interval |
41+
| `EVENT_IP_LIMITER_INTERVAL` | Interval in seconds |
42+
| `EVENT_IP_LIMITER_MAX_TOKENS` | Burst capacity |
43+
| `ALLOW_EMPTY_FILTERS` | Allow filters with no criteria |
44+
| `ALLOW_COMPLEX_FILTERS` | Allow multi-criteria filters |
45+
| `CONNECTION_RATE_LIMITER_TOKENS_PER_INTERVAL` | Connection tokens per interval |
46+
| `CONNECTION_RATE_LIMITER_INTERVAL` | Connection interval in seconds |
47+
| `CONNECTION_RATE_LIMITER_MAX_TOKENS` | Connection burst capacity |
48+
49+
### Chat-specific
50+
51+
| Variable | Description |
52+
|----------|-------------|
53+
| `CHAT_RELAY_WOT_DEPTH` | Web-of-trust traversal depth |
54+
| `CHAT_RELAY_WOT_REFRESH_INTERVAL_HOURS` | WoT graph refresh interval |
55+
| `CHAT_RELAY_MINIMUM_FOLLOWERS` | Min followers to pass WoT gate |
56+
57+
### Inbox-specific
58+
59+
| Variable | Description |
60+
|----------|-------------|
61+
| `INBOX_PULL_INTERVAL_SECONDS` | How often to pull from other relays |
62+
63+
## Relay JSON Files
64+
65+
- `relays_blastr.json` — list of relay URLs to blast public notes to
66+
- `relays_import.json` — list of relay URLs to import notes from
67+
68+
Both are JSON arrays of relay URL strings. Copy from the `.example` versions.
69+
70+
## Import & Backup
71+
72+
| Variable | Description |
73+
|----------|-------------|
74+
| `IMPORT_START_DATE` | Start date for note import (YYYY-MM-DD) |
75+
| `IMPORT_QUERY_INTERVAL_SECONDS` | Query interval during import |
76+
| `IMPORT_SEED_RELAYS_FILE` | Path to import relays JSON |
77+
| `BACKUP_PROVIDER` | `aws`, `gcp`, or `none` |
78+
| `BACKUP_INTERVAL_HOURS` | Backup frequency |
79+
80+
### AWS backup vars
81+
`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, `AWS_REGION`, `AWS_BUCKET_NAME`
82+
83+
### GCP backup vars
84+
`GCP_BUCKET_NAME`
85+
86+
## Blastr
87+
88+
| Variable | Description |
89+
|----------|-------------|
90+
| `BLASTR_RELAYS_FILE` | Path to blastr relays JSON (default: `relays_blastr.json`) |
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
---
2+
name: tor-integration
3+
description: Tor hidden service integration for Haven relay including start.sh entrypoint logic, torrc configuration, and data volume persistence. Use when enabling Tor, debugging hidden service issues, or modifying the container entrypoint.
4+
---
5+
6+
# Tor Integration
7+
8+
Haven can expose the relay as a Tor hidden service, running inside the same container.
9+
10+
## How It Works
11+
12+
The entrypoint `start.sh` checks the `TOR_ENABLED` env var:
13+
14+
```bash
15+
#!/usr/bin/env bash
16+
if [[ "$TOR_ENABLED" == 1 ]]; then
17+
echo "HiddenServiceDir /var/lib/tor/haven/" >> /etc/tor/torrc
18+
echo "HiddenServicePort 80 ${RELAY_BIND_ADDRESS}:${RELAY_PORT}" >> /etc/tor/torrc
19+
tor &
20+
fi
21+
cd /haven
22+
./haven
23+
```
24+
25+
When `TOR_ENABLED=1`:
26+
1. Appends hidden service config to `/etc/tor/torrc`
27+
2. Starts `tor` in the background
28+
3. Haven binds on `RELAY_BIND_ADDRESS:RELAY_PORT` (default `0.0.0.0:3355`)
29+
4. Tor maps port 80 of the `.onion` address to Haven's port
30+
31+
## Persistent State
32+
33+
Tor keys are persisted via the volume mount:
34+
35+
```yaml
36+
- ./data/tor:/var/lib/tor
37+
```
38+
39+
This ensures the `.onion` address survives container restarts. The hidden service hostname is stored at `/var/lib/tor/haven/hostname`.
40+
41+
## Usage
42+
43+
### Enable Tor
44+
45+
Set in `.env`:
46+
```
47+
TOR_ENABLED=1
48+
```
49+
50+
### Start and get the .onion address
51+
52+
```bash
53+
docker compose up -d
54+
docker compose exec haven cat /var/lib/tor/haven/hostname
55+
```
56+
57+
The relay is then available at `ws://<address>.onion`.
58+
59+
## Tor Installation in Dockerfile
60+
61+
Tor is installed from the official Tor Project apt repository (not Debian's package). The Dockerfile adds the Tor Project GPG key and apt source to get the latest stable version.

.agent/workflows/release.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
description: Release a new Haven version to DockerHub and GHCR
3+
---
4+
5+
## Steps
6+
7+
1. Check the latest Haven release at https://github.com/bitvora/haven/releases
8+
9+
2. Update the version in `Dockerfile`:
10+
```
11+
ARG TAG=<new-version>
12+
ARG COMMIT=<new-commit-hash>
13+
```
14+
15+
3. Test the build locally:
16+
// turbo
17+
```bash
18+
docker compose build --no-cache
19+
```
20+
21+
4. Verify the container starts:
22+
```bash
23+
docker compose up -d
24+
docker logs -f haven
25+
```
26+
27+
5. Push to DockerHub (requires `docker login`):
28+
```bash
29+
./release-to-dockerhub.sh
30+
```
31+
32+
This will also create a git tag `dockerhub-<version>` and push it.
33+
34+
6. The GitHub Actions workflow will automatically build and push to GHCR on the next push to `main`.

0 commit comments

Comments
 (0)