package templates
import (
timoniv1 "timoni.sh/core/v1alpha1"
)
#NodeGroup: {
role: string
replicas: int | *1
repository: string | *"quay.io/radicle_garden/radicle-node"
pullPolicy: string | *"IfNotPresent"
version: string | *"latest"
nodeIdSeed: string | *""
storage: {
className: string | *"local-path"
size: string | *"1Gi"
}
resources: {...}
sidecars: {
events: bool | *true
}
scripts: {
init: string | *"""
#!/bin/sh
set -e
KUBE_CONFIG_DIR=/tmp/config-source
RAD_HOME=/home/radicle/.radicle
RAD_CONFIG=${RAD_HOME}/config.json
echo "[INIT] Hostname: $(hostname)"
# --- STANDARD INIT LOGIC (User 11011) ---
mkdir -p "${RAD_HOME}"
if [ -f "${KUBE_CONFIG_DIR}/config.json" ]; then
cp "${KUBE_CONFIG_DIR}/config.json" "${RAD_CONFIG}"
echo "[INIT] Config copied successfully."
else
echo "[INIT] ERROR: Source config not found."
exit 1
fi
"""
start: string | *"""
#!/bin/sh
set -e
RAD_HOME=/home/radicle/.radicle
RAD_ALIAS=$(hostname)
RAD_KEY=${RAD_HOME}/keys/radicle
RAD_CONFIG=${RAD_HOME}/config.json
# Configure the external address by prepending the pod's hostname.
# We only do this for seeds and bootstraps to ensure proper routing.
configure_external_address() {
# Extract the first external address, stripping JSON formatting
EXT_ADDRESS=$(rad config | jq -r '.node.externalAddresses[0]')
if [ -n "$EXT_ADDRESS" ]; then
# Check if it already starts with the pod's hostname to prevent stuttering
case "$EXT_ADDRESS" in
${RAD_ALIAS}.*)
echo "[START] External address already correct: ${EXT_ADDRESS}"
;;
*)
rad config remove node.externalAddresses "${EXT_ADDRESS}"
NEW_ADDRESS="${RAD_ALIAS}.${EXT_ADDRESS}"
rad config push node.externalAddresses "${NEW_ADDRESS}"
echo "[START] Node's external address updated to: ${NEW_ADDRESS}"
;;
esac
fi
}
#
# Generate keys
#
if [ ! -f "${RAD_KEY}" ]; then
echo "[START] Generating identity for: ${RAD_ALIAS}..."
# We move the config out of the way so 'rad auth' doesn't complain
if [ -f "${RAD_CONFIG}" ]; then
mv "${RAD_CONFIG}" "${RAD_CONFIG}.bak"
fi
# RAD_KEYGEN_SEED requires a 32-byte hex string.
# We hash either the injected NODE_ID_SEED or the hostname to generate it.
if [ -n "${NODE_ID_SEED}" ]; then
export RAD_KEYGEN_SEED=$(echo "${NODE_ID_SEED}" | sha256sum | tr -d "\\n *-")
else
export RAD_KEYGEN_SEED=$(hostname | sha256sum | tr -d "\\n *-")
fi
rad auth --alias "${RAD_ALIAS}"
if [ -f "${RAD_CONFIG}.bak" ]; then
mv "${RAD_CONFIG}.bak" "${RAD_CONFIG}"
fi
echo "[START] Identity generated"
fi
#
# Update config settings
#
echo "[START] Node's alias set to: $(rad config set node.alias "${RAD_ALIAS}")"
if [ "\(role)" = "seed" ] || [ "\(role)" = "bootstrap" ]; then
configure_external_address
fi
#
# Start node
#
echo "[START] Starting Radicle node..."
exec rad node start --foreground
"""
events: string | *"""
#!/bin/sh
RAD_HOME=/home/radicle/.radicle
RAD_NODE_SOCKET=${RAD_HOME}/node/control.sock
echo "[EVENTS] Waiting for node socket..."
while [ ! -S ${RAD_NODE_SOCKET} ]; do
sleep 1
done
echo "[EVENTS] Socket found. Streaming events..."
exec rad node events
"""
}
radicleConfig: {
node: {
// Automatically generate the base external address using the role.
// The start.sh script will dynamically prepend the pod's hostname to this at boot.
externalAddresses: [...string] | *["\(role).default.svc.cluster.local:8776"]
// Set network to "test" by default.
network: "test"
...
}
...
}
}
// Config defines the schema and defaults for the Instance values.
#Config: {
kubeVersion!: string
clusterVersion: timoniv1.#SemVer & {#Version: kubeVersion, #Minimum: "1.20.0"}
moduleVersion!: string
metadata: timoniv1.#Metadata & {#Version: moduleVersion}
metadata: labels: timoniv1.#Labels
metadata: annotations?: timoniv1.#Annotations
// The topology map is merged with the #NodeGroup schema
topology: [string]: #NodeGroup
// Helper to generate metadata with a specific name
#Meta: {
name: string
out: {
"name": name
namespace: metadata.namespace
if metadata.annotations != _|_ {
annotations: metadata.annotations
}
}
}
}
// Instance takes the config values and outputs the Kubernetes objects.
#Instance: {
config: #Config
// Extract unique roles to create headless services
let _roles = {
for name, group in config.topology {
"\(group.role)": true
}
}
objects: {
// Generate one Headless Service per role (e.g. seed, peer, bootstrap)
for roleName, _ in _roles {
"svc-\(roleName)": #Service & {
#config: config
#role: roleName
}
}
// Generate a StatefulSet and ConfigMap for each group in the topology
for name, group in config.topology {
"cm-\(name)": #ConfigMap & {
#config: config
#name: name
#group: group
}
"sts-\(name)": #StatefulSet & {
#config: config
#name: name
#group: group
#cmName: name + "-config"
}
}
}
}