Initial commit: VaultMesh Skills collection
Collection of operational skills for VaultMesh infrastructure including: - backup-sovereign: Backup and recovery operations - btc-anchor: Bitcoin anchoring - cloudflare-tunnel-manager: Cloudflare tunnel management - container-registry: Container registry operations - disaster-recovery: Disaster recovery procedures - dns-sovereign: DNS management - eth-anchor: Ethereum anchoring - gitea-bootstrap: Gitea setup and configuration - hetzner-bootstrap: Hetzner server provisioning - merkle-forest: Merkle tree operations - node-hardening: Node security hardening - operator-bootstrap: Operator initialization - proof-verifier: Cryptographic proof verification - rfc3161-anchor: RFC3161 timestamping - secrets-vault: Secrets management 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
90
container-registry/SKILL.md
Normal file
90
container-registry/SKILL.md
Normal file
@@ -0,0 +1,90 @@
|
||||
---
|
||||
name: container-registry
|
||||
description: >
|
||||
Bootstrap a sovereign container registry (OCI/Docker) with plan/apply/rollback,
|
||||
signature verification hooks, backups, and audit report. Designed to pair with
|
||||
gitea-bootstrap on Node B. Triggers: 'container registry', 'docker registry',
|
||||
'oci registry', 'self-host registry', 'registry plan'.
|
||||
version: 1.0.0
|
||||
---
|
||||
|
||||
# Container Registry (Sovereign)
|
||||
|
||||
Tier 2 skill: establish a **self-hosted OCI registry** you control.
|
||||
|
||||
This skill deploys a Docker Registry v2 (with optional UI) using
|
||||
plan/apply gates and produces verifiable artifacts.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
cd ~/.claude/skills/container-registry
|
||||
|
||||
export MODE="docker" # docker only in v1
|
||||
export NODE_NAME="node-b"
|
||||
|
||||
# Network
|
||||
export REGISTRY_PORT=5000
|
||||
export DOMAIN="registry.example.com" # optional (for reverse proxy)
|
||||
|
||||
# Storage
|
||||
export DATA_DIR="$HOME/registry"
|
||||
export AUTH_DIR="$HOME/registry/auth"
|
||||
|
||||
# Auth (basic auth for v1)
|
||||
export REGISTRY_USER="sovereign"
|
||||
|
||||
# Safety
|
||||
export DRY_RUN=1
|
||||
export REQUIRE_CONFIRM=1
|
||||
export CONFIRM_PHRASE="I UNDERSTAND THIS WILL DEPLOY A CONTAINER REGISTRY"
|
||||
|
||||
./scripts/00_preflight.sh
|
||||
./scripts/10_plan.sh
|
||||
|
||||
export DRY_RUN=0
|
||||
./scripts/11_apply.sh
|
||||
|
||||
./scripts/90_verify.sh
|
||||
./scripts/99_report.sh
|
||||
```
|
||||
|
||||
## Inputs
|
||||
|
||||
| Parameter | Required | Default | Description |
|
||||
|---|---:|---|---|
|
||||
| MODE | Yes | docker | docker |
|
||||
| REGISTRY_PORT | No | 5000 | Registry port |
|
||||
| DOMAIN | No | (empty) | Hostname if proxied |
|
||||
| DATA_DIR | No | ~/registry | Registry storage |
|
||||
| AUTH_DIR | No | ~/registry/auth | htpasswd storage |
|
||||
| REGISTRY_USER | Yes | (none) | Registry username |
|
||||
| DRY_RUN | No | 1 | Apply refuses unless DRY_RUN=0 |
|
||||
| REQUIRE_CONFIRM | No | 1 | Require confirmation phrase |
|
||||
| CONFIRM_PHRASE | No | I UNDERSTAND THIS WILL DEPLOY A CONTAINER REGISTRY | Safety phrase |
|
||||
|
||||
## Outputs
|
||||
|
||||
- `outputs/compose.yml`
|
||||
- `outputs/htpasswd`
|
||||
- `outputs/status_matrix.json`
|
||||
- `outputs/audit_report.md`
|
||||
- Backups under `outputs/backups/`
|
||||
|
||||
## Security Notes (v1)
|
||||
|
||||
- Basic auth (htpasswd)
|
||||
- TLS termination expected via reverse proxy or Cloudflare Tunnel
|
||||
- Image signing handled upstream (cosign/notation integration planned)
|
||||
|
||||
## EU Compliance
|
||||
|
||||
| Aspect | Value |
|
||||
|---|---|
|
||||
| Data Residency | EU (Ireland - Dublin) |
|
||||
| Jurisdiction | Irish Law |
|
||||
| Images | Stored on Node B |
|
||||
| Access | Authenticated |
|
||||
|
||||
## References
|
||||
- [OCI Registry Notes](references/registry_notes.md)
|
||||
41
container-registry/config.json
Normal file
41
container-registry/config.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "container-registry",
|
||||
"version": "1.0.0",
|
||||
"description": "Bootstrap a sovereign container registry with plan/apply/rollback.",
|
||||
"defaults": {
|
||||
"MODE": "docker",
|
||||
"REGISTRY_PORT": "5000",
|
||||
"DATA_DIR": "~/registry",
|
||||
"AUTH_DIR": "~/registry/auth",
|
||||
"DRY_RUN": "1",
|
||||
"REQUIRE_CONFIRM": "1",
|
||||
"CONFIRM_PHRASE": "I UNDERSTAND THIS WILL DEPLOY A CONTAINER REGISTRY"
|
||||
},
|
||||
"phases": {
|
||||
"preflight": [
|
||||
"00_preflight.sh"
|
||||
],
|
||||
"registry": {
|
||||
"plan": [
|
||||
"10_plan.sh"
|
||||
],
|
||||
"apply": [
|
||||
"11_apply.sh"
|
||||
],
|
||||
"rollback": [
|
||||
"rollback/undo.sh"
|
||||
]
|
||||
},
|
||||
"verify": [
|
||||
"90_verify.sh"
|
||||
],
|
||||
"report": [
|
||||
"99_report.sh"
|
||||
]
|
||||
},
|
||||
"eu_compliance": {
|
||||
"data_residency": "EU",
|
||||
"jurisdiction": "Ireland",
|
||||
"gdpr_applicable": true
|
||||
}
|
||||
}
|
||||
17
container-registry/references/registry_notes.md
Normal file
17
container-registry/references/registry_notes.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# OCI Registry Notes
|
||||
|
||||
## Why Self-Host
|
||||
- Full sovereignty over artifacts
|
||||
- Pair with Gitea for source + images
|
||||
|
||||
## TLS
|
||||
Terminate TLS via:
|
||||
- Reverse proxy (nginx/Traefik)
|
||||
- Cloudflare Tunnel
|
||||
|
||||
## Signing (Next)
|
||||
Integrate:
|
||||
- cosign (Sigstore)
|
||||
- Notation v2
|
||||
|
||||
These will be added as future skills or extensions.
|
||||
23
container-registry/scripts/00_preflight.sh
Normal file
23
container-registry/scripts/00_preflight.sh
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
source "$SCRIPT_DIR/_common.sh"
|
||||
|
||||
: "${REGISTRY_USER:=}"
|
||||
: "${DATA_DIR:=$HOME/registry}"
|
||||
: "${AUTH_DIR:=$HOME/registry/auth}"
|
||||
: "${REGISTRY_PORT:=5000}"
|
||||
|
||||
main() {
|
||||
[[ -n "$REGISTRY_USER" ]] || die "REGISTRY_USER is required."
|
||||
need docker
|
||||
need curl
|
||||
need htpasswd || log_warn "htpasswd not found (apache2-utils). Will attempt install guidance only."
|
||||
|
||||
mkdir -p "$SKILL_ROOT/outputs" "$SKILL_ROOT/outputs/backups"
|
||||
mkdir -p "$DATA_DIR" "$AUTH_DIR"
|
||||
|
||||
log_info "Preflight OK."
|
||||
}
|
||||
main "$@"
|
||||
21
container-registry/scripts/10_plan.sh
Normal file
21
container-registry/scripts/10_plan.sh
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
source "$SCRIPT_DIR/_common.sh"
|
||||
|
||||
: "${REGISTRY_PORT:=5000}"
|
||||
: "${DATA_DIR:=$HOME/registry}"
|
||||
: "${AUTH_DIR:=$HOME/registry/auth}"
|
||||
: "${REGISTRY_USER:=}"
|
||||
|
||||
main() {
|
||||
[[ -n "$REGISTRY_USER" ]] || die "REGISTRY_USER is required."
|
||||
echo "[PLAN] $(date -Iseconds) Container Registry"
|
||||
echo "[PLAN] Port: $REGISTRY_PORT"
|
||||
echo "[PLAN] Data: $DATA_DIR"
|
||||
echo "[PLAN] Auth: $AUTH_DIR (basic auth)"
|
||||
echo "[PLAN] Compose file: outputs/compose.yml"
|
||||
echo "[PLAN] Next: export DRY_RUN=0 && ./scripts/11_apply.sh"
|
||||
}
|
||||
main "$@"
|
||||
63
container-registry/scripts/11_apply.sh
Normal file
63
container-registry/scripts/11_apply.sh
Normal file
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
source "$SCRIPT_DIR/_common.sh"
|
||||
|
||||
: "${REGISTRY_PORT:=5000}"
|
||||
: "${DATA_DIR:=$HOME/registry}"
|
||||
: "${AUTH_DIR:=$HOME/registry/auth}"
|
||||
: "${REGISTRY_USER:=}"
|
||||
|
||||
compose_cmd() {
|
||||
if command -v docker-compose >/dev/null 2>&1; then
|
||||
echo "docker-compose"
|
||||
else
|
||||
echo "docker compose"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
confirm_gate
|
||||
need docker
|
||||
[[ -n "$REGISTRY_USER" ]] || die "REGISTRY_USER is required."
|
||||
|
||||
local ts; ts="$(date -Iseconds | tr ':' '-')"
|
||||
local backup_dir="$SKILL_ROOT/outputs/backups/$ts"
|
||||
mkdir -p "$backup_dir"
|
||||
|
||||
# Auth
|
||||
if command -v htpasswd >/dev/null 2>&1; then
|
||||
log_warn "Creating htpasswd entry for $REGISTRY_USER"
|
||||
htpasswd -B -c "$AUTH_DIR/htpasswd" "$REGISTRY_USER"
|
||||
cp -a "$AUTH_DIR/htpasswd" "$backup_dir/htpasswd"
|
||||
else
|
||||
die "htpasswd not available; install apache2-utils."
|
||||
fi
|
||||
|
||||
# Compose
|
||||
cat > "$SKILL_ROOT/outputs/compose.yml" <<EOF
|
||||
version: "3"
|
||||
services:
|
||||
registry:
|
||||
image: registry:2
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "${REGISTRY_PORT}:5000"
|
||||
environment:
|
||||
REGISTRY_AUTH: htpasswd
|
||||
REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
|
||||
REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
|
||||
volumes:
|
||||
- ${DATA_DIR}:/var/lib/registry
|
||||
- ${AUTH_DIR}:/auth
|
||||
EOF
|
||||
|
||||
cp -a "$SKILL_ROOT/outputs/compose.yml" "$backup_dir/compose.yml"
|
||||
|
||||
cd "$SKILL_ROOT/outputs"
|
||||
$(compose_cmd) -f compose.yml up -d
|
||||
|
||||
log_info "Registry started on port $REGISTRY_PORT"
|
||||
}
|
||||
main "$@"
|
||||
42
container-registry/scripts/90_verify.sh
Normal file
42
container-registry/scripts/90_verify.sh
Normal file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
source "$SCRIPT_DIR/_common.sh"
|
||||
|
||||
: "${REGISTRY_PORT:=5000}"
|
||||
|
||||
main() {
|
||||
local status="$SKILL_ROOT/outputs/status_matrix.json"
|
||||
local ok_container=false ok_http=false
|
||||
|
||||
if docker ps --format '{{.Names}}' | grep -q registry; then ok_container=true; fi
|
||||
if curl -fsS "http://127.0.0.1:${REGISTRY_PORT}/v2/" >/dev/null 2>&1; then ok_http=true; fi
|
||||
|
||||
blockers="[]"
|
||||
if [[ "$ok_container" != "true" ]]; then blockers='["registry_not_running"]'
|
||||
elif [[ "$ok_http" != "true" ]]; then blockers='["registry_http_unreachable"]'
|
||||
fi
|
||||
|
||||
cat > "$status" <<EOF
|
||||
{
|
||||
"skill": "container-registry",
|
||||
"timestamp": "$(date -Iseconds)",
|
||||
"checks": [
|
||||
{"name":"container_running", "ok": $ok_container},
|
||||
{"name":"registry_http", "ok": $ok_http}
|
||||
],
|
||||
"blockers": $blockers,
|
||||
"warnings": [],
|
||||
"next_steps": [
|
||||
"Configure TLS via reverse proxy or tunnel",
|
||||
"Integrate image signing (cosign/notation)",
|
||||
"Proceed to dns-sovereign"
|
||||
]
|
||||
}
|
||||
EOF
|
||||
|
||||
log_info "Wrote $status"
|
||||
cat "$status"
|
||||
}
|
||||
main "$@"
|
||||
61
container-registry/scripts/99_report.sh
Normal file
61
container-registry/scripts/99_report.sh
Normal file
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
source "$SCRIPT_DIR/_common.sh"
|
||||
|
||||
: "${REGISTRY_PORT:=5000}"
|
||||
: "${DATA_DIR:=$HOME/registry}"
|
||||
|
||||
main() {
|
||||
mkdir -p "$SKILL_ROOT/outputs"
|
||||
local report="$SKILL_ROOT/outputs/audit_report.md"
|
||||
local status="$SKILL_ROOT/outputs/status_matrix.json"
|
||||
|
||||
cat > "$report" <<EOF
|
||||
# Container Registry Audit Report
|
||||
|
||||
**Generated:** $(date -Iseconds)
|
||||
**Port:** $REGISTRY_PORT
|
||||
**Data Dir:** $DATA_DIR
|
||||
**Skill Version:** 1.0.0
|
||||
|
||||
---
|
||||
|
||||
## Artifacts
|
||||
|
||||
| Item | Path |
|
||||
|---|---|
|
||||
| Compose | \`$SKILL_ROOT/outputs/compose.yml\` |
|
||||
| Status Matrix | \`$SKILL_ROOT/outputs/status_matrix.json\` |
|
||||
| Backups | \`$SKILL_ROOT/outputs/backups/\` |
|
||||
|
||||
---
|
||||
|
||||
## Status Matrix
|
||||
|
||||
$(if [[ -f "$status" ]]; then
|
||||
echo '```json'
|
||||
cat "$status"
|
||||
echo '```'
|
||||
else
|
||||
echo "_Missing status_matrix.json — run 90_verify.sh first._"
|
||||
fi)
|
||||
|
||||
---
|
||||
|
||||
## EU Compliance Declaration
|
||||
|
||||
| Aspect | Value |
|
||||
|---|---|
|
||||
| Data Residency | EU (Ireland - Dublin) |
|
||||
| Jurisdiction | Irish Law |
|
||||
| Registry | Self-hosted |
|
||||
| Access | Authenticated (basic) |
|
||||
|
||||
EOF
|
||||
|
||||
log_info "Wrote $report"
|
||||
cat "$report"
|
||||
}
|
||||
main "$@"
|
||||
19
container-registry/scripts/_common.sh
Normal file
19
container-registry/scripts/_common.sh
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
log_info(){ echo "[INFO] $(date -Iseconds) $*"; }
|
||||
log_warn(){ echo "[WARN] $(date -Iseconds) $*" >&2; }
|
||||
log_error(){ echo "[ERROR] $(date -Iseconds) $*" >&2; }
|
||||
die(){ log_error "$*"; exit 1; }
|
||||
need(){ command -v "$1" >/dev/null 2>&1 || die "Missing required tool: $1"; }
|
||||
confirm_gate() {
|
||||
: "${DRY_RUN:=1}"
|
||||
: "${REQUIRE_CONFIRM:=1}"
|
||||
: "${CONFIRM_PHRASE:=I UNDERSTAND THIS WILL DEPLOY A CONTAINER REGISTRY}"
|
||||
[[ "$DRY_RUN" == "0" ]] || die "DRY_RUN=$DRY_RUN (set DRY_RUN=0)."
|
||||
if [[ "$REQUIRE_CONFIRM" == "1" ]]; then
|
||||
echo "Type to confirm:"
|
||||
echo " $CONFIRM_PHRASE"
|
||||
read -r input
|
||||
[[ "$input" == "$CONFIRM_PHRASE" ]] || die "Confirmation phrase mismatch."
|
||||
fi
|
||||
}
|
||||
15
container-registry/scripts/rollback/undo.sh
Normal file
15
container-registry/scripts/rollback/undo.sh
Normal file
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
SKILL_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
|
||||
source "$SKILL_ROOT/scripts/_common.sh"
|
||||
|
||||
main() {
|
||||
confirm_gate
|
||||
if docker ps --format '{{.Names}}' | grep -q registry; then
|
||||
log_warn "Stopping registry container..."
|
||||
docker rm -f registry || true
|
||||
fi
|
||||
log_info "Rollback complete. Data preserved."
|
||||
}
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user