diff --git a/.github/workflows/helm-template-check.yml b/.github/workflows/helm-template-check.yml index 5ebbc90..df5e251 100644 --- a/.github/workflows/helm-template-check.yml +++ b/.github/workflows/helm-template-check.yml @@ -32,6 +32,15 @@ jobs: run: | helm template intel ./charts/intel --values ./charts/intel/values.yaml + - name: Validate AI Chart + run: | + helm lint ./charts/ai + helm template test ./charts/ai \ + --set-file ctaiPropertiesFile=./charts/ai/ctai-configuration.properties.template \ + --set service.fqdn=https://example.codetogether.io \ + --set imageCredentials.username=test \ + --set imageCredentials.password=test + - name: Validate Live Chart run: | helm template live ./charts/live --values ./charts/live/values.yaml diff --git a/charts/ai/.helmignore b/charts/ai/.helmignore new file mode 100644 index 0000000..a81648f --- /dev/null +++ b/charts/ai/.helmignore @@ -0,0 +1,22 @@ +# Patterns to ignore when building packages. +.DS_Store +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +*.swp +*.bak +*.tmp +*.orig +*~ +.project +.idea/ +*.tmproj +.vscode/ +SPEC.md +values-aitrax.yaml +values-cluster.yaml +values-local.yaml diff --git a/charts/ai/Chart.yaml b/charts/ai/Chart.yaml new file mode 100644 index 0000000..71962b1 --- /dev/null +++ b/charts/ai/Chart.yaml @@ -0,0 +1,21 @@ +apiVersion: v2 +name: codetogether-ai +description: CodeTogether AI on-premises Kubernetes deployment + +type: application +version: 0.1.0 +appVersion: "0.1.1204" + +icon: https://www.codetogether.com/wp-content/uploads/2020/02/codetogether-circle-128.png +home: https://www.codetogether.com + +maintainers: + - email: info@codetogether.com + name: CodeTogether Inc. + +keywords: + - codetogether + - ctai + - ai + - aitrax + - on-premises diff --git a/charts/ai/README.md b/charts/ai/README.md new file mode 100644 index 0000000..565cd4f --- /dev/null +++ b/charts/ai/README.md @@ -0,0 +1,294 @@ +# Helm Chart for CodeTogether AI + +## Summary + +This chart deploys CodeTogether AI on a Kubernetes cluster using Helm. Runtime +configuration is supplied as `ctai-configuration.properties`, and the protected +storage master key is stored in a separate Kubernetes Secret. + +## Prerequisites + +- Helm v3.5+ +- Kubernetes v1.19+ +- PostgreSQL reachable from the cluster. This chart does not install Postgres. +- A DNS-routable HTTPS host for CodeTogether AI, configured as `service_fqdn`. +- A TLS Secret for Ingress when `ingress.enabled=true`. +- CodeTogether-provided credentials for `hub.edge.codetogether.com`. + +## PostgreSQL Prerequisites + +This chart does not install Postgres. Provide an external database reachable +from the cluster and configure it in `ctai-configuration.properties`. + +Before the first pod starts, run the database grants as a Postgres admin. A +dedicated application user is recommended: + +```sql +GRANT ALL PRIVILEGES ON DATABASE TO ; +GRANT CREATE ON DATABASE TO ; +ALTER DATABASE OWNER TO ; +ALTER ROLE BYPASSRLS; +``` + +`BYPASSRLS` matches the production application role shape. CodeTogether uses +row-level security on platform tables, including `oauth2_cache`, where SSO +stores OAuth state. Without it, SSO redirects can fail because OAuth state +cannot be inserted or read. + +Managed Postgres superusers, such as DigitalOcean's `doadmin`, bypass RLS but +are not recommended for production application connections. If your provider +requires SSL, append `?sslmode=require` to the JDBC URL, for example: + +```properties +database_url=jdbc:postgresql://postgres.example.internal:5432/codetogether?sslmode=require +``` + +## Install + +### 1. Prepare the configuration file + +Copy `ctai-configuration.properties.template` to +`ctai-configuration.properties`, then replace the example values with the values +for your environment. + +```bash +cp ctai-configuration.properties.template ctai-configuration.properties +``` + +Set `service_fqdn` to the public HTTPS URL for the deployment, for example: + +```properties +service_fqdn=https://ai.example.com +``` + +Set the external Postgres connection properties: + +```properties +database_url=jdbc:postgresql://postgres.example.internal:5432/codetogether +database_username=codetogether +database_password=replace-with-postgres-password +``` + +Generate the random properties-file secrets and paste the output into +`ctai-configuration.properties`: + +```bash +./generate-properties-secrets.sh +``` + +The configuration file contains deployment secrets. Store it securely and do +not commit customer-specific values. + +### 2. Configure SSO + +Register this redirect URI with your identity provider: + +```text +https://ai.example.com/api/v1/auth/sso/provider/callback +``` + +Use the same host as `service_fqdn`. For Google OAuth or Google OIDC, add that +URI as an Authorized redirect URI in the Google Cloud Console, then set the +provider values in `ctai-configuration.properties`, for example: + +```properties +sso_provider1_type=google +sso_provider1=google +sso_provider1_client_id=replace-with-google-client-id +sso_provider1_client_secret=replace-with-google-client-secret +sso_provider1_client_issuer_url=https://accounts.google.com +``` + +Before testing SSO, complete the [PostgreSQL prerequisites](#postgresql-prerequisites). + +### 3. Create the TLS Secret + +Create the TLS Secret in the namespace where CodeTogether AI will run. The +default Secret name is `codetogether-ai-tls`. + +```bash +kubectl create namespace codetogether-ai +kubectl create secret tls codetogether-ai-tls \ + --namespace codetogether-ai \ + --key /path/to/tls.key \ + --cert /path/to/tls.crt +``` + +### 4. Create the protected storage master-key Secret + +CodeTogether AI uses a 32-byte AES master key to encrypt sensitive values stored +in Postgres. Generate this key once per deployment environment and database. +Changing the key after encrypted data exists prevents that data from being +decrypted. + +```bash +./generate-master-key.sh --k8s-secret-yaml --secret-name ctai-master-key \ + > ctai-master-key-secret.yaml + +kubectl apply -f ctai-master-key-secret.yaml -n codetogether-ai +``` + +Save the generated base64 key in your password manager and keep it with your +Postgres backup procedure. + +### 5. Install the chart + +```bash +helm repo add codetogether https://helm.codetogether.io +helm repo update + +helm install codetogether-ai codetogether/codetogether-ai \ + --namespace codetogether-ai --create-namespace \ + --set-file ctaiPropertiesFile=./ctai-configuration.properties \ + --set imageCredentials.username=replace-with-hub-edge-username \ + --set imageCredentials.password=replace-with-hub-edge-password \ + --set masterKey.source=secret \ + --set masterKey.existingSecret=ctai-master-key +``` + +Ingress host and TLS are derived from `service_fqdn` in +`ctai-configuration.properties`. If you also set `service.fqdn` in Helm values, +it must exactly match `service_fqdn`. + +By default, the chart does not set `ingress.className`, matching the +CodeTogether Intel chart. Clusters without a default IngressClass should set one +explicitly: + +```bash +--set ingress.className=nginx +``` + +If you set `ingress.className=codetogether-nginx`, the chart also creates a +matching `IngressClass`. + +## Configuration + +The following table lists the primary configurable parameters and defaults. + +| Parameter | Description | Default | +| --------- | ----------- | ------- | +| `nameOverride` | Overrides the chart name | `""` | +| `fullnameOverride` | Overrides the full release name | `""` | +| `image.repository` | Container image repository | `hub.edge.codetogether.com/releases/codetogether-ai` | +| `image.pullPolicy` | Image pull policy | `Always` | +| `image.tag` | Container image tag | `latest` | +| `image.digest` | Optional image digest; overrides the tag when set | `""` | +| `imageCredentials.enabled` | Create a pull Secret from registry credentials | `true` | +| `imageCredentials.registry` | Private registry host | `hub.edge.codetogether.com` | +| `imageCredentials.username` | Registry username | `my-customer-username` | +| `imageCredentials.password` | Registry password | `my-customer-password` | +| `service.fqdn` | Public HTTPS URL; optional with `--set-file`, required for pre-created properties Secrets | `""` | +| `service.type` | Kubernetes Service type | `ClusterIP` | +| `service.port` | Service port for nginx / portal | `1080` | +| `service.annotations` | Kubernetes Service annotations | `{}` | +| `ctaiPropertiesFile` | Properties file contents, normally set with `--set-file` | `""` | +| `ctaipropertiessecret.enabled` | Mount a pre-created properties Secret instead of creating one from `ctaiPropertiesFile` | `false` | +| `ctaipropertiessecret.ref` | Name of the pre-created properties Secret | `""` | +| `masterKey.source` | Master key source: `secret`, `auto`, or `external` | `secret` | +| `masterKey.existingSecret` | Pre-created master-key Secret name when `source=secret` | `ctai-master-key` | +| `masterKey.existingSecretKey` | Key in the master-key Secret | `CTAI_PROTECTED_STORAGE_MASTER_KEY_B64` | +| `masterKey.existingSecretVersionKey` | Optional version key in the master-key Secret | `CTAI_PROTECTED_STORAGE_MASTER_KEY_VERSION` | +| `properties.mountPath` | Pod mount path for the properties file | `/opt/ctai/server/config` | +| `properties.fileName` | Properties filename inside the Secret | `ctai-configuration.properties` | +| `ingress.enabled` | Create an Ingress resource | `true` | +| `ingress.annotations` | Ingress annotations | `nginx.ingress.kubernetes.io/proxy-body-size: "16m"` | +| `ingress.tls.secretName` | TLS Secret for Ingress | `codetogether-ai-tls` | +| `serviceAccount.create` | Create a ServiceAccount | `true` | +| `serviceAccount.name` | ServiceAccount name | `codetogether-ai` | +| `replicaCount` | Deployment replica count | `1` | +| `readinessProbe.path` | Readiness endpoint on port 1080 | `/healthz` | +| `livenessProbe.path` | Liveness endpoint on port 8080 | `/actuator/health` | +| `startupProbe.enabled` | Enable startup probe | `true` | + +`image.tag` controls which container image the Deployment pulls at install or +upgrade time. `appVersion` in `Chart.yaml` is chart metadata only; it does not +pin the running image unless you set `image.tag` or `image.digest`. Most +installations can use the default `latest` tag and receive hub.edge updates on +`helm upgrade` after the registry tag moves. + +## Pre-Created Properties Secret + +For GitOps workflows, create and manage the properties Secret outside Helm: + +```bash +kubectl create secret generic ctai-properties \ + --namespace codetogether-ai \ + --from-file=ctai-configuration.properties=./ctai-configuration.properties +``` + +Then install or upgrade with: + +```yaml +ctaipropertiessecret: + enabled: true + ref: ctai-properties + +service: + fqdn: https://ai.example.com +``` + +When Helm cannot read the properties file through `--set-file`, `service.fqdn` +is required for Ingress and install notes. It must match `service_fqdn` inside +the Secret. + +## Validation and Troubleshooting + +Render the chart locally before installing: + +```bash +helm template codetogether-ai codetogether/codetogether-ai \ + --set-file ctaiPropertiesFile=./ctai-configuration.properties \ + --set service.fqdn=https://ai.example.com \ + --set imageCredentials.username=test \ + --set imageCredentials.password=test +``` + +`service.fqdn` must match `service_fqdn` in the properties file when both are set. + +At runtime, CodeTogether AI validates the mounted configuration before the +service starts. If validation fails, the pod exits and logs a block beginning +with `Configuration errors:`. + +```bash +kubectl logs -n codetogether-ai deploy/codetogether-ai +kubectl logs -n codetogether-ai deploy/codetogether-ai --previous +``` + +Common causes include unfilled example values, Postgres connectivity problems, +missing or changed master-key Secret, an HTTP `service_fqdn`, missing TLS +Secret, or SSO provider settings that do not match the registered redirect URI. +If login fails after returning from Google or another identity provider with +`authorization_request_not_found`, check that the database user has `BYPASSRLS` +and that the registered OAuth redirect URI exactly matches +`{service_fqdn}/api/v1/auth/sso/provider/callback`. + +## Upgrade + +```bash +helm repo update +helm upgrade codetogether-ai codetogether/codetogether-ai \ + --namespace codetogether-ai \ + --set-file ctaiPropertiesFile=./ctai-configuration.properties \ + --set imageCredentials.username=replace-with-hub-edge-username \ + --set imageCredentials.password=replace-with-hub-edge-password +``` + +Using `latest` is fine for most installs. Pin a known GA build only when change +control requires a fixed container version: + +```bash +helm upgrade codetogether-ai codetogether/codetogether-ai \ + --namespace codetogether-ai \ + --set-file ctaiPropertiesFile=./ctai-configuration.properties \ + --set imageCredentials.username=replace-with-hub-edge-username \ + --set imageCredentials.password=replace-with-hub-edge-password \ + --set image.tag=0.1.1204 +``` + +The Deployment rolls when the chart-managed properties Secret changes. + +## Uninstall + +```bash +helm uninstall codetogether-ai -n codetogether-ai +``` diff --git a/charts/ai/ctai-configuration.properties.template b/charts/ai/ctai-configuration.properties.template new file mode 100644 index 0000000..7b78b54 --- /dev/null +++ b/charts/ai/ctai-configuration.properties.template @@ -0,0 +1,152 @@ +# ============================================================================= +# CodeTogether — configuration template (manual fill-in) +# ============================================================================= +# +# Copy this file, replace placeholder values, and save as: +# ctai-configuration.properties +# +# Lines starting with # are comments and are ignored when the file is loaded. +# Each setting uses key=value format (same keys written by the config wizard). +# +# Prefer the config wizard when possible — it validates settings, generates +# secrets, and can test database, SSO, and SMTP connectivity. +# +# After editing manually, you can open the saved file in the wizard (Intro → +# Open existing properties file...) to review or update settings. +# +# ============================================================================= + +# Company display name shown in the application. +# You can update this later from the dashboard. +company_name=Example Company + +# Public Fully Qualified Domain Name (fqdn) for the CodeTogether service. This hostname must be DNS routable and must use https://. +service_fqdn=https://example.codetogether.io + +# Sender address and display name for outbound mail. +email_from_address=noreply@example.com +email_from_name=Example Company + +# SMTP server hostname and port (587 is typical with STARTTLS). +smtp_host=smtp.example.com +smtp_port=587 + +# true when the SMTP server requires authentication; false for open relay / +# IP-allowlisted servers. +smtp_auth_enabled=false +# required when smtp_auth_enabled=true +smtp_username= +smtp_password= + +# STARTTLS: connect on a plain connection first, then upgrade to TLS before +# sending credentials (typical on port 587). +smtp_start_tls=true + +# Connection timeouts in ISO-8601 duration form (defaults shown). +smtp_connect_timeout=PT10S +smtp_read_timeout=PT30S + +# PostgreSQL JDBC URL. Pattern: jdbc:postgresql:/// +# Use a dedicated database for CodeTogether rather than reusing an existing one. +database_url=jdbc:postgresql://postgres.example.internal:5432/codetogether +database_username=codetogether +database_password=replace-with-postgres-password + +# Schemas for telemetry, platform, and datamart data (defaults shown). +# Telemetry: developer and AI activity. +# Datamart: analytics calculated from telemetry. +# Platform: backend server data. +database_telemetry_schema=telemetry +database_platform_schema=platform +database_datamart_schema=datamart + +# Signs short-lived state tokens during GitHub OAuth flows — used when connecting +# a GitHub App and when creating a new GitHub App via manifest provisioning. +# Generate a unique random value; do not reuse across deployments. +# Or run: ./generate-properties-secrets.sh (in this chart directory) +github_state_secret=replace-with-generated-github-state-secret + +# Signs JWT session cookies to secure user sessions after SSO login. +# Generate a unique random value; must differ from the other deployment secrets. +# Do not change after login to the dashboard unless you want your login cookies to change +# Or run: ./generate-properties-secrets.sh (in this chart directory) +sso_jwt_secret=replace-with-generated-sso-jwt-secret + +# Register the full callback URL at your IdP. Full callback URL: +# {service_fqdn}/api/v1/auth/sso/provider/callback +# It must match your IdP registration exactly. +# Example: https://mycompany.codetogether.com/api/v1/auth/sso/provider/callback +sso_redirect_uri=/api/v1/auth/sso/provider/callback +# +# ----------------------------------------------------------------------------- +# SSO providers +# ----------------------------------------------------------------------------- +# +# Each provider uses a numbered prefix: sso_provider1, sso_provider2, ... +# Use consecutive integers starting at 1 with no gaps. +# +# Required keys per provider (replace N with 1, 2, 3, ...): +# sso_providerN_type Provider kind (see valid types below). +# sso_providerN Provider name (see naming rules below). +# sso_providerN_client_id OAuth client ID from your identity provider. +# sso_providerN_client_secret OAuth client secret. +# +# Valid sso_providerN_type values: +# keycloak, github, google, azure, okta, custom +# +# sso_providerN (name field) rules: +# - For keycloak, google, azure, okta: use the same string as _type +# (e.g. sso_provider1_type=keycloak and sso_provider1=keycloak). +# - For github: use github. +# - For custom (optional second sign-in provider): use a unique lowercase +# name (e.g. customprovidername) or a built-in kind for an alternate +# account (github, keycloak, etc.). Only one custom provider is allowed. +# - You cannot register two providers with the same registration id +# (e.g. two keycloak entries); use custom for a second GitHub or Keycloak. +# +# OIDC providers (keycloak, google, azure, okta, and custom with issuer): +# sso_providerN_client_issuer_url= +# +# GitHub (and custom providers using GitHub OAuth endpoints): set _auth_uri, +# _token_uri, _info_uri, _jwt_set_uri, and optionally _logout_uri instead of +# client_issuer_url. Standard GitHub values: +# auth: https://github.com/login/oauth/authorize +# token: https://github.com/login/oauth/access_token +# info: https://api.github.com/user +# jwks: https://token.actions.githubusercontent.com/.well-known/jwks +# logout: https://github.com/logout +# +# Optional keys (omit to use runtime defaults): +# sso_providerN_client_authentication_method +# sso_providerN_display_label Custom provider label shown to users. +# sso_providerN_name_attribute Default varies by type (e.g. login for GitHub). +# sso_providerN_scopes Default: openid,email,profile (email,profile for GitHub). +# sso_providerN_authorization_grant_type Default: authorization_code +# sso_providerN_role_mapping_claim +# sso_providerN_role-mappings Note: hyphen in the property name. +# +# Example — first provider (Keycloak / OIDC): +sso_provider1_type=keycloak +sso_provider1=keycloak +sso_provider1_client_id=replace-with-client-id +sso_provider1_client_secret=replace-with-client-secret +sso_provider1_client_issuer_url=https://auth.example.com/realms/codetogether + +# Example — second provider (uncomment and renumber if needed): +# sso_provider2_type=custom +# sso_provider2= +# sso_provider2_client_id= +# sso_provider2_client_secret= +# sso_provider2_display_label=