All the deploy

This commit is contained in:
2026-04-08 21:05:44 +02:00
commit 513c2e803c
11 changed files with 655 additions and 0 deletions

View File

@@ -0,0 +1,45 @@
# Deploy this to pieced-gitops alongside the other ArgoCD Applications.
# The operator manages itself from the Helm chart in its own repo.
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: pieced-operator
namespace: argocd
labels:
pieced.ch/managed-by: argocd
spec:
project: default
source:
repoURL: https://git.c5ai.ch/pieced/pieced-operator.git
targetRevision: main
path: deploy/helm/pieced-operator
helm:
valueFiles:
- values.yaml
# Override values per environment if needed:
# parameters:
# - name: image.tag
# value: "v0.1.0"
destination:
server: https://kubernetes.default.svc
namespace: pieced-system
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
- ServerSideApply=true # Needed for CRD
- RespectIgnoreDifferences=true
retry:
limit: 3
backoff:
duration: 5s
factor: 2
maxDuration: 1m
ignoreDifferences:
# controller-runtime updates status subresource
- group: pieced.ch
kind: PiecedTenant
jsonPointers:
- /status

View File

@@ -0,0 +1,90 @@
# Example: minimal tenant with no packages (just base OpenClaw + LiteLLM)
apiVersion: pieced.ch/v1alpha1
kind: PiecedTenant
metadata:
name: alpha
spec:
displayName: Alpha Corp
workspaceFiles:
SOUL.md: |
# Alpha Corp AI Assistant
You are Alpha Corp's AI assistant, hosted by PieCed IT in Switzerland.
Be professional, concise, helpful. Respond in the customer's language.
AGENTS.md: |
## Session start
- Read SOUL.md and USER.md before responding
- Read MEMORY.md if present; read today + yesterday in memory/
## Safety
- Don't dump secrets or credentials
- Don't run destructive commands unless explicitly asked
## Memory
- Daily log: memory/YYYY-MM-DD.md
- Long-term: MEMORY.md
TOOLS.md: |
# Tools & Skills Notes
- Hosted by PieCed IT on Swiss infrastructure
- LLM: Qwen 3.5 27B via LiteLLM
---
# Example: tenant with Telegram channel
apiVersion: pieced.ch/v1alpha1
kind: PiecedTenant
metadata:
name: alpha
spec:
displayName: Alpha Corp
agentName: Alpha Assistant
packages:
- telegram
workspaceFiles:
SOUL.md: |
# Alpha Corp AI Assistant
You are Alpha Corp's AI assistant, hosted by PieCed IT in Switzerland.
Be professional, concise, helpful. Respond in the customer's language.
## Boundaries
- Do not make commitments on behalf of Alpha Corp
- If unsure, say so and offer to escalate to a human
AGENTS.md: |
## Session start
- Read SOUL.md and USER.md before responding
- Read MEMORY.md if present; read today + yesterday in memory/
## Safety
- Don't dump secrets or credentials
- Don't run destructive commands unless explicitly asked
## Memory
- Daily log: memory/YYYY-MM-DD.md
- Long-term: MEMORY.md
TOOLS.md: |
# Tools & Skills Notes
- Hosted by PieCed IT on Swiss infrastructure
- LLM: Qwen 3.5 27B via LiteLLM
---
# Example: full-featured tenant
apiVersion: pieced.ch/v1alpha1
kind: PiecedTenant
metadata:
name: beta
spec:
displayName: Beta GmbH
agentName: Beta Assistant
storageSize: 10Gi
packages:
- telegram
- web-search
- document-processing
workspaceFiles:
SOUL.md: |
# Beta GmbH AI Assistant
You are Beta GmbH's assistant. Be precise, bilingual (DE/EN).
You have access to web search and document processing tools.
AGENTS.md: |
## Session start
- Read SOUL.md and USER.md before responding
- Read MEMORY.md if present; read today + yesterday in memory/
## Safety
- Don't dump secrets or credentials
- Don't run destructive commands unless explicitly asked
TOOLS.md: |
# Tools & Skills
- Web search: SearXNG-based, no external API keys needed
- Document processing: PDF, DOCX, spreadsheets via pandoc + libreoffice

