name: "workspace"
description: "Workspace backend element"
schema_version: 1
version: "0.1.1"
api_version: "v1"

requirements:
  core:
    from_version: "0.0.0"
  dbaas:
    from_version: "0.0.0"
  s3aas:
    from_version: "0.0.0"

imports:
  pg18:
    element: "$dbaas"
    kind: "resource"
    link: "$dbaas.types.postgres.versions.$pg18"
  rustfs_1:
    element: "$s3aas"
    kind: "resource"
    link: "$s3aas.types.s3.versions.$rustfs_1"
  core_local_domain:
    element: "$core"
    kind: "resource"
    link: "$core.dns.domains.$local_domain"
  var_default_cores:
    element: "$core"
    kind: "resource"
    link: "$core.vs.variables.$default_cores"
  var_default_ram:
    element: "$core"
    kind: "resource"
    link: "$core.vs.variables.$default_ram"
  var_core_ip_address:
    element: "$core"
    kind: "resource"
    link: "$core.vs.variables.$core_ip_address"
  var_hs256_jwks_encryption_key:
    element: "$core"
    kind: "resource"
    link: "$core.vs.variables.$hs256_jwks_encryption_key"
  var_iam_default_client_id:
    element: "$core"
    kind: "resource"
    link: "$core.vs.variables.$iam_default_client_id"

