Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Keep secrets, local state and build outputs out of every image build
# context. Every Dockerfile at the root does `COPY . .` into the builder
# stage; final images copy only the binary, but the builder layer still
# receives whatever is here.

# VCS metadata (binaries are built with -buildvcs=false)
.git
.gitignore

# Local control-plane / node state and credentials
*.db
*.db-wal
*.db-shm
*.key
*.pem
*.p12
*.jks
*.keystore
admin-token
join-token
policies.*
.env
.env.*
mobile/logcat.txt

# Build outputs and caches
bin/
dist/
node_modules/
__pycache__/
.venv/
*.test
*.out
coverage.*
mobile/sam-node-app/build/
site/public/
site/resources/
rootfs.ext4
rootfs.tar

# Test artifacts
tests/e2e/logs/
tests/integration/logs/
tests/integration/scratch/
tests/ui/playwright-report/
tests/ui/test-results/

# Reports written at the repo root
audit.md
security-audit.md
42 changes: 41 additions & 1 deletion .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,10 @@
version: 2
updates:
- package-ecosystem: "gomod"
directory: "/"
directories:
- "/"
- "/cmd/nano-init"
- "/cmd/sam-a2a-bridge"
schedule:
interval: "weekly"
cooldown:
Expand All @@ -32,6 +35,43 @@ updates:
cooldown:
default-days: 7

- package-ecosystem: "npm"
directories:
- "/tests/ui"
schedule:
interval: "weekly"
cooldown:
default-days: 7

- package-ecosystem: "pip"
directories:
- "/sam-mcp-python"
- "/cmd/chaos-agent"
- "/tests/e2e/docker/*"
- "/development/examples/*"
schedule:
interval: "weekly"
cooldown:
default-days: 7
groups:
python-deps:
patterns:
- "*"

- package-ecosystem: "pub"
directory: "/mobile/sam-node-app"
schedule:
interval: "weekly"
cooldown:
default-days: 7

- package-ecosystem: "gradle"
directory: "/mobile/sam-node-app/android"
schedule:
interval: "weekly"
cooldown:
default-days: 7

- package-ecosystem: "docker"
directories:
- "/"
Expand Down
1 change: 1 addition & 0 deletions .github/k8s/sam-box-canary-template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ spec:
- "run"
- "--config=/etc/sam/sam-node.yaml"
- "--control-plane=http://sam-control-plane-${ENV_NAME}.${NAMESPACE}.svc.cluster.local:8080"
- "--insecure-control-plane"
- "--jwt-path=/var/run/secrets/tokens/sam-token"
# Socket only: with no TCP listener there is no API token to leak, and
# the socket's permissions are the credential.
Expand Down
1 change: 1 addition & 0 deletions .github/k8s/sam-node-cop-template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ spec:
- "run"
- "--config=/etc/sam/sam-node.yaml"
- "--control-plane=http://sam-control-plane-${ENV_NAME}.${NAMESPACE}.svc.cluster.local:8080"
- "--insecure-control-plane"
- "--jwt-path=/var/run/secrets/tokens/sam-token"
- "--bind-addr=127.0.0.1:8080"
ports:
Expand Down
1 change: 1 addition & 0 deletions .github/k8s/sam-node-everything-template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ spec:
- "run"
- "--config=/etc/sam/sam-node.yaml"
- "--control-plane=http://sam-control-plane-${ENV_NAME}.${NAMESPACE}.svc.cluster.local:8080"
- "--insecure-control-plane"
- "--jwt-path=/var/run/secrets/tokens/sam-token"
- "--bind-addr=127.0.0.1:8080"
ports:
Expand Down
1 change: 1 addition & 0 deletions .github/k8s/sam-node-openclaw-template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ spec:
- "run"
- "--config=/etc/sam/sam-node.yaml"
- "--control-plane=http://sam-control-plane-${ENV_NAME}.${NAMESPACE}.svc.cluster.local:8080"
- "--insecure-control-plane"
- "--jwt-path=/var/run/secrets/tokens/sam-token"
- "--bind-addr=127.0.0.1:8080"
ports:
Expand Down
6 changes: 5 additions & 1 deletion .github/k8s/sam-node-openrouter-template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ spec:
- "4000"
env:
- name: OPENROUTER_API_KEY
value: "${OPENROUTER_API_KEY}"
valueFrom:
secretKeyRef:
name: openrouter-secret-${ENV_NAME}
key: api-key
ports:
- containerPort: 4000
resources:
Expand All @@ -61,6 +64,7 @@ spec:
- "run"
- "--config=/etc/sam/sam-node.yaml"
- "--control-plane=http://sam-control-plane-${ENV_NAME}.${NAMESPACE}.svc.cluster.local:8080"
- "--insecure-control-plane"
- "--jwt-path=/var/run/secrets/tokens/sam-token"
- "--bind-addr=127.0.0.1:8080"
ports:
Expand Down
1 change: 1 addition & 0 deletions .github/k8s/sam-node-template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ spec:
- "run"
- "--config=/etc/sam/sam-node.yaml"
- "--control-plane=http://sam-control-plane-${ENV_NAME}.${NAMESPACE}.svc.cluster.local:8080"
- "--insecure-control-plane"
- "--jwt-path=/var/run/secrets/tokens/sam-token"
ports:
- containerPort: 8080
Expand Down
1 change: 1 addition & 0 deletions .github/k8s/sam-node-vllm-template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ spec:
- "run"
- "--config=/etc/sam/sam-node.yaml"
- "--control-plane=http://sam-control-plane-${ENV_NAME}.${NAMESPACE}.svc.cluster.local:8080"
- "--insecure-control-plane"
- "--jwt-path=/var/run/secrets/tokens/sam-token"
- "--bind-addr=127.0.0.1:8080"
ports:
Expand Down
1 change: 1 addition & 0 deletions .github/k8s/sam-router-template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ spec:
name: p2p-udp
args:
- "--control-plane=http://sam-control-plane-${ENV_NAME}.${NAMESPACE}.svc.cluster.local:8080"
- "--insecure-control-plane"
- "--listen=/ip4/0.0.0.0/tcp/4501"
- "--listen=/ip4/0.0.0.0/udp/4501/quic-v1"
- "--external-addr=/dnsaddr/bootstrap.${ENV_NAME}.sam-mesh.dev"
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/chart-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

