- Add skills for dev-environment debugging, jellycompat diagnosis, Discord triage, and web UI testing under .claude/skills (symlinked as .agents/skills) - Add scripts/silo-dev plus .silo-dev.env.example for driving local or remote Silo deployments - Ignore .silo-dev.env and reference the shared skill config in AGENTS.md
188 lines
5.0 KiB
Bash
Executable File
188 lines
5.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Talk to a running Silo deployment — local or over SSH — using the settings in
|
|
# `.silo-dev.env`. Run `scripts/silo-dev help` for the subcommands.
|
|
set -euo pipefail
|
|
|
|
repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
env_file=${SILO_DEV_ENV_FILE:-"$repo_root/.silo-dev.env"}
|
|
|
|
if [[ -f "$env_file" ]]; then
|
|
set -a
|
|
# shellcheck disable=SC1090
|
|
. "$env_file"
|
|
set +a
|
|
fi
|
|
|
|
SILO_URL=${SILO_URL:-http://localhost:8090}
|
|
SILO_SSH=${SILO_SSH:-}
|
|
SILO_DIR=${SILO_DIR:-/opt/silo}
|
|
SILO_COMPOSE_FILE=${SILO_COMPOSE_FILE:-docker-compose.yml}
|
|
SILO_SERVICE=${SILO_SERVICE:-silo}
|
|
SILO_POSTGRES_SERVICE=${SILO_POSTGRES_SERVICE:-postgres}
|
|
SILO_DB_USER=${SILO_DB_USER:-silo}
|
|
SILO_DB_NAME=${SILO_DB_NAME:-silo}
|
|
|
|
die() {
|
|
printf 'silo-dev: %s\n' "$1" >&2
|
|
exit 1
|
|
}
|
|
|
|
need_env_file() {
|
|
[[ -f "$env_file" ]] || die "no ${env_file##*/} — copy .silo-dev.env.example to .silo-dev.env and fill it in"
|
|
}
|
|
|
|
# Run a shell command on whichever host Silo runs on.
|
|
on_host() {
|
|
if [[ -n "$SILO_SSH" ]]; then
|
|
ssh "$SILO_SSH" "$@"
|
|
else
|
|
bash -lc "$@"
|
|
fi
|
|
}
|
|
|
|
compose() {
|
|
on_host "cd $(printf '%q' "$SILO_DIR") && COMPOSE_FILE=$(printf '%q' "$SILO_COMPOSE_FILE") docker compose $*"
|
|
}
|
|
|
|
# POST /auth/login and print the access token.
|
|
token() {
|
|
need_env_file
|
|
[[ -n "${SILO_ADMIN_USER:-}" && -n "${SILO_ADMIN_PASSWORD:-}" ]] ||
|
|
die "set SILO_ADMIN_USER and SILO_ADMIN_PASSWORD in ${env_file##*/}"
|
|
|
|
local body
|
|
body=$(SILO_ADMIN_USER="$SILO_ADMIN_USER" SILO_ADMIN_PASSWORD="$SILO_ADMIN_PASSWORD" \
|
|
python3 -c 'import json,os;print(json.dumps({"username":os.environ["SILO_ADMIN_USER"],"password":os.environ["SILO_ADMIN_PASSWORD"]}))')
|
|
|
|
curl -fsS -X POST "$SILO_URL/api/v1/auth/login" \
|
|
-H 'Content-Type: application/json' \
|
|
--data-binary "$body" |
|
|
python3 -c 'import json,sys;print(json.load(sys.stdin)["access_token"])'
|
|
}
|
|
|
|
usage() {
|
|
cat <<'EOF'
|
|
usage: scripts/silo-dev <command> [args]
|
|
|
|
doctor check config and reachability, then report what works
|
|
env print the resolved settings (secrets masked)
|
|
sh <command...> run a shell command on the host running Silo
|
|
compose <args...> docker compose against the deployment's stack
|
|
logs [args...] compose logs for the Silo service (default: --tail 200)
|
|
psql <sql> run one SQL statement against the Silo database
|
|
token print an access token for SILO_ADMIN_USER
|
|
api <path> [curl...] authenticated request against the API, e.g. api /api/v1/health
|
|
|
|
Settings come from .silo-dev.env (see .silo-dev.env.example). With SILO_SSH
|
|
empty, host commands run locally.
|
|
EOF
|
|
}
|
|
|
|
cmd=${1:-help}
|
|
if [[ $# -gt 0 ]]; then shift; fi
|
|
|
|
case "$cmd" in
|
|
help | -h | --help)
|
|
usage
|
|
;;
|
|
|
|
env)
|
|
mask() { [[ -n "${1:-}" ]] && printf 'set' || printf 'empty'; }
|
|
cat <<EOF
|
|
config file $env_file $([[ -f "$env_file" ]] && echo '(found)' || echo '(MISSING)')
|
|
SILO_URL $SILO_URL
|
|
SILO_SSH ${SILO_SSH:-<local>}
|
|
SILO_DIR $SILO_DIR
|
|
SILO_COMPOSE_FILE $SILO_COMPOSE_FILE
|
|
SILO_SERVICE $SILO_SERVICE
|
|
database $SILO_DB_USER@$SILO_POSTGRES_SERVICE/$SILO_DB_NAME
|
|
SILO_ADMIN_USER ${SILO_ADMIN_USER:-<unset>}
|
|
SILO_ADMIN_PASSWORD $(mask "${SILO_ADMIN_PASSWORD:-}")
|
|
SILO_MEDIA_ROOT ${SILO_MEDIA_ROOT:-<unset>}
|
|
EOF
|
|
;;
|
|
|
|
doctor)
|
|
need_env_file
|
|
status=0
|
|
printf 'api '
|
|
if curl -fsS --max-time 10 "$SILO_URL/api/v1/health" >/dev/null 2>&1; then
|
|
printf 'ok (%s)\n' "$SILO_URL"
|
|
else
|
|
printf 'FAIL — %s/api/v1/health did not respond\n' "$SILO_URL"
|
|
status=1
|
|
fi
|
|
|
|
printf 'host '
|
|
if on_host 'true' >/dev/null 2>&1; then
|
|
printf 'ok (%s)\n' "${SILO_SSH:-local}"
|
|
else
|
|
printf 'FAIL — cannot run commands on %s\n' "${SILO_SSH:-local shell}"
|
|
status=1
|
|
fi
|
|
|
|
printf 'compose '
|
|
if compose ps --services >/dev/null 2>&1; then
|
|
printf 'ok (%s in %s)\n' "$SILO_COMPOSE_FILE" "$SILO_DIR"
|
|
else
|
|
printf 'FAIL — no compose stack at %s\n' "$SILO_DIR"
|
|
status=1
|
|
fi
|
|
|
|
printf 'database '
|
|
if compose "exec -T $SILO_POSTGRES_SERVICE psql -U $SILO_DB_USER -d $SILO_DB_NAME -tAc 'select 1'" >/dev/null 2>&1; then
|
|
printf 'ok (%s/%s)\n' "$SILO_DB_USER" "$SILO_DB_NAME"
|
|
else
|
|
printf 'FAIL — psql as %s on %s failed; check POSTGRES_USER/POSTGRES_DB in the deployment .env\n' \
|
|
"$SILO_DB_USER" "$SILO_DB_NAME"
|
|
status=1
|
|
fi
|
|
|
|
printf 'auth '
|
|
if [[ -z "${SILO_ADMIN_USER:-}" ]]; then
|
|
printf 'skipped — SILO_ADMIN_USER not set\n'
|
|
elif token >/dev/null 2>&1; then
|
|
printf 'ok (%s)\n' "$SILO_ADMIN_USER"
|
|
else
|
|
printf 'FAIL — login rejected for %s\n' "$SILO_ADMIN_USER"
|
|
status=1
|
|
fi
|
|
exit "$status"
|
|
;;
|
|
|
|
sh)
|
|
[[ $# -gt 0 ]] || die 'sh needs a command'
|
|
on_host "$*"
|
|
;;
|
|
|
|
compose)
|
|
compose "$*"
|
|
;;
|
|
|
|
logs)
|
|
compose "logs $SILO_SERVICE ${*:---tail 200}"
|
|
;;
|
|
|
|
psql)
|
|
[[ $# -gt 0 ]] || die 'psql needs a SQL statement'
|
|
sql=$*
|
|
compose "exec -T $SILO_POSTGRES_SERVICE psql -U $SILO_DB_USER -d $SILO_DB_NAME -P pager=off -c $(printf '%q' "$sql")"
|
|
;;
|
|
|
|
token)
|
|
token
|
|
;;
|
|
|
|
api)
|
|
[[ $# -gt 0 ]] || die 'api needs a path, e.g. /api/v1/health'
|
|
path=$1
|
|
shift
|
|
curl -fsS -H "Authorization: Bearer $(token)" "$SILO_URL$path" "$@"
|
|
;;
|
|
|
|
*)
|
|
usage >&2
|
|
die "unknown command: $cmd"
|
|
;;
|
|
esac
|