145 lines
5.1 KiB
Bash
145 lines
5.1 KiB
Bash
#!/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})"
|