View File

@@ -0,0 +1,6 @@
apiVersion: v2
name: pieced-operator
description: PieCed IT tenant lifecycle operator
version: 0.1.0
appVersion: "0.1.0"
type: application

View File

@@ -0,0 +1,112 @@
# The package catalog is deployed as a ConfigMap in the operator namespace.
# To update packages, edit the catalog data below and upgrade the Helm release.
apiVersion: v1
kind: ConfigMap
metadata:
name: pieced-package-catalog
namespace: {{ .Release.Namespace }}
labels:
app.kubernetes.io/name: pieced-operator
data:
catalog.yaml: |
packages:
telegram:
name: Telegram
category: channel
description: Telegram bot messaging channel
channels:
telegram:
enabled: true
env_vars:
- name: TELEGRAM_BOT_TOKEN
secret_key: bot-token
vault_path_suffix: telegram
required: true
bindings:
- match:
channel: telegram
egress_rules:
- host: api.telegram.org
port: 443
customer_instructions: |
1. Open Telegram and message @BotFather
2. Send /newbot and follow the prompts
3. Copy the bot token
disclaimer: >
Messages are relayed through Telegram servers outside Switzerland.
discord:
name: Discord
category: channel
description: Discord bot messaging channel
channels:
discord:
enabled: true
env_vars:
- name: DISCORD_BOT_TOKEN
secret_key: bot-token
vault_path_suffix: discord
- name: DISCORD_APP_ID
secret_key: app-id
vault_path_suffix: discord
bindings:
- match:
channel: discord
egress_rules:
- host: discord.com
port: 443
- host: gateway.discord.gg
port: 443
customer_instructions: |
1. Go to https://discord.com/developers/applications
2. Create app, add bot, copy token and app ID
3. Invite bot to server with messages scope
email:
name: Email (Gmail)
category: channel
description: Email integration via Gmail IMAP/SMTP
channels:
email:
enabled: true
settings:
provider: gmail
env_vars:
- name: EMAIL_ADDRESS
secret_key: address
vault_path_suffix: email
- name: EMAIL_APP_PASSWORD
secret_key: app-password
vault_path_suffix: email
bindings:
- match:
channel: email
egress_rules:
- host: imap.gmail.com
port: 993
- host: smtp.gmail.com
port: 465
web-search:
name: Web Search
category: skill
description: Web search via internal SearXNG
skills:
- "pack:openclaw/skills/web-search@latest"
env_vars:
- name: SEARXNG_URL
default: "http://searxng.searxng.svc.cluster.local:8080"
egress_rules: []
document-processing:
name: Document Processing
category: skill
description: PDF, DOCX, spreadsheet processing
skills:
- "pack:openclaw/skills/document-processing@latest"
init_deps:
apt:
- pandoc
- libreoffice-writer-nogui
- ffmpeg
egress_rules: []

View File

@@ -0,0 +1,13 @@
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.17.2
spec:
group: ""
names:
kind: ""
plural: ""
scope: ""
versions: null

View File

