Heck yes — let’s ship a clean, self-contained “Empire Node” stack you can paste straight into your forum or drop on disk. Per your style: full file list, complete code, lots of comments, no code windows.
============================================================
REPO NAME
empire-node/
============================================================
FOLDER TREE (copy/paste for your notes)
empire-node/
├─ README.md
├─ .env.example
├─ inventory/
│ ├─ hosts.ini
│ └─ nodes.yaml
├─ scripts/
│ ├─ 00_detect_platform.sh
│ ├─ 10_pi_setup_k3s_server.sh
│ ├─ 11_jetson_setup_k3s_agent.sh
│ ├─ 12_join_cluster.sh
│ ├─ 20_install_gpu_runtime_jetson.sh
│ ├─ 21_label_and_taint_nodes.sh
│ ├─ 30_deploy_stack.sh
│ ├─ 31_create_secrets.sh
│ ├─ 40_enable_heartbeat_service.sh
│ └─ util.sh
├─ services/
│ ├─ heartbeat-client.service
│ └─ heartbeat-client.timer
├─ python/
│ ├─ requirements.txt
│ ├─ heartbeat_client.py
│ └─ dashboard_app.py
├─ k8s/
│ ├─ 00-namespaces.yaml
│ ├─ 01-mosquitto-configmap.yaml
│ ├─ 01-mosquitto-deploy.yaml
│ ├─ 02-prometheus-config.yaml
│ ├─ 02-prometheus-deploy.yaml
│ ├─ 02-node-exporter-daemonset.yaml
│ ├─ 03-grafana-deploy.yaml
│ ├─ 04-minio-deploy.yaml
│ ├─ 05-postgres-statefulset.yaml
│ ├─ 06-nvidia-device-plugin-daemonset.yaml
│ ├─ 10-ai-worker-deploy.yaml
│ └─ 90-ingress-routes.yaml
└─ kubeconfig/
└─ README.txt
============================================================
HIGH-LEVEL CONCEPT
• 5 × Raspberry Pi 5 = control plane + services (k3s server + etcd embedded, MQTT, Postgres, MinIO, Prometheus, Grafana).
• 3 × Jetson Orin Nano = GPU agents (k3s agent) that run CUDA/TensorRT inference pods.
• Single secure mesh: k3s uses the Pi “master” as API server. Jetsons join with token, then get labeled gpu=jetson and resource nvidia.com/gpu via NVIDIA device plugin.
• Ops hygiene: heartbeat agent + systemd timer, Prometheus node metrics, Grafana, backups to MinIO.
• Networking: k3s includes Traefik by default; we add IngressRoutes for dashboard endpoints.
============================================================
ASCII TOPOLOGY
[User/Laptop/Phone]
|
Cloudflare (optional tunnel)
|
┌─────────────── Pi #1 (k3s server / control-plane) ────────────────┐
| - API server + scheduler + Traefik |
| - Mosquitto, Postgres, MinIO, Prometheus, Grafana |
| - Heartbeat dashboard (FastAPI) |
└───────────────────────────────────────────────────────────────────┘
| | | |
Pi #2 Pi #3 Pi #4 Pi #5 (k3s agents; can host non-GPU services)
| | | |
+---------------- Gigabit Switch / VLAN --------------------------+
|
+------------+------------+
| | |
Jetson Orin A Jetson Orin B Jetson Orin C
(k3s agent) (k3s agent) (k3s agent)
CUDA/TensorRT CUDA/TensorRT CUDA/TensorRT
nvidia.com/gpu nvidia.com/gpu nvidia.com/gpu
============================================================
ENV / INVENTORY
FILE: .env.example
PURPOSE: centralize secrets and hostnames. Copy to .env and adjust.
CONTENT:
K3S_TOKEN=REPLACE_WITH_LONG_RANDOM
K3S_SERVER_IP=192.168.1.50
K3S_SERVER_HOST=pi-master.local
DOMAIN_SUFFIX=.local
MQTT_USER=empire
MQTT_PASS=changeme
POSTGRES_USER=empire
POSTGRES_PASS=changeme
POSTGRES_DB=empiredb
MINIO_ACCESS_KEY=miniokey
MINIO_SECRET_KEY=miniopass
GRAFANA_ADMIN_USER=admin
GRAFANA_ADMIN_PASS=changeme
FILE: inventory/hosts.ini
PURPOSE: human reference list of nodes.
CONTENT:
[master]
pi-master ansible_host=192.168.1.50
[pis]
pi-2 ansible_host=192.168.1.51
pi-3 ansible_host=192.168.1.52
pi-4 ansible_host=192.168.1.53
pi-5 ansible_host=192.168.1.54
[jetsons]
jetson-a ansible_host=192.168.1.61
jetson-b ansible_host=192.168.1.62
jetson-c ansible_host=192.168.1.63
FILE: inventory/nodes.yaml
PURPOSE: machine-readable inventory.
CONTENT:
master:
host: pi-master
ip: 192.168.1.50
pis:
- host: pi-2
ip: 192.168.1.51
- host: pi-3
ip: 192.168.1.52
- host: pi-4
ip: 192.168.1.53
- host: pi-5
ip: 192.168.1.54
jetsons:
- host: jetson-a
ip: 192.168.1.61
- host: jetson-b
ip: 192.168.1.62
- host: jetson-c
ip: 192.168.1.63
============================================================
SCRIPTS
FILE: scripts/util.sh
PURPOSE: shared helpers for other scripts.
CONTENT:
#!/usr/bin/env bash
set -euo pipefail
if [ -f ".env" ]; then
export $(grep -v '^#' .env | xargs)
fi
say() { printf "[%s] %s\n" "$(date +%H:%M:%S)" "$*"; }
need() { command -v "$1" >/dev/null 2>&1 || { echo "Missing $1"; exit 1; }; }
FILE: scripts/00_detect_platform.sh
PURPOSE: prints detected platform.
CONTENT:
#!/usr/bin/env bash
set -euo pipefail
. "$(dirname "$0")/util.sh"
uname -a
echo "ARCH=$(uname -m)"
echo "OS=$(uname -s)"
FILE: scripts/10_pi_setup_k3s_server.sh
PURPOSE: install k3s server on Pi master.
CONTENT:
#!/usr/bin/env bash
set -euo pipefail
. "$(dirname "$0")/util.sh"
need curl
say "Installing k3s server on Pi master..."
curl -sfL
https://get.k3s.io | INSTALL_K3S_EXEC="server --write-kubeconfig-mode 644 --disable=servicelb --disable=traefik=false" K3S_TOKEN="${K3S_TOKEN}" sh -
mkdir -p kubeconfig
sudo cp /etc/rancher/k3s/k3s.yaml kubeconfig/config
sudo chown $USER:$USER kubeconfig/config
say "k3s server installed. API: https://${K3S_SERVER_IP}:6443"
FILE: scripts/11_jetson_setup_k3s_agent.sh
PURPOSE: install k3s agent on Jetson (run on each Jetson).
CONTENT:
#!/usr/bin/env bash
set -euo pipefail
. "$(dirname "$0")/util.sh"
need curl
say "Installing k3s agent on Jetson..."
curl -sfL
https://get.k3s.io | K3S_URL="https://${K3S_SERVER_IP}:6443" K3S_TOKEN="${K3S_TOKEN}" sh -
say "k3s agent installed."
FILE: scripts/12_join_cluster.sh
PURPOSE: helper to verify node join and show nodes.
CONTENT:
#!/usr/bin/env bash
set -euo pipefail
. "$(dirname "$0")/util.sh"
need kubectl
export KUBECONFIG=$PWD/kubeconfig/config
kubectl get nodes -o wide
FILE: scripts/20_install_gpu_runtime_jetson.sh
PURPOSE: set NVIDIA container runtime on Jetsons so pods can request GPUs.
CONTENT:
#!/usr/bin/env bash
set -euo pipefail
. "$(dirname "$0")/util.sh"
say "Configuring NVIDIA container runtime on Jetson..."
if ! grep -q "nvidia-container-runtime" /etc/docker/daemon.json 2>/dev/null; then
sudo mkdir -p /etc/docker
sudo bash -c 'cat >/etc/docker/daemon.json' <<'JSON'
{
"default-runtime": "nvidia",
"runtimes": {
"nvidia": {
"path": "nvidia-container-runtime",
"runtimeArgs": []
}
}
}
JSON
sudo systemctl restart docker || true
fi
say "NVIDIA runtime configured."
FILE: scripts/21_label_and_taint_nodes.sh
PURPOSE: label Jetsons for GPU workloads; optional taints.
CONTENT:
#!/usr/bin/env bash
set -euo pipefail
. "$(dirname "$0")/util.sh"
need kubectl
export KUBECONFIG=$PWD/kubeconfig/config
for n in $(kubectl get nodes -o jsonpath='{.items[*].metadata.name}'); do
if echo "$n" | grep -qi "jetson"; then
kubectl label node "$n" gpu=jetson --overwrite
# Optional: taint so only GPU-tolerating pods schedule here
# kubectl taint node "$n" dedicated=gpu:NoSchedule --overwrite
else
kubectl label node "$n" role=general --overwrite
fi
done
kubectl get nodes --show-labels
FILE: scripts/30_deploy_stack.sh
PURPOSE: apply all Kubernetes manifests in order.
CONTENT:
#!/usr/bin/env bash
set -euo pipefail
. "$(dirname "$0")/util.sh"
need kubectl
export KUBECONFIG=$PWD/kubeconfig/config
kubectl apply -f k8s/00-namespaces.yaml
kubectl apply -f k8s/01-mosquitto-configmap.yaml
kubectl apply -f k8s/01-mosquitto-deploy.yaml
kubectl apply -f k8s/02-prometheus-config.yaml
kubectl apply -f k8s/02-prometheus-deploy.yaml
kubectl apply -f k8s/02-node-exporter-daemonset.yaml
kubectl apply -f k8s/03-grafana-deploy.yaml
kubectl apply -f k8s/04-minio-deploy.yaml
kubectl apply -f k8s/05-postgres-statefulset.yaml
kubectl apply -f k8s/06-nvidia-device-plugin-daemonset.yaml
kubectl apply -f k8s/10-ai-worker-deploy.yaml
kubectl apply -f k8s/90-ingress-routes.yaml
kubectl get pods -A -o wide
FILE: scripts/31_create_secrets.sh
PURPOSE: create K8s secrets from .env.
CONTENT:
#!/usr/bin/env bash
set -euo pipefail
. "$(dirname "$0")/util.sh"
need kubectl
export KUBECONFIG=$PWD/kubeconfig/config
kubectl -n empire delete secret mqtt-credentials postgres-credentials minio-credentials grafana-admin 2>/dev/null || true
kubectl -n empire create secret generic mqtt-credentials --from-literal=MQTT_USER="$MQTT_USER" --from-literal=MQTT_PASS="$MQTT_PASS"
kubectl -n empire create secret generic postgres-credentials --from-literal=POSTGRES_USER="$POSTGRES_USER" --from-literal=POSTGRES_PASS="$POSTGRES_PASS"
kubectl -n empire create secret generic minio-credentials --from-literal=MINIO_ACCESS_KEY="$MINIO_ACCESS_KEY" --from-literal=MINIO_SECRET_KEY="$MINIO_SECRET_KEY"
kubectl -n empire create secret generic grafana-admin --from-literal=GF_SECURITY_ADMIN_USER="$GRAFANA_ADMIN_USER" --from-literal=GF_SECURITY_ADMIN_PASSWORD="$GRAFANA_ADMIN_PASS"
echo "Secrets created in namespace 'empire'."
FILE: scripts/40_enable_heartbeat_service.sh
PURPOSE: install Python deps and systemd heartbeat timer on each node.
CONTENT:
#!/usr/bin/env bash
set -euo pipefail
. "$(dirname "$0")/util.sh"
say "Installing Python heartbeat client requirements..."
python3 -m pip install --upgrade pip
pip install -r python/requirements.txt
say "Installing systemd units..."
sudo cp services/heartbeat-client.service /etc/systemd/system/
sudo cp services/heartbeat-client.timer /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now heartbeat-client.timer
systemctl status heartbeat-client.timer --no-pager
SCRIPT INTERUPTED...NEEDS REGENERATION IN AI.