# helm-lint already runs in test.yaml
- name: Run chart unit tests
Expand Down
86 changes: 49 additions & 37 deletions .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -383,6 +383,7 @@ jobs:

echo "Seeding Control Plane Policies..."
ADMIN_TOKEN=$(kubectl get secret sam-control-plane-secret-${ENV_NAME} -n ${NAMESPACE} -o jsonpath='{.data.admin-token}' | base64 -d)
echo "::add-mask::${ADMIN_TOKEN}"

kubectl port-forward deployment/sam-control-plane-${ENV_NAME} -n ${NAMESPACE} 8080:8080 &
PF_PID=$!
Expand Down Expand Up @@ -465,42 +466,39 @@ jobs:

echo "Retrieving Admin Token to generate Bootstrap Token..."
ADMIN_TOKEN=$(kubectl get secret sam-control-plane-secret-${ENV_NAME} -n ${NAMESPACE} -o jsonpath='{.data.admin-token}' | base64 -d)
echo "::add-mask::${ADMIN_TOKEN}"

echo "Generating Bootstrap Token via internal GCP/GKE Control Plane service..."

# Clean up any leftover token generator pod
kubectl delete pod curl-token-gen-vm -n ${NAMESPACE} --ignore-not-found || true

# Run temporary curl pod in control plane namespace
kubectl run curl-token-gen-vm \
-n ${NAMESPACE} \
--image=curlimages/curl:8.6.0 \
--restart=Never \
--overrides='{"spec": {"activeDeadlineSeconds": 30}}' \
-- \
curl -s -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-d '{"role": "sam:role:router", "max_usages": 1}' \
http://sam-control-plane-${ENV_NAME}:8080/admin/bootstrap-tokens

if ! kubectl wait -n ${NAMESPACE} --for=jsonpath='{.status.phase}'=Succeeded pod/curl-token-gen-vm --timeout=15s; then
echo "ERROR: Token generation pod failed!"
kubectl describe pod curl-token-gen-vm -n ${NAMESPACE} || true
kubectl logs pod/curl-token-gen-vm -n ${NAMESPACE} || true
kubectl delete pod curl-token-gen-vm -n ${NAMESPACE} --ignore-not-found || true
exit 1
fi
# Mint the token from the runner over a port-forward: a helper pod
# would carry the admin token in its spec, where kubectl describe
# and anyone with pod read access can see it.
echo "Generating Bootstrap Token via port-forward to the Control Plane..."
kubectl port-forward deployment/sam-control-plane-${ENV_NAME} -n ${NAMESPACE} 8080:8080 &
PF_PID=$!
for i in {1..15}; do
if nc -z localhost 8080; then
break
fi
sleep 1
done