@@ -0,0 +1,116 @@
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: piecedtenants.pieced.ch
annotations:
controller-gen.kubebuilder.io/version: v0.17.2
spec:
group: pieced.ch
names:
kind: PiecedTenant
listKind: PiecedTenantList
plural: piecedtenants
singular: piecedtenant
shortNames:
- pt
scope: Cluster
versions:
- name: v1alpha1
served: true
storage: true
subresources:
status: {}
additionalPrinterColumns:
- name: Display Name
type: string
jsonPath: .spec.displayName
- name: Phase
type: string
jsonPath: .status.phase
- name: Namespace
type: string
jsonPath: .status.tenantNamespace
- name: Age
type: date
jsonPath: .metadata.creationTimestamp
schema:
openAPIV3Schema:
type: object
description: PiecedTenant represents a customer tenant on the PieCed IT multi-tenant platform.
properties:
apiVersion:
type: string
kind:
type: string
metadata:
type: object
spec:
type: object
required:
- displayName
properties:
displayName:
type: string
description: Human-readable tenant name.
packages:
type: array
description: Enabled package IDs from the catalog.
items:
type: string
agentId:
type: string
description: OpenClaw agent identifier. Defaults to "assistant".
agentName:
type: string
description: Display name of the agent. Defaults to "Assistant".
storageSize:
type: string
description: PVC size for OpenClaw workspace. Defaults to "5Gi".
workspaceFiles:
type: object
description: >
MD files seeded into the agent workspace on first boot.
Keys are filenames (SOUL.md, AGENTS.md, TOOLS.md), values are content.
Seeded once — runtime mutations persist on PVC.
additionalProperties:
type: string
suspend:
type: boolean
description: Stops reconciliation without deleting resources.
status:
type: object
properties:
conditions:
type: array
items:
type: object
properties:
type:
type: string
status:
type: string
lastTransitionTime:
type: string
format: date-time
reason:
type: string
message:
type: string
required:
- type
- status
- lastTransitionTime
- reason
phase:
type: string
tenantNamespace:
type: string
litellmTeamId:
type: string
enabledPackages:
type: array
items:
type: string
observedGeneration:
type: integer
format: int64

View File

