Files
anchorage/configs/anchorage.example.yaml
William Gill 12bf35caf8 anchorage v1.0 initial tree
Greenfield Go multi-tenant IPFS Pinning Service wire-compatible with the
IPFS Pinning Services API spec. Paired 1:1 with Kubo over localhost RPC,
clustered via embedded NATS JetStream, Postgres source-of-truth with
RLS-enforced tenancy, Fiber + huma v2 for the HTTP surface, Authentik
OIDC for session login with kid-rotated HS256 JWT API tokens.

Feature-complete against the 22-milestone build plan, including the
ship-it v1.0 gap items:

  * admin CLIs: drain/uncordon, maintenance, mint-token, rotate-key,
    prune-denylist, rebalance --dry-run, cache-stats, cluster-presences
  * TTL leader election via NATS KV, fence tokens, JetStream dedup
  * rebalancer (plan/apply split), reconciler, requeue sweeper
  * ristretto caches with NATS-backed cross-node invalidation
    (placements live-nodes + token denylist)
  * maintenance watchdog for stuck cluster-pause flag
  * Prometheus /metrics with CIDR ACL, HTTP/pin/scheduler/cache gauges
  * rate limiting: session (10/min) + anonymous global (120/min)
  * integration tests: rebalance, refcount multi-org, RLS belt
  * goreleaser (tar + deb/rpm/apk + Alpine Docker) targeting Gitea

Stack: Cobra/Viper, Fiber v2 + huma v2, embedded NATS JetStream,
pgx/sqlc/golang-migrate, ristretto, TypeID, prometheus/client_golang,
testcontainers-go.
2026-04-16 18:13:36 -05:00

132 lines
4.3 KiB
YAML

# anchorage — example configuration.
#
# Copy to configs/anchorage.yaml (or /etc/anchorage/anchorage.yaml) and edit
# as needed. All keys can also be overridden via environment variables with
# the ANCHORAGE_ prefix, e.g. ANCHORAGE_SERVER_PORT=9090.
server:
host: 0.0.0.0
port: 8080
readTimeout: 10s
writeTimeout: 30s
metrics:
# Prometheus /metrics is gated by a CIDR allowlist checked against
# the direct TCP peer IP (not X-Forwarded-For). Unset → loopback +
# RFC1918 defaults, which works for compose / swarm / k8s scrapers
# on the same internal network. Explicit empty list ([]) disables
# the ACL — use a firewall in that case.
# allowCIDRs:
# - "10.0.0.0/8"
# - "127.0.0.1/32"
rateLimit:
# POST /v1/auth/session attempts per IP per minute (brute-force guard).
sessionPerMinute: 10
# Unauthenticated requests per IP per minute across the API.
# Authenticated traffic (valid Bearer or session cookie) is exempt.
anonymousPerMinute: 120
node:
# Unique ID of this anchorage instance within the cluster.
# Defaults to the OS hostname when omitted.
id: node-1
# Public libp2p multiaddrs of this node's paired Kubo daemon.
# Clients receive these as PinStatus.delegates for direct data push.
multiaddrs:
- /dns4/node-1.example.com/tcp/4001/p2p/12D3Koo...
ipfs:
# HTTP RPC endpoint of the local paired Kubo daemon (same host / pod).
rpc: http://localhost:5001
timeout: 2m
reconciler:
# How often each node diffs its Postgres placements vs local `ipfs pin ls`.
interval: 10m
# When true, the reconciler re-pins drifted CIDs instead of just reporting.
autoRepair: false
cluster:
# 1 on a single-node install, 2+ for HA. See plan for placement rules.
minReplicas: 2
heartbeatInterval: 5s
downAfter: 30s
rebalanceInterval: 1m
# When true, the rebalancer acts; when false, it only logs what it would do.
autoRepair: false
# Time a drained node gives in-flight jobs before shutting down its consumer.
drainGracePeriod: 2m
maintenance:
# Safety rail: if cluster-wide maintenance has been on longer than this,
# emit a loud warning so forgotten flags don't silently mask outages.
maxDuration: 1h
postgres:
dsn: postgres://anchorage:***@pg-primary/anchorage?sslmode=require
maxConns: 20
# Run golang-migrate on boot. Safe with N replicas starting simultaneously
# thanks to Postgres advisory locks.
autoMigrate: true
requeueSweeper: 30s
nats:
dataDir: /var/lib/anchorage/nats
client:
host: 0.0.0.0
port: 4222
cluster:
name: anchorage
host: 0.0.0.0
port: 6222
# Peers discovered via gossip; list at least one existing cluster member.
routes:
- nats://peer-a:6222
- nats://peer-b:6222
jetstream:
# Stream replication factor. Capped at clusterSize at runtime.
replicas: 3
auth:
authentik:
issuer: https://auth.example.com/application/o/anchorage/
clientID: anchorage-web
audience: anchorage
apiToken:
# Signing keys. Exactly one entry must have `primary: true` — that's
# the key anchorage mints new tokens with; any additional entries
# are verify-only, used during a rotation overlap window. See
# deploy/README.md "Rotating the JWT signing key" for the
# three-step procedure.
#
# Leave signingKeys empty for local dev — anchorage will fall back
# to a built-in dev key with kid="dev" and log a loud warning.
signingKeys:
- id: "2026-04"
path: /etc/anchorage/jwt.key
primary: true
# Interactive / web-session tokens.
defaultTTL: 24h
# IPFS clients, CI, long-lived service identities — 1 year plus a
# 30-day grace window so operators have time to rotate before expiry.
maxTTL: 9480h
bootstrap:
# Emails promoted to sysadmin on their first Authentik login.
sysadmins:
- admin@example.com
logging:
# debug | info | warn | error. Controls the minimum level for the file sink.
level: info
# text | json — controls the stderr sink (warn+). The file sink is always JSON.
format: text
# When true, every record carries source file:line attributes.
source: false
# Empty disables the file sink; stderr then receives every level.
file: /var/log/anchorage/anchorage.log
maxSizeMB: 100
maxBackups: 10
maxAgeDays: 30
compress: true