forked from sagnik/Velocity-OS
119 lines
4.3 KiB
Bash
119 lines
4.3 KiB
Bash
#!/usr/bin/env bash
|
|
# ============================================================
|
|
# Velocity-OS — Ingress Box: Air-Gap Transfer Agent
|
|
# Runs on a LAN-connected node (Raspberry Pi / VM).
|
|
# Polls ECR every 5 minutes for new signed images.
|
|
# Verifies cosign signature. Transfers to air-gapped workstation.
|
|
# Triggers K3s rolling restart on new image.
|
|
#
|
|
# Install as systemd service:
|
|
# sudo cp poll_and_transfer.service /etc/systemd/system/
|
|
# sudo systemctl enable --now poll_and_transfer
|
|
# ============================================================
|
|
set -euo pipefail
|
|
|
|
# ── Configuration ────────────────────────────────────────────
|
|
AWS_REGION="${AWS_REGION:-ap-south-1}"
|
|
AWS_ACCOUNT_ID="${AWS_ACCOUNT_ID:?Must set AWS_ACCOUNT_ID}"
|
|
ECR_REGISTRY="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com"
|
|
REGISTRY_PREFIX="velocity-os"
|
|
SERVICES=("core" "webos" "media-engine" "agents")
|
|
|
|
# Air-gapped workstation (LAN only — no internet)
|
|
WORKSTATION_IP="${WORKSTATION_IP:-192.168.1.100}"
|
|
WORKSTATION_USER="${WORKSTATION_USER:-ubuntu}"
|
|
WORKSTATION_SSH_KEY="${WORKSTATION_SSH_KEY:-/home/ingress/.ssh/velocity_workstation_ed25519}"
|
|
|
|
# State file: tracks last-transferred digest per service
|
|
STATE_DIR="/var/lib/velocity-ingress"
|
|
mkdir -p "${STATE_DIR}"
|
|
|
|
# Temp dir for image tarballs
|
|
TRANSFER_DIR="/tmp/velocity-transfer"
|
|
mkdir -p "${TRANSFER_DIR}"
|
|
|
|
# ── Functions ─────────────────────────────────────────────────
|
|
log() { echo "[$(date -u +%Y-%m-%dT%H:%M:%SZ)] $*"; }
|
|
|
|
get_latest_digest() {
|
|
local repo="${REGISTRY_PREFIX}/$1"
|
|
aws ecr describe-images \
|
|
--repository-name "${repo}" \
|
|
--image-ids imageTag=latest \
|
|
--region "${AWS_REGION}" \
|
|
--query 'imageDetails[0].imageDigest' \
|
|
--output text 2>/dev/null || echo "NONE"
|
|
}
|
|
|
|
transfer_image() {
|
|
local svc="$1"
|
|
local digest="$2"
|
|
local full_image="${ECR_REGISTRY}/${REGISTRY_PREFIX}/${svc}@${digest}"
|
|
local tar_file="${TRANSFER_DIR}/${svc}.tar"
|
|
|
|
log " [${svc}] Pulling from ECR..."
|
|
docker pull "${ECR_REGISTRY}/${REGISTRY_PREFIX}/${svc}:latest"
|
|
|
|
log " [${svc}] Verifying cosign signature..."
|
|
cosign verify \
|
|
--certificate-identity-regexp ".*" \
|
|
--certificate-oidc-issuer-regexp ".*" \
|
|
"${full_image}" || {
|
|
log " [${svc}] ERROR: Signature verification FAILED. Refusing transfer."
|
|
return 1
|
|
}
|
|
|
|
log " [${svc}] Saving image to tarball..."
|
|
docker save "${ECR_REGISTRY}/${REGISTRY_PREFIX}/${svc}:latest" \
|
|
-o "${tar_file}"
|
|
|
|
log " [${svc}] Transferring to workstation via SCP..."
|
|
scp -i "${WORKSTATION_SSH_KEY}" \
|
|
-o StrictHostKeyChecking=yes \
|
|
"${tar_file}" \
|
|
"${WORKSTATION_USER}@${WORKSTATION_IP}:/tmp/${svc}.tar"
|
|
|
|
log " [${svc}] Importing into K3s containerd + rolling restart..."
|
|
ssh -i "${WORKSTATION_SSH_KEY}" \
|
|
-o StrictHostKeyChecking=yes \
|
|
"${WORKSTATION_USER}@${WORKSTATION_IP}" \
|
|
"sudo k3s ctr images import /tmp/${svc}.tar && \
|
|
sudo kubectl rollout restart deployment/${svc} -n velocity-os && \
|
|
rm /tmp/${svc}.tar"
|
|
|
|
# Record transferred digest
|
|
echo "${digest}" > "${STATE_DIR}/${svc}.last_digest"
|
|
log " [${svc}] ✓ Transfer complete. Digest: ${digest}"
|
|
rm -f "${tar_file}"
|
|
}
|
|
|
|
# ── Main poll loop ────────────────────────────────────────────
|
|
log "=== Velocity-OS Ingress Box polling ECR ==="
|
|
|
|
# Login to ECR (token expires every 12h; cron re-runs this)
|
|
aws ecr get-login-password --region "${AWS_REGION}" | \
|
|
docker login --username AWS --password-stdin "${ECR_REGISTRY}"
|
|
|
|
for svc in "${SERVICES[@]}"; do
|
|
log "[${svc}] Checking for updates..."
|
|
|
|
CURRENT_DIGEST=$(get_latest_digest "${svc}")
|
|
LAST_DIGEST=$(cat "${STATE_DIR}/${svc}.last_digest" 2>/dev/null || echo "NONE")
|
|
|
|
if [[ "${CURRENT_DIGEST}" == "NONE" ]]; then
|
|
log " [${svc}] No image found in ECR. Skipping."
|
|
continue
|
|
fi
|
|
|
|
if [[ "${CURRENT_DIGEST}" == "${LAST_DIGEST}" ]]; then
|
|
log " [${svc}] Up to date. No transfer needed."
|
|
continue
|
|
fi
|
|
|
|
log " [${svc}] New digest detected: ${CURRENT_DIGEST}"
|
|
transfer_image "${svc}" "${CURRENT_DIGEST}" || \
|
|
log " [${svc}] Transfer FAILED. Will retry next cycle."
|
|
done
|
|
|
|
log "=== Poll cycle complete ==="
|