# yaml-language-server: $schema=https://schema.zeabur.app/template.json
apiVersion: zeabur.com/v1
kind: Template
metadata:
    name: Inngest
spec:
    description: |
        Self-hosted Inngest — a durable execution engine for reliable background jobs and AI workflows. Includes PostgreSQL and Redis.
    icon: https://avatars.githubusercontent.com/u/78935958
    variables:
        - key: PUBLIC_DOMAIN
          type: DOMAIN
          name: Dashboard Domain
          description: Public domain for the Inngest dashboard and API (port 8288)
        - key: CONNECT_DOMAIN
          type: DOMAIN
          name: Connect Gateway Domain
          description: Public domain for the Inngest Connect WebSocket gateway (port 8289). SDK workers use this to establish persistent connections.
        - key: INNGEST_EVENT_KEY
          type: STRING
          name: Event Key
          description: Hex string for authenticating event ingestion (e.g. a1b2c3d4e5f6). Must be an even number of hex characters.
        - key: INNGEST_SIGNING_KEY
          type: STRING
          name: Signing Key
          description: Hex string for signing SDK requests (e.g. a1b2c3d4e5f6). Must be an even number of hex characters.
    tags:
        - Developer Tools
        - Background Jobs
        - Workflow
    readme: |
        # Inngest — Self-Hosted

        [Inngest](https://www.inngest.com/) is an open-source durable execution engine that lets you write reliable background jobs, scheduled functions, and AI workflows with built-in retries, concurrency control, and observability.

        This template deploys a self-hosted Inngest instance with **PostgreSQL** (state persistence) and **Redis** (queue and cache) in a single click.

        ## Services

        | Service | Port | Purpose |
        |---------|------|---------|
        | Inngest Dashboard & API | 8288 (HTTP) | UI, event ingestion, REST API |
        | Inngest Connect Gateway | 8289 (HTTP/WSS) | WebSocket gateway for SDK workers |
        | PostgreSQL | 5432 | Durable state storage |
        | Redis | 6379 | Queue and cache |

        ## Required Setup Before Deploying

        You need two hex strings (even number of characters, using 0-9 and a-f only).

        **Generate on macOS/Linux:**
        ```bash
        openssl rand -hex 16
        ```

        Use one value for **Event Key** and another for **Signing Key**.

        ## ⚠️ Security Warning

        By design, Inngest self-hosted does **not** protect the dashboard UI or GraphQL API with authentication. The source code only applies the signing key middleware to SDK-facing endpoints:

        | Endpoint | Protected |
        |----------|-----------|
        | Dashboard UI | ❌ Public |
        | GraphQL API (`/v0/gql`) | ❌ Public — anyone can query all apps, functions, and events |
        | Event ingestion (`/e/*`) | ✅ Requires Event Key |
        | SDK sync (`/fn/register`) | ✅ Requires Signing Key |
        | Connect WebSocket | ✅ Requires Signing Key |

        **Recommended:** do not expose the Dashboard domain publicly. Place it behind a VPN, IP allowlist, or HTTP Basic Auth proxy. The Connect Gateway domain (port 8289) must remain publicly accessible for SDK workers to connect.

        ## Connecting SDK Workers

        After deployment, configure your Inngest SDK to point to your self-hosted instance:

        ```ts
        const inngest = new Inngest({
          id: "my-app",
          baseUrl: "https://YOUR_DOMAIN",   // Dashboard domain (port 8288)
          eventKey: "YOUR_EVENT_KEY",
          signingKey: "YOUR_SIGNING_KEY",
        });
        ```

        For Connect (persistent WebSocket workers), override the gateway URL with your Connect domain:

        ```ts
        import { connect } from "inngest/connect";

        // ⚠️ Use `triggers` (plural array), NOT `trigger` (singular)
        const myFn = inngest.createFunction(
          { id: "my-fn", triggers: [{ event: "my/event" }] },
          async ({ event, step }) => { /* ... */ }
        );

        await connect({
          apps: [{ client: inngest, functions: [myFn] }],
          gatewayUrl: "wss://YOUR_CONNECT_DOMAIN/v0/connect",
        });
        ```

        > **Note:** The Connect gateway WebSocket path is `/v0/connect`. Always use `triggers: [...]` (plural array) when defining functions — using `trigger:` (singular) will result in empty triggers and functions will never execute.

        ## License

        Inngest is open-source under the [Apache 2.0 License](https://github.com/inngest/inngest/blob/main/LICENSE).
        - GitHub: https://github.com/inngest/inngest
        - Docs: https://www.inngest.com/docs/self-hosting
    services:
        - name: postgresql
          icon: https://cdn.zeabur.com/marketplace/postgresql.svg
          template: PREBUILT_V2
          spec:
            id: postgresql
            source:
                image: postgres:17
                command:
                    - docker-entrypoint.sh
                    - -c
                    - config_file=/etc/postgresql/postgresql.conf
            ports:
                - id: database
                  port: 5432
                  type: TCP
            volumes:
                - id: data
                  dir: /var/lib/postgresql/17/docker
            env:
                PGDATA:
                    default: /var/lib/postgresql/17/docker/pgdata
                POSTGRES_CONNECTION_STRING:
                    default: postgresql://${POSTGRES_USERNAME}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DATABASE}
                    expose: true
                POSTGRES_DATABASE:
                    default: ${POSTGRES_DB}
                    expose: true
                POSTGRES_DB:
                    default: inngest
                POSTGRES_HOST:
                    default: ${CONTAINER_HOSTNAME}
                    expose: true
                POSTGRES_PASSWORD:
                    default: ${PASSWORD}
                    expose: true
                POSTGRES_PORT:
                    default: ${DATABASE_PORT}
                    expose: true
                POSTGRES_URI:
                    default: ${POSTGRES_CONNECTION_STRING}
                    expose: true
                POSTGRES_USER:
                    default: root
                POSTGRES_USERNAME:
                    default: ${POSTGRES_USER}
                    expose: true
            configs:
                - path: /etc/postgresql/postgresql.conf
                  template: |
                    listen_addresses = '*'
                    max_connections = 256
                    shared_buffers = 256MB
                    dynamic_shared_memory_type = posix
                    max_wal_size = 1GB
                    min_wal_size = 80MB
                    log_timezone = 'UTC'
                    datestyle = 'iso, mdy'
                    timezone = 'UTC'
                    lc_messages = 'en_US.UTF-8'
                    lc_monetary = 'en_US.UTF-8'
                    lc_numeric = 'en_US.UTF-8'
                    lc_time = 'en_US.UTF-8'
                    default_text_search_config = 'pg_catalog.english'
                  permission: null
                  envsubst: null
        - name: redis
          icon: https://raw.githubusercontent.com/zeabur/service-icons/main/marketplace/redis.svg
          template: PREBUILT_V2
          spec:
            id: redis
            source:
                image: redis:7-alpine
                command:
                    - redis-server
                    - /usr/local/etc/redis/redis.conf
            ports:
                - id: database
                  port: 6379
                  type: TCP
            volumes:
                - id: data
                  dir: /data
            env:
                REDIS_CONNECTION_STRING:
                    default: redis://:${REDIS_PASSWORD}@${REDIS_HOST}:${REDIS_PORT}
                    expose: true
                REDIS_HOST:
                    default: ${CONTAINER_HOSTNAME}
                    expose: true
                REDIS_PASSWORD:
                    default: ${PASSWORD}
                    expose: true
                REDIS_PORT:
                    default: ${DATABASE_PORT}
                    expose: true
            configs:
                - path: /usr/local/etc/redis/redis.conf
                  template: |
                    requirepass ${REDIS_PASSWORD}
                    appendonly yes
                    dir /data
                  permission: null
                  envsubst: true
        - name: inngest
          icon: https://avatars.githubusercontent.com/u/78935958
          dependencies:
            - postgresql
            - redis
          template: PREBUILT_V2
          spec:
            id: inngest
            source:
                image: inngest/inngest:v1.17.9
                command:
                    - inngest
                    - start
            ports:
                - id: web
                  port: 8288
                  type: HTTP
                - id: connect
                  port: 8289
                  type: HTTP
            env:
                INNGEST_EVENT_KEY:
                    default: ${INNGEST_EVENT_KEY}
                INNGEST_POSTGRES_URI:
                    default: ${POSTGRES_CONNECTION_STRING}
                INNGEST_REDIS_URI:
                    default: ${REDIS_CONNECTION_STRING}
                INNGEST_SIGNING_KEY:
                    default: ${INNGEST_SIGNING_KEY}
            healthCheck:
                type: HTTP
                port: web
                http:
                    path: /health
          domainKey:
            - port: web
              variable: PUBLIC_DOMAIN
            - port: connect
              variable: CONNECT_DOMAIN
localization:
    es-ES:
        description: |
            Inngest auto-hospedado — motor de ejecución durable para trabajos en segundo plano y flujos de trabajo de IA. Incluye PostgreSQL y Redis.
        variables:
            - key: PUBLIC_DOMAIN
              type: STRING
              name: Dominio del Dashboard
              description: Dominio público para el dashboard y API de Inngest (puerto 8288)
            - key: CONNECT_DOMAIN
              type: STRING
              name: Dominio del Gateway de Connect
              description: Dominio público para el gateway WebSocket de Inngest Connect (puerto 8289). Los workers del SDK usan esto para establecer conexiones persistentes.
            - key: INNGEST_EVENT_KEY
              type: STRING
              name: Clave de Evento
              description: Cadena hexadecimal para autenticar eventos (ej. a1b2c3d4e5f6). Debe tener número par de caracteres.
            - key: INNGEST_SIGNING_KEY
              type: STRING
              name: Clave de Firma
              description: Cadena hexadecimal para firmar peticiones del SDK (ej. a1b2c3d4e5f6). Debe tener número par de caracteres.
        readme: |
            # Inngest — Auto-Hospedado

            [Inngest](https://www.inngest.com/) es un motor de ejecución durable de código abierto para trabajos en segundo plano, funciones programadas y flujos de trabajo de IA con reintentos y control de concurrencia integrados.

            Esta plantilla despliega una instancia auto-hospedada de Inngest con **PostgreSQL** y **Redis** en un solo clic.

            ## Servicios

            | Servicio | Puerto | Propósito |
            |----------|--------|-----------|
            | Inngest Dashboard & API | 8288 (HTTP) | UI, ingesta de eventos, REST API |
            | Inngest Connect Gateway | 8289 (HTTP/WSS) | Puerta de enlace WebSocket para workers SDK |
            | PostgreSQL | 5432 | Almacenamiento de estado persistente |
            | Redis | 6379 | Cola y caché |

            ## Preparación

            Necesitas dos cadenas hexadecimales (número par de caracteres, solo 0-9 y a-f):

            ```bash
            openssl rand -hex 16
            ```

            Usa una para **Event Key** y otra para **Signing Key**.

            ## ⚠️ Advertencia de seguridad

            Inngest self-hosted **por diseño** no aplica autenticación al Dashboard UI ni a la GraphQL API. El código fuente solo aplica la validación de signing key a los endpoints del SDK:

            | Endpoint | Protegido |
            |----------|-----------|
            | Dashboard UI | ❌ Público |
            | GraphQL API (`/v0/gql`) | ❌ Público — cualquiera puede consultar todas las apps, funciones y eventos |
            | Envío de eventos (`/e/*`) | ✅ Requiere Event Key |
            | SDK Sync (`/fn/register`) | ✅ Requiere Signing Key |
            | Connect WebSocket | ✅ Requiere Signing Key |

            **Recomendado:** no expongas el dominio del Dashboard públicamente. Colócalo detrás de una VPN, lista de IPs permitidas o un proxy con HTTP Basic Auth. El dominio del Connect Gateway (puerto 8289) debe permanecer público para los workers del SDK.

            ## Conectar workers SDK

            ```ts
            const inngest = new Inngest({
              id: "my-app",
              baseUrl: "https://YOUR_DOMAIN",
              eventKey: "YOUR_EVENT_KEY",
              signingKey: "YOUR_SIGNING_KEY",
            });
            ```

            Para el modo Connect (workers WebSocket), especifica tu dominio Connect en `gatewayUrl`:

            ```ts
            // ⚠️ Usa `triggers` (array plural), NO `trigger` (singular)
            const myFn = inngest.createFunction(
              { id: "my-fn", triggers: [{ event: "my/event" }] },
              async ({ event, step }) => { /* ... */ }
            );

            await connect({
              apps: [{ client: inngest, functions: [myFn] }],
              gatewayUrl: "wss://YOUR_CONNECT_DOMAIN/v0/connect",
            });
            ```

            > **Importante:** Usa siempre `triggers: [...]` (array plural) al definir funciones. Usar `trigger:` (singular) dejará los triggers vacíos y las funciones nunca se ejecutarán.

            ## Licencia

            Inngest es de código abierto bajo la licencia [Apache 2.0](https://github.com/inngest/inngest/blob/main/LICENSE).
            - GitHub: https://github.com/inngest/inngest
    id-ID:
        description: |
            Inngest self-hosted — mesin eksekusi durable untuk background jobs dan AI workflow yang andal. Dilengkapi PostgreSQL dan Redis.
        variables:
            - key: PUBLIC_DOMAIN
              type: STRING
              name: Domain Dashboard
              description: Domain publik untuk dashboard dan API Inngest (port 8288)
            - key: CONNECT_DOMAIN
              type: STRING
              name: Domain Gateway Connect
              description: Domain publik untuk gateway WebSocket Inngest Connect (port 8289). Worker SDK menggunakan ini untuk membuat koneksi persisten.
            - key: INNGEST_EVENT_KEY
              type: STRING
              name: Event Key
              description: 'String heksadesimal untuk autentikasi event (contoh: a1b2c3d4e5f6). Harus berjumlah karakter genap.'
            - key: INNGEST_SIGNING_KEY
              type: STRING
              name: Signing Key
              description: 'String heksadesimal untuk menandatangani permintaan SDK (contoh: a1b2c3d4e5f6). Harus berjumlah karakter genap.'
        readme: |
            # Inngest — Self-Hosted

            [Inngest](https://www.inngest.com/) adalah mesin eksekusi durable open-source untuk membangun background jobs, fungsi terjadwal, dan AI workflow yang andal dengan retry dan kontrol konkurensi bawaan.

            Template ini men-deploy instance Inngest self-hosted dengan **PostgreSQL** dan **Redis** dalam satu klik.

            ## Layanan

            | Layanan | Port | Fungsi |
            |---------|------|--------|
            | Inngest Dashboard & API | 8288 (HTTP) | UI, penerimaan event, REST API |
            | Inngest Connect Gateway | 8289 (HTTP/WSS) | Gateway WebSocket untuk SDK worker |
            | PostgreSQL | 5432 | Penyimpanan state persisten |
            | Redis | 6379 | Antrean dan cache |

            ## Persiapan

            Anda memerlukan dua string heksadesimal (jumlah karakter genap, hanya 0-9 dan a-f):

            ```bash
            openssl rand -hex 16
            ```

            Gunakan satu untuk **Event Key** dan satu lagi untuk **Signing Key**.

            ## ⚠️ Peringatan Keamanan

            Inngest self-hosted **secara desain** tidak menerapkan autentikasi pada Dashboard UI atau GraphQL API. Source code hanya menerapkan validasi signing key pada endpoint SDK:

            | Endpoint | Dilindungi |
            |----------|-----------|
            | Dashboard UI | ❌ Publik |
            | GraphQL API (`/v0/gql`) | ❌ Publik — siapa pun dapat query semua app, fungsi, dan event |
            | Kirim Event (`/e/*`) | ✅ Memerlukan Event Key |
            | SDK Sync (`/fn/register`) | ✅ Memerlukan Signing Key |
            | Connect WebSocket | ✅ Memerlukan Signing Key |

            **Disarankan:** jangan ekspos domain Dashboard secara publik. Tempatkan di belakang VPN, IP allowlist, atau proxy HTTP Basic Auth. Domain Connect Gateway (port 8289) harus tetap publik agar SDK worker dapat terhubung.

            ## Menghubungkan SDK Worker

            ```ts
            const inngest = new Inngest({
              id: "my-app",
              baseUrl: "https://YOUR_DOMAIN",
              eventKey: "YOUR_EVENT_KEY",
              signingKey: "YOUR_SIGNING_KEY",
            });
            ```

            Untuk mode Connect (WebSocket worker), tentukan domain Connect di `gatewayUrl`:

            ```ts
            // ⚠️ Gunakan `triggers` (array jamak), BUKAN `trigger` (tunggal)
            const myFn = inngest.createFunction(
              { id: "my-fn", triggers: [{ event: "my/event" }] },
              async ({ event, step }) => { /* ... */ }
            );

            await connect({
              apps: [{ client: inngest, functions: [myFn] }],
              gatewayUrl: "wss://YOUR_CONNECT_DOMAIN/v0/connect",
            });
            ```

            > **Penting:** Selalu gunakan `triggers: [...]` (array jamak) saat mendefinisikan fungsi. Menggunakan `trigger:` (tunggal) akan membuat triggers kosong dan fungsi tidak akan pernah dieksekusi.

            ## Lisensi

            Inngest open-source di bawah lisensi [Apache 2.0](https://github.com/inngest/inngest/blob/main/LICENSE).
            - GitHub: https://github.com/inngest/inngest
    ja-JP:
        description: |
            セルフホスト型 Inngest — 信頼性の高いバックグラウンドジョブと AI ワークフローのための永続実行エンジン。PostgreSQL と Redis 付き。
        variables:
            - key: PUBLIC_DOMAIN
              type: STRING
              name: ダッシュボードドメイン
              description: Inngest ダッシュボードと API の公開ドメイン（ポート 8288）
            - key: CONNECT_DOMAIN
              type: STRING
              name: Connect ゲートウェイドメイン
              description: Inngest Connect WebSocket ゲートウェイの公開ドメイン（ポート 8289）。SDK ワーカーが持続接続に使用します。
            - key: INNGEST_EVENT_KEY
              type: STRING
              name: イベントキー
              description: イベント認証用の16進数文字列（例：a1b2c3d4e5f6）。偶数桁である必要があります。
            - key: INNGEST_SIGNING_KEY
              type: STRING
              name: 署名キー
              description: SDK リクエスト署名用の16進数文字列（例：a1b2c3d4e5f6）。偶数桁である必要があります。
        readme: |
            # Inngest — セルフホスト版

            [Inngest](https://www.inngest.com/) はオープンソースの永続実行エンジンで、信頼性の高いバックグラウンドジョブ、スケジュール関数、AI ワークフローをリトライ・並行制御・可観測性付きで構築できます。

            このテンプレートは **PostgreSQL**（状態保存）と **Redis**（キューとキャッシュ）を含むセルフホスト型 Inngest インスタンスをワンクリックでデプロイします。

            ## サービス構成

            | サービス | ポート | 用途 |
            |----------|--------|------|
            | Inngest Dashboard & API | 8288 (HTTP) | UI、イベント受信、REST API |
            | Inngest Connect Gateway | 8289 (HTTP/WSS) | SDK ワーカー用 WebSocket ゲートウェイ |
            | PostgreSQL | 5432 | 永続状態ストレージ |
            | Redis | 6379 | キューとキャッシュ |

            ## 事前準備

            2つの16進数文字列が必要です（偶数桁、0-9 と a-f のみ）：

            ```bash
            openssl rand -hex 16
            ```

            1つを **Event Key**、もう1つを **Signing Key** として使用してください。

            ## ⚠️ セキュリティ警告

            Inngest self-hosted は**設計上**、Dashboard UI や GraphQL API に認証を適用しません。ソースコードは SDK 関連エンドポイントにのみ signing key 検証を適用します：

            | エンドポイント | 保護 |
            |--------------|------|
            | Dashboard UI | ❌ 公開 |
            | GraphQL API（`/v0/gql`）| ❌ 公開 — 誰でもすべての app・関数・イベントを照会可能 |
            | イベント送信（`/e/*`）| ✅ Event Key が必要 |
            | SDK Sync（`/fn/register`）| ✅ Signing Key が必要 |
            | Connect WebSocket | ✅ Signing Key が必要 |

            **推奨：** Dashboardドメインを公開しないでください。VPN、IPホワイトリスト、またはHTTP Basic Auth プロキシの後ろに配置してください。SDK ワーカーの接続のためにConnect Gatewayドメイン（ポート8289）は公開が必要です。

            ## SDK ワーカーの接続

            ```ts
            const inngest = new Inngest({
              id: "my-app",
              baseUrl: "https://YOUR_DOMAIN",
              eventKey: "YOUR_EVENT_KEY",
              signingKey: "YOUR_SIGNING_KEY",
            });
            ```

            Connect モード（WebSocket ワーカー）の場合は、Connect ドメインを `gatewayUrl` に指定してください：

            ```ts
            // ⚠️ `triggers`（複数配列）を使用してください。`trigger`（単数）は使用しないでください
            const myFn = inngest.createFunction(
              { id: "my-fn", triggers: [{ event: "my/event" }] },
              async ({ event, step }) => { /* ... */ }
            );

            await connect({
              apps: [{ client: inngest, functions: [myFn] }],
              gatewayUrl: "wss://YOUR_CONNECT_DOMAIN/v0/connect",
            });
            ```

            > **注意：** 関数定義には必ず `triggers: [...]`（複数配列）を使用してください。`trigger:`（単数）を使用するとトリガーが空になり、関数が実行されません。

            ## ライセンス

            Inngest は [Apache 2.0](https://github.com/inngest/inngest/blob/main/LICENSE) ライセンスでオープンソース公開されています。
            - GitHub：https://github.com/inngest/inngest
    zh-CN:
        description: |
            自托管 Inngest — 可靠的持久执行引擎，适用于后台任务与 AI 工作流。包含 PostgreSQL 和 Redis。
        variables:
            - key: PUBLIC_DOMAIN
              type: STRING
              name: Dashboard 域名
              description: Inngest 仪表板与 API 的公开域名（Port 8288）
            - key: CONNECT_DOMAIN
              type: STRING
              name: Connect Gateway 域名
              description: Inngest Connect WebSocket 网关的公开域名（Port 8289），SDK Worker 用此建立持久连接。
            - key: INNGEST_EVENT_KEY
              type: STRING
              name: Event Key
              description: 用于事件认证的十六进制字符串（例如 a1b2c3d4e5f6），必须为偶数位。
            - key: INNGEST_SIGNING_KEY
              type: STRING
              name: Signing Key
              description: 用于 SDK 请求签名的十六进制字符串（例如 a1b2c3d4e5f6），必须为偶数位。
        readme: |
            # Inngest — 自托管版

            [Inngest](https://www.inngest.com/) 是开源的持久执行引擎，用于构建可靠的后台任务、定时函数与 AI 工作流，内置重试、并发控制与可观测性。

            此模板一键部署包含 **PostgreSQL**（状态存储）和 **Redis**（队列与缓存）的自托管 Inngest 实例。

            ## 服务说明

            | 服务 | Port | 用途 |
            |------|------|------|
            | Inngest Dashboard & API | 8288 (HTTP) | 界面、事件接收、REST API |
            | Inngest Connect Gateway | 8289 (HTTP/WSS) | SDK Worker 的 WebSocket 网关 |
            | PostgreSQL | 5432 | 持久状态存储 |
            | Redis | 6379 | 队列与缓存 |

            ## 部署前准备

            需要两组十六进制字符串（偶数位，仅含 0-9 和 a-f）：

            ```bash
            openssl rand -hex 16
            ```

            一个用于 **Event Key**，另一个用于 **Signing Key**。

            ## ⚠️ 安全警告

            Inngest self-hosted **设计上** 不对 Dashboard UI 或 GraphQL API 进行身份验证，原始码只对 SDK 相关端点应用 signing key 验证：

            | 端点 | 是否保护 |
            |------|---------|
            | Dashboard UI | ❌ 公开 |
            | GraphQL API（`/v0/gql`）| ❌ 公开 — 任何人都能查询所有 app、函数和事件 |
            | 发送 Event（`/e/*`）| ✅ 需要 Event Key |
            | SDK Sync（`/fn/register`）| ✅ 需要 Signing Key |
            | Connect WebSocket | ✅ 需要 Signing Key |

            **建议：** 不要将 Dashboard 域名公开到网络，请放在 VPN、IP 白名单或 HTTP Basic Auth Proxy 之后。Connect Gateway 域名（port 8289）需保持公开，供 SDK Worker 连接。

            ## 连接 SDK Worker

            ```ts
            const inngest = new Inngest({
              id: "my-app",
              baseUrl: "https://YOUR_DOMAIN",
              eventKey: "YOUR_EVENT_KEY",
              signingKey: "YOUR_SIGNING_KEY",
            });
            ```

            Connect 模式（WebSocket Worker）需覆盖 gateway URL，指向你的 Connect 域名：

            ```ts
            // ⚠️ 请用 `triggers`（复数数组），不要用 `trigger`（单数）
            const myFn = inngest.createFunction(
              { id: "my-fn", triggers: [{ event: "my/event" }] },
              async ({ event, step }) => { /* ... */ }
            );

            await connect({
              apps: [{ client: inngest, functions: [myFn] }],
              gatewayUrl: "wss://YOUR_CONNECT_DOMAIN/v0/connect",
            });
            ```

            > **注意：** 定义函式时请使用 `triggers: [...]`（复数数组）。若使用 `trigger:`（单数）会导致 triggers 为空，函式永远不会执行。

            ## 授权

            Inngest 以 [Apache 2.0](https://github.com/inngest/inngest/blob/main/LICENSE) 授权开源。
            - GitHub：https://github.com/inngest/inngest
    zh-TW:
        description: |
            自架 Inngest — 可靠的持久執行引擎，適用於背景任務與 AI 工作流程。包含 PostgreSQL 和 Redis。
        variables:
            - key: PUBLIC_DOMAIN
              type: STRING
              name: Dashboard 網域
              description: Inngest 儀表板與 API 的公開網域（Port 8288）
            - key: CONNECT_DOMAIN
              type: STRING
              name: Connect Gateway 網域
              description: Inngest Connect WebSocket 閘道的公開網域（Port 8289），SDK Worker 用此建立持久連線。
            - key: INNGEST_EVENT_KEY
              type: STRING
              name: Event Key
              description: 用於事件驗證的十六進位字串（例如 a1b2c3d4e5f6），必須為偶數位。
            - key: INNGEST_SIGNING_KEY
              type: STRING
              name: Signing Key
              description: 用於 SDK 請求簽署的十六進位字串（例如 a1b2c3d4e5f6），必須為偶數位。
        readme: |
            # Inngest — 自架版

            [Inngest](https://www.inngest.com/) 是開源的持久執行引擎，用於建立可靠的背景任務、排程函式與 AI 工作流程，內建重試、並發控制與可觀測性。

            此模板一鍵部署包含 **PostgreSQL**（狀態儲存）和 **Redis**（佇列與快取）的自架 Inngest 實例。

            ## 服務說明

            | 服務 | Port | 用途 |
            |------|------|------|
            | Inngest Dashboard & API | 8288 (HTTP) | 介面、事件接收、REST API |
            | Inngest Connect Gateway | 8289 (HTTP/WSS) | SDK Worker 的 WebSocket 閘道 |
            | PostgreSQL | 5432 | 持久狀態儲存 |
            | Redis | 6379 | 佇列與快取 |

            ## 部署前準備

            您需要兩組十六進位字串（偶數位，使用 0-9 和 a-f）：

            ```bash
            openssl rand -hex 16
            ```

            一組用於 **Event Key**，另一組用於 **Signing Key**。

            ## ⚠️ 安全警告

            Inngest self-hosted **設計上** 不對 Dashboard UI 或 GraphQL API 進行身份驗證，原始碼只對 SDK 相關端點套用 signing key 驗證：

            | 端點 | 是否保護 |
            |------|---------|
            | Dashboard UI | ❌ 公開 |
            | GraphQL API（`/v0/gql`）| ❌ 公開 — 任何人都能查詢所有 app、函式和事件 |
            | 送 Event（`/e/*`）| ✅ 需要 Event Key |
            | SDK Sync（`/fn/register`）| ✅ 需要 Signing Key |
            | Connect WebSocket | ✅ 需要 Signing Key |

            **建議：** 不要將 Dashboard 域名公開到網路，請放在 VPN、IP 白名單或 HTTP Basic Auth Proxy 之後。Connect Gateway 域名（port 8289）需保持公開，供 SDK Worker 連線。

            ## 連接 SDK Worker

            ```ts
            const inngest = new Inngest({
              id: "my-app",
              baseUrl: "https://YOUR_DOMAIN",
              eventKey: "YOUR_EVENT_KEY",
              signingKey: "YOUR_SIGNING_KEY",
            });
            ```

            Connect 模式（WebSocket Worker）需覆蓋 gateway URL，指向你的 Connect 域名：

            ```ts
            // ⚠️ 請用 `triggers`（複數陣列），不要用 `trigger`（單數）
            const myFn = inngest.createFunction(
              { id: "my-fn", triggers: [{ event: "my/event" }] },
              async ({ event, step }) => { /* ... */ }
            );

            await connect({
              apps: [{ client: inngest, functions: [myFn] }],
              gatewayUrl: "wss://YOUR_CONNECT_DOMAIN/v0/connect",
            });
            ```

            > **注意：** 定義函式時請使用 `triggers: [...]`（複數陣列）。若使用 `trigger:`（單數）會導致 triggers 為空，函式永遠不會執行。

            ## 授權

            Inngest 以 [Apache 2.0](https://github.com/inngest/inngest/blob/main/LICENSE) 授權開源。
            - GitHub：https://github.com/inngest/inngest
