наблюдаемость: завершить базовый контур Community
Добавить структурированные журналы, метрики, трассировку и безопасный канал критических ошибок. Усилить границы рантайма, тесты, проверку зависимостей и сценарии развёртывания.
This commit is contained in:
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
command -v cargo-deny >/dev/null 2>&1 || {
|
||||
echo "cargo-deny 0.20.2 is required" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
cargo deny --locked check advisories bans licenses sources
|
||||
(cd apps/ui && npm audit --audit-level=high)
|
||||
@@ -49,6 +49,8 @@ def package_category(name: str, manifest_path: Path, workspace_root: Path) -> st
|
||||
return "app"
|
||||
if name == "crank-core":
|
||||
return "core"
|
||||
if name == "crank-observability":
|
||||
return "observability"
|
||||
if name == "crank-registry":
|
||||
return "registry"
|
||||
if name == "crank-runtime":
|
||||
@@ -99,6 +101,15 @@ def boundary_reason(source: Package, dependency: Package) -> str | None:
|
||||
if dependency.category == "app":
|
||||
return "workspace crates must not depend on apps"
|
||||
|
||||
if source.category == "observability":
|
||||
return "crank-observability must not depend on other workspace crates"
|
||||
|
||||
if (
|
||||
source.category in {"core", "registry", "runtime"}
|
||||
and dependency.category == "observability"
|
||||
):
|
||||
return "domain and runtime crates must not depend on crank-observability"
|
||||
|
||||
if source.category == "core" and dependency.category in {"runtime", "registry", "adapter"}:
|
||||
return "crank-core must stay below runtime, registry and adapters"
|
||||
|
||||
|
||||
Executable
+136
@@ -0,0 +1,136 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
cd "${1:-.}"
|
||||
|
||||
env_value_from() {
|
||||
env_file="$1"
|
||||
key="$2"
|
||||
default_value="${3:-}"
|
||||
value="$(grep -E "^${key}=" "$env_file" | tail -n 1 | cut -d= -f2- || true)"
|
||||
if [ -n "$value" ]; then
|
||||
printf '%s' "$value"
|
||||
else
|
||||
printf '%s' "$default_value"
|
||||
fi
|
||||
}
|
||||
|
||||
env_value() {
|
||||
key="$1"
|
||||
default_value="${2:-}"
|
||||
env_value_from .env "$key" "$default_value"
|
||||
}
|
||||
|
||||
cache_backend="$(env_value CRANK_CACHE_BACKEND memory)"
|
||||
compose_profiles=""
|
||||
if [ "$cache_backend" = "valkey" ] || [ "$cache_backend" = "redis" ]; then
|
||||
compose_profiles="--profile cache"
|
||||
fi
|
||||
|
||||
compose() {
|
||||
# Intentional word splitting: compose_profiles is either empty or two arguments.
|
||||
# shellcheck disable=SC2086
|
||||
docker compose $compose_profiles "$@"
|
||||
}
|
||||
|
||||
wait_for_stack() {
|
||||
readiness_path="$1"
|
||||
attempt=1
|
||||
while [ "$attempt" -le 45 ]; do
|
||||
if curl --fail --silent http://127.0.0.1:3000/ >/dev/null \
|
||||
&& curl --fail --silent "http://127.0.0.1:3001/${readiness_path}" >/dev/null \
|
||||
&& curl --fail --silent "http://127.0.0.1:3002/${readiness_path}" >/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
sleep 2
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
create_backup() {
|
||||
timestamp="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||
backup_dir="$(pwd)/backups/${timestamp}"
|
||||
mkdir -p "$backup_dir"
|
||||
chmod 700 "$backup_dir"
|
||||
|
||||
backup_env_file=.env
|
||||
previous_deployment=false
|
||||
if [ -f .env.previous ]; then
|
||||
backup_env_file=.env.previous
|
||||
previous_deployment=true
|
||||
cp "$backup_env_file" "$backup_dir/runtime.env"
|
||||
else
|
||||
cp .env "$backup_dir/runtime.env"
|
||||
fi
|
||||
if [ -f docker-compose.previous.yml ]; then
|
||||
cp docker-compose.previous.yml "$backup_dir/docker-compose.yml"
|
||||
else
|
||||
cp docker-compose.yml "$backup_dir/docker-compose.yml"
|
||||
fi
|
||||
|
||||
postgres_host="$(env_value_from "$backup_env_file" POSTGRES_HOST)"
|
||||
postgres_port="$(env_value_from "$backup_env_file" POSTGRES_PORT 5432)"
|
||||
postgres_db="$(env_value_from "$backup_env_file" POSTGRES_DB crank)"
|
||||
postgres_user="$(env_value_from "$backup_env_file" POSTGRES_USER crank)"
|
||||
postgres_password="$(env_value_from "$backup_env_file" POSTGRES_PASSWORD)"
|
||||
if [ -z "$postgres_host" ] || [ -z "$postgres_password" ]; then
|
||||
echo "PostgreSQL credentials are required for the pre-update backup" >&2
|
||||
return 1
|
||||
fi
|
||||
docker run --rm --network host \
|
||||
-e PGPASSWORD="$postgres_password" \
|
||||
-v "$backup_dir:/backup" \
|
||||
postgres:16-alpine \
|
||||
pg_dump --host "$postgres_host" --port "$postgres_port" \
|
||||
--username "$postgres_user" --dbname "$postgres_db" \
|
||||
--format custom --file /backup/postgres.dump
|
||||
|
||||
admin_container="$(compose ps -q admin-api 2>/dev/null || true)"
|
||||
if [ -n "$admin_container" ]; then
|
||||
storage_root="$(env_value_from "$backup_env_file" CRANK_STORAGE_ROOT /var/lib/crank/storage)"
|
||||
docker run --rm --volumes-from "$admin_container" \
|
||||
-v "$backup_dir:/backup" alpine:3.21 \
|
||||
tar -C "$storage_root" -czf /backup/artifacts.tar.gz .
|
||||
elif [ "$previous_deployment" = true ]; then
|
||||
echo "Existing deployment container is unavailable; artifact backup cannot be verified" >&2
|
||||
return 1
|
||||
else
|
||||
tar -czf "$backup_dir/artifacts.tar.gz" --files-from /dev/null
|
||||
fi
|
||||
|
||||
(cd "$backup_dir" && sha256sum postgres.dump artifacts.tar.gz runtime.env docker-compose.yml > SHA256SUMS)
|
||||
find backups -mindepth 1 -maxdepth 1 -type d -printf '%T@ %p\n' \
|
||||
| sort -nr | awk 'NR > 5 { print $2 }' | xargs -r rm -rf
|
||||
}
|
||||
|
||||
rollback() {
|
||||
echo "New release failed readiness; restoring previous deployment" >&2
|
||||
if [ ! -f .env.previous ] || [ ! -f docker-compose.previous.yml ]; then
|
||||
echo "Previous deployment metadata is unavailable" >&2
|
||||
return 1
|
||||
fi
|
||||
cp .env .env.failed
|
||||
cp docker-compose.yml docker-compose.failed.yml
|
||||
cp .env.previous .env
|
||||
cp docker-compose.previous.yml docker-compose.yml
|
||||
cache_backend="$(env_value CRANK_CACHE_BACKEND memory)"
|
||||
compose_profiles=""
|
||||
if [ "$cache_backend" = "valkey" ] || [ "$cache_backend" = "redis" ]; then
|
||||
compose_profiles="--profile cache"
|
||||
fi
|
||||
compose up -d --remove-orphans
|
||||
wait_for_stack health
|
||||
}
|
||||
|
||||
compose config -q
|
||||
create_backup
|
||||
compose pull
|
||||
|
||||
if ! compose up -d --remove-orphans || ! wait_for_stack ready; then
|
||||
compose ps >&2 || true
|
||||
rollback
|
||||
exit 1
|
||||
fi
|
||||
|
||||
compose ps
|
||||
Executable
+72
@@ -0,0 +1,72 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
deploy_dir="${1:-.}"
|
||||
backup_dir="${2:-}"
|
||||
if [ -z "$backup_dir" ] || [ ! -d "$backup_dir" ]; then
|
||||
echo "usage: CRANK_RESTORE_CONFIRM=restore scripts/restore-community.sh DEPLOY_DIR BACKUP_DIR" >&2
|
||||
exit 2
|
||||
fi
|
||||
if [ "${CRANK_RESTORE_CONFIRM:-}" != "restore" ]; then
|
||||
echo "set CRANK_RESTORE_CONFIRM=restore to confirm destructive restore" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
cd "$deploy_dir"
|
||||
backup_dir="$(cd "$backup_dir" && pwd)"
|
||||
(cd "$backup_dir" && sha256sum --check SHA256SUMS)
|
||||
|
||||
env_value() {
|
||||
key="$1"
|
||||
default_value="${2:-}"
|
||||
value="$(grep -E "^${key}=" .env | tail -n 1 | cut -d= -f2- || true)"
|
||||
if [ -n "$value" ]; then printf '%s' "$value"; else printf '%s' "$default_value"; fi
|
||||
}
|
||||
|
||||
cache_backend="$(env_value CRANK_CACHE_BACKEND memory)"
|
||||
compose_profiles=""
|
||||
if [ "$cache_backend" = "valkey" ] || [ "$cache_backend" = "redis" ]; then
|
||||
compose_profiles="--profile cache"
|
||||
fi
|
||||
compose() {
|
||||
# shellcheck disable=SC2086
|
||||
docker compose $compose_profiles "$@"
|
||||
}
|
||||
|
||||
compose stop admin-api mcp-server ui
|
||||
|
||||
postgres_host="$(env_value POSTGRES_HOST)"
|
||||
postgres_port="$(env_value POSTGRES_PORT 5432)"
|
||||
postgres_db="$(env_value POSTGRES_DB crank)"
|
||||
postgres_user="$(env_value POSTGRES_USER crank)"
|
||||
postgres_password="$(env_value POSTGRES_PASSWORD)"
|
||||
docker run --rm --network host \
|
||||
-e PGPASSWORD="$postgres_password" \
|
||||
-v "$backup_dir:/backup:ro" \
|
||||
postgres:16-alpine \
|
||||
pg_restore --host "$postgres_host" --port "$postgres_port" \
|
||||
--username "$postgres_user" --dbname "$postgres_db" \
|
||||
--clean --if-exists --no-owner --no-privileges /backup/postgres.dump
|
||||
|
||||
admin_container="$(compose ps -aq admin-api)"
|
||||
storage_root="$(env_value CRANK_STORAGE_ROOT /var/lib/crank/storage)"
|
||||
docker run --rm --volumes-from "$admin_container" \
|
||||
-v "$backup_dir:/backup:ro" alpine:3.21 sh -eu -c \
|
||||
"find '$storage_root' -mindepth 1 -delete; tar -C '$storage_root' -xzf /backup/artifacts.tar.gz"
|
||||
|
||||
compose up -d --remove-orphans
|
||||
attempt=1
|
||||
while [ "$attempt" -le 45 ]; do
|
||||
if curl --fail --silent http://127.0.0.1:3000/ >/dev/null \
|
||||
&& curl --fail --silent http://127.0.0.1:3001/ready >/dev/null \
|
||||
&& curl --fail --silent http://127.0.0.1:3002/ready >/dev/null; then
|
||||
echo "Community state restored from $backup_dir"
|
||||
exit 0
|
||||
fi
|
||||
sleep 2
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
|
||||
compose ps >&2
|
||||
echo "restored stack failed readiness" >&2
|
||||
exit 1
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
if [ "$#" -eq 0 ]; then
|
||||
echo "usage: scripts/scan-images.sh IMAGE [IMAGE ...]" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
trivy_version="0.70.0"
|
||||
archive="trivy_${trivy_version}_Linux-64bit.tar.gz"
|
||||
expected_sha256="8b4376d5d6befe5c24d503f10ff136d9e0c49f9127a4279fd110b727929a5aa9"
|
||||
temp_dir="$(mktemp -d)"
|
||||
trap 'rm -rf "$temp_dir"' EXIT HUP INT TERM
|
||||
|
||||
curl --fail --silent --show-error --location \
|
||||
"https://github.com/aquasecurity/trivy/releases/download/v${trivy_version}/${archive}" \
|
||||
--output "$temp_dir/$archive"
|
||||
printf '%s %s\n' "$expected_sha256" "$temp_dir/$archive" | sha256sum --check --status
|
||||
tar -C "$temp_dir" -xzf "$temp_dir/$archive" trivy
|
||||
|
||||
for image in "$@"; do
|
||||
"$temp_dir/trivy" image \
|
||||
--exit-code 1 \
|
||||
--ignore-unfixed \
|
||||
--severity HIGH,CRITICAL \
|
||||
--scanners vuln \
|
||||
--no-progress \
|
||||
"$image"
|
||||
done
|
||||
Reference in New Issue
Block a user