# yaml-language-server: $schema=https://schema.zeabur.app/template.json
apiVersion: zeabur.com/v1
kind: Template
metadata:
    name: Multica
spec:
    description: |
        AI-native task management where coding agents are first-class teammates.
        Deploys PostgreSQL (pgvector), the Go backend, and the Next.js web app.
    coverImage: https://raw.githubusercontent.com/multica-ai/multica/main/docs/assets/banner.jpg
    icon: https://raw.githubusercontent.com/multica-ai/multica/main/docs/assets/logo-dark.svg
    variables:
        - key: PUBLIC_DOMAIN
          type: DOMAIN
          name: Domain
          description: The domain your Multica instance is served on.
        - key: PUBLIC_URL
          type: STRING
          name: Public URL
          description: The full URL the domain above resolves to, scheme included and no trailing slash — for example https://multica.zeabur.app. The backend needs the complete origin for CORS and cookies, and a template variable is the only way to hand it one.
        - key: BACKEND_DOMAIN
          type: DOMAIN
          name: API Domain
          description: A second domain for the backend. Browsers never use it — it exists so the `multica` CLI on your own machine can reach the API, its health check, and the daemon WebSocket, which is how agents connect. Pick something like multica-api.
        - key: RESEND_API_KEY
          type: STRING
          name: Resend API Key
          description: Optional. Without it, login verification codes are written to the backend log instead of being emailed. You can add it later.
        - key: RESEND_FROM_EMAIL
          type: STRING
          name: Resend Sender Address
          description: Required if you set a Resend API key. Must be an address on a domain you have verified in your own Resend account — Resend rejects anything else, and the backend then returns 500 on every login request. Leave empty if you left the API key empty.
    tags:
        - Developer Tools
        - Project Management
    readme: |
        # Multica on Zeabur

        Three services, no external object storage required. Attachments go to a
        persistent volume on the backend.

        ## After deploying

        1. Open your domain. Enter an email to request a login code.
        2. If you left `RESEND_API_KEY` empty, read the code from the backend
           service log — search for `Verification code`.
        3. Create your workspace.
        4. Lock the instance down: set `ALLOW_SIGNUP=false` and
           `DISABLE_WORKSPACE_CREATION=true` on the backend, then restart it.

        ## Connecting an agent runtime

        The daemon runs on **your own computer**, not on Zeabur. Multica's server
        is only the API, WebSocket hub, and database.

        ```bash
        brew install multica-ai/tap/multica
        multica setup self-host \
          --server-url https://YOUR_API_DOMAIN \
          --app-url    https://YOUR_DOMAIN
        ```

        The two URLs are different on purpose. `--app-url` is the web app you log
        in to; `--server-url` is the API domain, which the CLI probes at
        `/health` before it will save anything. That path is not one the web app
        forwards, so pointing `--server-url` at the app domain makes setup report
        the server as unreachable.

        Browsers are unaffected either way — they stay on the app domain for
        everything including the WebSocket, so cookies never cross origins and
        `COOKIE_DOMAIN` stays unset.

        ## Changing the domain later

        The browser side follows whatever host you visit: the web app serves
        `/api`, `/auth`, `/uploads` and `/ws` from its own origin, and the client
        derives `wss://` from `window.location`. Nothing there is pinned to a
        domain, so the UI and the WebSocket keep working on a new one with no
        configuration.

        The backend is the part that will not notice. It never reads the request
        host — every public URL it emits comes from environment variables. After
        binding a new domain, update these on the **backend** service and restart
        it:

        | Variable | Set it to | What breaks if it is stale |
        | --- | --- | --- |
        | `MULTICA_APP_URL` | new app origin | Onboarding shows the old host; invite and login-code emails link to it |
        | `FRONTEND_ORIGIN` | new app origin | Fallback for the above; CSRF origin check |
        | `CORS_ALLOWED_ORIGINS` | new app origin | Cross-origin calls rejected |
        | `MULTICA_PUBLIC_URL` | new API origin | `multica setup self-host` hands the daemon the old URL |

        Scheme included, no trailing slash. **Every one of these fails quietly** —
        the app still loads and looks fine. After changing them, open
        `/api/config` and confirm `daemon_app_url` and `daemon_server_url` both
        match the domains you are actually on.

        A caveat worth knowing before you attach a custom domain:
        `MULTICA_PUBLIC_URL` ships as `${ZEABUR_API_URL}`, and that built-in
        resolves to the **generated** `*.zeabur.app` domain even after you bind a
        custom one alongside it. It does not follow the custom domain. So the
        moment you put your own domain on the backend, replace
        `${ZEABUR_API_URL}` with that domain, written out in full.

        Then re-run `multica setup self-host` with the new `--server-url` on every
        machine running a daemon — the CLI stores the URL, so existing daemons
        keep using the old one until you do.

        ## Storage

        `S3_BUCKET` is left unset, so the backend falls back to local disk and
        writes to the `/app/data/uploads` volume. To move to S3-compatible storage
        later (R2, MinIO, B2), set `S3_BUCKET`, `AWS_ENDPOINT_URL`,
        `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` on the backend. Add
        `ATTACHMENT_DOWNLOAD_MODE=proxy` when the endpoint is not reachable from
        the browser. Existing files are not migrated automatically.

        ## Scaling note

        The backend is pinned to one replica by the uploads volume. Switch to S3
        first if you need more, and add `REDIS_URL` — without Redis, realtime
        events fall back to in-process memory and auth rate limiting is off.

        ## License and attribution

        Multica is licensed under a modified Apache License 2.0 with additional
        conditions covering hosted/embedded commercial use, branding, and
        attribution. Internal use within a single organization does not require a
        commercial license; offering it as a service to third parties does.
        Removing the Multica name, logo, or copyright notices from the UI requires
        a written branding waiver.

        Source: https://github.com/multica-ai/multica ·
        License: https://github.com/multica-ai/multica/blob/main/LICENSE
    services:
        - name: postgres
          icon: https://raw.githubusercontent.com/zeabur/service-icons/main/marketplace/postgresql.svg
          template: PREBUILT_V2
          spec:
            id: postgres
            source:
                image: pgvector/pgvector:pg17
            ports:
                - id: database
                  port: 5432
                  type: TCP
            volumes:
                - id: pgdata
                  dir: /var/lib/postgresql/data
            env:
                PGDATA:
                    default: /var/lib/postgresql/data/pgdata
                POSTGRES_DB:
                    default: multica
                POSTGRES_HOST:
                    default: ${CONTAINER_HOSTNAME}
                    expose: true
                POSTGRES_PASSWORD:
                    default: ${PASSWORD}
                    expose: true
                POSTGRES_PORT:
                    default: ${DATABASE_PORT}
                    expose: true
                POSTGRES_USER:
                    default: multica
            healthCheck:
                type: TCP
                port: database
            portForwarding:
                enabled: false
        - name: backend
          icon: https://raw.githubusercontent.com/multica-ai/multica/main/docs/assets/logo-dark.svg
          template: PREBUILT_V2
          spec:
            id: backend
            source:
                image: ghcr.io/multica-ai/multica-backend:v0.4.15
            ports:
                - id: api
                  port: 8080
                  type: HTTP
            volumes:
                - id: uploads
                  dir: /app/data/uploads
            env:
                ALLOW_SIGNUP:
                    default: "true"
                APP_ENV:
                    default: production
                BACKEND_HOST:
                    default: ${CONTAINER_HOSTNAME}
                    expose: true
                CORS_ALLOWED_ORIGINS:
                    default: ${PUBLIC_URL}
                DATABASE_URL:
                    default: postgres://multica:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/multica?sslmode=disable
                FRONTEND_ORIGIN:
                    default: ${PUBLIC_URL}
                JWT_SECRET:
                    default: ${PASSWORD}
                LOCAL_UPLOAD_DIR:
                    default: /app/data/uploads
                MULTICA_APP_URL:
                    default: ${PUBLIC_URL}
                MULTICA_PUBLIC_URL:
                    default: ${ZEABUR_API_URL}
                MULTICA_TRUSTED_PROXIES:
                    default: 10.0.0.0/8,172.16.0.0/12,192.168.0.0/16
                MULTICA_VCS_INTEGRATION_ENABLED:
                    default: "true"
                MULTICA_VCS_SECRET_KEY:
                    default: ${PASSWORD}
                PORT:
                    default: "8080"
                RESEND_API_KEY:
                    default: ${RESEND_API_KEY}
                RESEND_FROM_EMAIL:
                    default: ${RESEND_FROM_EMAIL}
            healthCheck:
                type: HTTP
                port: api
                http:
                    path: /readyz
          domainKey: BACKEND_DOMAIN
        - name: web
          icon: https://raw.githubusercontent.com/multica-ai/multica/main/docs/assets/logo-dark.svg
          template: PREBUILT_V2
          spec:
            id: web
            source:
                image: ghcr.io/multica-ai/multica-web:v0.4.15
            ports:
                - id: web
                  port: 3000
                  type: HTTP
            env:
                HOSTNAME:
                    default: 0.0.0.0
                NEXT_PUBLIC_API_URL:
                    default: ""
                NEXT_PUBLIC_WS_URL:
                    default: ""
                PORT:
                    default: "3000"
                REMOTE_API_URL:
                    default: http://backend.zeabur.internal:8080
            healthCheck:
                type: TCP
                port: web
          domainKey: PUBLIC_DOMAIN
