unrip/scripts/deploy/bootstrap.sh
philipp c5a214ce06
Some checks failed
deploy / deploy (push) Failing after 3m30s
Notify on durable fund and trade outcomes
Proof: npm test; npm run operator-dashboard:build; PYTHONPATH=. python3 test/render_release_manifest_test.py; PYTHONPATH=. python3 test/repo_deployments_test.py; PYTHONPATH=. python3 test/ntfy_manifest_test.py; kubectl kustomize deploy/k8s/base.

Assumptions: notifications should be emitted by history-writer after durable writes and outcome refreshes, and only for credited deposits, completed withdrawals, and completed trades with linked inventory movement evidence.

Still fake: Generic alert notification policy is not re-enabled; withdrawal submitted notifications are not emitted; old historical outcomes are not backfilled as notifications; fee-complete realized PnL is still unavailable.
2026-04-16 14:23:29 +02:00

251 lines
8.1 KiB
Bash
Executable file

#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR=$(cd "$(dirname "$0")/../.." && pwd)
PLATFORM_REPO_DIR="${PLATFORM_REPO_DIR:-$ROOT_DIR/../unrip3}"
BOOTSTRAP_ENV_FILE="${BOOTSTRAP_ENV_FILE:-$PLATFORM_REPO_DIR/.state/hetzner/bootstrap-secrets.resolved.env}"
PLATFORM_APP_ENV_FILE="${PLATFORM_APP_ENV_FILE:-$PLATFORM_REPO_DIR/.env}"
APP_ENV_FILE="${APP_ENV_FILE:-$ROOT_DIR/.env}"
FORGEJO_REMOTE_NAME="${FORGEJO_REMOTE_NAME:-forgejo}"
PROJECT_NAME="${PROJECT_NAME:-unrip}"
PROJECT_NAMESPACE="${PROJECT_NAMESPACE:-$PROJECT_NAME}"
PROJECT_DEPLOYMENTS="${PROJECT_DEPLOYMENTS:-$(python3 "$ROOT_DIR/scripts/deploy/repo_deployments.py" --format csv)}"
PROJECT_REGISTRY_SECRET_NAME="${PROJECT_REGISTRY_SECRET_NAME:-${PROJECT_NAME}-registry-creds}"
APP_SECRET_NAME="${APP_SECRET_NAME:-${PROJECT_NAME}-secrets}"
SYNC_FORGEJO_REMOTE="${SYNC_FORGEJO_REMOTE:-1}"
require() {
command -v "$1" >/dev/null 2>&1 || {
echo "missing required command: $1" >&2
exit 1
}
}
load_env_defaults() {
local file="$1"
[[ -f "$file" ]] || return 0
eval "$(
python3 - "$file" <<'PY'
import os
import shlex
import sys
for raw in open(sys.argv[1], 'r', encoding='utf-8'):
line = raw.strip()
if not line or line.startswith('#'):
continue
if line.startswith('export '):
line = line[len('export '):]
if '=' not in line:
continue
key, value = line.split('=', 1)
key = key.strip()
if key in os.environ:
continue
print(f'export {key}={shlex.quote(value)}')
PY
)"
}
require git
require kubectl
require python3
require base64
load_env_defaults "$BOOTSTRAP_ENV_FILE"
load_env_defaults "$PLATFORM_APP_ENV_FILE"
load_env_defaults "$APP_ENV_FILE"
REMOTE_URL="${FORGEJO_REMOTE_URL:-$(git -C "$ROOT_DIR" remote get-url "$FORGEJO_REMOTE_NAME")}"
eval "$(
python3 - "$REMOTE_URL" <<'PY'
import sys
from urllib.parse import urlparse
remote = sys.argv[1]
parsed = urlparse(remote)
if parsed.scheme not in {'http', 'https'}:
raise SystemExit('forgejo remote must use http(s) for automatic bootstrap')
path = parsed.path.rstrip('/')
if path.endswith('.git'):
path = path[:-4]
parts = [part for part in path.split('/') if part]
if len(parts) < 2:
raise SystemExit('could not parse repo owner/name from forgejo remote')
base_url = f'{parsed.scheme}://{parsed.hostname}'
if parsed.port:
base_url += f':{parsed.port}'
if parsed.username and parsed.password:
print(f'export FORGEJO_API_PASSWORD={parsed.password}')
if parsed.username:
print(f'export FORGEJO_API_USERNAME={parsed.username}')
print(f'export FORGEJO_URL={base_url}')
print(f'export FORGEJO_REPO_OWNER={parts[-2]}')
print(f'export FORGEJO_REPO_NAME={parts[-1]}')
PY
)"
: "${KUBECONFIG_PATH:=${KUBECONFIG:-$PLATFORM_REPO_DIR/.state/hetzner/kubeconfig.yaml}}"
: "${CI_KUBECONFIG_PATH:=$PLATFORM_REPO_DIR/.state/hetzner/kubeconfig.incluster.yaml}"
: "${FORGEJO_URL:?set FORGEJO_URL or configure a forgejo remote}"
: "${FORGEJO_REPO_OWNER:?set FORGEJO_REPO_OWNER}"
: "${FORGEJO_REPO_NAME:?set FORGEJO_REPO_NAME}"
: "${FORGEJO_ADMIN_USERNAME:=${FORGEJO_API_USERNAME:-}}"
if [[ -z "${FORGEJO_TOKEN:-}" ]]; then
: "${FORGEJO_ADMIN_USERNAME:=${FORGEJO_API_USERNAME:-}}"
: "${FORGEJO_ADMIN_USERNAME:?set FORGEJO_TOKEN or FORGEJO_ADMIN_USERNAME}"
: "${FORGEJO_ADMIN_PASSWORD:=${FORGEJO_API_PASSWORD:-}}"
: "${FORGEJO_ADMIN_PASSWORD:?set FORGEJO_TOKEN or FORGEJO_ADMIN_PASSWORD}"
fi
if [[ ! -f "$KUBECONFIG_PATH" ]]; then
echo "missing kubeconfig: $KUBECONFIG_PATH" >&2
exit 1
fi
if [[ ! -f "$CI_KUBECONFIG_PATH" ]]; then
echo "missing in-cluster kubeconfig: $CI_KUBECONFIG_PATH" >&2
exit 1
fi
export KUBECONFIG="$KUBECONFIG_PATH"
if [[ -z "${REGISTRY_HOST:-}" ]]; then
REGISTRY_HOST="${REGISTRY_DOMAIN:-}"
fi
if [[ -z "${REGISTRY_HOST:-}" ]]; then
REGISTRY_HOST="$(kubectl get ingress -n registry registry -o jsonpath='{.spec.rules[0].host}' 2>/dev/null || true)"
fi
if [[ -z "${REGISTRY_USERNAME:-}" ]]; then
REGISTRY_USERNAME="$(kubectl get secret registry-secrets -n registry -o jsonpath='{.data.htpasswd}' 2>/dev/null | base64 -d | cut -d: -f1 || true)"
fi
: "${REGISTRY_HOST:?set REGISTRY_HOST or configure the registry ingress first}"
: "${REGISTRY_USERNAME:?set REGISTRY_USERNAME or bootstrap the shared registry first}"
: "${REGISTRY_PASSWORD:?set REGISTRY_PASSWORD}"
: "${NEAR_INTENTS_API_KEY:?set NEAR_INTENTS_API_KEY}"
: "${POSTGRES_PASSWORD:=}"
: "${POSTGRES_URL:=}"
: "${NEAR_INTENTS_SIGNER_PRIVATE_KEY:=}"
: "${NOTIFICATION_NTFY_TOKEN:=}"
secret_value() {
local key="$1"
kubectl -n "$PROJECT_NAMESPACE" get secret "$APP_SECRET_NAME" -o "jsonpath={.data.${key}}" 2>/dev/null | base64 -d 2>/dev/null || true
}
if [[ -z "$POSTGRES_PASSWORD" ]]; then
POSTGRES_PASSWORD="$(secret_value POSTGRES_PASSWORD)"
fi
if [[ -z "$POSTGRES_URL" ]]; then
POSTGRES_URL="$(secret_value POSTGRES_URL)"
fi
if [[ -z "$NEAR_INTENTS_SIGNER_PRIVATE_KEY" ]]; then
NEAR_INTENTS_SIGNER_PRIVATE_KEY="$(secret_value NEAR_INTENTS_SIGNER_PRIVATE_KEY)"
fi
if [[ -z "$NOTIFICATION_NTFY_TOKEN" ]]; then
NOTIFICATION_NTFY_TOKEN="$(secret_value NOTIFICATION_NTFY_TOKEN)"
fi
if [[ -z "$POSTGRES_PASSWORD" ]]; then
POSTGRES_PASSWORD="$(python3 - <<'PY'
import secrets
print(secrets.token_urlsafe(24))
PY
)"
fi
if [[ -z "$POSTGRES_URL" ]]; then
POSTGRES_URL="postgresql://unrip:${POSTGRES_PASSWORD}@postgres:5432/unrip"
fi
echo "bootstrapping namespace $PROJECT_NAMESPACE"
kubectl apply -f "$ROOT_DIR/deploy/k8s/base/namespace.yaml"
echo "upserting runtime secret $APP_SECRET_NAME"
secret_args=(
--from-literal=NEAR_INTENTS_API_KEY="$NEAR_INTENTS_API_KEY"
--from-literal=POSTGRES_PASSWORD="$POSTGRES_PASSWORD"
--from-literal=POSTGRES_URL="$POSTGRES_URL"
)
if [[ -n "$NEAR_INTENTS_SIGNER_PRIVATE_KEY" ]]; then
secret_args+=(--from-literal=NEAR_INTENTS_SIGNER_PRIVATE_KEY="$NEAR_INTENTS_SIGNER_PRIVATE_KEY")
fi
if [[ -n "$NOTIFICATION_NTFY_TOKEN" ]]; then
secret_args+=(--from-literal=NOTIFICATION_NTFY_TOKEN="$NOTIFICATION_NTFY_TOKEN")
fi
kubectl -n "$PROJECT_NAMESPACE" create secret generic "$APP_SECRET_NAME" \
"${secret_args[@]}" \
--dry-run=client -o yaml | kubectl apply -f -
echo "upserting registry pull/push secret $PROJECT_REGISTRY_SECRET_NAME"
kubectl -n "$PROJECT_NAMESPACE" create secret docker-registry "$PROJECT_REGISTRY_SECRET_NAME" \
--docker-server="$REGISTRY_HOST" \
--docker-username="$REGISTRY_USERNAME" \
--docker-password="$REGISTRY_PASSWORD" \
--dry-run=client -o yaml | kubectl apply -f -
echo "applying app manifests"
kubectl apply -k "$ROOT_DIR/deploy/k8s/base"
echo "upserting Forgejo repo settings"
forgejo_args=()
if [[ -n "${FORGEJO_TOKEN:-}" ]]; then
forgejo_args+=(--token "$FORGEJO_TOKEN")
fi
if [[ -n "${FORGEJO_ADMIN_USERNAME:-}" ]]; then
forgejo_args+=(--admin-username "$FORGEJO_ADMIN_USERNAME")
fi
if [[ -n "${FORGEJO_ADMIN_PASSWORD:-}" ]]; then
forgejo_args+=(--admin-password "$FORGEJO_ADMIN_PASSWORD")
fi
python3 "$ROOT_DIR/scripts/deploy/forgejo_repo_bootstrap.py" \
--forgejo-url "$FORGEJO_URL" \
--repo-owner "$FORGEJO_REPO_OWNER" \
--repo-name "$FORGEJO_REPO_NAME" \
--ci-kubeconfig "$CI_KUBECONFIG_PATH" \
--registry-host "$REGISTRY_HOST" \
--project-name "$PROJECT_NAME" \
--project-namespace "$PROJECT_NAMESPACE" \
--project-deployments "$PROJECT_DEPLOYMENTS" \
--project-registry-secret-name "$PROJECT_REGISTRY_SECRET_NAME" \
"${forgejo_args[@]}"
if [[ "$SYNC_FORGEJO_REMOTE" == "1" ]]; then
: "${FORGEJO_PUSH_USERNAME:=${FORGEJO_API_USERNAME:-$FORGEJO_REPO_OWNER}}"
: "${FORGEJO_PUSH_PASSWORD:=${FORGEJO_ADMIN_PASSWORD:-${FORGEJO_API_PASSWORD:-}}}"
if [[ -n "${FORGEJO_PUSH_USERNAME:-}" && -n "${FORGEJO_PUSH_PASSWORD:-}" ]]; then
push_url="$(
python3 - "$FORGEJO_URL" "$FORGEJO_REPO_OWNER" "$FORGEJO_REPO_NAME" "$FORGEJO_PUSH_USERNAME" "$FORGEJO_PUSH_PASSWORD" <<'PY'
import sys
from urllib.parse import quote
base_url, owner, repo, username, password = sys.argv[1:]
print(f"{base_url.rstrip('/')}".replace('://', f'://{quote(username, safe="")}:{quote(password, safe="")}@') + f'/{owner}/{repo}.git')
PY
)"
git -C "$ROOT_DIR" remote set-url "$FORGEJO_REMOTE_NAME" "$push_url"
echo "updated git remote $FORGEJO_REMOTE_NAME for HTTPS push auth"
fi
fi
cat <<EOF
bootstrap complete
next:
git commit -am "..."
git push $FORGEJO_REMOTE_NAME main
EOF