#!/usr/bin/env bash set -euo pipefail [[ "${DEBUG_DEPLOY:-0}" = "1" ]] && set -x trap 'echo "[deploy-vault] error at line $LINENO" >&2' ERR : "${REMOTE_BASE:?missing REMOTE_BASE}" : "${SSH_USER:?missing SSH_USER}" : "${SSH_HOST:?missing SSH_HOST}" : "${VAULT_DIR:?missing VAULT_DIR}" : "${VAULT_COMPOSE_PROJECT:?missing VAULT_COMPOSE_PROJECT}" REMOTE_DIR="${REMOTE_BASE%/}/${VAULT_DIR}" REMOTE_TARGET="${SSH_USER}@${SSH_HOST}" RUNTIME_ENV_FILE="${RUNTIME_ENV_FILE:-ci/prod/.env.runtime}" COMPOSE_FILE="vault.yml" SEED_ENV_FILE="${DEV_VAULT_SEED_FILE:-}" SSH_OPTS=( -i /root/.ssh/id_rsa -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR -o BatchMode=yes -o PreferredAuthentications=publickey -o ConnectTimeout=10 -q ) if [[ "${DEBUG_DEPLOY:-0}" = "1" ]]; then SSH_OPTS=("${SSH_OPTS[@]/-q/}" -vv) fi RSYNC_FLAGS=(-az --delete) [[ "${DEBUG_DEPLOY:-0}" = "1" ]] && RSYNC_FLAGS=(-avz --delete) ssh "${SSH_OPTS[@]}" "$REMOTE_TARGET" "mkdir -p ${REMOTE_DIR}/{compose,env}" rsync "${RSYNC_FLAGS[@]}" -e "ssh ${SSH_OPTS[*]}" ci/prod/compose/ "$REMOTE_TARGET:${REMOTE_DIR}/compose/" rsync "${RSYNC_FLAGS[@]}" -e "ssh ${SSH_OPTS[*]}" "${RUNTIME_ENV_FILE}" "$REMOTE_TARGET:${REMOTE_DIR}/env/.env.runtime" if [[ -n "${SEED_ENV_FILE}" && -f "${SEED_ENV_FILE}" ]]; then rsync "${RSYNC_FLAGS[@]}" -e "ssh ${SSH_OPTS[*]}" "${SEED_ENV_FILE}" "$REMOTE_TARGET:${REMOTE_DIR}/env/dev-vault-seed.env" fi ssh "${SSH_OPTS[@]}" "$REMOTE_TARGET" \ REMOTE_BASE="$REMOTE_BASE" \ REMOTE_DIR="$REMOTE_DIR" \ COMPOSE_FILE="$COMPOSE_FILE" \ COMPOSE_PROJECT="$VAULT_COMPOSE_PROJECT" \ bash -s <<'EOSSH' set -euo pipefail trap 'status=$?; echo "[deploy-vault:remote] error at line $LINENO" >&2; docker logs --tail 50 dev-vault >&2 || true; exit "$status"' ERR cd "${REMOTE_DIR}/compose" set -a . ../env/.env.runtime set +a COMPOSE_PROJECT_NAME="$COMPOSE_PROJECT" export COMPOSE_PROJECT_NAME if base64 -d >/dev/null 2>&1 <<<'AA=='; then BASE64_DECODE_FLAG='-d' else BASE64_DECODE_FLAG='--decode' fi decode_b64() { val="$1" if [[ -z "$val" ]]; then printf '' return fi printf '%s' "$val" | base64 "${BASE64_DECODE_FLAG}" } docker compose -f "$COMPOSE_FILE" pull --quiet 2>/dev/null || docker compose -f "$COMPOSE_FILE" pull docker compose -f "$COMPOSE_FILE" up -d --remove-orphans status_json="" for _ in $(seq 1 30); do status_json="$(docker exec dev-vault sh -lc 'export VAULT_ADDR=http://127.0.0.1:8200; vault status -format=json' 2>/dev/null || true)" if [[ -n "$status_json" ]]; then break fi sleep 2 done if [[ -z "$status_json" ]]; then echo "vault status did not become available" >&2 exit 65 fi INIT_FILE="../env/vault-init.json" if printf '%s' "$status_json" | grep -Eq '"initialized"[[:space:]]*:[[:space:]]*false'; then docker exec dev-vault sh -lc 'export VAULT_ADDR=http://127.0.0.1:8200; vault operator init -format=json -key-shares=1 -key-threshold=1' >"$INIT_FILE" chmod 600 "$INIT_FILE" status_json="$(docker exec dev-vault sh -lc 'export VAULT_ADDR=http://127.0.0.1:8200; vault status -format=json')" fi if [[ ! -s "$INIT_FILE" ]]; then echo "vault init file is missing: $INIT_FILE" >&2 exit 66 fi INIT_JSON_COMPACT="$(tr -d '\r\n\t ' <"$INIT_FILE")" UNSEAL_KEY="$(printf '%s' "$INIT_JSON_COMPACT" | sed -n 's/.*"unseal_keys_b64":\["\([^"]*\)".*/\1/p')" ROOT_TOKEN="$(printf '%s' "$INIT_JSON_COMPACT" | sed -n 's/.*"root_token":"\([^"]*\)".*/\1/p')" if [[ -z "$UNSEAL_KEY" || -z "$ROOT_TOKEN" ]]; then echo "failed to extract vault init credentials" >&2 exit 67 fi if printf '%s' "$status_json" | grep -Eq '"sealed"[[:space:]]*:[[:space:]]*true'; then docker exec dev-vault sh -lc "export VAULT_ADDR=http://127.0.0.1:8200; vault operator unseal '${UNSEAL_KEY}' >/dev/null" fi docker exec dev-vault sh -lc "export VAULT_ADDR=http://127.0.0.1:8200 VAULT_TOKEN='${ROOT_TOKEN}'; vault auth list -format=json | grep -q '\"approle/\"' || vault auth enable approle >/dev/null" docker exec dev-vault sh -lc "export VAULT_ADDR=http://127.0.0.1:8200 VAULT_TOKEN='${ROOT_TOKEN}'; vault secrets list -format=json | grep -q '\"kv/\"' || vault secrets enable -path=kv kv-v2 >/dev/null" if [[ -f ../env/dev-vault-seed.env ]]; then set -a . ../env/dev-vault-seed.env set +a docker exec -e VAULT_ADDR=http://127.0.0.1:8200 -e VAULT_TOKEN="${ROOT_TOKEN}" dev-vault \ vault kv put -mount=kv registry \ user="$(decode_b64 "${REGISTRY_USER_B64:-}")" \ password="$(decode_b64 "${REGISTRY_PASSWORD_B64:-}")" >/dev/null docker exec -e VAULT_ADDR=http://127.0.0.1:8200 -e VAULT_TOKEN="${ROOT_TOKEN}" dev-vault \ vault kv put -mount=kv sendico/db \ user="$(decode_b64 "${SENDICO_DB_USER_B64:-}")" \ password="$(decode_b64 "${SENDICO_DB_PASSWORD_B64:-}")" \ key="$(decode_b64 "${SENDICO_DB_KEY_B64:-}")" >/dev/null docker exec -e VAULT_ADDR=http://127.0.0.1:8200 -e VAULT_TOKEN="${ROOT_TOKEN}" dev-vault \ vault kv put -mount=kv sendico/nats \ user="$(decode_b64 "${SENDICO_NATS_USER_B64:-}")" \ password="$(decode_b64 "${SENDICO_NATS_PASSWORD_B64:-}")" >/dev/null docker exec -e VAULT_ADDR=http://127.0.0.1:8200 -e VAULT_TOKEN="${ROOT_TOKEN}" dev-vault \ vault kv put -mount=kv sendico/api/endpoint \ secret="$(decode_b64 "${SENDICO_API_ENDPOINT_SECRET_B64:-}")" >/dev/null docker exec -e VAULT_ADDR=http://127.0.0.1:8200 -e VAULT_TOKEN="${ROOT_TOKEN}" dev-vault \ vault kv put -mount=kv sendico/notification/mail \ user="$(decode_b64 "${NOTIFICATION_MAIL_USER_B64:-}")" \ password="$(decode_b64 "${NOTIFICATION_MAIL_PASSWORD_B64:-}")" >/dev/null docker exec -e VAULT_ADDR=http://127.0.0.1:8200 -e VAULT_TOKEN="${ROOT_TOKEN}" dev-vault \ vault kv put -mount=kv sendico/notification/telegram \ bot_token="$(decode_b64 "${NOTIFICATION_TELEGRAM_BOT_TOKEN_B64:-}")" \ chat_id="$(decode_b64 "${NOTIFICATION_TELEGRAM_CHAT_ID_B64:-}")" \ thread_id="$(decode_b64 "${NOTIFICATION_TELEGRAM_THREAD_ID_B64:-}")" >/dev/null docker exec -e VAULT_ADDR=http://127.0.0.1:8200 -e VAULT_TOKEN="${ROOT_TOKEN}" dev-vault \ vault kv put -mount=kv sendico/gateway/chain \ arbitrum_rpc_url="$(decode_b64 "${CHAIN_GATEWAY_RPC_URL_B64:-}")" >/dev/null docker exec -e VAULT_ADDR=http://127.0.0.1:8200 -e VAULT_TOKEN="${ROOT_TOKEN}" dev-vault \ vault kv put -mount=kv sendico/gateway/chain/wallet \ private_key="$(decode_b64 "${CHAIN_GATEWAY_WALLET_PRIVATE_KEY_B64:-}")" \ address="$(decode_b64 "${CHAIN_GATEWAY_WALLET_ADDRESS_B64:-}")" >/dev/null docker exec -e VAULT_ADDR=http://127.0.0.1:8200 -e VAULT_TOKEN="${ROOT_TOKEN}" dev-vault \ vault kv put -mount=kv sendico/gateway/tron \ rpc_url="$(decode_b64 "${TRON_GATEWAY_RPC_URL_B64:-}")" \ grpc_url="$(decode_b64 "${TRON_GATEWAY_GRPC_URL_B64:-}")" \ grpc_token="$(decode_b64 "${TRON_GATEWAY_GRPC_TOKEN_B64:-}")" >/dev/null fi docker exec -i dev-vault sh -lc "export VAULT_ADDR=http://127.0.0.1:8200 VAULT_TOKEN='${ROOT_TOKEN}'; vault policy write sendico-dev-apps -" <<'EOF' path "kv/data/*" { capabilities = ["create", "read", "update", "delete", "list"] } path "kv/metadata/*" { capabilities = ["create", "read", "update", "delete", "list"] } EOF docker exec dev-vault sh -lc "export VAULT_ADDR=http://127.0.0.1:8200 VAULT_TOKEN='${ROOT_TOKEN}'; vault write auth/approle/role/sendico-dev-apps token_policies='sendico-dev-apps' token_ttl='24h' token_max_ttl='720h' >/dev/null" APPROLE_ROLE_ID="$(docker exec dev-vault sh -lc "export VAULT_ADDR=http://127.0.0.1:8200 VAULT_TOKEN='${ROOT_TOKEN}'; vault read -field=role_id auth/approle/role/sendico-dev-apps/role-id")" APPROLE_SECRET_ID="$(docker exec dev-vault sh -lc "export VAULT_ADDR=http://127.0.0.1:8200 VAULT_TOKEN='${ROOT_TOKEN}'; vault write -force -field=secret_id auth/approle/role/sendico-dev-apps/secret-id")" if [[ -z "$APPROLE_ROLE_ID" || -z "$APPROLE_SECRET_ID" ]]; then echo "failed to create dev approle credentials" >&2 exit 68 fi write_vault_env() { local service_dir="$1" local role_var="$2" local secret_var="$3" local env_dir="${REMOTE_BASE%/}/${service_dir}/env" mkdir -p "$env_dir" cat >"${env_dir}/vault.env" < .last_deploy logger -t "deploy-${COMPOSE_PROJECT_NAME}" "${COMPOSE_PROJECT_NAME} deployed at $(date -Is) in ${REMOTE_DIR}" EOSSH