resources:

  # Canonical action permissions for realm-level external-provider operations.
  # The element owns only the permission catalog. Operators grant the required
  # subset through a project-scoped role binding; deployment never elevates a
  # Workspace user implicitly.
  $core.iam.permissions:
    workspace_external_provider_policy_read:
      name: "workspace.external_provider_policy.read"
      description: "Read Workspace external provider policy"
    workspace_external_provider_policy_update:
      name: "workspace.external_provider_policy.update"
      description: "Update Workspace external provider policy"
    workspace_external_provider_policy_suspend:
      name: "workspace.external_provider_policy.suspend"
      description: "Suspend a Workspace external provider"
    workspace_external_provider_policy_resume:
      name: "workspace.external_provider_policy.resume"
      description: "Resume a Workspace external provider"
    workspace_external_provider_health_read:
      name: "workspace.external_provider_health.read"
      description: "Read Workspace external provider health"
    workspace_external_bridge_instance_read:
      name: "workspace.external_bridge_instance.read"
      description: "Read Workspace external bridge instances"
    workspace_external_bridge_instance_suspend:
      name: "workspace.external_bridge_instance.suspend"
      description: "Suspend a Workspace external bridge instance"
    workspace_external_bridge_instance_resume:
      name: "workspace.external_bridge_instance.resume"
      description: "Resume a Workspace external bridge instance"
    workspace_external_bridge_instance_revoke:
      name: "workspace.external_bridge_instance.revoke"
      description: "Revoke a Workspace external bridge instance"

  $core.compute.nodes:
    workspace_backend:
      name: workspace-backend
      project_id: "12345678-c625-4fee-81d5-f691897b8142"
      cores: $workspace.imports.$var_default_cores:value
      ram: 4096
      disk_spec:
        kind: "disks"
        disks:
          - size: 10
            image: "urn:images:abbd4db3-a75f-53c5-a6a3-cb2d51feab1f"
            label: root
          - size: 2
            label: external-bridge-control

  $core.secret.passwords:
    workspace_projection_db_password:
      project_id: "12345678-c625-4fee-81d5-f691897b8142"
      name: workspace_projection_db_password
      description: "Workspace rebuildable PostgreSQL projection password"
      constructor:
        kind: plain
      method: AUTO_HEX
    workspace_zulip_bridge_enrollment_secret:
      project_id: "12345678-c625-4fee-81d5-f691897b8142"
      name: workspace_zulip_bridge_enrollment_secret
      description: "One-time Zulip bridge Provider API enrollment secret"
      default_length: 40
      constructor:
        kind: plain
      method: AUTO_HEX
    workspace_s3_access_key:
      project_id: "12345678-c625-4fee-81d5-f691897b8142"
      name: workspace_s3_access_key
      description: "Workspace messenger files S3 access key"
      default_length: 20
      constructor:
        kind: plain
      method: AUTO_HEX
    workspace_s3_secret_key:
      project_id: "12345678-c625-4fee-81d5-f691897b8142"
      name: workspace_s3_secret_key
      description: "Workspace messenger files S3 secret key"
      default_length: 40
      constructor:
        kind: plain
      method: AUTO_HEX

  $dbaas.types.postgres.instances:
    workspace_projection_cluster:
      name: "workspace-projection-pg-cluster"
      nodes_number: 1
      project_id: "12345678-c625-4fee-81d5-f691897b8142"
      cpu: 1
      ram: 1024
      disk_size: 15
      version: $workspace.imports.$pg18:uuid

  $dbaas.types.postgres.instances.$workspace_projection_cluster.users:
    workspace_projection_user:
      project_id: "12345678-c625-4fee-81d5-f691897b8142"
      name: workspace
      password: $core.secret.passwords.$workspace_projection_db_password:value
      instance: $dbaas.types.postgres.instances.$workspace_projection_cluster:uuid
  $dbaas.types.postgres.instances.$workspace_projection_cluster.databases:
    workspace_projection_db:
      project_id: "12345678-c625-4fee-81d5-f691897b8142"
      name: workspace
      owner: $dbaas.types.postgres.instances.$workspace_projection_cluster.users.$workspace_projection_user:uuid
      instance: $dbaas.types.postgres.instances.$workspace_projection_cluster:uuid

  $s3aas.types.s3.instances:
    workspace_s3:
      name: "workspace-s3-service"
      project_id: "12345678-c625-4fee-81d5-f691897b8142"
      cpu: $workspace.imports.$var_default_cores:value
      ram: $workspace.imports.$var_default_ram:value
      disk_size: 51
      nodes_number: 1
      kind: "single_node"
      version: $workspace.imports.$rustfs_1:uuid

  $s3aas.types.s3.instances.$workspace_s3.buckets:
    workspace_files_bucket:
      name: "workspace-files"
      project_id: "12345678-c625-4fee-81d5-f691897b8142"
      instance: $s3aas.types.s3.instances.$workspace_s3:uuid
      versioning_enabled: false
      quota_bytes: 0
      object_lock_enabled: false
      public: false

  $s3aas.types.s3.instances.$workspace_s3.policies:
    workspace_files_policy:
      name: "workspace-files-policy"
      project_id: "12345678-c625-4fee-81d5-f691897b8142"
      instance: $s3aas.types.s3.instances.$workspace_s3:uuid
      content:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Action:
              - "s3:GetObject"
              - "s3:PutObject"
              - "s3:DeleteObject"
              - "s3:ListBucket"
            Resource:
              - f"arn:aws:s3:::{$s3aas.types.s3.instances.$workspace_s3.buckets.$workspace_files_bucket:name}"
              - f"arn:aws:s3:::{$s3aas.types.s3.instances.$workspace_s3.buckets.$workspace_files_bucket:name}/*"

  $s3aas.types.s3.instances.$workspace_s3.users:
    workspace_files_user:
      name: "workspace-files"
      project_id: "12345678-c625-4fee-81d5-f691897b8142"
      instance: $s3aas.types.s3.instances.$workspace_s3:uuid

  $s3aas.types.s3.instances.$workspace_s3.users.$workspace_files_user.policies:
    workspace_files_policy_attachment:
      project_id: "12345678-c625-4fee-81d5-f691897b8142"
      policy: $s3aas.types.s3.instances.$workspace_s3.policies.$workspace_files_policy:uuid
      user: $s3aas.types.s3.instances.$workspace_s3.users.$workspace_files_user:uuid
      instance: $s3aas.types.s3.instances.$workspace_s3:uuid

  $s3aas.types.s3.instances.$workspace_s3.users.$workspace_files_user.access_keys:
    workspace_files_access_key:
      project_id: "12345678-c625-4fee-81d5-f691897b8142"
      access_key: $core.secret.passwords.$workspace_s3_access_key:value
      secret_key: $core.secret.passwords.$workspace_s3_secret_key:value
      user: $s3aas.types.s3.instances.$workspace_s3.users.$workspace_files_user:uuid
      instance: $s3aas.types.s3.instances.$workspace_s3:uuid

  $core.config.configs:
    workspace_external_bridge_enrollment_config:
      project_id: "12345678-c625-4fee-81d5-f691897b8142"
      path: /etc/workspace/external-bridge-enrollment.json
      mode: "0600"
      owner: root
      target:
        kind: node
        node: $core.compute.nodes.$workspace_backend:uuid
      on_change:
        kind: shell
        command: /usr/local/bin/workspace-restart-services
      body:
        kind: text
        content: |
          f"{
            "schema_version": 1,
            "enrollments": [{
              "bridge_instance_uuid": "{$core.secret.passwords.$workspace_zulip_bridge_enrollment_secret:uuid}",
              "provider_kind": "zulip",
              "enrollment_generation": 1,
              "enrollment_token": "{$core.secret.passwords.$workspace_zulip_bridge_enrollment_secret:value}"
            }]
          }"
    workspace_backend_config:
      project_id: "12345678-c625-4fee-81d5-f691897b8142"
      path: /etc/workspace/workspace.conf
      mode: "0600"
      target:
        kind: node
        node: $core.compute.nodes.$workspace_backend:uuid
      on_change:
        kind: shell
        command: /usr/local/bin/workspace-reload-config
      body:
        kind: text
        content: |
          f"
          [db]
          connection_url = postgresql://{$dbaas.types.postgres.instances.$workspace_projection_cluster.users.$workspace_projection_user:name}:{$core.secret.passwords.$workspace_projection_db_password:value}@{$dbaas.types.postgres.instances.$workspace_projection_cluster:join(ipsv4)}:5432/{$dbaas.types.postgres.instances.$workspace_projection_cluster.databases.$workspace_projection_db:name}


          [logging]
          config = logging.yaml


          [iam]
          iam_endpoint = http://core.{$workspace.imports.$core_local_domain:name}/api/core/v1/iam/clients/default
          audience = {$workspace.imports.$var_iam_default_client_id:value}
          hs256_jwks_decryption_key = {$workspace.imports.$var_hs256_jwks_encryption_key:value}


          [HS256]


          [workspace_api]
          bind_host = 127.0.0.1
          bind_port = 21084
          workers = 1


          [messenger_api]
          bind_host = 127.0.0.1
          bind_port = 21081
          workers = 1


          [messenger_events]
          bind_host = 127.0.0.1
          bind_port = 21082
          workers = 1
          heartbeat_interval = 25
          client_timeout = 60
          catchup_limit = 500
          send_queue_limit = 32
          poll_interval = 3


          [messenger_files]
          default_type = s3
          storage_path = /var/lib/workspace/messenger/files


          [messenger_files_s3]
          endpoint_url = http://{$s3aas.types.s3.instances.$workspace_s3:join(ipsv4)}:9000
          bucket_name = {$s3aas.types.s3.instances.$workspace_s3.buckets.$workspace_files_bucket:name}
          access_key_id = {$core.secret.passwords.$workspace_s3_access_key:value}
          secret_access_key = {$core.secret.passwords.$workspace_s3_secret_key:value}
          region_name = us-east-1


          [external_bridge]
          realm_uuid = {$workspace.imports.$core_local_domain:uuid}
          bridge_instance_uuid = {$core.secret.passwords.$workspace_zulip_bridge_enrollment_secret:uuid}
          identity_generation = 1
          enrollment_secret = {$core.secret.passwords.$workspace_zulip_bridge_enrollment_secret:value}


          [external_bridge_control]
          bind_host = 0.0.0.0
          bootstrap_port = 21085
          https_port = 21443
          realm_uuid = {$workspace.imports.$core_local_domain:uuid}
          hostname = workspace-bridge-control.{$workspace.imports.$core_local_domain:name}
          persistent_store_path = /var/lib/workspace/external-bridge-control
          require_dedicated_filesystem = true
          enrollment_config_path = /etc/workspace/external-bridge-enrollment.json
          "
    workspace_backend_nginx:
      project_id: "12345678-c625-4fee-81d5-f691897b8142"
      path: /etc/nginx/sites-available/workspace.conf
      target:
        kind: node
        node: $core.compute.nodes.$workspace_backend:uuid
      on_change:
        kind: shell
        command: /usr/local/bin/workspace-nginx-reload
      body:
        kind: text
        content: |
          upstream workspace_core_api {
              include /etc/nginx/workspace.d/core-upstream.conf;
          }

          server {
              listen 80;
              server_name _;
              client_max_body_size 50m;

              location /api/core/ {
                  proxy_pass http://workspace_core_api/api/core/;
                  proxy_set_header Host $http_host;
                  proxy_set_header X-Real-IP $remote_addr;
                  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                  proxy_set_header X-Forwarded-Proto https;
              }

              location = /api/workspace/v1/events/ws {
                  proxy_pass http://127.0.0.1:21082/v1/events/ws;
                  proxy_set_header Host $http_host;
                  proxy_set_header X-Real-IP $remote_addr;
                  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                  proxy_set_header X-Forwarded-Proto https;

                  proxy_http_version 1.1;
                  proxy_set_header Upgrade $http_upgrade;
                  proxy_set_header Connection "upgrade";
                  proxy_read_timeout 3600s;
                  proxy_send_timeout 3600s;
              }

              location /api/workspace/v1/messenger/ {
                  proxy_pass http://127.0.0.1:21081/v1/;
                  proxy_set_header Host $http_host;
                  proxy_set_header X-Real-IP $remote_addr;
                  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                  proxy_set_header X-Forwarded-Proto https;
              }

              location /api/workspace/ {
                  proxy_pass http://127.0.0.1:21084/;
                  proxy_set_header Host $http_host;
                  proxy_set_header X-Real-IP $remote_addr;
                  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                  proxy_set_header X-Forwarded-Proto https;
              }

              location = /logo-512x512.png {
                  alias /opt/workspace-ui/packages/web/dist/pwa-512x512.png;
              }

              location / {
                  root /opt/workspace-ui/packages/web/dist;
                  try_files $uri $uri/ /index.html;
              }
          }

    workspace_backend_nginx_core_upstream:
      project_id: "12345678-c625-4fee-81d5-f691897b8142"
      path: /etc/nginx/workspace.d/core-upstream.conf
      target:
        kind: node
        node: $core.compute.nodes.$workspace_backend:uuid
      on_change:
        kind: shell
        command: /usr/local/bin/workspace-nginx-reload
      body:
        kind: text
        content: |
          f"server {$workspace.imports.$var_core_ip_address:value}:80;"

  $core.em.services:
    workspace_bootstrap:
      project_id: "12345678-c625-4fee-81d5-f691897b8142"
      name: workspace-bootstrap
      path: /usr/local/bin/workspace-bootstrap
      user: root
      target:
        kind: node
        node: $core.compute.nodes.$workspace_backend:uuid
      service_type:
        kind: oneshot
      target_status: enabled
    workspace_nginx:
      project_id: "12345678-c625-4fee-81d5-f691897b8142"
      name: workspace-nginx
      path: '/usr/sbin/nginx -g "daemon off;"'
      user: root
      target:
        kind: node
        node: $core.compute.nodes.$workspace_backend:uuid
      service_type:
        kind: simple
        count: 1
      target_status: enabled
      before:
        - kind: shell
          command: /usr/local/bin/workspace-nginx-reload --check-only
    workspace_api:
      project_id: "12345678-c625-4fee-81d5-f691897b8142"
      name: workspace-api
      path: /usr/bin/workspace-api --config-file /etc/workspace/workspace.conf
      user: root
      target:
        kind: node
        node: $core.compute.nodes.$workspace_backend:uuid
      service_type:
        kind: simple
        count: 1
      target_status: enabled
      before:
        - kind: shell
          command: /usr/local/bin/workspace-bootstrap
    workspace_messenger_api:
      project_id: "12345678-c625-4fee-81d5-f691897b8142"
      name: workspace-messenger-api
      path: /usr/bin/workspace-messenger-api --config-file /etc/workspace/workspace.conf
      user: root
      target:
        kind: node
        node: $core.compute.nodes.$workspace_backend:uuid
      service_type:
        kind: simple
        count: 1
      target_status: enabled
      before:
        - kind: shell
          command: /usr/local/bin/workspace-bootstrap
    workspace_messenger_worker:
      project_id: "12345678-c625-4fee-81d5-f691897b8142"
      name: workspace-messenger-worker
      path: /usr/bin/workspace-messenger-worker --config-file /etc/workspace/workspace.conf
      user: root
      target:
        kind: node
        node: $core.compute.nodes.$workspace_backend:uuid
      service_type:
        kind: simple
        count: 1
      target_status: enabled
      before:
        - kind: shell
          command: /usr/local/bin/workspace-bootstrap
    workspace_external_bridge_control:
      project_id: "12345678-c625-4fee-81d5-f691897b8142"
      name: workspace-external-bridge-control
      path: /usr/bin/workspace-external-bridge-api --config-file /etc/workspace/workspace.conf
      user: root
      target:
        kind: node
        node: $core.compute.nodes.$workspace_backend:uuid
      service_type:
        kind: simple
        count: 1
      target_status: enabled
      before:
        - kind: shell
          command: /usr/local/bin/workspace-bootstrap
        - kind: shell
          command: /usr/local/bin/workspace-external-bridge-control-prepare /var/lib/workspace/external-bridge-control root:root 2
    workspace_messenger_events:
      project_id: "12345678-c625-4fee-81d5-f691897b8142"
      name: workspace-messenger-events
      path: /usr/bin/workspace-messenger-events --config-file /etc/workspace/workspace.conf
      user: root
      target:
        kind: node
        node: $core.compute.nodes.$workspace_backend:uuid
      service_type:
        kind: simple
        count: 1
      target_status: enabled
      before:
        - kind: shell
          command: /usr/local/bin/workspace-bootstrap

exports:
  backend_node:
    link: "$core.compute.nodes.$workspace_backend"
    kind: "resource"
  zulip_bridge_enrollment_secret:
    link: "$core.secret.passwords.$workspace_zulip_bridge_enrollment_secret"
    kind: "resource"
  projection_db_instance:
    link: "$dbaas.types.postgres.instances.$workspace_projection_cluster"
    kind: "resource"