From d197ab613952427c917110af6930a57366d99cdc Mon Sep 17 00:00:00 2001 From: Adrian Duke Date: Thu, 9 Apr 2026 16:55:17 +0100 Subject: [PATCH] simulation: Add justfile and network.cue topology definition Introduce the `network.cue` topology definition for creating a set of Radicle nodes and deploying them with the `justfile`. A set of predefined bootstrap keys are provided with helpers to generate connection strings inside Node config. Node config can be overwritten directly inside the CUE config. Nodes versions are taken from quay.io. Talos is the cluster manager. K8s hosts the pods. Timoni aplpys the CUE configs to the K8s cluster. --- simulation/instances/network.cue | 80 ++++++++++++++++++ simulation/justfile | 135 +++++++++++++++++++++++++++++++ 2 files changed, 215 insertions(+) create mode 100644 simulation/instances/network.cue create mode 100644 simulation/justfile diff --git a/simulation/instances/network.cue b/simulation/instances/network.cue new file mode 100644 index 00000000..96f446cb --- /dev/null +++ b/simulation/instances/network.cue @@ -0,0 +1,80 @@ +package main + +// Pre-calculated NIDs. +#BootstrapNIDs: { + "bootstrap-0": "z6MkhJ3cwzpAoNjFnJXWETSPHcDyw2HuBVEhgkyTfbjQHY1B" + "bootstrap-1": "z6MkjcaeSHhQVJU1UeXpnHHZ6mp67zDfQYNMDotHGxbrk7Nj" + "bootstrap-2": "z6MkjNGhuJvdp2noidRMLqco4jFnNNSWzCxSZH5nJV1pGrwQ" + "bootstrap-3": "z6MkpEsXUMSnmyfwdEVkAKijTxGy9WKmNoHWpoxxLM6bbz9M" +} + +// Shared configs +#SeedAddress: { + nid: string + name: string + role: string | *"bootstrap" + index: int | *0 + out: "\(nid)@\(name)-\(index).\(role).default.svc.cluster.local:8776" +} + +#BaseBootstrapSeedConfig: { + node: { + listen: ["0.0.0.0:8776"] + seedingPolicy: { + default: "allow" + scope: "all" + } + ... + } + ... +} + +#BasePeerConfig: { + node: { + listen: [] + peers: type: "dynamic" + connect: [] + externalAddresses: [] + log: "INFO" + relay: "auto" + limits: { + routingMaxSize: 1000 + routingMaxAge: 604800 + gossipMaxAge: 1209600 + fetchConcurrency: 1 + maxOpenFiles: 4096 + rate: { + inbound: {fillRate: 5.0, capacity: 1024} + outbound: {fillRate: 10.0, capacity: 2048} + } + connection: {inbound: 128, outbound: 16} + fetchPackReceive: "500.0 MiB" + } + seedingPolicy: default: "block" + ... + } + ... +} + +values: { + topology: { + // Instances + "bootstrap-v1-8-0": { + role: "bootstrap" + version: "1.8.0" + replicas: 1 + nodeIdSeed: "bootstrap-0" + radicleConfig: #BaseBootstrapSeedConfig + } + "peer-v1-8-0": { + role: "peer" + version: "1.8.0" + replicas: 1 + radicleConfig: #BasePeerConfig & { + preferredSeeds: [ + (#SeedAddress & {nid: #BootstrapNIDs["bootstrap-0"], name: "bootstrap-v1-8-0"}).out, + ] + } + } + } +} diff --git a/simulation/justfile b/simulation/justfile new file mode 100644 index 00000000..8fb2caa3 --- /dev/null +++ b/simulation/justfile @@ -0,0 +1,135 @@ +provisioner := env_var_or_default("PROVISIONER", "qemu") +cluster_name := "radicle-" + provisioner +clusters_dir := env_var("HOME") + "/.talos/clusters" +radicle_node_module := "./modules/radicle-node" +kubectl_context := `kubectl config current-context 2>/dev/null || echo "none"` + +SUCCESS := "✅ " + GREEN + BOLD +CHECK := "🔄 " + BOLD +WARN := "⚠️ " + YELLOW + BOLD +ERROR := "❌ " + RED + BOLD +HINT := "💡 " + BOLD + +default: + @just --list + +# Setup and start the complete simulation environment +[group('start')] +[group('setup')] +start: setup start-network + @echo "" + @echo "{{SUCCESS}}Simulation started!{{NORMAL}}" + @echo "" + +# Setup cluster and dependencies +[group('setup')] +setup: configure-cluster + @echo "{{SUCCESS}}Setup complete{{NORMAL}}" + +# Create the Talos cluster if it doesn't exist +[private] +create-cluster: (verify-tool "talosctl") + #!/usr/bin/env bash + set -e + if [ ! -d "{{clusters_dir}}/{{cluster_name}}" ]; then + echo "{{CHECK}}Creating Talos cluster '{{cluster_name}}' using {{provisioner}}...{{NORMAL}}" + mkdir -p "{{clusters_dir}}" + if [ "{{provisioner}}" = "qemu" ]; then + sudo --preserve-env=HOME,PATH talosctl cluster create --name={{cluster_name}} {{provisioner}} --config-patch-controlplanes '{"cluster": {"allowSchedulingOnControlPlanes": true}}' + else + talosctl cluster create --name={{cluster_name}} {{provisioner}} --config-patch-controlplanes '{"cluster": {"allowSchedulingOnControlPlanes": true}}' + fi + else + echo "{{SUCCESS}}Cluster '{{cluster_name}}' already exists.{{NORMAL}}" + fi + +# Configure the Kubernetes cluster +[private] +configure-cluster: (verify-tool "kubectl") create-cluster + @echo "{{CHECK}}Configuring cluster...{{NORMAL}}" + # Add local-path storage system + @kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml + # Relax security on namespaces + @kubectl label namespace local-path-storage pod-security.kubernetes.io/enforce=privileged pod-security.kubernetes.io/warn=privileged pod-security.kubernetes.io/audit=privileged --overwrite + @kubectl label namespace default pod-security.kubernetes.io/enforce=privileged pod-security.kubernetes.io/warn=privileged pod-security.kubernetes.io/audit=privileged --overwrite + # Set default storage class + @kubectl patch storageclass local-path -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}' + +# Start the simulation network only +[group('start')] +start-network: (verify-tool "timoni") + @echo "{{CHECK}}Starting simulation network...{{NORMAL}}" + @timoni apply radicle-network {{radicle_node_module}} -f instances/network.cue + @just show-cluster + +# Show cluster status +[group('inspect')] +show-cluster: (verify-tool "kubectl") (verify-tool "talosctl") + @echo "Cluster: {{cluster_name}}" + @echo "Context: {{kubectl_context}}" + @talosctl cluster show --name {{cluster_name}} --provisioner {{provisioner}} || true + @kubectl get pods -o wide + +# Delete simulation pods and resources +[group('delete')] +delete: delete-pods delete-pvc + @echo "{{SUCCESS}}Simulation cleaned up{{NORMAL}}" + +# Delete pods only +[group('delete')] +delete-pods: (verify-tool "kubectl") + @echo "{{CHECK}}Deleting pods...{{NORMAL}}" + @kubectl delete pods -l app=radicle-node --wait=false + +# Delete storage volumes +[group('delete')] +delete-pvc: (verify-tool "kubectl") + @echo "{{CHECK}}Deleting storage volumes...{{NORMAL}}" + @kubectl delete pvc -l app=radicle-node --wait=false + +# Destroy the Talos cluster and clean up kubeconfig +[group('delete')] +destroy: (verify-tool "kubectl") (verify-tool "talosctl") show-cluster + #!/usr/bin/env bash + set -e + echo "" + echo -n "Are you sure you want to destroy the cluster and remove kubeconfig entries? [y/N] " + read answer + if [ "${answer:-N}" != "y" ]; then + echo "Aborted." + exit 1 + fi + + echo "{{CHECK}}Destroying talos cluster '{{cluster_name}}'...{{NORMAL}}" + if [ "{{provisioner}}" = "qemu" ]; then + sudo --preserve-env=HOME,PATH talosctl cluster destroy --name {{cluster_name}} --provisioner {{provisioner}} + else + talosctl cluster destroy --name {{cluster_name}} --provisioner {{provisioner}} + fi + + echo "{{CHECK}}Removing kube config entries...{{NORMAL}}" + CONTEXT=$(kubectl config current-context 2>/dev/null || echo "") + if [ -n "$CONTEXT" ]; then + CLUSTER=$(echo "$CONTEXT" | cut -d '@' -f 2) + kubectl config delete-context "$CONTEXT" || true + kubectl config delete-cluster "$CLUSTER" || true + kubectl config unset "users.$CONTEXT" || true + fi + echo "{{WARN}}Make sure you remove the '{{cluster_name}}' entry from: ~/.talos/config{{NORMAL}}" + echo "{{SUCCESS}}Cluster destroyed.{{NORMAL}}" + +# Check if required tools are in PATH. +[private] +verify-tool tool package_name="": + #!/usr/bin/env bash + set -e + if ! command -v {{tool}} >/dev/null 2>&1; then + PKG="{{package_name}}" + if [ -z "$PKG" ]; then + PKG="{{tool}}" + fi + echo "{{ERROR}}Missing required tool: {{tool + NORMAL}}" + echo "{{HINT}}Use your systems package manager to install '$PKG'.{{NORMAL}}" + exit 1 + fi +