# Methods library for radicle_seed — sourced by install-radicle_seed.sh (and the # orchestrator's generated run-*.sh). SCRIPT_DIR and the RADICLE_* env must be set # before sourcing. Mirrors the forgejo-lib.sh structure. # # Multi-instance: RADICLE_NAME drives all resource names so radicle-src and # radicle-pub can coexist with radicle-seed on the same cluster. _kubectl() { if command -v kubectl >/dev/null 2>&1; then command kubectl "$@" elif command -v k0s >/dev/null 2>&1; then k0s kubectl "$@" else echo "ERROR: no kubectl or k0s binary found" >&2; exit 127 fi } _render_config_json() { local policy if [ "${RADICLE_SEEDING_DEFAULT}" = "allow" ]; then policy="\"seedingPolicy\": { \"default\": \"allow\", \"scope\": \"${RADICLE_SEEDING_SCOPE}\" }" else policy="\"seedingPolicy\": { \"default\": \"block\" }" fi # Build the connect array as JSON from the space-separated peer list — pure # bash construction (no parser needed): quote each peer, comma-join. local connect_json="[]" if [ -n "${RADICLE_NODE_CONNECTS:-}" ]; then connect_json="[" local _sep="" _peer for _peer in ${RADICLE_NODE_CONNECTS}; do connect_json="${connect_json}${_sep}\"${_peer}\"" _sep="," done connect_json="${connect_json}]" fi cat <&2; exit 1; } local kf pf kf="$(mktemp)"; pf="$(mktemp)" printf '%s' "$RADICLE_IDENTITY_KEY_B64" | base64 -d > "$kf" printf '%s' "$RADICLE_IDENTITY_PUB_B64" | base64 -d > "$pf" _kubectl create secret generic "${RADICLE_NAME}-identity" \ --namespace "$RADICLE_NAMESPACE" \ --from-file=radicle="$kf" \ --from-file=radicle.pub="$pf" \ --dry-run=client -o yaml | _kubectl apply -f - rm -f "$kf" "$pf" } _apply_config() { _kubectl create configmap "${RADICLE_NAME}-config" \ --namespace "$RADICLE_NAMESPACE" \ --from-literal=config.json="$(_render_config_json)" \ --dry-run=client -o yaml | _kubectl apply -f - } _hostnet_spec() { # Emit hostNetwork/dnsPolicy/nodeSelector lines only for VPN-only seeds that need # to be reachable at the node's wuwei IP without a LoadBalancer Service. # RADICLE_HOST_NETWORK=true + RADICLE_NODE_SELECTOR= required together. [ "${RADICLE_HOST_NETWORK:-false}" = "true" ] || return 0 echo " hostNetwork: true" echo " dnsPolicy: ClusterFirstWithHostNet" [ -n "${RADICLE_NODE_SELECTOR:-}" ] && echo " nodeSelector:" [ -n "${RADICLE_NODE_SELECTOR:-}" ] && echo " kubernetes.io/hostname: ${RADICLE_NODE_SELECTOR}" } _apply_statefulset() { _kubectl apply -f - <&2; exit 1; } # shellcheck source=/dev/null source "${SCRIPT_DIR}/lib/gateway-tls.sh" _lib_ensure_certificate "${RADICLE_CERT_SECRET}" "${RADICLE_CERT_NAMESPACE}" \ "${RADICLE_HOSTNAME}" "${RADICLE_CLUSTER_ISSUER}" _lib_gateway_patch_https "${RADICLE_GATEWAY_NAME}" "${RADICLE_GATEWAY_NS}" \ "${RADICLE_LISTENER_NAME}" "${RADICLE_HOSTNAME}" "${RADICLE_CERT_SECRET}" "${RADICLE_CERT_NAMESPACE}" _kubectl apply -f - <&2; exit 1; } [ "${RADICLE_TCP_PORT:-0}" -gt 0 ] || { echo "ERROR: RADICLE_TCP_PORT unset or zero" >&2; exit 1; } [ -f "${SCRIPT_DIR}/lib/gateway-tcp.sh" ] || { echo "ERROR: lib/gateway-tcp.sh missing from bundle" >&2; exit 1; } # shellcheck source=/dev/null source "${SCRIPT_DIR}/lib/gateway-tcp.sh" _lib_gateway_patch_tcp "${RADICLE_GATEWAY_NAME}" "${RADICLE_GATEWAY_NS}" \ "${RADICLE_TCP_LISTENER_NAME}" "${RADICLE_TCP_PORT}" _lib_upsert_tcproute "${RADICLE_NAME}-p2p" "${RADICLE_NAMESPACE}" \ "${RADICLE_GATEWAY_NAME}" "${RADICLE_GATEWAY_NS}" "${RADICLE_TCP_LISTENER_NAME}" \ "${RADICLE_NAME}" "${RADICLE_TCP_PORT}" } # Selective seeding: with default=block, explicitly seed each allowed RID. _seed_rids() { [ -n "${RADICLE_SEED_RIDS}" ] || { echo "[${RADICLE_NAME}] no RIDs to seed (allow-list empty)"; return 0; } for rid in ${RADICLE_SEED_RIDS}; do echo "[${RADICLE_NAME}] rad seed ${rid} --scope ${RADICLE_SEEDING_SCOPE}" _kubectl exec -n "$RADICLE_NAMESPACE" "statefulset/${RADICLE_NAME}" -c radicle-node -- \ env RAD_HOME="${RADICLE_RAD_HOME}" rad seed "${rid}" --scope "${RADICLE_SEEDING_SCOPE}" || \ echo " (warn) seed ${rid} failed — node may still be starting" done } cmd_install() { echo "[${RADICLE_NAME}] namespace ${RADICLE_NAMESPACE}" _apply_namespace echo "[${RADICLE_NAME}] identity secret" _apply_identity_secret echo "[${RADICLE_NAME}] config.json (seedingPolicy=${RADICLE_SEEDING_DEFAULT}/${RADICLE_SEEDING_SCOPE}, connects=$(echo "${RADICLE_NODE_CONNECTS:-none}" | wc -w | tr -d ' '))" _apply_config echo "[${RADICLE_NAME}] StatefulSet (${RADICLE_IMAGE})" _apply_statefulset echo "[${RADICLE_NAME}] Service (p2p ${RADICLE_P2P_PORT}, http ${RADICLE_HTTP_PORT})" _apply_service echo "[${RADICLE_NAME}] HTTPS gateway (${RADICLE_HOSTNAME})" _apply_gateway echo "[${RADICLE_NAME}] TCP gateway (P2P port ${RADICLE_TCP_PORT:-disabled})" _apply_tcp_gateway echo "[${RADICLE_NAME}] waiting for rollout..." _wait echo "[${RADICLE_NAME}] selective seeding" _seed_rids echo "[${RADICLE_NAME}] node id: $(_kubectl exec -n "$RADICLE_NAMESPACE" "statefulset/${RADICLE_NAME}" -c radicle-node -- env RAD_HOME=${RADICLE_RAD_HOME} rad self --nid 2>/dev/null || echo '')" } cmd_uninstall() { if [ "${RADICLE_TCP_GATEWAY_ENABLED:-false}" = "true" ] && [ -f "${SCRIPT_DIR}/lib/gateway-tcp.sh" ]; then # shellcheck source=/dev/null source "${SCRIPT_DIR}/lib/gateway-tcp.sh" _lib_delete_tcproute "${RADICLE_NAME}-p2p" "${RADICLE_NAMESPACE}" _lib_gateway_remove_tcp "${RADICLE_GATEWAY_NAME}" "${RADICLE_GATEWAY_NS}" "${RADICLE_TCP_LISTENER_NAME}" fi if [ "${RADICLE_GATEWAY_ENABLED}" = "true" ] && [ -f "${SCRIPT_DIR}/lib/gateway-tcp.sh" ]; then # shellcheck source=/dev/null source "${SCRIPT_DIR}/lib/gateway-tls.sh" _kubectl delete "httproute/${RADICLE_NAME}-https" --namespace "$RADICLE_NAMESPACE" --ignore-not-found _lib_gateway_remove_https "${RADICLE_GATEWAY_NAME}" "${RADICLE_GATEWAY_NS}" "${RADICLE_LISTENER_NAME}" _kubectl delete "certificate/${RADICLE_CERT_SECRET}" --namespace "$RADICLE_CERT_NAMESPACE" --ignore-not-found fi _kubectl delete \ "statefulset/${RADICLE_NAME}" \ "service/${RADICLE_NAME}" \ "configmap/${RADICLE_NAME}-config" \ "secret/${RADICLE_NAME}-identity" \ --namespace "$RADICLE_NAMESPACE" --ignore-not-found echo "[${RADICLE_NAME}] removed (PVC retained by storage class policy)" }