Files
Omarchy-Stream/scripts/cert-bootstrap.sh
Levi Woodard e878b392e4 Sign Sunshine certs with a 1Password-backed root CA
Replaces Sunshine's self-signed cert with one minted from a private root CA
whose key material lives in 1Password. Every host running install.sh fetches
the CA via 'op read', mints itself a host cert with SANs for <hostname>.lan
and the current LAN IP, and installs the CA into the system trust store.

Bootstrap (run once, anywhere)
- scripts/cert-bootstrap.sh: generates a 4096-bit RSA root CA (10y validity),
  uploads it as a Secure Note titled "Omarchy-Stream Root CA" in the Private
  vault with two fields: cert (text) and key (concealed). Refuses to overwrite
  an existing item without --force.

Per-host (lib/certs.sh)
- fetch_and_install_certs: reads op://Private/Omarchy-Stream Root CA/{cert,key}
  to a tmpfs-staged temp dir (XDG_RUNTIME_DIR), mints a host cert via openssl
  with serverAuth + clientAuth EKU, drops cert/key at ~/.config/sunshine/
  credentials/{cacert,cakey}.pem, installs the CA at
  /etc/ca-certificates/trust-source/anchors/omarchy-stream-ca.pem and runs
  update-ca-trust.
- Idempotent: skips re-mint when on-disk cert is signed by the current CA,
  has the expected SANs, and isn't within 30 days of expiry. Override with
  FORCE_CERTS=1 or --force-certs.

install.sh
- Adds --no-certs, --force-certs flags; sources lib/certs.sh; runs cert step
  after permissions/config and before firewall so the service restart at the
  end of install picks up the new cert.

client/install-macos.sh
- After installing Moonlight, if `op` is available and signed in, fetches the
  CA and adds it as a trusted root to /Library/Keychains/System.keychain via
  `security add-trusted-cert -d -r trustRoot`. Skips cleanly when op isn't
  ready.

uninstall.sh
- Adds --remove-ca-trust to delete the system trust anchor. By default the
  CA is left in place since other tools may rely on it.

verify.sh
- Adds checks for: cert signed by omarchy-stream CA, cert >30 days from
  expiry, CA present in system trust store.

Docs
- README "Trusted TLS certs via 1Password" section: bootstrap flow, per-host
  flow, client trust matrix (Linux / macOS / iOS / Android / Apple TV),
  re-pairing note (first cert install on a host invalidates pinned Moonlight
  fingerprints), config env vars.
- client/README gains per-platform CA-trust install steps with concrete
  `op read` + platform-specific commands.
2026-05-18 10:43:33 -06:00

104 lines
3.4 KiB
Bash
Executable File

#!/usr/bin/env bash
# One-time bootstrap: generate a root CA for omarchy-stream and upload it to
# 1Password. Run this on ONE machine; every host install.sh thereafter pulls
# the CA from 1Password to mint per-host certs.
#
# Re-running this script will refuse to overwrite an existing item unless
# --force is passed. The CA's private key is the trust root for every paired
# host; replacing it forces re-trust on every device.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=../lib/common.sh
source "$SCRIPT_DIR/../lib/common.sh"
# shellcheck source=../lib/certs.sh
source "$SCRIPT_DIR/../lib/certs.sh"
FORCE=0
CA_VALID_DAYS=3650
CA_CN="omarchy-stream Root CA"
CA_O="omarchy-stream"
usage() {
cat <<EOF
Usage: $(basename "$0") [--force]
Generates a new root CA and uploads it to 1Password.
--force Overwrite an existing CA item in the vault (DANGEROUS: invalidates
every host cert previously signed by the existing CA).
Configuration (env vars, all optional):
OP_VAULT 1Password vault (default: Private)
OP_CA_ITEM Item title in that vault (default: Omarchy-Stream Root CA)
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--force) FORCE=1 ;;
-h|--help) usage; exit 0 ;;
*) err "Unknown option: $1"; usage; exit 2 ;;
esac
shift
done
require_not_root
op_require_signin
# Check whether the item already exists. `op item get` exits 0 if found.
if op item get "$OP_CA_ITEM" --vault "$OP_VAULT" >/dev/null 2>&1; then
if [[ $FORCE -eq 0 ]]; then
err "Item '$OP_CA_ITEM' already exists in vault '$OP_VAULT'."
err "Re-running cert-bootstrap with --force will replace it, which invalidates"
err "every host cert minted from the existing CA. If you just want to refresh"
err "host certs on this machine, run install.sh (with FORCE_CERTS=1 to mint)."
exit 1
fi
warn "Overwriting existing CA item (--force given)"
op item delete "$OP_CA_ITEM" --vault "$OP_VAULT" >/dev/null
fi
tmpdir="$(mktemp -d "${XDG_RUNTIME_DIR:-/tmp}/omarchy-ca-bootstrap.XXXXXX")"
chmod 700 "$tmpdir"
trap "rm -rf '$tmpdir'" EXIT
step "Generating root CA (4096-bit RSA, ${CA_VALID_DAYS} days)"
openssl genrsa -out "$tmpdir/ca-key.pem" 4096 2>/dev/null
chmod 600 "$tmpdir/ca-key.pem"
openssl req -new -x509 \
-key "$tmpdir/ca-key.pem" \
-out "$tmpdir/ca-cert.pem" \
-days "$CA_VALID_DAYS" \
-sha256 \
-subj "/CN=${CA_CN}/O=${CA_O}" \
-addext "basicConstraints=critical,CA:TRUE" \
-addext "keyUsage=critical,keyCertSign,cRLSign" 2>/dev/null
ok "Generated CA cert and key"
info "CA fingerprint (SHA256):"
openssl x509 -in "$tmpdir/ca-cert.pem" -noout -fingerprint -sha256 \
| sed 's/^/ /'
step "Uploading to 1Password (vault: $OP_VAULT, item: $OP_CA_ITEM)"
op item create \
--category "Secure Note" \
--vault "$OP_VAULT" \
--title "$OP_CA_ITEM" \
"notesPlain=Root CA for omarchy-stream Sunshine certs. Bootstrapped $(date -Iseconds) on $(hostname -s)." \
"cert[text]=$(cat "$tmpdir/ca-cert.pem")" \
"key[concealed]=$(cat "$tmpdir/ca-key.pem")" \
>/dev/null
ok "Uploaded CA to 1Password"
info ""
info "References for install.sh / lib/certs.sh:"
info " op://${OP_VAULT}/${OP_CA_ITEM}/cert"
info " op://${OP_VAULT}/${OP_CA_ITEM}/key"
info ""
info "Next: on each host (including this one if it'll be a Sunshine host), run:"
info " ./install.sh"
info "and the cert step will pull the CA from 1Password and mint a host cert."