From 513c2e803c33453b96eeed25c15494e46d95085e Mon Sep 17 00:00:00 2001 From: admin Date: Wed, 8 Apr 2026 21:05:44 +0200 Subject: [PATCH] All the deploy --- deploy/argocd-application.yaml | 45 +++++++ deploy/examples/tenants.yaml | 90 ++++++++++++++ deploy/helm/pieced-operator/Chart.yaml | 6 + .../pieced-operator/templates/catalog-cm.yaml | 112 +++++++++++++++++ .../pieced-operator/templates/crds/_.yaml | 13 ++ .../crds/pieced.ch_piecedtenants.yaml | 116 ++++++++++++++++++ .../pieced-operator/templates/deployment.yaml | 64 ++++++++++ .../templates/network-policy.yaml | 62 ++++++++++ .../helm/pieced-operator/templates/rbac.yaml | 80 ++++++++++++ .../templates/serviceaccount.yaml | 12 ++ deploy/helm/pieced-operator/values.yaml | 55 +++++++++ 11 files changed, 655 insertions(+) create mode 100644 deploy/argocd-application.yaml create mode 100644 deploy/examples/tenants.yaml create mode 100644 deploy/helm/pieced-operator/Chart.yaml create mode 100644 deploy/helm/pieced-operator/templates/catalog-cm.yaml create mode 100644 deploy/helm/pieced-operator/templates/crds/_.yaml create mode 100644 deploy/helm/pieced-operator/templates/crds/pieced.ch_piecedtenants.yaml create mode 100644 deploy/helm/pieced-operator/templates/deployment.yaml create mode 100644 deploy/helm/pieced-operator/templates/network-policy.yaml create mode 100644 deploy/helm/pieced-operator/templates/rbac.yaml create mode 100644 deploy/helm/pieced-operator/templates/serviceaccount.yaml create mode 100644 deploy/helm/pieced-operator/values.yaml diff --git a/deploy/argocd-application.yaml b/deploy/argocd-application.yaml new file mode 100644 index 0000000..92fad71 --- /dev/null +++ b/deploy/argocd-application.yaml @@ -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 diff --git a/deploy/examples/tenants.yaml b/deploy/examples/tenants.yaml new file mode 100644 index 0000000..c0e34cf --- /dev/null +++ b/deploy/examples/tenants.yaml @@ -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 diff --git a/deploy/helm/pieced-operator/Chart.yaml b/deploy/helm/pieced-operator/Chart.yaml new file mode 100644 index 0000000..1fb99a6 --- /dev/null +++ b/deploy/helm/pieced-operator/Chart.yaml @@ -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 diff --git a/deploy/helm/pieced-operator/templates/catalog-cm.yaml b/deploy/helm/pieced-operator/templates/catalog-cm.yaml new file mode 100644 index 0000000..5bc03ee --- /dev/null +++ b/deploy/helm/pieced-operator/templates/catalog-cm.yaml @@ -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: [] diff --git a/deploy/helm/pieced-operator/templates/crds/_.yaml b/deploy/helm/pieced-operator/templates/crds/_.yaml new file mode 100644 index 0000000..d2bf2f8 --- /dev/null +++ b/deploy/helm/pieced-operator/templates/crds/_.yaml @@ -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 diff --git a/deploy/helm/pieced-operator/templates/crds/pieced.ch_piecedtenants.yaml b/deploy/helm/pieced-operator/templates/crds/pieced.ch_piecedtenants.yaml new file mode 100644 index 0000000..68c6750 --- /dev/null +++ b/deploy/helm/pieced-operator/templates/crds/pieced.ch_piecedtenants.yaml @@ -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 diff --git a/deploy/helm/pieced-operator/templates/deployment.yaml b/deploy/helm/pieced-operator/templates/deployment.yaml new file mode 100644 index 0000000..1030510 --- /dev/null +++ b/deploy/helm/pieced-operator/templates/deployment.yaml @@ -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 diff --git a/deploy/helm/pieced-operator/templates/network-policy.yaml b/deploy/helm/pieced-operator/templates/network-policy.yaml new file mode 100644 index 0000000..190a8b0 --- /dev/null +++ b/deploy/helm/pieced-operator/templates/network-policy.yaml @@ -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 }} diff --git a/deploy/helm/pieced-operator/templates/rbac.yaml b/deploy/helm/pieced-operator/templates/rbac.yaml new file mode 100644 index 0000000..f81622b --- /dev/null +++ b/deploy/helm/pieced-operator/templates/rbac.yaml @@ -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 }} diff --git a/deploy/helm/pieced-operator/templates/serviceaccount.yaml b/deploy/helm/pieced-operator/templates/serviceaccount.yaml new file mode 100644 index 0000000..3f52ac9 --- /dev/null +++ b/deploy/helm/pieced-operator/templates/serviceaccount.yaml @@ -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 }} diff --git a/deploy/helm/pieced-operator/values.yaml b/deploy/helm/pieced-operator/values.yaml new file mode 100644 index 0000000..8afb887 --- /dev/null +++ b/deploy/helm/pieced-operator/values.yaml @@ -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