Compare commits
5 commits
289f389398
...
ec58cb1745
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec58cb1745 | ||
|
|
1b52761336 | ||
| d70301766c | |||
|
|
e351e02f60 | ||
|
|
3d84390a54 |
3 changed files with 90 additions and 53 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -22,3 +22,6 @@ metrics/supervisor-metrics.jsonl
|
||||||
.DS_Store
|
.DS_Store
|
||||||
dev/ci-fixes-*.json
|
dev/ci-fixes-*.json
|
||||||
gardener/dust.jsonl
|
gardener/dust.jsonl
|
||||||
|
|
||||||
|
# Individual encrypted secrets (managed by disinto secrets add)
|
||||||
|
secrets/
|
||||||
|
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
# .woodpecker/smoke-init.yml — End-to-end smoke test for disinto init
|
|
||||||
#
|
|
||||||
# Uses the Forgejo image directly (not as a service) so we have CLI
|
|
||||||
# access to set up Forgejo and create the bootstrap admin user.
|
|
||||||
# Then runs disinto init --bare --yes against the local Forgejo instance.
|
|
||||||
#
|
|
||||||
# Forgejo refuses to run as root, so all forgejo commands use su-exec
|
|
||||||
# to run as the 'git' user (pre-created in the Forgejo Docker image).
|
|
||||||
|
|
||||||
when:
|
|
||||||
- event: pull_request
|
|
||||||
path:
|
|
||||||
- "bin/disinto"
|
|
||||||
- "lib/load-project.sh"
|
|
||||||
- "tests/smoke-init.sh"
|
|
||||||
- ".woodpecker/smoke-init.yml"
|
|
||||||
- "docker/**"
|
|
||||||
- event: push
|
|
||||||
branch: main
|
|
||||||
path:
|
|
||||||
- "bin/disinto"
|
|
||||||
- "lib/load-project.sh"
|
|
||||||
- "tests/smoke-init.sh"
|
|
||||||
- ".woodpecker/smoke-init.yml"
|
|
||||||
- "docker/**"
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: smoke-init
|
|
||||||
image: codeberg.org/forgejo/forgejo:11.0
|
|
||||||
environment:
|
|
||||||
SMOKE_FORGE_URL: http://localhost:3000
|
|
||||||
commands:
|
|
||||||
# Install test dependencies (Alpine-based image)
|
|
||||||
- apk add --no-cache bash curl jq python3 git >/dev/null 2>&1
|
|
||||||
# Set up Forgejo data directories and config (owned by git user)
|
|
||||||
- mkdir -p /data/gitea/conf /data/gitea/repositories /data/gitea/lfs /data/gitea/log /data/git/.ssh /data/ssh
|
|
||||||
- printf '[database]\nDB_TYPE = sqlite3\nPATH = /data/gitea/forgejo.db\n\n[server]\nHTTP_PORT = 3000\nROOT_URL = http://localhost:3000/\nLFS_START_SERVER = false\n\n[security]\nINSTALL_LOCK = true\n\n[service]\nDISABLE_REGISTRATION = true\n' > /data/gitea/conf/app.ini
|
|
||||||
- chown -R git:git /data
|
|
||||||
# Start Forgejo as git user in background and wait for API
|
|
||||||
- su-exec git forgejo web --config /data/gitea/conf/app.ini &
|
|
||||||
- for i in $(seq 1 30); do curl -sf http://localhost:3000/api/v1/version >/dev/null 2>&1 && break; sleep 1; done
|
|
||||||
# Create bootstrap admin user via CLI
|
|
||||||
- su-exec git forgejo admin user create --admin --username setup-admin --password "SetupPass-789xyz" --email "setup-admin@smoke.test" --must-change-password=false --config /data/gitea/conf/app.ini
|
|
||||||
# Run the smoke test (as root is fine — only forgejo binary needs git user)
|
|
||||||
- bash tests/smoke-init.sh
|
|
||||||
95
bin/disinto
95
bin/disinto
|
|
@ -232,6 +232,7 @@ services:
|
||||||
- ${HOME}/.claude.json:/home/agent/.claude.json:ro
|
- ${HOME}/.claude.json:/home/agent/.claude.json:ro
|
||||||
- CLAUDE_BIN_PLACEHOLDER:/usr/local/bin/claude:ro
|
- CLAUDE_BIN_PLACEHOLDER:/usr/local/bin/claude:ro
|
||||||
- \${HOME}/.ssh:/home/agent/.ssh:ro
|
- \${HOME}/.ssh:/home/agent/.ssh:ro
|
||||||
|
- \${HOME}/.config/sops/age:/home/agent/.config/sops/age:ro
|
||||||
environment:
|
environment:
|
||||||
FORGE_URL: http://forgejo:3000
|
FORGE_URL: http://forgejo:3000
|
||||||
WOODPECKER_SERVER: http://woodpecker:8000
|
WOODPECKER_SERVER: http://woodpecker:8000
|
||||||
|
|
@ -2022,7 +2023,88 @@ disinto_secrets() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local secrets_dir="${FACTORY_ROOT}/secrets"
|
||||||
|
local age_key_file="${HOME}/.config/sops/age/keys.txt"
|
||||||
|
|
||||||
|
# Shared helper: ensure age key exists and export AGE_PUBLIC_KEY
|
||||||
|
_secrets_ensure_age_key() {
|
||||||
|
if ! command -v age &>/dev/null; then
|
||||||
|
echo "Error: age is required." >&2
|
||||||
|
echo " Install age: apt install age / brew install age" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ ! -f "$age_key_file" ]; then
|
||||||
|
echo "Error: age key not found at ${age_key_file}" >&2
|
||||||
|
echo " Run 'disinto init' to generate one, or create manually with:" >&2
|
||||||
|
echo " mkdir -p ~/.config/sops/age && age-keygen -o ${age_key_file}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
AGE_PUBLIC_KEY="$(age-keygen -y "$age_key_file" 2>/dev/null)"
|
||||||
|
if [ -z "$AGE_PUBLIC_KEY" ]; then
|
||||||
|
echo "Error: failed to read public key from ${age_key_file}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
export AGE_PUBLIC_KEY
|
||||||
|
}
|
||||||
|
|
||||||
case "$subcmd" in
|
case "$subcmd" in
|
||||||
|
add)
|
||||||
|
local name="${2:-}"
|
||||||
|
if [ -z "$name" ]; then
|
||||||
|
echo "Usage: disinto secrets add <NAME>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
_secrets_ensure_age_key
|
||||||
|
mkdir -p "$secrets_dir"
|
||||||
|
|
||||||
|
printf 'Enter value for %s: ' "$name" >&2
|
||||||
|
local value
|
||||||
|
IFS= read -rs value
|
||||||
|
echo >&2
|
||||||
|
if [ -z "$value" ]; then
|
||||||
|
echo "Error: empty value" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local enc_path="${secrets_dir}/${name}.enc"
|
||||||
|
if [ -f "$enc_path" ]; then
|
||||||
|
printf 'Secret %s already exists. Overwrite? [y/N] ' "$name" >&2
|
||||||
|
local confirm
|
||||||
|
read -r confirm
|
||||||
|
if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then
|
||||||
|
echo "Aborted." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if ! printf '%s' "$value" | age -r "$AGE_PUBLIC_KEY" -o "$enc_path"; then
|
||||||
|
echo "Error: encryption failed" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Stored: ${enc_path}"
|
||||||
|
;;
|
||||||
|
show)
|
||||||
|
local name="${2:-}"
|
||||||
|
if [ -n "$name" ]; then
|
||||||
|
# Show individual secret: disinto secrets show <NAME>
|
||||||
|
local enc_path="${secrets_dir}/${name}.enc"
|
||||||
|
if [ ! -f "$enc_path" ]; then
|
||||||
|
echo "Error: ${enc_path} not found" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ ! -f "$age_key_file" ]; then
|
||||||
|
echo "Error: age key not found at ${age_key_file}" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
age -d -i "$age_key_file" "$enc_path"
|
||||||
|
else
|
||||||
|
# Show all agent secrets: disinto secrets show
|
||||||
|
if [ ! -f "$enc_file" ]; then
|
||||||
|
echo "Error: ${enc_file} not found." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
sops -d "$enc_file"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
edit)
|
edit)
|
||||||
if [ ! -f "$enc_file" ]; then
|
if [ ! -f "$enc_file" ]; then
|
||||||
echo "Error: ${enc_file} not found. Run 'disinto secrets migrate' first." >&2
|
echo "Error: ${enc_file} not found. Run 'disinto secrets migrate' first." >&2
|
||||||
|
|
@ -2030,13 +2112,6 @@ disinto_secrets() {
|
||||||
fi
|
fi
|
||||||
sops "$enc_file"
|
sops "$enc_file"
|
||||||
;;
|
;;
|
||||||
show)
|
|
||||||
if [ ! -f "$enc_file" ]; then
|
|
||||||
echo "Error: ${enc_file} not found." >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
sops -d "$enc_file"
|
|
||||||
;;
|
|
||||||
migrate)
|
migrate)
|
||||||
if [ ! -f "$env_file" ]; then
|
if [ ! -f "$env_file" ]; then
|
||||||
echo "Error: ${env_file} not found — nothing to migrate." >&2
|
echo "Error: ${env_file} not found — nothing to migrate." >&2
|
||||||
|
|
@ -2076,9 +2151,13 @@ disinto_secrets() {
|
||||||
cat <<EOF >&2
|
cat <<EOF >&2
|
||||||
Usage: disinto secrets <subcommand>
|
Usage: disinto secrets <subcommand>
|
||||||
|
|
||||||
|
Individual secrets (secrets/<NAME>.enc):
|
||||||
|
add <NAME> Prompt for value, encrypt, store in secrets/<NAME>.enc
|
||||||
|
show <NAME> Decrypt and print an individual secret
|
||||||
|
|
||||||
Agent secrets (.env.enc):
|
Agent secrets (.env.enc):
|
||||||
edit Edit agent secrets (FORGE_TOKEN, CLAUDE_API_KEY, etc.)
|
edit Edit agent secrets (FORGE_TOKEN, CLAUDE_API_KEY, etc.)
|
||||||
show Show decrypted agent secrets
|
show Show decrypted agent secrets (no argument)
|
||||||
migrate Encrypt .env -> .env.enc
|
migrate Encrypt .env -> .env.enc
|
||||||
|
|
||||||
Vault secrets (.env.vault.enc):
|
Vault secrets (.env.vault.enc):
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue