# yaml-language-server: $schema=https://schema.zeabur.app/template.json
apiVersion: zeabur.com/v1
kind: Template
metadata:
    name: OpenAB Copilot
spec:
    description: |
        Open Agent Broker — a lightweight Rust harness that bridges Discord to any ACP-compatible coding CLI (Claude Code, Codex, Gemini, Kiro, Copilot) over stdio JSON-RPC.
        Source: https://github.com/openabdev/openab
    coverImage: https://cdn-console.zeabur.com/f/Xp0H6/openab-cover.webp
    icon: https://cdn-console.zeabur.com/f/0Ewi6/openab-icon.webp
    variables:
        - key: DISCORD_BOT_TOKEN
          type: STRING
          name: Discord Bot Token
          description: Token from https://discord.com/developers/applications
        - key: OPENAB_ALLOWED_CHANNELS
          type: STRING
          name: Discord Channel IDs (optional)
          description: 'Optional. Comma-separated Discord channel IDs where the bot listens. Leave empty to allow all channels. Example: 123456789012345678,234567890123456789'
        - key: OPENAB_ALLOWED_USERS
          type: STRING
          name: Discord User IDs (optional)
          description: Optional. Comma-separated Discord user IDs allowed to interact with the bot. Leave empty to allow everyone in the allowed channels.
        - key: SLACK_BOT_TOKEN
          type: STRING
          name: Slack Bot Token (optional)
          description: Optional. Bot User OAuth Token (xoxb-...) from Slack App settings. Leave empty to disable Slack adapter.
        - key: SLACK_APP_TOKEN
          type: STRING
          name: Slack App Token (optional)
          description: Optional. App-Level Token (xapp-...) for Socket Mode. Required when Slack Bot Token is set.
    tags:
        - AI
        - Developer Tools
        - Discord
    readme: |
        # OpenAB Copilot

        [OpenAB](https://github.com/openabdev/openab) is a lightweight, open-source Rust harness that bridges Discord to any [Agent Client Protocol](https://github.com/anthropics/agent-protocol)-compatible coding CLI over stdio JSON-RPC.

        This template deploys the Copilot variant (`openab-copilot`) which uses [GitHub Copilot CLI](https://github.com/github/copilot-cli) as the agent backend.

        ## Important Notes

        - **Paid subscription required:** GitHub Copilot CLI requires a **paid** Copilot plan (Pro, Pro+, Business, or Enterprise). The Free tier does not include CLI/ACP access.
        - **Public preview:** Copilot CLI ACP support has been in [public preview](https://github.blog/changelog/2026-01-28-acp-support-in-copilot-cli-is-now-in-public-preview/) since Jan 2026. Behavior may change.
        - **Auth after deploy:** Unlike Claude/Codex, there is no API-key env var for Copilot. You must run `gh auth login` inside the container after the first deploy (see below).
        - **Image tag:** This template uses a pinned version (`0.8.0`). To upgrade, change the image tag in Zeabur Dashboard → Service → Settings. Available tags: [GitHub Packages](https://github.com/openabdev/openab/pkgs/container/openab-copilot).
        - **Persistent storage:** `/home/node` is mounted as a persistent volume. Plugins, settings, config, and GitHub OAuth credentials (`~/.config/gh/`) survive restarts.
        - **Config ownership:** The container starts as root and switches to `node` user (uid 1000) before running openab. If you encounter permission issues, restart the service — ownership is fixed automatically on boot.

        ## Setup

        ### 1. Get a Discord Bot Token

        1. Go to https://discord.com/developers/applications and click **New Application**
        2. Navigate to **Bot** tab → click **Reset Token** → copy the token
        3. On the same page, scroll down and enable **Message Content Intent** under Privileged Gateway Intents
        4. Go to **OAuth2 → URL Generator** → check scope `bot` → check permissions: Send Messages, Send Messages in Threads, Create Public Threads, Read Message History, Add Reactions, Manage Messages
        5. Copy the generated URL and open it in your browser to invite the bot to your server

        ### 2. Deploy

        Fill in the Discord bot token and (optional) channel IDs, then click deploy. The container will start in sleep mode until Copilot is authenticated.

        ### 3. Authenticate with GitHub Copilot (device flow)

        After the service starts, open Zeabur Dashboard → Service → **Exec** and run:

        ```bash
        runuser -u node -- gh auth login --hostname github.com --git-protocol https -p https -w
        ```

        Follow the device-code URL in your browser to authorize. The OAuth token is stored under `/home/node/.config/gh/` and persisted across restarts.

        Verify with:

        ```bash
        runuser -u node -- gh auth status
        ```

        Then restart the service so openab picks up the new credentials.

        ### 4. Get Discord Channel IDs

        1. Open Discord → go to **User Settings** (gear icon) → **Advanced** → enable **Developer Mode**
        2. Right-click the channel where you want the bot to respond → **Copy Channel ID**
        3. For multiple channels, separate IDs with commas: `123456789012345678,234567890123456789`

        ## Usage

        - **@mention the bot** in an allowed channel to start a conversation
        - OpenAB creates a **thread** for multi-turn conversations — no @mention needed for follow-ups
        - Each thread maps to a persistent Copilot session (24h TTL)
        - Copilot CLI defaults to Claude Sonnet 4.6; other models (Opus 4.6, Haiku 4.5, GPT-5.3-Codex, Gemini 3 Pro) are selected interactively via `/model` and are not controllable in ACP mode

        ## Customization

        | File | Description |
        |------|-------------|
        | `/home/node/.config/openab/config.toml` | OpenAB config (sessions, reactions, STT, etc.) |
        | `/home/node/.config/gh/` | GitHub OAuth credentials (from `gh auth login`) |

        `config.toml` is created from a built-in template on first boot. After that, edit the file directly — environment variables are only used for initial setup. To reset to defaults, delete the file and restart: `rm /home/node/.config/openab/config.toml`

        For config options, see the [OpenAB documentation](https://github.com/openabdev/openab) and the [Copilot agent guide](https://github.com/openabdev/openab/blob/main/docs/copilot.md).

        ## Webhook / Bot Integration

        By default, openab ignores messages from other bots and webhooks. To allow webhook-triggered messages (e.g. automated testing or CI pipelines), add the following to your `config.toml`:

        ```toml
        [discord]
        allow_bot_messages = "mentions"
        ```

        With this setting, openab responds to any bot or webhook message that @mentions it. To restrict to a specific webhook, also add:

        ```toml
        trusted_bot_ids = ["YOUR_WEBHOOK_USER_ID"]
        ```

        The webhook user ID is the numeric ID in the webhook URL: `https://discord.com/api/webhooks/<ID>/...`

        ## Disabling

        Leave `Discord Bot Token` empty. The service sleeps instead of crashing.

        ## Links

        - [OpenAB GitHub](https://github.com/openabdev/openab)
        - [Agent Client Protocol](https://github.com/anthropics/agent-protocol)
        - [GitHub Copilot CLI](https://github.com/github/copilot-cli)
        - [gh CLI device-flow auth](https://github.com/openabdev/openab/blob/main/docs/gh-auth-device-flow.md)
    resourceRequirement:
        minConfig:
            cpu: 2
            ram: 4
        recommendedConfig:
            cpu: 4
            ram: 8
    services:
        - name: openab
          icon: https://cdn-console.zeabur.com/f/0Ewi6/openab-icon.webp
          template: PREBUILT_V2
          spec:
            id: openab
            source:
                image: ghcr.io/openabdev/openab-copilot:0.8.0
                command:
                    - /bin/sh
                    - -c
                    - /opt/start-openab.sh
            volumes:
                - id: node-home
                  dir: /home/node
            env:
                DISCORD_BOT_TOKEN:
                    default: ${DISCORD_BOT_TOKEN}
                OPENAB_ALLOWED_CHANNELS:
                    default: ""
                OPENAB_ALLOWED_USERS:
                    default: ""
                SLACK_APP_TOKEN:
                    default: ""
                SLACK_BOT_TOKEN:
                    default: ""
            configs:
                - path: /opt/start-openab.sh
                  template: |
                    #!/bin/sh
                    set -e

                    # Initialize shell config on first boot (volume is empty)
                    if [ ! -f /home/node/.bashrc ]; then
                      cp /etc/skel/.bashrc /etc/skel/.profile /etc/skel/.bash_logout /home/node/ 2>/dev/null || true
                    fi

                    # Fix ownership for persistent volume
                    chown -R node:node /home/node
                    chmod 755 /home/node/.config 2>/dev/null || true

                    if [ -z "$DISCORD_BOT_TOKEN" ] && [ -z "$SLACK_BOT_TOKEN" ]; then
                      echo "openab: neither DISCORD_BOT_TOKEN nor SLACK_BOT_TOKEN set — sleeping"
                      exec sleep infinity
                    fi

                    # Copilot auth check: gh auth must be set up before openab can talk to copilot
                    if ! runuser -u node -- gh auth status >/dev/null 2>&1; then
                      echo "openab: gh CLI is not authenticated — sleeping"
                      echo "openab: exec into the container and run: runuser -u node -- gh auth login --hostname github.com --git-protocol https -p https -w"
                      exec sleep infinity
                    fi

                    CONFIG_DIR=/home/node/.config/openab
                    CONFIG_FILE=$CONFIG_DIR/config.toml
                    mkdir -p "$CONFIG_DIR"

                    # Generate config.toml on first boot only
                    # To regenerate, delete the file and restart
                    if [ ! -f "$CONFIG_FILE" ]; then
                      cp /opt/config.toml.template "$CONFIG_FILE"

                      # Add [discord] section if token provided
                      if [ -n "$DISCORD_BOT_TOKEN" ]; then
                        printf '\n[discord]\nbot_token = "${DISCORD_BOT_TOKEN}"\n' >> "$CONFIG_FILE"
                        if [ -n "$OPENAB_ALLOWED_CHANNELS" ]; then
                          IFS=','; channels=""; for id in $OPENAB_ALLOWED_CHANNELS; do
                            id=$(echo "$id" | tr -d ' '); [ -n "$id" ] && channels="${channels}\"${id}\","; done; unset IFS
                          printf 'allowed_channels = [%s]\n' "$(echo "$channels" | sed 's/,$//')" >> "$CONFIG_FILE"
                        fi
                        if [ -n "$OPENAB_ALLOWED_USERS" ]; then
                          IFS=','; users=""; for id in $OPENAB_ALLOWED_USERS; do
                            id=$(echo "$id" | tr -d ' '); [ -n "$id" ] && users="${users}\"${id}\","; done; unset IFS
                          printf 'allowed_users = [%s]\n' "$(echo "$users" | sed 's/,$//')" >> "$CONFIG_FILE"
                        fi
                      fi

                      # Add [slack] section if tokens provided
                      if [ -n "$SLACK_BOT_TOKEN" ] && [ -n "$SLACK_APP_TOKEN" ]; then
                        printf '\n[slack]\nbot_token = "${SLACK_BOT_TOKEN}"\napp_token = "${SLACK_APP_TOKEN}"\n' >> "$CONFIG_FILE"
                      fi

                      echo "openab: config.toml generated"
                    else
                      echo "openab: using existing config.toml (delete to regenerate)"
                    fi

                    chown -R node:node "$CONFIG_DIR"
                    exec runuser -u node --preserve-environment -- openab run "$CONFIG_FILE"
                  permission: 493
                  envsubst: null
                - path: /opt/config.toml.template
                  template: |
                    [agent]
                    command = "copilot"
                    args = ["--acp", "--stdio"]
                    working_dir = "/home/node"
                  permission: null
                  envsubst: null
