diff --git a/README.md b/README.md index a82561a..61d3067 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,6 @@ directory containing `Chart.yaml` is an independently versioned chart. | --- | --- | | `agent-pipe-uploader/` | Internal presigned-S3 artifact uploader Service and Deployment | | `opencode-server/` | OpenCode Deployment and non-secret configuration | -| `opencode-sms-bridge/` (deprecated at 0.1.4) | Signed Twilio SMS/MMS bridge Deployment for fixed OpenCode mobile agents; final release, no further maintenance | ## Guides diff --git a/opencode-sms-bridge/Chart.yaml b/opencode-sms-bridge/Chart.yaml deleted file mode 100644 index 5864bc4..0000000 --- a/opencode-sms-bridge/Chart.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v2 -name: opencode-sms-bridge -description: Signed Twilio SMS and MMS bridge for fixed primary OpenCode agents -type: application -version: 0.1.4 -appVersion: "1" -deprecated: true diff --git a/opencode-sms-bridge/README.md b/opencode-sms-bridge/README.md deleted file mode 100644 index 4a1f630..0000000 --- a/opencode-sms-bridge/README.md +++ /dev/null @@ -1,40 +0,0 @@ -# OpenCode SMS bridge chart - -> **Deprecated.** Version 0.1.4 is the final release and only adds this deprecation marker; no template or values changed from 0.1.3. The chart receives no further maintenance, and published OCI versions up to 0.1.4 remain available at `oci://ghcr.io/makeitworkcloud/charts/opencode-sms-bridge` with no artifacts removed. Do not select this chart for new deployments. - -This chart deploys the portable, single-replica `opencode-sms-bridge` Pod: a public signed-webhook ingress container and a private queue worker container. It routes each destination number to one of four existing primary OpenCode agents: `lawnmowerman`, `grillmaster`, `homesteader`, or `homerepair`. It is not a Twilio-account or cluster-integration chart. - -## Ownership - -The chart owns a Deployment and non-secret ConfigMap. `makeitworkcloud/kustomize-cluster` owns the existing `opencode` namespace, RWO state PVC, stable ClusterIP Service, public workload `TunnelBinding`, and all SOPS-encrypted Secret values. Do not define those resources here. - -The Pod uses `Recreate` because the SQLite queue/session store is intentionally single-writer on an RWO PVC. It is annotated with the installed Reloader's named-Secret annotation so rotation of any referenced bridge or OpenCode Basic-auth Secret replaces both containers. - -## Prerequisites - -The GitOps consumer must provide these Secrets in the `opencode` namespace: - -- `opencode-sms-bridge-routing` with `routing.json`, containing the Twilio account identifier, initial approved sender set, and exactly four fixed destination-to-primary-agent mappings: one each for `lawnmowerman`, `grillmaster`, `homesteader`, and `homerepair`; -- `opencode-sms-bridge-shared` with the webhook-validation token, Fernet state-encryption key, and independent sender-HMAC key; -- `opencode-sms-bridge-worker` with a least-privilege Twilio API Key SID/secret for outbound replies; and -- the existing `opencode-server-auth` Secret for the worker's private OpenCode HTTP request. - -Only a signed request from an approved source number is queued or answered. The bridge invokes the existing primary agent IDs, so they retain their normal OpenCode permissions, MCPs, skills, and shared instructions. The source allowlist is an ingress identity gate, not standing authorization; the existing explicit-confirmation requirements govern individual mutations. - -Use an immutable published image SHA in `image.tag`; `latest` is only the source-chart default and must never be selected by the GitOps consumer. - -## Configuration - -`config.maxMediaBytes` is a quoted decimal string (for example, `"5242880"`). It is rendered into ConfigMap data and consumed by the worker's integer parser; do not use YAML numeric or floating-point notation for this value. - -`config.messagingServiceSid` is a non-secret Twilio Messaging Service identifier. Keep it empty until Terraform has created the service and the operator has associated its sender pool with an approved A2P campaign. The chart passes it only to the worker through the ConfigMap. A non-empty value makes a compatible bridge image submit outbound replies through the Messaging Service; an empty value preserves direct `from_` sending for rollback. Never place API keys or other credentials in this value. - -## Image and audio gates - -Image parts are disabled by default. Enable `config.imagePartsEnabled` only after the selected OpenCode model and deployed file-part API are functionally verified. Keep `config.whisperUrl` empty until a local Whisper-compatible service, capacity, and retention boundary are selected; audio MMS then receives a bounded non-processing reply rather than unvalidated forwarding. - -## Rendered resource - -- Deployment, sync wave `2`, with two hardened containers and a `Recreate` strategy. - -The corresponding cluster overlay must place the RWO state PVC in wave `2` with the Deployment so `WaitForFirstConsumer` storage can bind. Secrets and the Service may be earlier; the `TunnelBinding` belongs in wave `3`. These waves order only resources within the existing `opencode` child Application. diff --git a/opencode-sms-bridge/templates/_helpers.tpl b/opencode-sms-bridge/templates/_helpers.tpl deleted file mode 100644 index 35ef00e..0000000 --- a/opencode-sms-bridge/templates/_helpers.tpl +++ /dev/null @@ -1,13 +0,0 @@ -{{- define "opencode-sms-bridge.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{- define "opencode-sms-bridge.fullname" -}} -{{- default (include "opencode-sms-bridge.name" .) .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} -{{- end }} - -{{- define "opencode-sms-bridge.labels" -}} -app: {{ include "opencode-sms-bridge.fullname" . }} -app.kubernetes.io/name: {{ include "opencode-sms-bridge.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -{{- end }} diff --git a/opencode-sms-bridge/templates/configmap.yaml b/opencode-sms-bridge/templates/configmap.yaml deleted file mode 100644 index 7aed081..0000000 --- a/opencode-sms-bridge/templates/configmap.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: {{ include "opencode-sms-bridge.fullname" . }}-config - labels: - {{- include "opencode-sms-bridge.labels" . | nindent 4 }} -data: - CANONICAL_WEBHOOK_URL: {{ .Values.config.canonicalWebhookUrl | quote }} - STATE_PATH: {{ .Values.config.statePath | quote }} - TWILIO_MEDIA_ALLOWED_HOSTS: {{ join "," .Values.config.mediaAllowedHosts | quote }} - TWILIO_MESSAGING_SERVICE_SID: {{ .Values.config.messagingServiceSid | quote }} - MAX_MEDIA_BYTES: {{ .Values.config.maxMediaBytes | quote }} - MAX_AUDIO_SECONDS: {{ .Values.config.maxAudioSeconds | quote }} - OPENCODE_IMAGE_PARTS_ENABLED: {{ .Values.config.imagePartsEnabled | quote }} - WHISPER_URL: {{ .Values.config.whisperUrl | quote }} - WHISPER_MODEL: {{ .Values.config.whisperModel | quote }} - OPENCODE_API_BASE_URL: {{ .Values.config.opencodeApiBaseUrl | quote }} - OPENCODE_SERVER_USERNAME: {{ .Values.config.opencodeUsername | quote }} diff --git a/opencode-sms-bridge/templates/deployment.yaml b/opencode-sms-bridge/templates/deployment.yaml deleted file mode 100644 index 69d1dcd..0000000 --- a/opencode-sms-bridge/templates/deployment.yaml +++ /dev/null @@ -1,171 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "opencode-sms-bridge.fullname" . }} - annotations: - argocd.argoproj.io/sync-wave: "2" - ignore-check.kube-linter.io/unset-cpu-requirements: "single-node policy" - ignore-check.kube-linter.io/unset-memory-requirements: "single-node policy" - secret.reloader.stakater.com/reload: {{ join "," (list .Values.secrets.routing .Values.secrets.shared .Values.secrets.worker .Values.secrets.opencodeServerAuth) | quote }} - labels: - {{- include "opencode-sms-bridge.labels" . | nindent 4 }} -spec: - replicas: 1 - strategy: - type: Recreate - selector: - matchLabels: - app: {{ include "opencode-sms-bridge.fullname" . }} - template: - metadata: - annotations: - checksum/opencode-sms-bridge-config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} - labels: - {{- include "opencode-sms-bridge.labels" . | nindent 8 }} - spec: - automountServiceAccountToken: false - securityContext: - fsGroup: 1000 - runAsGroup: 1000 - runAsNonRoot: true - runAsUser: 1000 - seccompProfile: - type: RuntimeDefault - containers: - - name: ingress - image: {{ .Values.image.repository }}:{{ .Values.image.tag }} - env: - - name: BRIDGE_MODE - value: ingress - - name: ROUTING_CONFIG_PATH - value: /etc/opencode-sms-bridge/routing.json - - name: STATE_ENCRYPTION_KEY - valueFrom: - secretKeyRef: {name: {{ .Values.secrets.shared }}, key: state-encryption-key} - - name: SENDER_HASH_KEY - valueFrom: - secretKeyRef: {name: {{ .Values.secrets.shared }}, key: sender-hash-key} - - name: TWILIO_AUTH_TOKEN - valueFrom: - secretKeyRef: {name: {{ .Values.secrets.shared }}, key: twilio-auth-token} - - name: CANONICAL_WEBHOOK_URL - valueFrom: - configMapKeyRef: {name: {{ include "opencode-sms-bridge.fullname" . }}-config, key: CANONICAL_WEBHOOK_URL} - - name: STATE_PATH - valueFrom: - configMapKeyRef: {name: {{ include "opencode-sms-bridge.fullname" . }}-config, key: STATE_PATH} - ports: - - name: http - containerPort: 8080 - readinessProbe: - httpGet: {path: /healthz, port: http} - livenessProbe: - httpGet: {path: /healthz, port: http} - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: [ALL] - readOnlyRootFilesystem: true - volumeMounts: - - name: state - mountPath: /state - - name: routing - mountPath: /etc/opencode-sms-bridge - readOnly: true - - name: tmp - mountPath: /tmp - - name: worker - image: {{ .Values.image.repository }}:{{ .Values.image.tag }} - env: - - name: BRIDGE_MODE - value: worker - - name: ROUTING_CONFIG_PATH - value: /etc/opencode-sms-bridge/routing.json - - name: STATE_ENCRYPTION_KEY - valueFrom: - secretKeyRef: {name: {{ .Values.secrets.shared }}, key: state-encryption-key} - - name: SENDER_HASH_KEY - valueFrom: - secretKeyRef: {name: {{ .Values.secrets.shared }}, key: sender-hash-key} - - name: TWILIO_AUTH_TOKEN - valueFrom: - secretKeyRef: {name: {{ .Values.secrets.shared }}, key: twilio-auth-token} - - name: TWILIO_API_KEY_SID - valueFrom: - secretKeyRef: {name: {{ .Values.secrets.worker }}, key: twilio-api-key-sid} - - name: TWILIO_API_KEY_SECRET - valueFrom: - secretKeyRef: {name: {{ .Values.secrets.worker }}, key: twilio-api-key-secret} - - name: OPENCODE_SERVER_PASSWORD - valueFrom: - secretKeyRef: {name: {{ .Values.secrets.opencodeServerAuth }}, key: password} - - name: CANONICAL_WEBHOOK_URL - valueFrom: - configMapKeyRef: {name: {{ include "opencode-sms-bridge.fullname" . }}-config, key: CANONICAL_WEBHOOK_URL} - - name: STATE_PATH - valueFrom: - configMapKeyRef: {name: {{ include "opencode-sms-bridge.fullname" . }}-config, key: STATE_PATH} - - name: TWILIO_MEDIA_ALLOWED_HOSTS - valueFrom: - configMapKeyRef: {name: {{ include "opencode-sms-bridge.fullname" . }}-config, key: TWILIO_MEDIA_ALLOWED_HOSTS} - - name: TWILIO_MESSAGING_SERVICE_SID - valueFrom: - configMapKeyRef: {name: {{ include "opencode-sms-bridge.fullname" . }}-config, key: TWILIO_MESSAGING_SERVICE_SID} - - name: MAX_MEDIA_BYTES - valueFrom: - configMapKeyRef: {name: {{ include "opencode-sms-bridge.fullname" . }}-config, key: MAX_MEDIA_BYTES} - - name: MAX_AUDIO_SECONDS - valueFrom: - configMapKeyRef: {name: {{ include "opencode-sms-bridge.fullname" . }}-config, key: MAX_AUDIO_SECONDS} - - name: OPENCODE_IMAGE_PARTS_ENABLED - valueFrom: - configMapKeyRef: {name: {{ include "opencode-sms-bridge.fullname" . }}-config, key: OPENCODE_IMAGE_PARTS_ENABLED} - - name: WHISPER_URL - valueFrom: - configMapKeyRef: {name: {{ include "opencode-sms-bridge.fullname" . }}-config, key: WHISPER_URL} - - name: WHISPER_MODEL - valueFrom: - configMapKeyRef: {name: {{ include "opencode-sms-bridge.fullname" . }}-config, key: WHISPER_MODEL} - - name: OPENCODE_API_BASE_URL - valueFrom: - configMapKeyRef: {name: {{ include "opencode-sms-bridge.fullname" . }}-config, key: OPENCODE_API_BASE_URL} - - name: OPENCODE_SERVER_USERNAME - valueFrom: - configMapKeyRef: {name: {{ include "opencode-sms-bridge.fullname" . }}-config, key: OPENCODE_SERVER_USERNAME} - livenessProbe: - exec: - command: - - python3 - - -c - - import urllib.request; urllib.request.urlopen('http://127.0.0.1:8081/healthz', timeout=2) - readinessProbe: - exec: - command: - - python3 - - -c - - import urllib.request; urllib.request.urlopen('http://127.0.0.1:8081/healthz', timeout=2) - securityContext: - allowPrivilegeEscalation: false - capabilities: - drop: [ALL] - readOnlyRootFilesystem: true - volumeMounts: - - name: state - mountPath: /state - - name: routing - mountPath: /etc/opencode-sms-bridge - readOnly: true - - name: tmp - mountPath: /tmp - volumes: - - name: state - persistentVolumeClaim: - claimName: {{ .Values.persistence.existingClaim }} - - name: routing - secret: - secretName: {{ .Values.secrets.routing }} - items: - - key: routing.json - path: routing.json - - name: tmp - emptyDir: {} diff --git a/opencode-sms-bridge/values.yaml b/opencode-sms-bridge/values.yaml deleted file mode 100644 index 27fcda6..0000000 --- a/opencode-sms-bridge/values.yaml +++ /dev/null @@ -1,33 +0,0 @@ -fullnameOverride: opencode-sms-bridge - -image: - repository: ghcr.io/makeitworkcloud/opencode-sms-bridge - # GitOps must replace this with the immutable image commit SHA after publication. - tag: latest - -persistence: - existingClaim: opencode-sms-bridge-state - -secrets: - routing: opencode-sms-bridge-routing - shared: opencode-sms-bridge-shared - worker: opencode-sms-bridge-worker - opencodeServerAuth: opencode-server-auth - -config: - canonicalWebhookUrl: https://sms-opencode.makeitwork.cloud/twilio/inbound - statePath: /state/opencode-sms-bridge.db - mediaAllowedHosts: - - api.twilio.com - # Set only after a Twilio Messaging Service exists and its sender pool is associated with an approved campaign. - # This non-secret identifier is passed to the worker through the ConfigMap; empty preserves direct from_ sending. - messagingServiceSid: "" - # Keep decimal text intact when Helm renders ConfigMap data for the worker's integer parser. - maxMediaBytes: "5242880" - maxAudioSeconds: 120 - imagePartsEnabled: false - # Set only after a local Whisper-compatible service is selected and validated. - whisperUrl: "" - whisperModel: base - opencodeApiBaseUrl: http://opencode.opencode.svc:4096 - opencodeUsername: opencode