localization:
    es-ES:
        description: |
            Gestión de tareas nativa de IA donde los agentes de código son compañeros de
            pleno derecho. Despliega PostgreSQL (pgvector), el backend en Go y la app web
            en Next.js.
        variables:
            - key: PUBLIC_DOMAIN
              type: STRING
              name: Dominio
              description: |
                El dominio donde se sirve tu instancia de Multica.
            - key: PUBLIC_URL
              type: STRING
              name: URL completa
              description: |
                La URL completa a la que resuelve el dominio anterior, con esquema y sin barra final — por ejemplo https://multica.zeabur.app. El backend necesita el origen completo para CORS y cookies, y una variable de plantilla es la única forma de proporcionárselo.
            - key: BACKEND_DOMAIN
              type: STRING
              name: Dominio de la API
              description: |
                Un segundo dominio para el backend. Los navegadores nunca lo usan: existe para que la CLI `multica` de tu propia máquina pueda alcanzar la API, su health check y el WebSocket del daemon, que es como se conectan los agentes. Elige algo como multica-api.
            - key: RESEND_API_KEY
              type: STRING
              name: Clave de API de Resend
              description: |
                Opcional. Sin ella, los códigos de verificación se escriben en el log del backend en lugar de enviarse por correo. Puedes añadirla más tarde.
            - key: RESEND_FROM_EMAIL
              type: STRING
              name: Dirección de envío de Resend
              description: |
                Obligatorio si defines una clave de API de Resend. Debe ser una dirección de un dominio verificado en tu propia cuenta de Resend; de lo contrario Resend la rechaza y el backend devuelve 500 en cada inicio de sesión. Déjalo vacío si dejaste vacía la clave de API.
        readme: |
            # Multica en Zeabur

            Tres servicios, sin necesidad de almacenamiento de objetos externo. Los adjuntos
            van a un volumen persistente en el backend.

            ## Después de desplegar

            1. Abre tu dominio. Introduce un correo para pedir un código de acceso.
            2. Si dejaste `RESEND_API_KEY` vacío, lee el código en el log del servicio
               backend — busca `Verification code`.
            3. Crea tu espacio de trabajo.
            4. Cierra la instancia: define `ALLOW_SIGNUP=false` y
               `DISABLE_WORKSPACE_CREATION=true` en el backend y reinícialo.

            ## Conectar un runtime de agentes

            El daemon se ejecuta en **tu propio ordenador**, no en Zeabur. El servidor de
            Multica es solo la API, el hub de WebSocket y la base de datos.

            ```bash
            brew install multica-ai/tap/multica
            multica setup self-host \
              --server-url https://TU_DOMINIO_API \
              --app-url    https://TU_DOMINIO
            ```

            Las dos URL son distintas a propósito. `--app-url` es la aplicación web donde
            inicias sesión; `--server-url` es el dominio de la API, que la CLI comprueba en
            `/health` antes de guardar nada. Esa ruta no la reenvía la aplicación web, así
            que apuntar `--server-url` al dominio de la app hace que la configuración
            informe de que el servidor no es accesible.

            Los navegadores no se ven afectados en ningún caso: permanecen en el dominio de
            la app para todo, incluido el WebSocket, por lo que las cookies nunca cruzan
            orígenes y `COOKIE_DOMAIN` se queda sin definir.

            ## Cambiar el dominio más adelante

            El lado del navegador sigue al host que visitas: la aplicación web sirve `/api`,
            `/auth`, `/uploads` y `/ws` desde su propio origen, y el cliente deriva `wss://`
            de `window.location`. Nada de eso está fijado a un dominio, así que la interfaz
            y el WebSocket siguen funcionando en uno nuevo sin configuración.

            El backend es la parte que no se entera. Nunca lee el host de la petición: todas
            las URL públicas que emite vienen de variables de entorno. Tras asignar un
            dominio nuevo, actualiza estas en el servicio **backend** y reinícialo:

            | Variable | Dale el valor | Qué se rompe si queda obsoleta |
            | --- | --- | --- |
            | `MULTICA_APP_URL` | nuevo origen de la app | El onboarding muestra el host antiguo; las invitaciones y los correos con código enlazan a él |
            | `FRONTEND_ORIGIN` | nuevo origen de la app | Respaldo de lo anterior; comprobación de origen CSRF |
            | `CORS_ALLOWED_ORIGINS` | nuevo origen de la app | Las llamadas de origen cruzado se rechazan |
            | `MULTICA_PUBLIC_URL` | nuevo origen de la API | `multica setup self-host` entrega al daemon la URL antigua |

            Con esquema y sin barra final. **Todas fallan en silencio**: la aplicación sigue
            cargando y parece correcta. Después de cambiarlas, abre `/api/config` y confirma
            que `daemon_app_url` y `daemon_server_url` coinciden con los dominios que estás
            usando realmente.

            Una advertencia que conviene conocer antes de asociar un dominio propio:
            `MULTICA_PUBLIC_URL` viene como `${ZEABUR_API_URL}`, y esa variable integrada se
            resuelve al dominio `*.zeabur.app` **generado automáticamente** incluso después
            de asociar uno personalizado junto a él. No sigue al dominio personalizado. Así
            que en cuanto pongas tu propio dominio en el backend, sustituye
            `${ZEABUR_API_URL}` por ese dominio, escrito completo.

            Después vuelve a ejecutar `multica setup self-host` con el nuevo `--server-url`
            en cada máquina que ejecute un daemon: la CLI guarda la URL, así que los daemons
            existentes seguirán usando la antigua hasta que lo hagas.

            ## Almacenamiento

            `S3_BUCKET` se deja sin definir, así que el backend recurre al disco local y
            escribe en el volumen `/app/data/uploads`. Para pasar más adelante a
            almacenamiento compatible con S3 (R2, MinIO, B2), define `S3_BUCKET`,
            `AWS_ENDPOINT_URL`, `AWS_ACCESS_KEY_ID` y `AWS_SECRET_ACCESS_KEY` en el backend.
            Añade `ATTACHMENT_DOWNLOAD_MODE=proxy` cuando el endpoint no sea accesible desde
            el navegador. Los archivos existentes no se migran automáticamente.

            ## Nota sobre escalado

            El volumen de uploads fija el backend a una sola réplica. Pasa primero a S3 si
            necesitas más, y añade `REDIS_URL`: sin Redis, los eventos en tiempo real
            recurren a la memoria del proceso y la limitación de peticiones de autenticación
            queda desactivada.

            ## Licencia y atribución

            Multica se distribuye bajo una Apache License 2.0 modificada con condiciones
            adicionales sobre uso comercial alojado/incrustado, marca y atribución. El uso
            interno dentro de una sola organización no requiere licencia comercial;
            ofrecerlo como servicio a terceros sí. Retirar el nombre, el logotipo o los
            avisos de copyright de Multica de la interfaz requiere una exención de marca por
            escrito.

            Código: https://github.com/multica-ai/multica ·
            Licencia: https://github.com/multica-ai/multica/blob/main/LICENSE
    id-ID:
        description: |
            Manajemen tugas AI-native di mana agent coding adalah anggota tim sepenuhnya.
            Men-deploy PostgreSQL (pgvector), backend Go, dan aplikasi web Next.js.
        variables:
            - key: PUBLIC_DOMAIN
              type: STRING
              name: Domain
              description: |
                Domain tempat instance Multica kamu disajikan.
            - key: PUBLIC_URL
              type: STRING
              name: URL lengkap
              description: |
                URL lengkap dari domain di atas, termasuk skema dan tanpa garis miring di akhir — misalnya https://multica.zeabur.app. Backend membutuhkan origin lengkap untuk CORS dan cookie, dan variabel template adalah satu-satunya cara memberikannya.
            - key: BACKEND_DOMAIN
              type: STRING
              name: Domain API
              description: |
                Domain kedua khusus untuk backend. Browser tidak pernah memakainya — ini ada supaya CLI `multica` di komputermu bisa menjangkau API, health check-nya, dan WebSocket daemon, yang merupakan cara agent terhubung. Pilih nama seperti multica-api.
            - key: RESEND_API_KEY
              type: STRING
              name: Kunci API Resend
              description: |
                Opsional. Tanpa ini, kode verifikasi login ditulis ke log backend alih-alih dikirim lewat email. Bisa ditambahkan nanti.
            - key: RESEND_FROM_EMAIL
              type: STRING
              name: Alamat pengirim Resend
              description: |
                Wajib jika kamu mengisi kunci API Resend. Harus berupa alamat pada domain yang sudah kamu verifikasi di akun Resend-mu sendiri, kalau tidak Resend akan menolak pengiriman dan backend mengembalikan 500 pada setiap login. Kosongkan jika kunci API juga kosong.
        readme: |
            # Multica di Zeabur

            Tiga layanan, tanpa perlu object storage eksternal. Lampiran disimpan di volume
            persisten pada backend.

            ## Setelah deploy

            1. Buka domainmu. Masukkan email untuk meminta kode login.
            2. Kalau `RESEND_API_KEY` dibiarkan kosong, baca kodenya dari log layanan
               backend — cari `Verification code`.
            3. Buat workspace-mu.
            4. Kunci instance-nya: set `ALLOW_SIGNUP=false` dan
               `DISABLE_WORKSPACE_CREATION=true` di backend, lalu restart.

            ## Menghubungkan runtime agent

            Daemon berjalan di **komputermu sendiri**, bukan di Zeabur. Server Multica hanya
            berisi API, hub WebSocket, dan basis data.

            ```bash
            brew install multica-ai/tap/multica
            multica setup self-host \
              --server-url https://DOMAIN_API_KAMU \
              --app-url    https://DOMAIN_KAMU
            ```

            Kedua URL sengaja dibuat berbeda. `--app-url` adalah aplikasi web tempat kamu
            login; `--server-url` adalah domain API, yang diperiksa CLI di `/health` sebelum
            menyimpan apa pun. Path itu tidak diteruskan oleh aplikasi web, jadi mengarahkan
            `--server-url` ke domain aplikasi membuat setup melaporkan server tidak
            terjangkau.

            Browser tidak terpengaruh dalam kedua kasus — ia tetap berada di domain aplikasi
            untuk semuanya termasuk WebSocket, sehingga cookie tidak pernah lintas origin
            dan `COOKIE_DOMAIN` dibiarkan kosong.

            ## Mengganti domain nanti

            Sisi browser mengikuti host yang kamu buka: aplikasi web menyajikan `/api`,
            `/auth`, `/uploads`, dan `/ws` dari origin-nya sendiri, dan klien menurunkan
            `wss://` dari `window.location`. Tidak ada yang dipaku ke domain di sisi ini,
            jadi UI dan WebSocket tetap jalan di domain baru tanpa konfigurasi.

            Backend-lah yang tidak menyadarinya. Ia tidak pernah membaca host permintaan —
            setiap URL publik yang ia hasilkan berasal dari variabel lingkungan. Setelah
            memasang domain baru, perbarui ini di layanan **backend** lalu restart:

            | Variabel | Isi dengan | Yang rusak kalau basi |
            | --- | --- | --- |
            | `MULTICA_APP_URL` | origin aplikasi baru | Onboarding menampilkan host lama; email undangan dan kode login menaut ke sana |
            | `FRONTEND_ORIGIN` | origin aplikasi baru | Cadangan untuk yang di atas; pemeriksaan origin CSRF |
            | `CORS_ALLOWED_ORIGINS` | origin aplikasi baru | Panggilan lintas origin ditolak |
            | `MULTICA_PUBLIC_URL` | origin API baru | `multica setup self-host` memberi daemon URL lama |

            Sertakan skema, tanpa garis miring di akhir. **Semuanya gagal secara diam-diam**
            — aplikasi tetap termuat dan tampak baik-baik saja. Setelah mengubahnya, buka
            `/api/config` dan pastikan `daemon_app_url` dan `daemon_server_url` cocok dengan
            domain yang benar-benar kamu pakai.

            Satu hal yang perlu diketahui sebelum memasang domain kustom:
            `MULTICA_PUBLIC_URL` dikirim sebagai `${ZEABUR_API_URL}`, dan variabel bawaan itu
            tetap mengarah ke domain `*.zeabur.app` yang **dibuat otomatis**, bahkan setelah
            kamu memasang domain kustom di sampingnya. Ia tidak mengikuti domain kustom.
            Jadi begitu kamu memasang domainmu sendiri di backend, ganti
            `${ZEABUR_API_URL}` dengan domain itu, ditulis lengkap.

            Lalu jalankan ulang `multica setup self-host` dengan `--server-url` yang baru di
            setiap mesin yang menjalankan daemon — CLI menyimpan URL-nya, jadi daemon yang
            sudah ada akan terus memakai yang lama sampai kamu melakukannya.

            ## Penyimpanan

            `S3_BUCKET` dibiarkan kosong, jadi backend jatuh kembali ke disk lokal dan
            menulis ke volume `/app/data/uploads`. Untuk pindah ke penyimpanan kompatibel S3
            nanti (R2, MinIO, B2), set `S3_BUCKET`, `AWS_ENDPOINT_URL`,
            `AWS_ACCESS_KEY_ID`, dan `AWS_SECRET_ACCESS_KEY` di backend. Tambahkan
            `ATTACHMENT_DOWNLOAD_MODE=proxy` bila endpoint-nya tidak terjangkau dari
            browser. Berkas yang sudah ada tidak dimigrasikan otomatis.

            ## Catatan penskalaan

            Volume uploads mengunci backend ke satu replika. Pindah ke S3 dulu kalau butuh
            lebih, dan tambahkan `REDIS_URL` — tanpa Redis, event realtime jatuh kembali ke
            memori dalam proses dan rate limiting autentikasi mati.

            ## Lisensi dan atribusi

            Multica dilisensikan di bawah Apache License 2.0 yang dimodifikasi dengan
            ketentuan tambahan yang mencakup penggunaan komersial hosted/embedded, branding,
            dan atribusi. Penggunaan internal dalam satu organisasi tidak memerlukan lisensi
            komersial; menawarkannya sebagai layanan ke pihak ketiga memerlukannya.
            Menghapus nama, logo, atau pemberitahuan hak cipta Multica dari UI memerlukan
            pengecualian branding tertulis.

            Sumber: https://github.com/multica-ai/multica ·
            Lisensi: https://github.com/multica-ai/multica/blob/main/LICENSE
    ja-JP:
        description: |
            AI ネイティブなタスク管理プラットフォーム。コーディングエージェントが
            一級のメンバーとして扱われます。PostgreSQL (pgvector)、Go バックエンド、
            Next.js Web アプリの 3 サービスをデプロイします。
        variables:
            - key: PUBLIC_DOMAIN
              type: STRING
              name: ドメイン
              description: |
                Multica インスタンスを公開するドメイン。
            - key: PUBLIC_URL
              type: STRING
              name: 完全な URL
              description: |
                上のドメインに対応する完全な URL。スキームを含め、末尾のスラッシュは付けません（例: https://multica.zeabur.app）。バックエンドの CORS と Cookie には完全な origin が必要で、テンプレート変数がそれを渡す唯一の方法です。
            - key: BACKEND_DOMAIN
              type: STRING
              name: API ドメイン
              description: |
                バックエンド専用の 2 つめのドメイン。ブラウザーは使いません。手元の PC の `multica` CLI が API・ヘルスチェック・daemon WebSocket に到達するために必要です（エージェントはこの接続で参加します）。multica-api のような名前で構いません。
            - key: RESEND_API_KEY
              type: STRING
              name: Resend API キー
              description: |
                任意。未設定の場合、ログイン確認コードはメール送信されずバックエンドのログに出力されます。後から追加できます。
            - key: RESEND_FROM_EMAIL
              type: STRING
              name: Resend 送信元アドレス
              description: |
                Resend API キーを設定した場合は必須です。自分の Resend アカウントで検証済みのドメインのアドレスにしてください。そうでないと Resend が送信を拒否し、ログインのたびにバックエンドが 500 を返してログインできなくなります。API キーが空ならここも空にしてください。
        readme: |
            # Zeabur 上の Multica

            3 つのサービスだけで動き、外部オブジェクトストレージは不要です。添付ファイルは
            バックエンドの永続ボリュームに保存されます。

            ## デプロイ後

            1. 自分のドメインを開き、メールアドレスを入力してログインコードを要求します。
            2. `RESEND_API_KEY` を空にした場合は、バックエンドのサービスログから
               `Verification code` を検索してコードを読み取ります。
            3. ワークスペースを作成します。
            4. インスタンスを締めます: バックエンドに `ALLOW_SIGNUP=false` と
               `DISABLE_WORKSPACE_CREATION=true` を設定して再起動します。

            ## エージェントランタイムの接続

            daemon は Zeabur 上ではなく **自分のコンピューター** で動きます。Multica の
            サーバーは API、WebSocket ハブ、データベースだけです。

            ```bash
            brew install multica-ai/tap/multica
            multica setup self-host \
              --server-url https://あなたのAPIドメイン \
              --app-url    https://あなたのドメイン
            ```

            2 つの URL が異なるのは **意図的** です。`--app-url` はログインする Web アプリ、
            `--server-url` は API ドメインで、CLI は設定を保存する前に `/health` を
            プローブします。このパスは Web アプリが転送しないため、`--server-url` に
            アプリのドメインを指定するとサーバーに到達できないと報告されます。

            ブラウザーはどちらの場合も影響を受けません。WebSocket を含めて常にアプリの
            ドメインに留まるので、Cookie がオリジンをまたぐことはなく、`COOKIE_DOMAIN` は
            未設定のままで構いません。

            ## あとからドメインを変更する

            ブラウザー側はアクセスしたホストに追従します。Web アプリは `/api`、`/auth`、
            `/uploads`、`/ws` を自身のオリジンから提供し、クライアントは
            `window.location` から `wss://` を導出します。この側にドメインを固定している
            ものはないため、新しいドメインでも設定なしで UI と WebSocket は動きます。

            追従しないのはバックエンドです。リクエストの Host を読むことは一切なく、
            公開する URL はすべて環境変数から来ます。新しいドメインを割り当てたら、
            **backend** サービスで次を更新して再起動してください:

            | 変数 | 設定値 | 古いままだと壊れるもの |
            | --- | --- | --- |
            | `MULTICA_APP_URL` | 新しいアプリのオリジン | オンボーディングが旧ホストを表示。招待とログインコードのメールが旧ホストにリンク |
            | `FRONTEND_ORIGIN` | 新しいアプリのオリジン | 上のフォールバック。CSRF オリジンチェック |
            | `CORS_ALLOWED_ORIGINS` | 新しいアプリのオリジン | クロスオリジンの呼び出しが拒否される |
            | `MULTICA_PUBLIC_URL` | 新しい API のオリジン | `multica setup self-host` が daemon に古い URL を渡す |

            スキームを含め、末尾のスラッシュは付けません。**いずれも失敗は静かです** ——
            アプリは変わらず読み込まれ、一見問題なく見えます。変更後は `/api/config` を開き、
            `daemon_app_url` と `daemon_server_url` が実際に使っているドメインと一致するか
            確認してください。

            カスタムドメインを付ける前に知っておくべき注意点があります。
            `MULTICA_PUBLIC_URL` の初期値は `${ZEABUR_API_URL}` ですが、この組み込み変数は
            カスタムドメインを併せて割り当てたあとでも **自動生成された** `*.zeabur.app`
            ドメインに解決され、カスタムドメインには追従しません。したがってバックエンドに
            自分のドメインを付けた時点で、`${ZEABUR_API_URL}` をそのドメインの完全な表記に
            置き換えてください。

            そのうえで、daemon を動かしているすべてのマシンで新しい `--server-url` を指定して
            `multica setup self-host` を実行し直します。CLI は URL を保存するため、
            実行し直すまで既存の daemon は古い URL を使い続けます。

            ## ストレージ

            `S3_BUCKET` は未設定のままなので、バックエンドはローカルディスクにフォールバックし
            `/app/data/uploads` ボリュームに書き込みます。あとから S3 互換ストレージ
            （R2、MinIO、B2）に移す場合は、バックエンドに `S3_BUCKET`、`AWS_ENDPOINT_URL`、
            `AWS_ACCESS_KEY_ID`、`AWS_SECRET_ACCESS_KEY` を設定します。エンドポイントに
            ブラウザーから到達できない場合は `ATTACHMENT_DOWNLOAD_MODE=proxy` も追加します。
            既存のファイルは自動では移行されません。

            ## スケーリングについて

            uploads ボリュームがあるため、バックエンドはレプリカ 1 に固定されます。増やす
            必要がある場合はまず S3 に移し、`REDIS_URL` を追加してください。Redis がないと
            リアルタイムイベントはプロセス内メモリにフォールバックし、認証のレート制限は
            無効になります。

            ## ライセンスと表示

            Multica は、ホスティング／組み込みでの商用利用、ブランディング、表示に関する追加
            条件を伴う修正版 Apache License 2.0 の下で提供されます。単一組織内での利用に商用
            ライセンスは不要ですが、第三者へのサービス提供には必要です。UI から Multica の
            名称、ロゴ、著作権表示を削除するには書面によるブランディング免除が必要です。

            ソース: https://github.com/multica-ai/multica ·
            ライセンス: https://github.com/multica-ai/multica/blob/main/LICENSE
    zh-CN:
        description: |
            AI 原生的任务管理平台，编码 Agent 是一等公民。部署 PostgreSQL (pgvector)、
            Go 后端和 Next.js 前端三个服务。
        variables:
            - key: PUBLIC_DOMAIN
              type: STRING
              name: 域名
              description: |
                Multica 实例对外提供服务的域名。
            - key: PUBLIC_URL
              type: STRING
              name: 完整 URL
              description: |
                上面这个域名对应的完整 URL，要带协议、结尾不要带斜杠，例如 https://multica.zeabur.app。后端的 CORS 和 Cookie 需要完整的 origin，而模板变量是唯一能把它传进去的方式。
            - key: BACKEND_DOMAIN
              type: STRING
              name: API 域名
              description: |
                给后端单独用的第二个域名。浏览器不会用到它，它的存在是为了让你本机上的 `multica` CLI 能访问 API、健康检查和 daemon WebSocket——Agent 就是靠这条连接接入的。填个类似 multica-api 的名字即可。
            - key: RESEND_API_KEY
              type: STRING
              name: Resend API Key
              description: |
                可选。不填时登录验证码会写入后端日志而不是发送邮件，之后可以再补上。
            - key: RESEND_FROM_EMAIL
              type: STRING
              name: Resend 发件地址
              description: |
                填了 Resend API Key 就必须填这个。必须是你自己 Resend 账号里已验证域名下的地址，否则 Resend 会拒发，后端每次登录都返回 500，导致无法登录。API Key 留空时这里也留空。
        readme: |
            # 在 Zeabur 上部署 Multica

            三个服务，不需要外部对象存储。附件写入后端的持久卷。

            ## 部署完成后

            1. 打开你的域名，输入邮箱请求登录验证码。
            2. 如果 `RESEND_API_KEY` 留空，去 backend 服务日志里找验证码，搜索
               `Verification code`。
            3. 创建工作区。
            4. 收紧实例：在 backend 上设置 `ALLOW_SIGNUP=false` 和
               `DISABLE_WORKSPACE_CREATION=true`，然后重启。

            ## 接入 Agent 运行时

            daemon 跑在**你自己的电脑上**，不在 Zeabur。Multica 服务端只是 API、
            WebSocket 和数据库。

            ```bash
            brew install multica-ai/tap/multica
            multica setup self-host \
              --server-url https://你的API域名 \
              --app-url    https://你的域名
            ```

            两个 URL **不一样，这是刻意的**。`--app-url` 是你登录用的 Web 应用；
            `--server-url` 是 API 域名，CLI 在保存配置前会先探测它的 `/health`。
            这个路径 Web 应用不转发，所以把 `--server-url` 指向应用域名会让 setup
            报告服务器不可达。

            浏览器两种情况都不受影响：它始终留在应用域名上，包括 WebSocket，因此
            Cookie 不会跨域，`COOKIE_DOMAIN` 保持不设置。

            ## 之后更换域名

            浏览器侧会跟随你访问的主机：Web 应用从自己的 origin 提供 `/api`、
            `/auth`、`/uploads` 和 `/ws`，客户端从 `window.location` 推导 `wss://`。
            这一侧没有任何东西钉死域名，所以换域名后 UI 和 WebSocket 无需配置即可工作。

            不会自动适应的是后端。它从不读取请求的 Host —— 它输出的每个公开 URL 都
            来自环境变量。绑定新域名后，在 **backend** 服务上更新这些变量并重启：

            | 变量 | 设置为 | 不更新会怎样 |
            | --- | --- | --- |
            | `MULTICA_APP_URL` | 新的应用 origin | 引导页显示旧域名；邀请和验证码邮件链接到旧域名 |
            | `FRONTEND_ORIGIN` | 新的应用 origin | 上一项的回退值；CSRF origin 校验 |
            | `CORS_ALLOWED_ORIGINS` | 新的应用 origin | 跨域请求被拒绝 |
            | `MULTICA_PUBLIC_URL` | 新的 API origin | `multica setup self-host` 会把旧 URL 交给 daemon |

            要带协议、结尾不带斜杠。**这几项失败时都是静默的** —— 应用照常加载、
            看起来一切正常。改完后打开 `/api/config`，确认 `daemon_app_url` 和
            `daemon_server_url` 都是你实际使用的域名。

            绑自定义域名之前有个坑值得先知道：`MULTICA_PUBLIC_URL` 默认是
            `${ZEABUR_API_URL}`，而这个内置变量即使在你另外绑了自定义域名之后，
            仍然解析成**自动生成的** `*.zeabur.app` 域名，不会跟随自定义域名。
            所以一旦你给后端挂上自己的域名，就要把 `${ZEABUR_API_URL}` 换成写全的
            那个域名。

            然后在每台跑 daemon 的机器上用新的 `--server-url` 重新执行
            `multica setup self-host` —— CLI 会把 URL 存下来，不重新执行的话现有
            daemon 会一直用旧的。

            ## 存储

            `S3_BUCKET` 留空，后端会回退到本地磁盘并写入 `/app/data/uploads` 卷。
            以后要换成 S3 兼容存储（R2 / MinIO / B2），在 backend 上设置
            `S3_BUCKET`、`AWS_ENDPOINT_URL`、`AWS_ACCESS_KEY_ID`、
            `AWS_SECRET_ACCESS_KEY`。如果 endpoint 浏览器访问不到，还要加
            `ATTACHMENT_DOWNLOAD_MODE=proxy`。已上传的旧文件不会自动迁移。

            ## 扩容提示

            uploads 卷把 backend 限制在单副本。需要多副本时先迁到 S3，并补上
            `REDIS_URL` —— 没有 Redis 时实时事件退化为进程内内存，认证限流关闭。

            ## 许可与署名

            Multica 采用修改版 Apache License 2.0，附加条款覆盖托管/嵌入式商业用途、
            品牌标识和署名要求。单一组织内部使用不需要商业授权；对第三方提供服务则需要。
            移除 UI 中的 Multica 名称、Logo 或版权信息需要书面的品牌豁免。

            源码：https://github.com/multica-ai/multica ·
            许可：https://github.com/multica-ai/multica/blob/main/LICENSE
    zh-TW:
        description: |
            AI 原生的任務管理平台，程式撰寫 Agent 是一等公民。部署 PostgreSQL (pgvector)、
            Go 後端與 Next.js 前端三個服務。
        variables:
            - key: PUBLIC_DOMAIN
              type: STRING
              name: 網域
              description: |
                Multica 執行個體對外提供服務的網域。
            - key: PUBLIC_URL
              type: STRING
              name: 完整 URL
              description: |
                上面那個網域對應的完整 URL，需帶協定、結尾不要有斜線，例如 https://multica.zeabur.app。後端的 CORS 與 Cookie 需要完整的 origin，而範本變數是唯一能傳進去的方式。
            - key: BACKEND_DOMAIN
              type: STRING
              name: API 網域
              description: |
                給後端單獨使用的第二個網域。瀏覽器不會用到它，它的存在是為了讓你本機的 `multica` CLI 能存取 API、健康檢查與 daemon WebSocket——Agent 就是透過這條連線接入的。填類似 multica-api 的名稱即可。
            - key: RESEND_API_KEY
              type: STRING
              name: Resend API Key
              description: |
                選填。不填時登入驗證碼會寫入後端日誌而不寄送郵件，之後可以再補上。
            - key: RESEND_FROM_EMAIL
              type: STRING
              name: Resend 寄件地址
              description: |
                填了 Resend API Key 就必須填這個。必須是你自己 Resend 帳號中已驗證網域底下的地址，否則 Resend 會拒絕寄送，後端每次登入都回傳 500，導致無法登入。API Key 留空時這裡也留空。
        readme: |
            # 在 Zeabur 上部署 Multica

            三個服務，不需要外部物件儲存。附件寫入後端的持久卷。

            ## 部署完成後

            1. 打開你的網域，輸入信箱請求登入驗證碼。
            2. 如果 `RESEND_API_KEY` 留空，去 backend 服務日誌裡找驗證碼，搜尋
               `Verification code`。
            3. 建立工作區。
            4. 收緊執行個體：在 backend 上設定 `ALLOW_SIGNUP=false` 和
               `DISABLE_WORKSPACE_CREATION=true`，然後重啟。

            ## 接入 Agent 執行環境

            daemon 跑在**你自己的電腦上**，不在 Zeabur。Multica 伺服器端只是 API、
            WebSocket 和資料庫。

            ```bash
            brew install multica-ai/tap/multica
            multica setup self-host \
              --server-url https://你的API網域 \
              --app-url    https://你的網域
            ```

            兩個 URL **不一樣，這是刻意的**。`--app-url` 是你登入用的 Web 應用；
            `--server-url` 是 API 網域，CLI 在儲存設定前會先探測它的 `/health`。
            這個路徑 Web 應用不轉發，所以把 `--server-url` 指向應用網域會讓 setup
            回報伺服器無法連線。

            瀏覽器兩種情況都不受影響：它始終留在應用網域上，包括 WebSocket，因此
            Cookie 不會跨來源，`COOKIE_DOMAIN` 保持不設定。

            ## 之後更換網域

            瀏覽器端會跟隨你造訪的主機：Web 應用從自己的 origin 提供 `/api`、
            `/auth`、`/uploads` 和 `/ws`，用戶端從 `window.location` 推導 `wss://`。
            這一側沒有任何東西綁死網域，所以換網域後 UI 和 WebSocket 無需設定即可運作。

            不會自動適應的是後端。它從不讀取請求的 Host —— 它輸出的每個公開 URL 都
            來自環境變數。綁定新網域後，在 **backend** 服務上更新這些變數並重啟：

            | 變數 | 設定為 | 不更新會怎樣 |
            | --- | --- | --- |
            | `MULTICA_APP_URL` | 新的應用 origin | 引導頁顯示舊網域；邀請和驗證碼郵件連到舊網域 |
            | `FRONTEND_ORIGIN` | 新的應用 origin | 上一項的後備值；CSRF origin 檢查 |
            | `CORS_ALLOWED_ORIGINS` | 新的應用 origin | 跨來源請求被拒絕 |
            | `MULTICA_PUBLIC_URL` | 新的 API origin | `multica setup self-host` 會把舊 URL 交給 daemon |

            要帶協定、結尾不帶斜線。**這幾項失敗時都是靜默的** —— 應用照常載入、
            看起來一切正常。改完後打開 `/api/config`，確認 `daemon_app_url` 和
            `daemon_server_url` 都是你實際使用的網域。

            綁自訂網域之前有個坑值得先知道：`MULTICA_PUBLIC_URL` 預設是
            `${ZEABUR_API_URL}`，而這個內建變數即使在你另外綁了自訂網域之後，
            仍然解析成**自動產生的** `*.zeabur.app` 網域，不會跟隨自訂網域。
            所以一旦你給後端掛上自己的網域，就要把 `${ZEABUR_API_URL}` 換成寫全的
            那個網域。

            然後在每台跑 daemon 的機器上用新的 `--server-url` 重新執行
            `multica setup self-host` —— CLI 會把 URL 存下來，不重新執行的話現有
            daemon 會一直用舊的。

            ## 儲存

            `S3_BUCKET` 留空，後端會回退到本機磁碟並寫入 `/app/data/uploads` 卷。
            之後要換成 S3 相容儲存（R2 / MinIO / B2），在 backend 上設定
            `S3_BUCKET`、`AWS_ENDPOINT_URL`、`AWS_ACCESS_KEY_ID`、
            `AWS_SECRET_ACCESS_KEY`。如果 endpoint 瀏覽器連不到，還要加
            `ATTACHMENT_DOWNLOAD_MODE=proxy`。已上傳的舊檔案不會自動遷移。

            ## 擴充提示

            uploads 卷把 backend 限制在單一副本。需要多副本時先遷到 S3，並補上
            `REDIS_URL` —— 沒有 Redis 時即時事件退化為行程內記憶體，認證速率限制關閉。

            ## 授權與姓名標示

            Multica 採用修改版 Apache License 2.0，附加條款涵蓋託管／嵌入式商業用途、
            品牌標識和姓名標示要求。單一組織內部使用不需要商業授權；對第三方提供服務則需要。
            移除 UI 中的 Multica 名稱、Logo 或版權資訊需要書面的品牌豁免。

            原始碼：https://github.com/multica-ai/multica ·
            授權：https://github.com/multica-ai/multica/blob/main/LICENSE
