#!/usr/bin/env bash # O1 target-side verifier + installer (pull model, per manifest-spec v1.0). # Runs ON THE TARGET. Nothing is deployed unless signature + verify pass. # # Usage: # install.sh --check # verify signature + hash + package integrity only # install.sh --install # full install (phase 2: enabled on Aura tower) set -euo pipefail PKG_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" MANIFEST="$PKG_ROOT/manifest.json" PUBKEY="${DEPO_PUBKEY:-/etc/depo/depo-signing.pub}" say() { printf '\033[1;32m[install]\033[0m %s\n' "$*"; } die() { printf '\033[1;31m[install FAIL]\033[0m %s\n' "$*" >&2; exit 1; } [ -f "$MANIFEST" ] || die "manifest.json missing at $PKG_ROOT" verify_signature() { [ -f "$PUBKEY" ] || die "signing pubkey not found at $PUBKEY (set DEPO_PUBKEY)" [ -f "$MANIFEST.sig" ] || die "no signature (.sig) — refusal per spec: No signature = refuse" [ -f "$MANIFEST.sha256" ] || die "no manifest.sha256 — refusal per spec" local got want got="$(sha256sum "$MANIFEST" | cut -d' ' -f1)" want="$(cat "$MANIFEST.sha256")" [ "$got" = "$want" ] || die "hash mismatch: manifest was modified after signing" python3 - "$PUBKEY" "$MANIFEST.sig" "$MANIFEST" <<'PY' || die "ed25519 signature verification failed" import base64, hashlib, sys from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey pub_path, sig_path, manifest_path = sys.argv[1], sys.argv[2], sys.argv[3] pub = Ed25519PublicKey.from_public_bytes(open(pub_path, "rb").read()) sig = base64.b64decode(open(sig_path, "rb").read()) data = open(manifest_path, "rb").read() pub.verify(sig, data) print("ed25519 OK") PY say "signature verified" } run_verify() { verify_signature python3 - "$MANIFEST" <<'PY' || die "install.verify checklist failed" import json, os, sys m = json.load(open(sys.argv[1])) root = os.path.dirname(os.path.abspath(sys.argv[1])) missing = [] for layer, files in m["layers"].items(): for f in files: if layer == "odoo_modules": p = os.path.join(root, "modules", f, "__manifest__.py") elif layer == "agents": p = os.path.join(root, "agents", f + ".json") else: p = os.path.join(root, f) if not os.path.exists(p): missing.append(f) if missing: print("MISSING LAYER FILES:", missing) sys.exit(1) print("all layer files present:", sum(len(v) for v in m["layers"].values())) PY say "verify checklist passed" } case "${1:---check}" in --check) run_verify ;; --install) run_verify die "install is phase 2 (target: Aura tower Odoo). Nothing was deployed." ;; *) die "usage: $0 [--check|--install]" ;; esac