TOKEN_JSON=$(kubectl logs pod/curl-token-gen-vm -n ${NAMESPACE})
kubectl delete pod curl-token-gen-vm -n ${NAMESPACE} --ignore-not-found
TOKEN_JSON=$(curl -fsS -X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${ADMIN_TOKEN}" \
-d '{"role": "sam:role:router", "max_usages": 1}' \
http://localhost:8080/admin/bootstrap-tokens) || {
echo "ERROR: bootstrap token request failed"
kill $PF_PID
exit 1
}
kill $PF_PID

BOOTSTRAP_TOKEN=$(echo "${TOKEN_JSON}" | jq -r .token)
if [ -z "${BOOTSTRAP_TOKEN}" ] || [ "${BOOTSTRAP_TOKEN}" = "null" ]; then
echo "ERROR: Generated bootstrap token is empty or invalid!"
echo "Response: ${TOKEN_JSON}"
echo "Response (token redacted): $(echo "${TOKEN_JSON}" | jq -c 'del(.token)' 2>/dev/null || echo '<not JSON>')"
exit 1
fi
echo "::add-mask::${BOOTSTRAP_TOKEN}"

echo "Successfully generated bootstrap token."

Expand Down Expand Up @@ -566,7 +564,7 @@ jobs:
export IMAGE_TAG="${VAR_IMAGE_TAG}"
export BANANA_BOT_SCRIPT=$(cat site/content/docs/snippets/banana_bot_playground.py | sed 's/^/ /')

