#!/usr/bin/env bash set -euo pipefail NODE_JSON="/etc/flowercore/signage-node.json" CERT_DIR="/etc/fc-signage-player" SIGNAGE_URL="${FC_SIGNAGE_URL:-https://signage.iamworkin.lan}" SETUP_CODE_FILE="/etc/flowercore/signage-setup-code" mkdir -p /etc/flowercore "$CERT_DIR" /var/log/fc-signage-player chown fc-signage:fc-signage /etc/flowercore "$CERT_DIR" /var/log/fc-signage-player chmod 0750 "$CERT_DIR" if [[ -s "$NODE_JSON" && -s "$CERT_DIR/client.p12" ]]; then ENROLLED=$(jq -r '.enrolledAt // empty' "$NODE_JSON") if [[ -n "$ENROLLED" ]]; then echo "[$(date -Is)] bootstrap: already enrolled at $ENROLLED; skipping" exit 0 fi fi if [[ -s "$NODE_JSON" ]]; then NODE_UUID=$(jq -r '.nodeUuid // empty' "$NODE_JSON") MACHINE_ID=$(jq -r '.machineId // empty' "$NODE_JSON") else NODE_UUID=$(uuidgen) MACHINE_ID=$(echo "$NODE_UUID" | tr -d '-' | cut -c1-16) jq -n --arg uuid "$NODE_UUID" --arg machine "$MACHINE_ID" --arg host "$(hostname -f)" --arg ts "$(date -Is)" \ '{nodeUuid: $uuid, machineId: $machine, hostname: $host, platform: "linux-arm64-pi", createdAt: $ts}' \ > "$NODE_JSON" chmod 0640 "$NODE_JSON" chown fc-signage:fc-signage "$NODE_JSON" fi SETUP_CODE="" if [[ -s "$SETUP_CODE_FILE" ]]; then SETUP_CODE=$(tr -d '\r\n\t ' < "$SETUP_CODE_FILE") fi MODEL=$(tr -d '\0' < /sys/firmware/devicetree/base/model 2>/dev/null || echo Unknown) REG_PAYLOAD=$(jq -n \ --arg machine "$MACHINE_ID" \ --arg name "$(hostname -f)" \ --arg setup "$SETUP_CODE" \ --arg resolution "1920x1080" \ --arg model "$MODEL" \ '{ machineId: $machine, name: $name, setupCode: ($setup | if . == "" then null else . end), resolution: $resolution, hardwareModel: $model, platform: "linux-arm64-pi" }') for attempt in 1 2; do HTTP_STATUS=$(curl -sk -o /tmp/register-response.json -w "%{http_code}" \ --max-time 15 \ -X POST "${SIGNAGE_URL}/api/v1/nodes/register" \ -H "Content-Type: application/json" \ -d "$REG_PAYLOAD" || echo "000") if [[ "$HTTP_STATUS" == "200" || "$HTTP_STATUS" == "201" ]]; then break fi echo "[$(date -Is)] bootstrap: register attempt $attempt returned $HTTP_STATUS" >&2 sleep 5 done if [[ "$HTTP_STATUS" != "200" && "$HTTP_STATUS" != "201" ]]; then echo "[$(date -Is)] bootstrap: register failed after 2 attempts" >&2 exit 2 fi NODE_ID=$(jq -r '.nodeId // empty' /tmp/register-response.json) if [[ -z "$NODE_ID" ]]; then echo "[$(date -Is)] bootstrap: register response did not include nodeId" >&2 exit 2 fi jq --arg id "$NODE_ID" '.nodeId = $id' "$NODE_JSON" > "${NODE_JSON}.tmp" && mv "${NODE_JSON}.tmp" "$NODE_JSON" if [[ -s "$SETUP_CODE_FILE" ]]; then curl -sk -X POST "${SIGNAGE_URL}/api/v1/nodes/${NODE_ID}/approve-via-setup-code" \ -H "Content-Type: application/json" \ -d "{\"setupCode\":\"${SETUP_CODE}\"}" \ -o /dev/null || true fi STATUS="" DEADLINE=$(( $(date +%s) + 1800 )) while (( $(date +%s) < DEADLINE )); do STATUS=$(curl -sk --max-time 5 "${SIGNAGE_URL}/api/v1/nodes/${NODE_ID}/status" | jq -r '.status // empty') if [[ "$STATUS" == "Approved" || "$STATUS" == "Enrolled" || "$STATUS" == "Online" ]]; then break fi sleep 15 done if [[ "$STATUS" != "Approved" && "$STATUS" != "Enrolled" && "$STATUS" != "Online" ]]; then echo "[$(date -Is)] bootstrap: approval not granted within 30min budget" >&2 exit 3 fi KEY_PATH="${CERT_DIR}/client.key" CSR_PATH="${CERT_DIR}/client.csr" openssl ecparam -genkey -name prime256v1 -out "$KEY_PATH" openssl req -new -key "$KEY_PATH" -out "$CSR_PATH" \ -subj "/CN=${NODE_ID}/O=FlowerCore/OU=SignagePlayer-Pi" ENROLL_PAYLOAD=$(jq -n --arg csr "$(cat "$CSR_PATH")" '{certificateSigningRequest: $csr}') HTTP_STATUS=$(curl -sk -o /tmp/enroll-response.json -w "%{http_code}" \ --max-time 15 \ -X POST "${SIGNAGE_URL}/api/v1/nodes/${NODE_ID}/enroll" \ -H "Content-Type: application/json" \ -d "$ENROLL_PAYLOAD") if [[ "$HTTP_STATUS" != "200" && "$HTTP_STATUS" != "201" ]]; then echo "[$(date -Is)] bootstrap: enroll failed with HTTP $HTTP_STATUS" >&2 exit 4 fi jq -r '.clientCertificatePem // .signedCertificatePem' /tmp/enroll-response.json > "${CERT_DIR}/client.crt" jq -r '.caCertificatePem' /tmp/enroll-response.json > "${CERT_DIR}/ca-chain.pem" P12_PASS=$(openssl rand -hex 24) echo -n "$P12_PASS" > "${CERT_DIR}/client.p12.pass" chmod 0600 "${CERT_DIR}/client.p12.pass" openssl pkcs12 -export \ -inkey "$KEY_PATH" \ -in "${CERT_DIR}/client.crt" \ -certfile "${CERT_DIR}/ca-chain.pem" \ -out "${CERT_DIR}/client.p12" \ -password "pass:${P12_PASS}" chown fc-signage:fc-signage "${CERT_DIR}"/* "$NODE_JSON" chmod 0640 "${CERT_DIR}/client.p12" "${CERT_DIR}/client.crt" "${CERT_DIR}/ca-chain.pem" "$KEY_PATH" chmod 0600 "${CERT_DIR}/client.p12.pass" EXPIRY=$(openssl x509 -in "${CERT_DIR}/client.crt" -enddate -noout | sed 's/notAfter=//') jq --arg ts "$(date -Is)" --arg exp "$EXPIRY" \ '.enrolledAt = $ts | .certExpiry = $exp' "$NODE_JSON" > "${NODE_JSON}.tmp" \ && mv "${NODE_JSON}.tmp" "$NODE_JSON" systemctl start flowercore-signage-detect-display.service || true systemctl start flowercore-signage-player-pi.service || true echo "[$(date -Is)] bootstrap: enrolled and kiosk started (NodeId=${NODE_ID})"