Add deployment
CI / frontend (push) Failing after 57s
CI / backend (push) Failing after 6m27s

This commit is contained in:
voltsrage
2026-08-05 00:26:20 +08:00
parent 9e88ff6113
commit 2a3ef62a7d
86 changed files with 2320 additions and 174 deletions
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/env bash
# Backs up the ASP.NET Data Protection keyring. Without this keyring, PHI
# encrypted by Phase 32 cannot be decrypted — a database backup alone is not
# sufficient to restore the system. See docs/ops/phi-encryption-runbook.md.
#
# Volume name matches docker-compose.prod.yml `name: vigilcare` → vigilcare_dp_keys.
# Do not derive the name from the host directory; /opt/vigilcare would otherwise
# produce a different volume than a checkout named VigilCareClinical.
#
# Usage (on the production host):
# ./scripts/backup-dp-keys.sh
# BACKUP_DIR=/mnt/offsite/vigilcare/dp-keys ./scripts/backup-dp-keys.sh
#
# Cron example (daily 02:15 UTC):
# 15 2 * * * /opt/vigilcare/scripts/backup-dp-keys.sh >> /var/log/vigilcare-dp-backup.log 2>&1
set -euo pipefail
VOLUME_NAME="${VOLUME_NAME:-vigilcare_dp_keys}"
BACKUP_DIR="${BACKUP_DIR:-/var/backups/vigilcare/dp-keys}"
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
ARCHIVE="${BACKUP_DIR}/dp-keys-${STAMP}.tar.gz"
if ! docker volume inspect "${VOLUME_NAME}" >/dev/null 2>&1; then
echo "ERROR: Docker volume '${VOLUME_NAME}' not found." >&2
echo "Confirm docker-compose.prod.yml uses 'name: vigilcare' and the API has started once." >&2
exit 1
fi
mkdir -p "${BACKUP_DIR}"
chmod 700 "${BACKUP_DIR}"
echo "Backing up volume ${VOLUME_NAME}${ARCHIVE}"
docker run --rm \
-v "${VOLUME_NAME}:/keys:ro" \
-v "${BACKUP_DIR}:/backup" \
alpine tar czf "/backup/dp-keys-${STAMP}.tar.gz" -C /keys .
chmod 600 "${ARCHIVE}"
# Refuse empty archives (volume had no key XML yet).
if ! tar tzf "${ARCHIVE}" | grep -q '\.xml$'; then
echo "ERROR: archive contains no *.xml key files — refusing to keep empty backup." >&2
rm -f "${ARCHIVE}"
exit 1
fi
echo "Keyring backed up to ${ARCHIVE}"
# Retain 30 days.
find "${BACKUP_DIR}" -name 'dp-keys-*.tar.gz' -mtime +30 -delete
echo "Retention: removed archives older than 30 days under ${BACKUP_DIR}"
+63
View File
@@ -0,0 +1,63 @@
#!/usr/bin/env bash
# Builds a self-contained EF Core migration bundle for VigilCareClinicalAPI.
# CD (Phase 36 Step 12) runs the same command; this script is for local dry-runs
# and for operators who apply schema changes before rolling the API image.
#
# Usage (from repo root or anywhere):
# ./scripts/build-api-migration-bundle.sh
# RID=win-x64 ./scripts/build-api-migration-bundle.sh # local Windows test
#
# Apply against a database (DDL-privileged connection string):
# ./artifacts/migrate-api --connection "$PG_CONNECTION_DDL"
#
# Zero-downtime discipline: every migration must be backwards-compatible with
# the previously deployed API image (expand-then-contract). The bundle runs
# while old containers are still serving traffic.
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
OUT_DIR="${OUT_DIR:-${ROOT_DIR}/artifacts}"
RID="${RID:-linux-x64}"
OUTPUT="${OUT_DIR}/migrate-api"
# Git Bash on Windows often lacks the host PATH; prefer known install locations.
if ! command -v dotnet >/dev/null 2>&1; then
for candidate in \
"/c/Program Files/dotnet/dotnet" \
"/mnt/c/Program Files/dotnet/dotnet" \
"${HOME}/.dotnet/dotnet"
do
if [[ -x "${candidate}" ]]; then
PATH="$(dirname "${candidate}"):${PATH}"
export PATH
break
fi
done
fi
if ! command -v dotnet >/dev/null 2>&1; then
echo "dotnet SDK is required (not found on PATH)." >&2
exit 1
fi
mkdir -p "${OUT_DIR}"
# Prefer a globally installed dotnet-ef; install 8.0.4 when nothing is present.
# A newer global tool is fine — do not downgrade an existing install.
if ! dotnet ef --version >/dev/null 2>&1; then
echo "Installing dotnet-ef 8.0.4..."
dotnet tool install --global dotnet-ef --version 8.0.4
export PATH="${PATH}:${HOME}/.dotnet/tools:${USERPROFILE:-}/.dotnet/tools"
fi
echo "Building API migration bundle → ${OUTPUT} (RID=${RID})"
dotnet ef migrations bundle \
--project "${ROOT_DIR}/VigilCareClinicalAPI" \
--startup-project "${ROOT_DIR}/VigilCareClinicalAPI" \
--configuration Release \
--self-contained -r "${RID}" \
--output "${OUTPUT}" \
--force
echo "Bundle ready: ${OUTPUT}"
echo "Apply with: ${OUTPUT} --connection \"\$PG_CONNECTION_DDL\""
View File
View File
Executable → Regular
View File
+58
View File
@@ -0,0 +1,58 @@
#!/usr/bin/env bash
# Restores the ASP.NET Data Protection keyring into the production Docker volume.
# REQUIRED after volume loss or host migration — without the matching keyring,
# every encrypted patient PHI column is permanently unreadable.
#
# See docs/ops/phi-encryption-runbook.md (Keyring restore).
#
# Usage:
# ./scripts/restore-dp-keys.sh /var/backups/vigilcare/dp-keys/dp-keys-20260804T021500Z.tar.gz
#
# Stops the API container, replaces volume contents, then leaves the operator
# to bring the stack back up (so schema/image tags stay under compose control).
set -euo pipefail
ARCHIVE="${1:-}"
VOLUME_NAME="${VOLUME_NAME:-vigilcare_dp_keys}"
COMPOSE_FILE="${COMPOSE_FILE:-/opt/vigilcare/docker-compose.prod.yml}"
COMPOSE_DIR="$(dirname "${COMPOSE_FILE}")"
API_SERVICE="${API_SERVICE:-api}"
if [[ -z "${ARCHIVE}" || ! -f "${ARCHIVE}" ]]; then
echo "Usage: $0 <path-to-dp-keys-YYYYMMDDThhmmssZ.tar.gz>" >&2
exit 1
fi
if ! tar tzf "${ARCHIVE}" | grep -q '\.xml$'; then
echo "ERROR: ${ARCHIVE} does not contain Data Protection *.xml key files." >&2
exit 1
fi
echo "Stopping API service (${API_SERVICE}) so the volume is unused..."
if [[ -f "${COMPOSE_FILE}" ]]; then
docker compose -f "${COMPOSE_FILE}" --env-file "${COMPOSE_DIR}/.env" stop "${API_SERVICE}" || true
else
echo "WARN: ${COMPOSE_FILE} not found — ensure no container has ${VOLUME_NAME} mounted." >&2
fi
if ! docker volume inspect "${VOLUME_NAME}" >/dev/null 2>&1; then
echo "Creating volume ${VOLUME_NAME}..."
docker volume create "${VOLUME_NAME}" >/dev/null
fi
echo "Restoring ${ARCHIVE} → volume ${VOLUME_NAME}"
# Clear existing keys then extract. Use a throwaway alpine container.
docker run --rm \
-v "${VOLUME_NAME}:/keys" \
-v "$(cd "$(dirname "${ARCHIVE}")" && pwd):/backup:ro" \
alpine sh -c "rm -rf /keys/* /keys/.[!.]* 2>/dev/null; tar xzf /backup/$(basename "${ARCHIVE}") -C /keys"
echo "Restored key files:"
docker run --rm -v "${VOLUME_NAME}:/keys:ro" alpine ls -la /keys
echo
echo "Next steps:"
echo " 1. docker compose -f ${COMPOSE_FILE} --env-file ${COMPOSE_DIR}/.env up -d ${API_SERVICE}"
echo " 2. curl -fsS http://localhost:5270/health/ready"
echo " 3. Fetch a known patient and confirm firstName/lastName decrypt"
echo " 4. Record this restore in the ops log (date, archive name, operator)"
Executable → Regular
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
View File
Executable → Regular
View File