envsubst < .github/k8s/sam-node-cop-template.yaml | kubectl apply -f -
envsubst '${ENV_NAME} ${NAMESPACE} ${IMAGE_TAG} ${BANANA_BOT_SCRIPT}' < .github/k8s/sam-node-cop-template.yaml | kubectl apply -f -
kubectl rollout status deployment/cop-canary-${ENV_NAME} -n ${CANARY_NAMESPACE} --timeout=120s || {
echo "Cop Canary Deployment failed!"
print_rollout_diagnostics "${CANARY_NAMESPACE}" "cop-canary-${ENV_NAME}" "app=cop-canary-${ENV_NAME}"
Expand Down Expand Up @@ -606,7 +604,7 @@ jobs:
export NAMESPACE="sam-${ENV_NAME}"
export IMAGE_TAG="${VAR_IMAGE_TAG}"

envsubst < .github/k8s/sam-box-canary-template.yaml | kubectl apply -f -
envsubst '${ENV_NAME} ${NAMESPACE} ${IMAGE_TAG}' < .github/k8s/sam-box-canary-template.yaml | kubectl apply -f -
kubectl rollout status deployment/box-canary-${ENV_NAME} -n ${CANARY_NAMESPACE} --timeout=120s || {
echo "sam-box Canary Deployment failed!"
print_rollout_diagnostics "${CANARY_NAMESPACE}" "box-canary-${ENV_NAME}" "app=box-canary-${ENV_NAME}"
Expand Down Expand Up @@ -646,7 +644,7 @@ jobs:
export NAMESPACE="sam-${ENV_NAME}"
export IMAGE_TAG="${VAR_IMAGE_TAG}"

envsubst < .github/k8s/sam-node-template.yaml | kubectl apply -f -
envsubst '${ENV_NAME} ${NAMESPACE} ${IMAGE_TAG}' < .github/k8s/sam-node-template.yaml | kubectl apply -f -
kubectl rollout status deployment/sam-canary-${ENV_NAME} -n ${CANARY_NAMESPACE} --timeout=120s || {
echo "Canary Deployment failed!"
print_rollout_diagnostics "${CANARY_NAMESPACE}" "sam-canary-${ENV_NAME}" "app=sam-canary-${ENV_NAME}"
Expand Down Expand Up @@ -692,14 +690,22 @@ jobs:
export IMAGE_TAG="${VAR_IMAGE_TAG}"
export GEMINI_API_KEY="${VAR_GEMINI_API_KEY}"

GATEWAY_TOKEN=$(kubectl get secret openclaw-secret-${ENV_NAME} -n ${CANARY_NAMESPACE} -o jsonpath="{.data.gateway-token}" 2>/dev/null | base64 -d || openssl rand -hex 16)
GATEWAY_TOKEN=$(kubectl get secret openclaw-secret-${ENV_NAME} -n ${CANARY_NAMESPACE} -o jsonpath="{.data.gateway-token}" 2>/dev/null | base64 -d || true)
# base64 -d on empty input exits 0, so a missing secret used to yield
# an empty token here; test the value, not the pipeline status.
if [ -z "${GATEWAY_TOKEN}" ]; then
GATEWAY_TOKEN=$(openssl rand -hex 16)
fi
echo "::add-mask::${GATEWAY_TOKEN}"
kubectl create secret generic openclaw-secret-${ENV_NAME} \
--namespace=${CANARY_NAMESPACE} \
--from-literal=gateway-token="${GATEWAY_TOKEN}" \
--from-literal=gemini-api-key="${GEMINI_API_KEY}" \
--dry-run=client -o yaml | kubectl apply -f -

envsubst < .github/k8s/sam-node-openclaw-template.yaml | kubectl apply -f -
# Allow-list the placeholders: ${OPENCLAW_GATEWAY_TOKEN} in the
# template is a container-runtime shell variable, not ours to expand.
envsubst '${ENV_NAME} ${NAMESPACE} ${IMAGE_TAG}' < .github/k8s/sam-node-openclaw-template.yaml | kubectl apply -f -
kubectl rollout status deployment/openclaw-canary-${ENV_NAME} -n ${CANARY_NAMESPACE} --timeout=300s || {
echo "OpenClaw Canary Deployment failed!"
print_rollout_diagnostics "${CANARY_NAMESPACE}" "openclaw-canary-${ENV_NAME}" "app=openclaw-canary-${ENV_NAME}"
Expand Down Expand Up @@ -751,7 +757,7 @@ jobs:
--from-literal=hf-token="${HF_TOKEN}" \
--dry-run=client -o yaml | kubectl apply -f -

envsubst < .github/k8s/sam-node-vllm-template.yaml | kubectl apply -f -
envsubst '${ENV_NAME} ${NAMESPACE} ${IMAGE_TAG}' < .github/k8s/sam-node-vllm-template.yaml | kubectl apply -f -
kubectl rollout status deployment/vllm-canary-${ENV_NAME} -n ${CANARY_NAMESPACE} --timeout=300s || {
echo "vLLM Canary Deployment failed!"
print_rollout_diagnostics "${CANARY_NAMESPACE}" "vllm-canary-${ENV_NAME}" "app=vllm-canary-${ENV_NAME}"
Expand Down Expand Up @@ -795,7 +801,7 @@ jobs:
export NAMESPACE="sam-${ENV_NAME}"
export IMAGE_TAG="${VAR_IMAGE_TAG}"

envsubst < .github/k8s/sam-node-everything-template.yaml | kubectl apply -f -
envsubst '${ENV_NAME} ${NAMESPACE} ${IMAGE_TAG}' < .github/k8s/sam-node-everything-template.yaml | kubectl apply -f -
kubectl rollout status deployment/everything-canary-${ENV_NAME} -n ${CANARY_NAMESPACE} --timeout=120s || {
echo "Everything Canary Deployment failed!"
print_rollout_diagnostics "${CANARY_NAMESPACE}" "everything-canary-${ENV_NAME}" "app=everything-canary-${ENV_NAME}"
Expand Down Expand Up @@ -839,9 +845,15 @@ jobs:
export NAMESPACE="sam-${ENV_NAME}"
export CANARY_NAMESPACE="sam-canary-${ENV_NAME}"
export IMAGE_TAG="${VAR_IMAGE_TAG}"

# The key lives in a Secret the pod references, never in the
# Deployment spec.
kubectl create secret generic openrouter-secret-${ENV_NAME} \
--namespace=${CANARY_NAMESPACE} \
--from-literal=api-key="${OPENROUTER_API_KEY}" \
--dry-run=client -o yaml | kubectl apply -f -

# Substitute the API key and other variables into the template and apply it to the cluster
envsubst < .github/k8s/sam-node-openrouter-template.yaml | kubectl apply -f -
envsubst '${ENV_NAME} ${NAMESPACE} ${IMAGE_TAG}' < .github/k8s/sam-node-openrouter-template.yaml | kubectl apply -f -

kubectl rollout status deployment/openrouter-canary-${ENV_NAME} -n ${CANARY_NAMESPACE} --timeout=120s || {
echo "OpenRouter Canary Deployment failed!"
Expand Down
5 changes: 2 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: goreleaser

on:
push:
# run only against tags
# run only against release tags
tags:
- "*"
- "v*"

permissions:
contents: read
Expand All @@ -14,7 +14,6 @@ jobs:
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
steps:
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
Expand Down
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -210,12 +210,13 @@ lint: fmt helm-lint
# fast chart template checks; no cluster needed
.PHONY: helm-test
helm-test:
@helm plugin list 2>/dev/null | grep -q '^unittest' || helm plugin install https://github.com/helm-unittest/helm-unittest
@helm plugin list 2>/dev/null | grep -q '^unittest' || helm plugin install https://github.com/helm-unittest/helm-unittest --version 0.8.2
helm unittest charts/sam-mesh charts/sam-node

.PHONY: verify
verify:
./hack/verify-generated.sh
./hack/verify-secrets.sh

update:
go mod tidy
Expand Down
Loading
Loading