@@ -0,0 +1,64 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: pieced-operator
namespace: {{ .Release.Namespace }}
labels:
app.kubernetes.io/name: pieced-operator
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app.kubernetes.io/name: pieced-operator
template:
metadata:
labels:
app.kubernetes.io/name: pieced-operator
spec:
serviceAccountName: {{ .Values.serviceAccount.name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 8 }}
automountServiceAccountToken: true # Required for K8s API + Vault K8s auth
containers:
- name: operator
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
args:
- --vault-addr={{ .Values.config.vault.address }}
- --vault-role={{ .Values.config.vault.role }}
- --vault-auth-path={{ .Values.config.vault.authPath }}
- --litellm-url={{ .Values.config.litellm.url }}
- --metrics-bind-address=:8080
- --health-probe-bind-address=:8081
{{- if .Values.leaderElection.enabled }}
# Leader election is built into the manager
{{- end }}
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
ports:
- name: metrics
containerPort: 8080
protocol: TCP
- name: health
containerPort: 8081
protocol: TCP
livenessProbe:
httpGet:
path: /healthz
port: health
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe:
httpGet:
path: /readyz
port: health
initialDelaySeconds: 5
periodSeconds: 10
securityContext:
{{- toYaml .Values.containerSecurityContext | nindent 12 }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
terminationGracePeriodSeconds: 10

View File

@@ -0,0 +1,62 @@
{{- if .Values.networkPolicy.enabled }}
# Network isolation for the operator itself.
# Restricts egress to only the services it needs: K8s API, OpenBao, LiteLLM, DNS.
# No ingress is needed except for metrics scraping (optional).
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: pieced-operator-egress
namespace: {{ .Release.Namespace }}
labels:
app.kubernetes.io/name: pieced-operator
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: pieced-operator
egress:
# DNS resolution
- toEndpoints:
- matchLabels:
k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name: kube-system
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
# Kubernetes API server (for controller-runtime)
- toEntities:
- kube-apiserver
toPorts:
- ports:
- port: "6443"
protocol: TCP
# OpenBao (policy/role management, secret read/write)
- toEndpoints:
- matchLabels:
k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name: openbao
toPorts:
- ports:
- port: "8200"
protocol: TCP
# LiteLLM (team/key provisioning)
- toEndpoints:
- matchLabels:
k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name: inference
toPorts:
- ports:
- port: "4000"
protocol: TCP
ingress:
# Allow Prometheus scraping metrics (optional, from monitoring namespace)
- fromEndpoints:
- matchLabels:
k8s:io.cilium.k8s.namespace.labels.kubernetes.io/metadata.name: monitoring
toPorts:
- ports:
- port: "8080"
protocol: TCP
{{- end }}

View File

@@ -0,0 +1,80 @@
# RBAC: Minimal permissions for the pieced-operator.
# Each rule is scoped to only what the controller actually needs.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: pieced-operator
labels:
app.kubernetes.io/name: pieced-operator
rules:
# --- PiecedTenant CRD ---
- apiGroups: ["pieced.ch"]
resources: ["piecedtenants"]
verbs: ["get", "list", "watch", "update", "patch"]
- apiGroups: ["pieced.ch"]
resources: ["piecedtenants/status"]
verbs: ["get", "update", "patch"]
- apiGroups: ["pieced.ch"]
resources: ["piecedtenants/finalizers"]
verbs: ["update"]
# --- Namespaces (tenant namespaces) ---
- apiGroups: [""]
resources: ["namespaces"]
verbs: ["get", "list", "watch", "create", "update", "delete"]
# --- ConfigMaps (workspace seed + catalog) ---
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "watch", "create", "update", "delete"]
# --- Events (controller status reporting) ---
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "patch"]
# --- Capsule Tenant ---
- apiGroups: ["capsule.clastix.io"]
resources: ["tenants"]
verbs: ["get", "list", "watch", "create", "update", "delete"]
# --- ESO SecretStore ---
- apiGroups: ["external-secrets.io"]
resources: ["secretstores"]
verbs: ["get", "list", "watch", "create", "update", "delete"]
# --- ESO ExternalSecret ---
- apiGroups: ["external-secrets.io"]
resources: ["externalsecrets"]
verbs: ["get", "list", "watch", "create", "update", "delete"]
# --- Cilium CiliumNetworkPolicy ---
- apiGroups: ["cilium.io"]
resources: ["ciliumnetworkpolicies"]
verbs: ["get", "list", "watch", "create", "update", "delete"]
# --- OpenClaw OpenClawInstance ---
- apiGroups: ["openclaw.rocks"]
resources: ["openclawinstances"]
verbs: ["get", "list", "watch", "create", "update", "delete"]
# --- Leader election (coordination) ---
- apiGroups: ["coordination.k8s.io"]
resources: ["leases"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: pieced-operator
labels:
app.kubernetes.io/name: pieced-operator
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: pieced-operator
subjects:
- kind: ServiceAccount
name: {{ .Values.serviceAccount.name }}
namespace: {{ .Release.Namespace }}

View File

@@ -0,0 +1,12 @@
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ .Values.serviceAccount.name }}
namespace: {{ .Release.Namespace }}
labels:
app.kubernetes.io/name: pieced-operator
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}

View File

@@ -0,0 +1,55 @@
image:
repository: registry.c5ai.ch/pieced/pieced-operator
tag: latest
pullPolicy: IfNotPresent
replicaCount: 1
# Operator configuration
config:
vault:
# Internal service URL for OpenBao
address: "http://openbao.openbao.svc:8200"
# K8s auth role for the operator (must be pre-created in OpenBao)
role: "pieced-operator"
authPath: "kubernetes"
litellm:
# Internal service URL for LiteLLM
url: "http://litellm.inference.svc:4000"
# Security context — non-root, read-only rootfs, no privileges
securityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
fsGroup: 65532
containerSecurityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
seccompProfile:
type: RuntimeDefault
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
# Leader election ensures only one instance reconciles
leaderElection:
enabled: true
# Service account — the operator's identity for RBAC and Vault K8s auth
serviceAccount:
name: pieced-operator
annotations: {}
# Network policy — restrict operator egress to only what it needs
networkPolicy:
enabled: true