From bc5c5afd15a22d5fb769f2084524a1550b1684ee Mon Sep 17 00:00:00 2001 From: DevelopmentCats Date: Wed, 19 Aug 2026 13:38:14 -0500 Subject: [PATCH 01/20] feat: add supabase module for CLI installation and auth - Install Supabase CLI via auto-detect, brew, scoop, or binary - Supports Coder external auth or direct access token - Multi-platform: Linux (deb/rpm/apk), macOS, Windows - Multi-architecture: x86_64/amd64, arm64/aarch64 - Sets SUPABASE_ACCESS_TOKEN and optional SUPABASE_DB_PASSWORD - Follows module data layout at ~/.coder-modules/coder/supabase/ - Uses coder-utils for script orchestration Includes: - main.tf with all variables and outputs - install.sh.tftpl script template - README.md with usage examples - main.tftest.hcl (11 Terraform tests) - main.test.ts (TypeScript e2e tests) - Supabase icon SVG --- .icons/supabase.svg | 1 + registry/coder/modules/supabase/README.md | 119 ++++++++ registry/coder/modules/supabase/main.test.ts | 247 ++++++++++++++++ registry/coder/modules/supabase/main.tf | 143 ++++++++++ .../coder/modules/supabase/main.tftest.hcl | 175 ++++++++++++ .../modules/supabase/scripts/install.sh.tftpl | 264 ++++++++++++++++++ 6 files changed, 949 insertions(+) create mode 100644 .icons/supabase.svg create mode 100644 registry/coder/modules/supabase/README.md create mode 100644 registry/coder/modules/supabase/main.test.ts create mode 100644 registry/coder/modules/supabase/main.tf create mode 100644 registry/coder/modules/supabase/main.tftest.hcl create mode 100644 registry/coder/modules/supabase/scripts/install.sh.tftpl diff --git a/.icons/supabase.svg b/.icons/supabase.svg new file mode 100644 index 000000000..46740b0c5 --- /dev/null +++ b/.icons/supabase.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/registry/coder/modules/supabase/README.md b/registry/coder/modules/supabase/README.md new file mode 100644 index 000000000..b2ce52a06 --- /dev/null +++ b/registry/coder/modules/supabase/README.md @@ -0,0 +1,119 @@ +--- +display_name: Supabase CLI +description: Install Supabase CLI and configure authentication via Coder external auth +icon: ../../../../.icons/supabase.svg +verified: false +tags: [supabase, database, cli, helper] +--- + +# Supabase CLI + +Installs the [Supabase CLI](https://supabase.com/docs/guides/cli) and configures authentication using Coder's external auth mechanism or a personal access token. The CLI is available immediately in your workspace without manual login flows. + +## Prerequisites + +### For External Auth (Recommended) + +Configure Supabase as an external auth provider in your Coder deployment. Generate a Personal Access Token at [Supabase Dashboard](https://supabase.com/dashboard/account/tokens). + +Example Coder server configuration: + +```bash +CODER_EXTERNAL_AUTH_0_ID="supabase" +CODER_EXTERNAL_AUTH_0_TYPE="custom" +CODER_EXTERNAL_AUTH_0_DISPLAY_NAME="Supabase" +# Add OAuth configuration as needed +``` + +### For Direct Token + +Generate a Personal Access Token at https://supabase.com/dashboard/account/tokens and pass it directly to the module. + +## Usage + +### With External Auth (Recommended) + +```tf +module "supabase" { + source = "registry.coder.com/coder/supabase/coder" + version = "1.0.0" + agent_id = coder_agent.example.id +} +``` + +### With Direct Token + +```tf +module "supabase" { + source = "registry.coder.com/coder/supabase/coder" + version = "1.0.0" + agent_id = coder_agent.example.id + use_external_auth = false + access_token = var.supabase_token # From Terraform variable or secret +} +``` + +### With Custom Install Method + +```tf +module "supabase" { + source = "registry.coder.com/coder/supabase/coder" + version = "1.0.0" + agent_id = coder_agent.example.id + install_method = "binary" # Force binary install instead of auto-detect +} +``` + +## Installation Methods + +The module supports multiple installation methods to work across different workspace environments: + +| Method | Description | Platforms | +| ---------------- | ----------------------- | -------------- | +| `auto` (default) | Auto-detect best method | All | +| `brew` | Homebrew | macOS, Linux | +| `scoop` | Scoop package manager | Windows | +| `binary` | Direct binary download | All (fallback) | + +Auto-detection priority: Homebrew → Scoop → Native packages (deb/rpm/apk) → Binary + +## Environment Variables + +The module sets the following environment variables in your workspace: + +| Variable | Description | +| ----------------------- | -------------------------------------------- | +| `SUPABASE_ACCESS_TOKEN` | Personal access token for CLI authentication | +| `SUPABASE_DB_PASSWORD` | Database password (if provided) | + +## Common CLI Commands + +After workspace start, you can use the Supabase CLI: + +```bash +# List your projects +supabase projects list + +# Link to a project +supabase link --project-ref + +# Database operations +supabase db pull # Pull remote schema +supabase db push # Push migrations +supabase migration new # Create migration + +# Local development (requires Docker) +supabase start # Start local stack +supabase stop # Stop local stack + +# Generate TypeScript types +supabase gen types typescript --project-id > types.ts +``` + +## Logs + +Installation logs are stored at: + +``` +$HOME/.coder-modules/coder/supabase/logs/install.log +``` diff --git a/registry/coder/modules/supabase/main.test.ts b/registry/coder/modules/supabase/main.test.ts new file mode 100644 index 000000000..1c20db7df --- /dev/null +++ b/registry/coder/modules/supabase/main.test.ts @@ -0,0 +1,247 @@ +import { + describe, + expect, + it, + beforeAll, + afterEach, + setDefaultTimeout, +} from "bun:test"; +import { + execContainer, + removeContainer, + runContainer, + runTerraformApply, + runTerraformInit, + testRequiredVariables, +} from "~test"; + +setDefaultTimeout(3 * 60 * 1000); // 3 minutes for CLI downloads + +let cleanupContainers: string[] = []; + +afterEach(async () => { + for (const id of cleanupContainers) { + try { + await removeContainer(id); + } catch { + // Ignore cleanup errors + } + } + cleanupContainers = []; +}); + +describe("supabase", async () => { + beforeAll(async () => { + await runTerraformInit(import.meta.dir); + }); + + testRequiredVariables(import.meta.dir, { + agent_id: "test-agent", + }); + + it("defaults to auto install method", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "test-agent", + }); + + // Verify the install script contains the expected ARG_INSTALL_METHOD + const installScript = state.outputs.scripts.value; + expect(installScript).toBeDefined(); + }); + + it("accepts binary install method", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "test-agent", + install_method: "binary", + }); + + expect(state.outputs.scripts.value).toBeDefined(); + }); + + it("accepts brew install method", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "test-agent", + install_method: "brew", + }); + + expect(state.outputs.scripts.value).toBeDefined(); + }); + + it("accepts scoop install method", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "test-agent", + install_method: "scoop", + }); + + expect(state.outputs.scripts.value).toBeDefined(); + }); + + it("rejects invalid install method", async () => { + await expect( + runTerraformApply(import.meta.dir, { + agent_id: "test-agent", + install_method: "invalid", + }), + ).rejects.toThrow(/install_method must be/); + }); + + it("sets access_token when use_external_auth is false", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "test-agent", + use_external_auth: "false", + access_token: "sbp_test_token_abc123", + }); + + expect(state.outputs.access_token.value).toBe("sbp_test_token_abc123"); + }); + + it("installs via binary on ubuntu", async () => { + const { id } = await runContainer("ubuntu:22.04"); + cleanupContainers.push(id); + + // Install curl (required for binary download) + await execContainer(id, ["apt-get", "update"]); + await execContainer(id, ["apt-get", "install", "-y", "curl", "tar"]); + + // Run the install script with binary method + const installScript = ` + set -e + export HOME=/root + export CODER_SCRIPT_BIN_DIR=/tmp/coder-bin + mkdir -p $CODER_SCRIPT_BIN_DIR + + MODULE_DIR="$HOME/.coder-modules/coder/supabase" + LOG_DIR="$MODULE_DIR/logs" + BIN_DIR="$MODULE_DIR/bin" + mkdir -p "$LOG_DIR" "$BIN_DIR" + + INSTALL_METHOD="binary" + VERSION="latest" + + # Platform detection + ARCH=$(uname -m) + case "$ARCH" in + x86_64 | amd64) ARCH="amd64" ;; + aarch64 | arm64) ARCH="arm64" ;; + esac + OS=$(uname -s | tr '[:upper:]' '[:lower:]') + + # Resolve version + API_RESPONSE=$(curl -fsSL "https://api.github.com/repos/supabase/cli/releases/latest") + VERSION=$(echo "$API_RESPONSE" | grep '"tag_name":' | sed -E 's/.*"v?([^"]+)".*/\\1/') + + # Download and install + DOWNLOAD_URL="https://github.com/supabase/cli/releases/download/v$VERSION/supabase_\${OS}_\${ARCH}.tar.gz" + curl -fsSL -o /tmp/supabase.tar.gz "$DOWNLOAD_URL" + tar -xzf /tmp/supabase.tar.gz -C /tmp + mv /tmp/supabase "$BIN_DIR/supabase" + chmod +x "$BIN_DIR/supabase" + + # Verify + "$BIN_DIR/supabase" --version + `; + + const result = await execContainer(id, ["bash", "-c", installScript]); + expect(result.exitCode).toBe(0); + expect(result.stdout).toContain("Supabase CLI"); + }); + + it("installs via binary on alpine", async () => { + const { id } = await runContainer("alpine:latest"); + cleanupContainers.push(id); + + // Install required tools + await execContainer(id, [ + "apk", + "add", + "--no-cache", + "curl", + "tar", + "bash", + ]); + + // Run the install script with binary method + const installScript = ` + set -e + export HOME=/root + export CODER_SCRIPT_BIN_DIR=/tmp/coder-bin + mkdir -p $CODER_SCRIPT_BIN_DIR + + MODULE_DIR="$HOME/.coder-modules/coder/supabase" + BIN_DIR="$MODULE_DIR/bin" + mkdir -p "$BIN_DIR" + + # Platform detection + ARCH=$(uname -m) + case "$ARCH" in + x86_64 | amd64) ARCH="amd64" ;; + aarch64 | arm64) ARCH="arm64" ;; + esac + OS=$(uname -s | tr '[:upper:]' '[:lower:]') + + # Resolve version + API_RESPONSE=$(curl -fsSL "https://api.github.com/repos/supabase/cli/releases/latest") + VERSION=$(echo "$API_RESPONSE" | grep '"tag_name":' | sed -E 's/.*"v?([^"]+)".*/\\1/') + + # Download and install + DOWNLOAD_URL="https://github.com/supabase/cli/releases/download/v$VERSION/supabase_\${OS}_\${ARCH}.tar.gz" + curl -fsSL -o /tmp/supabase.tar.gz "$DOWNLOAD_URL" + tar -xzf /tmp/supabase.tar.gz -C /tmp + mv /tmp/supabase "$BIN_DIR/supabase" + chmod +x "$BIN_DIR/supabase" + + # Verify + "$BIN_DIR/supabase" --version + `; + + const result = await execContainer(id, ["bash", "-c", installScript]); + expect(result.exitCode).toBe(0); + expect(result.stdout).toContain("Supabase CLI"); + }); + + it("creates CODER_SCRIPT_BIN_DIR symlink", async () => { + const { id } = await runContainer("ubuntu:22.04"); + cleanupContainers.push(id); + + await execContainer(id, ["apt-get", "update"]); + await execContainer(id, ["apt-get", "install", "-y", "curl", "tar"]); + + const installScript = ` + set -e + export HOME=/root + export CODER_SCRIPT_BIN_DIR=/tmp/coder-bin + mkdir -p $CODER_SCRIPT_BIN_DIR + + MODULE_DIR="$HOME/.coder-modules/coder/supabase" + BIN_DIR="$MODULE_DIR/bin" + mkdir -p "$BIN_DIR" + + ARCH=$(uname -m) + case "$ARCH" in + x86_64 | amd64) ARCH="amd64" ;; + aarch64 | arm64) ARCH="arm64" ;; + esac + OS=$(uname -s | tr '[:upper:]' '[:lower:]') + + API_RESPONSE=$(curl -fsSL "https://api.github.com/repos/supabase/cli/releases/latest") + VERSION=$(echo "$API_RESPONSE" | grep '"tag_name":' | sed -E 's/.*"v?([^"]+)".*/\\1/') + + DOWNLOAD_URL="https://github.com/supabase/cli/releases/download/v$VERSION/supabase_\${OS}_\${ARCH}.tar.gz" + curl -fsSL -o /tmp/supabase.tar.gz "$DOWNLOAD_URL" + tar -xzf /tmp/supabase.tar.gz -C /tmp + mv /tmp/supabase "$BIN_DIR/supabase" + chmod +x "$BIN_DIR/supabase" + + # Create symlink in CODER_SCRIPT_BIN_DIR + ln -sf "$BIN_DIR/supabase" "$CODER_SCRIPT_BIN_DIR/supabase" + + # Verify symlink works + ls -la "$CODER_SCRIPT_BIN_DIR/supabase" + "$CODER_SCRIPT_BIN_DIR/supabase" --version + `; + + const result = await execContainer(id, ["bash", "-c", installScript]); + expect(result.exitCode).toBe(0); + expect(result.stdout).toContain("Supabase CLI"); + }); +}); diff --git a/registry/coder/modules/supabase/main.tf b/registry/coder/modules/supabase/main.tf new file mode 100644 index 000000000..cd3d270af --- /dev/null +++ b/registry/coder/modules/supabase/main.tf @@ -0,0 +1,143 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + coder = { + source = "coder/coder" + version = ">= 2.0" + } + } +} + +variable "agent_id" { + description = "The ID of a Coder agent." + type = string +} + +variable "icon" { + description = "The icon to use for the module." + type = string + default = "/icon/supabase.svg" +} + +variable "external_auth_id" { + description = "Supabase external auth provider ID configured in Coder." + type = string + default = "supabase" +} + +variable "use_external_auth" { + description = "Use Coder external auth for Supabase authentication." + type = bool + default = true +} + +variable "access_token" { + description = "Supabase personal access token. Ignored if use_external_auth is true." + type = string + default = "" + sensitive = true +} + +variable "install_method" { + description = "Installation method: 'auto' (detect best), 'brew', 'scoop' (Windows), or 'binary'." + type = string + default = "auto" + validation { + condition = contains(["auto", "brew", "scoop", "binary"], var.install_method) + error_message = "install_method must be 'auto', 'brew', 'scoop', or 'binary'." + } +} + +variable "supabase_version" { + description = "Supabase CLI version to install. Use 'latest' for most recent." + type = string + default = "latest" +} + +variable "db_password" { + description = "Database password for non-interactive db commands (optional)." + type = string + default = "" + sensitive = true +} + +variable "pre_install_script" { + description = "Custom script to run before installing Supabase CLI." + type = string + default = null +} + +variable "post_install_script" { + description = "Custom script to run after installing Supabase CLI." + type = string + default = null +} + +data "coder_workspace" "me" {} + +data "coder_workspace_owner" "me" {} + +# External auth data source - only used when use_external_auth is true +data "coder_external_auth" "supabase" { + count = var.use_external_auth ? 1 : 0 + id = var.external_auth_id +} + +locals { + module_dir = "$HOME/.coder-modules/coder/supabase" + + # Determine the access token to use + access_token = var.use_external_auth ? try(data.coder_external_auth.supabase[0].access_token, "") : var.access_token + + # Render the install script + install_script = templatefile("${path.module}/scripts/install.sh.tftpl", { + ARG_INSTALL_METHOD = var.install_method + ARG_VERSION = var.supabase_version + }) +} + +module "coder_utils" { + source = "registry.coder.com/coder/coder-utils/coder" + version = "0.0.1" + + agent_id = var.agent_id + module_directory = local.module_dir + display_name_prefix = "Supabase" + icon = var.icon + pre_install_script = var.pre_install_script + install_script = local.install_script + post_install_script = var.post_install_script +} + +# Set SUPABASE_ACCESS_TOKEN environment variable +resource "coder_env" "supabase_access_token" { + count = local.access_token != "" ? 1 : 0 + agent_id = var.agent_id + name = "SUPABASE_ACCESS_TOKEN" + value = local.access_token +} + +# Set SUPABASE_DB_PASSWORD environment variable (optional) +resource "coder_env" "supabase_db_password" { + count = var.db_password != "" ? 1 : 0 + agent_id = var.agent_id + name = "SUPABASE_DB_PASSWORD" + value = var.db_password +} + +output "scripts" { + description = "Ordered list of coder exp sync names produced by this module." + value = module.coder_utils.scripts +} + +output "access_token" { + description = "The Supabase access token (from external auth or direct variable)." + value = local.access_token + sensitive = true +} + +output "module_directory" { + description = "The directory where Supabase CLI and logs are stored." + value = local.module_dir +} diff --git a/registry/coder/modules/supabase/main.tftest.hcl b/registry/coder/modules/supabase/main.tftest.hcl new file mode 100644 index 000000000..a66be4925 --- /dev/null +++ b/registry/coder/modules/supabase/main.tftest.hcl @@ -0,0 +1,175 @@ +run "test_supabase_basic" { + command = plan + + variables { + agent_id = "test-agent-123" + } + + assert { + condition = var.agent_id == "test-agent-123" + error_message = "Agent ID variable should be set correctly" + } + + assert { + condition = var.install_method == "auto" + error_message = "Install method should default to 'auto'" + } + + assert { + condition = var.supabase_version == "latest" + error_message = "Version should default to 'latest'" + } + + assert { + condition = var.use_external_auth == true + error_message = "use_external_auth should default to true" + } +} + +run "test_supabase_with_direct_token" { + command = plan + + variables { + agent_id = "test-agent-456" + use_external_auth = false + access_token = "sbp_test_token_1234567890abcdef12345678" + } + + assert { + condition = var.use_external_auth == false + error_message = "use_external_auth should be false" + } + + assert { + condition = var.access_token == "sbp_test_token_1234567890abcdef12345678" + error_message = "Access token should be set correctly" + } +} + +run "test_supabase_with_custom_install_method" { + command = plan + + variables { + agent_id = "test-agent-789" + install_method = "binary" + } + + assert { + condition = var.install_method == "binary" + error_message = "Install method should be 'binary'" + } +} + +run "test_supabase_with_brew_install" { + command = plan + + variables { + agent_id = "test-agent-brew" + install_method = "brew" + } + + assert { + condition = var.install_method == "brew" + error_message = "Install method should be 'brew'" + } +} + +run "test_supabase_with_scoop_install" { + command = plan + + variables { + agent_id = "test-agent-scoop" + install_method = "scoop" + } + + assert { + condition = var.install_method == "scoop" + error_message = "Install method should be 'scoop'" + } +} + +run "test_supabase_with_specific_version" { + command = plan + + variables { + agent_id = "test-agent-version" + supabase_version = "2.0.0" + } + + assert { + condition = var.supabase_version == "2.0.0" + error_message = "Version should be '2.0.0'" + } +} + +run "test_supabase_with_db_password" { + command = plan + + variables { + agent_id = "test-agent-db" + use_external_auth = false + access_token = "sbp_test_token" + db_password = "my-secret-password" + } + + assert { + condition = var.db_password == "my-secret-password" + error_message = "Database password should be set correctly" + } +} + +run "test_supabase_with_custom_external_auth_id" { + command = plan + + variables { + agent_id = "test-agent-auth" + external_auth_id = "my-supabase-oauth" + } + + assert { + condition = var.external_auth_id == "my-supabase-oauth" + error_message = "External auth ID should be 'my-supabase-oauth'" + } +} + +run "test_supabase_with_custom_icon" { + command = plan + + variables { + agent_id = "test-agent-icon" + icon = "/icon/custom-supabase.svg" + } + + assert { + condition = var.icon == "/icon/custom-supabase.svg" + error_message = "Icon should be set to custom path" + } +} + +run "test_supabase_with_pre_install_script" { + command = plan + + variables { + agent_id = "test-agent-pre" + pre_install_script = "echo 'Pre-install script'" + } + + assert { + condition = var.pre_install_script == "echo 'Pre-install script'" + error_message = "Pre-install script should be set" + } +} + +run "test_supabase_with_post_install_script" { + command = plan + + variables { + agent_id = "test-agent-post" + post_install_script = "echo 'Post-install script'" + } + + assert { + condition = var.post_install_script == "echo 'Post-install script'" + error_message = "Post-install script should be set" + } +} diff --git a/registry/coder/modules/supabase/scripts/install.sh.tftpl b/registry/coder/modules/supabase/scripts/install.sh.tftpl new file mode 100644 index 000000000..dbc43f7ef --- /dev/null +++ b/registry/coder/modules/supabase/scripts/install.sh.tftpl @@ -0,0 +1,264 @@ +#!/bin/bash +set -euo pipefail + +# === Module directory setup (per AGENTS.md data layout) === +MODULE_DIR="$${HOME}/.coder-modules/coder/supabase" +LOG_DIR="$${MODULE_DIR}/logs" +BIN_DIR="$${MODULE_DIR}/bin" +mkdir -p "$${LOG_DIR}" "$${BIN_DIR}" + +exec > >(tee -a "$${LOG_DIR}/install.log") 2>&1 + +INSTALL_METHOD='${ARG_INSTALL_METHOD}' +VERSION='${ARG_VERSION}' + +echo "Installing Supabase CLI (method: $${INSTALL_METHOD}, version: $${VERSION})..." + +# === Platform Detection (pattern from vault-cli/agentapi) === +detect_platform() { + ARCH=$(uname -m) + case "$${ARCH}" in + x86_64 | amd64) ARCH="amd64" ;; + aarch64 | arm64) ARCH="arm64" ;; + *) + echo "Error: Unsupported architecture: $${ARCH}" + exit 1 + ;; + esac + + OS=$(uname -s | tr '[:upper:]' '[:lower:]') + case "$${OS}" in + linux | darwin) ;; + mingw* | msys* | cygwin*) + OS="windows" + ;; + *) + echo "Error: Unsupported OS: $${OS}. Only linux, darwin, and windows are supported." + exit 1 + ;; + esac + + echo "Detected platform: $${OS}/$${ARCH}" +} + +# === Download with fallbacks (pattern from vault-cli) === +fetch_to_file() { + local dest="$1" url="$2" + if command -v curl > /dev/null 2>&1; then + curl -fsSL --retry 5 --retry-delay 5 -o "$${dest}" "$${url}" + elif command -v wget > /dev/null 2>&1; then + wget -q -O "$${dest}" "$${url}" + elif command -v busybox > /dev/null 2>&1; then + busybox wget -q -O "$${dest}" "$${url}" + else + echo "Error: curl, wget, or busybox is required" + return 1 + fi +} + +# === Version resolution (pattern from agentapi) === +resolve_version() { + local version="$${VERSION}" + if [ "$${version}" = "latest" ]; then + # GitHub API to get latest release tag + local api_response + if command -v curl > /dev/null 2>&1; then + api_response=$(curl -fsSL "https://api.github.com/repos/supabase/cli/releases/latest") + elif command -v wget > /dev/null 2>&1; then + api_response=$(wget -qO- "https://api.github.com/repos/supabase/cli/releases/latest") + else + echo "Error: curl or wget required for version resolution" + exit 1 + fi + version=$(echo "$${api_response}" | grep '"tag_name":' | sed -E 's/.*"v?([^"]+)".*/\1/') + echo "Resolved latest version: $${version}" >&2 + fi + echo "$${version}" +} + +# === PATH setup via CODER_SCRIPT_BIN_DIR (pattern from mux/code-server) === +setup_path() { + local bin_path="$1" + + # Primary: Use Coder's native bin directory (preferred) + if [ -n "$${CODER_SCRIPT_BIN_DIR:-}" ]; then + ln -sf "$${bin_path}" "$${CODER_SCRIPT_BIN_DIR}/supabase" + echo "Linked supabase to CODER_SCRIPT_BIN_DIR" + fi + + # Fallback: Add to shell profiles (pattern from claude-code) + local bin_dir + bin_dir=$(dirname "$${bin_path}") + for profile in "$${HOME}/.profile" "$${HOME}/.bash_profile" "$${HOME}/.bashrc" "$${HOME}/.zprofile" "$${HOME}/.zshrc"; do + if [ -f "$${profile}" ]; then + if ! grep -q "$${bin_dir}" "$${profile}" 2>/dev/null; then + echo "export PATH=\"\$PATH:$${bin_dir}\"" >> "$${profile}" + echo "Added $${bin_dir} to $${profile}" + fi + fi + done + + # Fish shell support + local fish_config="$${HOME}/.config/fish/config.fish" + if [ -f "$${fish_config}" ]; then + if ! grep -q "$${bin_dir}" "$${fish_config}" 2>/dev/null; then + echo "fish_add_path $${bin_dir}" >> "$${fish_config}" + echo "Added $${bin_dir} to $${fish_config}" + fi + fi +} + +# === Installation Methods === + +install_binary() { + detect_platform + local version + version=$(resolve_version) + + # Supabase CLI release naming: supabase_linux_amd64.tar.gz + local download_url="https://github.com/supabase/cli/releases/download/v$${version}/supabase_$${OS}_$${ARCH}.tar.gz" + + local tmp_dir + tmp_dir=$(mktemp -d) + trap "rm -rf $${tmp_dir}" EXIT + + echo "Downloading Supabase CLI v$${version} from $${download_url}..." + fetch_to_file "$${tmp_dir}/supabase.tar.gz" "$${download_url}" + + tar -xzf "$${tmp_dir}/supabase.tar.gz" -C "$${tmp_dir}" + mv "$${tmp_dir}/supabase" "$${BIN_DIR}/supabase" + chmod +x "$${BIN_DIR}/supabase" + + setup_path "$${BIN_DIR}/supabase" +} + +install_native_package() { + detect_platform + local version + version=$(resolve_version) + + local tmp_dir + tmp_dir=$(mktemp -d) + trap "rm -rf $${tmp_dir}" EXIT + + # Detect package manager and install appropriate package + if command -v dpkg > /dev/null 2>&1; then + # Debian/Ubuntu + local pkg_url="https://github.com/supabase/cli/releases/download/v$${version}/supabase_$${version}_$${OS}_$${ARCH}.deb" + echo "Installing via dpkg..." + fetch_to_file "$${tmp_dir}/supabase.deb" "$${pkg_url}" + if command -v sudo > /dev/null 2>&1; then + sudo dpkg -i "$${tmp_dir}/supabase.deb" || { + echo "Warning: dpkg install failed, trying binary fallback" + install_binary + return + } + else + dpkg -i "$${tmp_dir}/supabase.deb" 2>/dev/null || { + echo "Warning: dpkg install failed (no sudo), trying binary fallback" + install_binary + return + } + fi + elif command -v rpm > /dev/null 2>&1; then + # Fedora/RHEL + local pkg_url="https://github.com/supabase/cli/releases/download/v$${version}/supabase_$${version}_$${OS}_$${ARCH}.rpm" + echo "Installing via rpm..." + fetch_to_file "$${tmp_dir}/supabase.rpm" "$${pkg_url}" + if command -v sudo > /dev/null 2>&1; then + sudo rpm -i "$${tmp_dir}/supabase.rpm" || { + echo "Warning: rpm install failed, trying binary fallback" + install_binary + return + } + else + rpm -i "$${tmp_dir}/supabase.rpm" 2>/dev/null || { + echo "Warning: rpm install failed (no sudo), trying binary fallback" + install_binary + return + } + fi + elif command -v apk > /dev/null 2>&1; then + # Alpine + local pkg_url="https://github.com/supabase/cli/releases/download/v$${version}/supabase_$${version}_$${OS}_$${ARCH}.apk" + echo "Installing via apk..." + fetch_to_file "$${tmp_dir}/supabase.apk" "$${pkg_url}" + if command -v sudo > /dev/null 2>&1; then + sudo apk add --allow-untrusted "$${tmp_dir}/supabase.apk" || { + echo "Warning: apk install failed, trying binary fallback" + install_binary + return + } + else + apk add --allow-untrusted "$${tmp_dir}/supabase.apk" 2>/dev/null || { + echo "Warning: apk install failed (no sudo), trying binary fallback" + install_binary + return + } + fi + else + echo "No supported package manager found, falling back to binary install" + install_binary + fi +} + +install_brew() { + if ! command -v brew > /dev/null 2>&1; then + echo "Error: Homebrew is required for install_method=brew" + exit 1 + fi + echo "Installing via Homebrew..." + brew install supabase/tap/supabase || brew upgrade supabase/tap/supabase || true +} + +install_scoop() { + # Windows via Scoop + if ! command -v scoop > /dev/null 2>&1; then + echo "Error: Scoop is required for install_method=scoop" + exit 1 + fi + echo "Installing via Scoop..." + scoop bucket add supabase https://github.com/supabase/scoop-bucket.git 2>/dev/null || true + scoop install supabase || scoop update supabase || true +} + +# === Auto-detection (priority: brew → scoop → native pkg → binary) === +if [ "$${INSTALL_METHOD}" = "auto" ]; then + if command -v brew > /dev/null 2>&1; then + INSTALL_METHOD="brew" + elif command -v scoop > /dev/null 2>&1; then + INSTALL_METHOD="scoop" + elif command -v dpkg > /dev/null 2>&1 || command -v rpm > /dev/null 2>&1 || command -v apk > /dev/null 2>&1; then + INSTALL_METHOD="native" + else + INSTALL_METHOD="binary" + fi + echo "Auto-detected install method: $${INSTALL_METHOD}" +fi + +# === Execute Installation === +case "$${INSTALL_METHOD}" in + brew) install_brew ;; + scoop) install_scoop ;; + native) install_native_package ;; + binary) install_binary ;; + *) + echo "Error: Unknown install method: $${INSTALL_METHOD}" >&2 + exit 1 + ;; +esac + +# === Verify Installation === +SUPABASE_BIN="" +if command -v supabase > /dev/null 2>&1; then + SUPABASE_BIN="supabase" +elif [ -x "$${BIN_DIR}/supabase" ]; then + SUPABASE_BIN="$${BIN_DIR}/supabase" +fi + +if [ -n "$${SUPABASE_BIN}" ]; then + echo "✓ Supabase CLI installed: $($${SUPABASE_BIN} --version)" +else + echo "✗ Supabase CLI installation failed" >&2 + exit 1 +fi From e88772938ed8b29dd15e20c440c470052f787038 Mon Sep 17 00:00:00 2001 From: DevelopmentCats Date: Wed, 19 Aug 2026 14:00:34 -0500 Subject: [PATCH 02/20] fix: improve version resolution regex for GitHub API response The previous regex was too greedy and captured release notes content instead of the tag_name value. Now uses grep -o with head -1 and a more precise pattern to extract only the version number. --- registry/coder/modules/supabase/scripts/install.sh.tftpl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/registry/coder/modules/supabase/scripts/install.sh.tftpl b/registry/coder/modules/supabase/scripts/install.sh.tftpl index dbc43f7ef..a9ebea28c 100644 --- a/registry/coder/modules/supabase/scripts/install.sh.tftpl +++ b/registry/coder/modules/supabase/scripts/install.sh.tftpl @@ -70,7 +70,8 @@ resolve_version() { echo "Error: curl or wget required for version resolution" exit 1 fi - version=$(echo "$${api_response}" | grep '"tag_name":' | sed -E 's/.*"v?([^"]+)".*/\1/') + # Extract tag_name using head -1 to ensure we only get the first match, and a more precise regex + version=$(echo "$${api_response}" | grep -o '"tag_name"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed -E 's/.*"v?([0-9][^"]*).*/\1/') echo "Resolved latest version: $${version}" >&2 fi echo "$${version}" From f9af20426094c6bdac49e1ed092594f26599e65b Mon Sep 17 00:00:00 2001 From: DevelopmentCats Date: Wed, 19 Aug 2026 14:14:17 -0500 Subject: [PATCH 03/20] chore(supabase): rename auto to detect, align with claude-code patterns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Renamed install_method 'auto' to 'detect' for clarity - Reordered variable blocks: type → description → default - Placed data sources after agent_id variable - Improved variable and output descriptions - Updated tests and README to use 'detect' --- registry/coder/modules/supabase/README.md | 44 +++++++---------- registry/coder/modules/supabase/main.test.ts | 2 +- registry/coder/modules/supabase/main.tf | 49 ++++++++++--------- .../coder/modules/supabase/main.tftest.hcl | 4 +- .../modules/supabase/scripts/install.sh.tftpl | 6 +-- 5 files changed, 50 insertions(+), 55 deletions(-) diff --git a/registry/coder/modules/supabase/README.md b/registry/coder/modules/supabase/README.md index b2ce52a06..28a944c1b 100644 --- a/registry/coder/modules/supabase/README.md +++ b/registry/coder/modules/supabase/README.md @@ -1,6 +1,6 @@ --- display_name: Supabase CLI -description: Install Supabase CLI and configure authentication via Coder external auth +description: Install Supabase CLI and configure authentication via Coder external auth or access token icon: ../../../../.icons/supabase.svg verified: false tags: [supabase, database, cli, helper] @@ -8,40 +8,34 @@ tags: [supabase, database, cli, helper] # Supabase CLI -Installs the [Supabase CLI](https://supabase.com/docs/guides/cli) and configures authentication using Coder's external auth mechanism or a personal access token. The CLI is available immediately in your workspace without manual login flows. +Installs the [Supabase CLI](https://supabase.com/docs/guides/cli) and configures authentication. The CLI is available immediately in your workspace without manual login flows. -## Prerequisites +## Authentication -### For External Auth (Recommended) +Choose **one** of the following authentication methods: -Configure Supabase as an external auth provider in your Coder deployment. Generate a Personal Access Token at [Supabase Dashboard](https://supabase.com/dashboard/account/tokens). +### Option 1: Coder External Auth (Recommended) -Example Coder server configuration: +Configure Supabase as an [external auth provider](https://coder.com/docs/admin/external-auth) in your Coder deployment. Users authenticate via OAuth when launching a workspace. -```bash -CODER_EXTERNAL_AUTH_0_ID="supabase" -CODER_EXTERNAL_AUTH_0_TYPE="custom" -CODER_EXTERNAL_AUTH_0_DISPLAY_NAME="Supabase" -# Add OAuth configuration as needed -``` - -### For Direct Token +### Option 2: Personal Access Token -Generate a Personal Access Token at https://supabase.com/dashboard/account/tokens and pass it directly to the module. +Generate a token at [supabase.com/dashboard/account/tokens](https://supabase.com/dashboard/account/tokens) and pass it to the module via the `access_token` variable. ## Usage -### With External Auth (Recommended) +### With External Auth ```tf module "supabase" { source = "registry.coder.com/coder/supabase/coder" version = "1.0.0" agent_id = coder_agent.example.id + # external_auth_id = "supabase" # Default; change if your provider has a different ID } ``` -### With Direct Token +### With Personal Access Token ```tf module "supabase" { @@ -60,7 +54,7 @@ module "supabase" { source = "registry.coder.com/coder/supabase/coder" version = "1.0.0" agent_id = coder_agent.example.id - install_method = "binary" # Force binary install instead of auto-detect + install_method = "binary" # Force binary install instead of detect } ``` @@ -68,14 +62,14 @@ module "supabase" { The module supports multiple installation methods to work across different workspace environments: -| Method | Description | Platforms | -| ---------------- | ----------------------- | -------------- | -| `auto` (default) | Auto-detect best method | All | -| `brew` | Homebrew | macOS, Linux | -| `scoop` | Scoop package manager | Windows | -| `binary` | Direct binary download | All (fallback) | +| Method | Description | Platforms | +| ------------------ | ---------------------------- | -------------- | +| `detect` (default) | Detect best available method | All | +| `brew` | Homebrew | macOS, Linux | +| `scoop` | Scoop package manager | Windows | +| `binary` | Direct binary download | All (fallback) | -Auto-detection priority: Homebrew → Scoop → Native packages (deb/rpm/apk) → Binary +Detection priority: Homebrew → Scoop → Native packages (deb/rpm/apk) → Binary ## Environment Variables diff --git a/registry/coder/modules/supabase/main.test.ts b/registry/coder/modules/supabase/main.test.ts index 1c20db7df..1d0dec417 100644 --- a/registry/coder/modules/supabase/main.test.ts +++ b/registry/coder/modules/supabase/main.test.ts @@ -39,7 +39,7 @@ describe("supabase", async () => { agent_id: "test-agent", }); - it("defaults to auto install method", async () => { + it("defaults to detect install method", async () => { const state = await runTerraformApply(import.meta.dir, { agent_id: "test-agent", }); diff --git a/registry/coder/modules/supabase/main.tf b/registry/coder/modules/supabase/main.tf index cd3d270af..335d15f02 100644 --- a/registry/coder/modules/supabase/main.tf +++ b/registry/coder/modules/supabase/main.tf @@ -10,74 +10,74 @@ terraform { } variable "agent_id" { - description = "The ID of a Coder agent." type = string + description = "The ID of a Coder agent." } +data "coder_workspace" "me" {} + +data "coder_workspace_owner" "me" {} + variable "icon" { - description = "The icon to use for the module." type = string + description = "The icon to use for the Supabase app." default = "/icon/supabase.svg" } variable "external_auth_id" { - description = "Supabase external auth provider ID configured in Coder." type = string + description = "Supabase external auth provider ID configured in Coder." default = "supabase" } variable "use_external_auth" { - description = "Use Coder external auth for Supabase authentication." type = bool + description = "Use Coder external auth for Supabase authentication. When false, use the access_token variable instead." default = true } variable "access_token" { - description = "Supabase personal access token. Ignored if use_external_auth is true." type = string + description = "Supabase personal access token. Only used when use_external_auth is false." default = "" sensitive = true } variable "install_method" { - description = "Installation method: 'auto' (detect best), 'brew', 'scoop' (Windows), or 'binary'." type = string - default = "auto" + description = "How to install the Supabase CLI. 'detect' automatically selects the best available method (brew → scoop → native package → binary). Use 'brew', 'scoop', or 'binary' to force a specific method." + default = "detect" validation { - condition = contains(["auto", "brew", "scoop", "binary"], var.install_method) - error_message = "install_method must be 'auto', 'brew', 'scoop', or 'binary'." + condition = contains(["detect", "brew", "scoop", "binary"], var.install_method) + error_message = "The 'install_method' variable must be one of: 'detect', 'brew', 'scoop', 'binary'." } } variable "supabase_version" { - description = "Supabase CLI version to install. Use 'latest' for most recent." type = string + description = "The version of Supabase CLI to install. Use 'latest' for the most recent release." default = "latest" } variable "db_password" { - description = "Database password for non-interactive db commands (optional)." type = string + description = "Database password for non-interactive db commands (optional). Sets SUPABASE_DB_PASSWORD environment variable." default = "" sensitive = true } variable "pre_install_script" { - description = "Custom script to run before installing Supabase CLI." type = string + description = "Custom script to run before installing Supabase CLI. Can be used for dependency ordering between modules." default = null } variable "post_install_script" { - description = "Custom script to run after installing Supabase CLI." type = string + description = "Custom script to run after installing Supabase CLI." default = null } -data "coder_workspace" "me" {} - -data "coder_workspace_owner" "me" {} - # External auth data source - only used when use_external_auth is true data "coder_external_auth" "supabase" { count = var.use_external_auth ? 1 : 0 @@ -85,7 +85,7 @@ data "coder_external_auth" "supabase" { } locals { - module_dir = "$HOME/.coder-modules/coder/supabase" + module_dir_name = ".coder-modules/coder/supabase" # Determine the access token to use access_token = var.use_external_auth ? try(data.coder_external_auth.supabase[0].access_token, "") : var.access_token @@ -102,7 +102,7 @@ module "coder_utils" { version = "0.0.1" agent_id = var.agent_id - module_directory = local.module_dir + module_directory = "$HOME/${local.module_dir_name}" display_name_prefix = "Supabase" icon = var.icon pre_install_script = var.pre_install_script @@ -110,7 +110,6 @@ module "coder_utils" { post_install_script = var.post_install_script } -# Set SUPABASE_ACCESS_TOKEN environment variable resource "coder_env" "supabase_access_token" { count = local.access_token != "" ? 1 : 0 agent_id = var.agent_id @@ -118,7 +117,6 @@ resource "coder_env" "supabase_access_token" { value = local.access_token } -# Set SUPABASE_DB_PASSWORD environment variable (optional) resource "coder_env" "supabase_db_password" { count = var.db_password != "" ? 1 : 0 agent_id = var.agent_id @@ -126,8 +124,11 @@ resource "coder_env" "supabase_db_password" { value = var.db_password } +# Pass-through of coder-utils script outputs so upstream modules can serialize +# their coder_script resources behind this module's install pipeline using +# `coder exp sync want `. output "scripts" { - description = "Ordered list of coder exp sync names produced by this module." + description = "Ordered list of coder exp sync names for the coder_script resources this module creates, in run order (pre_install, install, post_install). Scripts that were not configured are absent from the list." value = module.coder_utils.scripts } @@ -138,6 +139,6 @@ output "access_token" { } output "module_directory" { - description = "The directory where Supabase CLI and logs are stored." - value = local.module_dir + description = "The directory where Supabase CLI logs and scripts are stored." + value = "$HOME/${local.module_dir_name}" } diff --git a/registry/coder/modules/supabase/main.tftest.hcl b/registry/coder/modules/supabase/main.tftest.hcl index a66be4925..160bb0536 100644 --- a/registry/coder/modules/supabase/main.tftest.hcl +++ b/registry/coder/modules/supabase/main.tftest.hcl @@ -11,8 +11,8 @@ run "test_supabase_basic" { } assert { - condition = var.install_method == "auto" - error_message = "Install method should default to 'auto'" + condition = var.install_method == "detect" + error_message = "Install method should default to 'detect'" } assert { diff --git a/registry/coder/modules/supabase/scripts/install.sh.tftpl b/registry/coder/modules/supabase/scripts/install.sh.tftpl index a9ebea28c..562b464ef 100644 --- a/registry/coder/modules/supabase/scripts/install.sh.tftpl +++ b/registry/coder/modules/supabase/scripts/install.sh.tftpl @@ -223,8 +223,8 @@ install_scoop() { scoop install supabase || scoop update supabase || true } -# === Auto-detection (priority: brew → scoop → native pkg → binary) === -if [ "$${INSTALL_METHOD}" = "auto" ]; then +# === Detection logic (priority: brew → scoop → native pkg → binary) === +if [ "$${INSTALL_METHOD}" = "detect" ]; then if command -v brew > /dev/null 2>&1; then INSTALL_METHOD="brew" elif command -v scoop > /dev/null 2>&1; then @@ -234,7 +234,7 @@ if [ "$${INSTALL_METHOD}" = "auto" ]; then else INSTALL_METHOD="binary" fi - echo "Auto-detected install method: $${INSTALL_METHOD}" + echo "Detected install method: $${INSTALL_METHOD}" fi # === Execute Installation === From 1e1dd6afe256bb452838d9d11712358cca09c3e4 Mon Sep 17 00:00:00 2001 From: DevelopmentCats Date: Wed, 19 Aug 2026 14:54:46 -0500 Subject: [PATCH 04/20] feat(supabase): add coder_app dashboard link and project_ref variable - Add coder_app resource that links to Supabase dashboard - Add project_ref variable for direct project link (optional) - URL defaults to dashboard list, or project-specific if project_ref set - Update README with OAuth setup instructions (verified working) - Update README with dashboard app documentation - Update db_password description to clarify it's for remote Postgres - Change use_external_auth default to false (PAT is simpler to start) - Add 2 new tests for coder_app URL behavior (13 total) Tested: CLI install working, both PAT and OAuth auth working. --- registry/coder/modules/supabase/README.md | 65 +++++++++++++++---- registry/coder/modules/supabase/main.tf | 22 ++++++- .../coder/modules/supabase/main.tftest.hcl | 47 +++++++++++++- 3 files changed, 116 insertions(+), 18 deletions(-) diff --git a/registry/coder/modules/supabase/README.md b/registry/coder/modules/supabase/README.md index 28a944c1b..e43f33967 100644 --- a/registry/coder/modules/supabase/README.md +++ b/registry/coder/modules/supabase/README.md @@ -14,28 +14,57 @@ Installs the [Supabase CLI](https://supabase.com/docs/guides/cli) and configures Choose **one** of the following authentication methods: -### Option 1: Coder External Auth (Recommended) +### Option 1: Personal Access Token + +Generate a token at [supabase.com/dashboard/account/tokens](https://supabase.com/dashboard/account/tokens) and pass it to the module via the `access_token` variable with `use_external_auth = false`. + +### Option 2: Coder External Auth (OAuth) Configure Supabase as an [external auth provider](https://coder.com/docs/admin/external-auth) in your Coder deployment. Users authenticate via OAuth when launching a workspace. -### Option 2: Personal Access Token +Required Coder environment variables: -Generate a token at [supabase.com/dashboard/account/tokens](https://supabase.com/dashboard/account/tokens) and pass it to the module via the `access_token` variable. +```bash +CODER_EXTERNAL_AUTH_0_ID=supabase +CODER_EXTERNAL_AUTH_0_TYPE=custom +CODER_EXTERNAL_AUTH_0_CLIENT_ID= +CODER_EXTERNAL_AUTH_0_CLIENT_SECRET= +CODER_EXTERNAL_AUTH_0_AUTH_URL=https://api.supabase.com/v1/oauth/authorize +CODER_EXTERNAL_AUTH_0_TOKEN_URL=https://api.supabase.com/v1/oauth/token +CODER_EXTERNAL_AUTH_0_SCOPES=all +CODER_EXTERNAL_AUTH_0_DISPLAY_NAME=Supabase +CODER_EXTERNAL_AUTH_0_DISPLAY_ICON=/icon/supabase.svg +``` + +Create your OAuth app in the [Supabase Dashboard](https://supabase.com/dashboard/account/oauth-apps) under "OAuth Apps" → "Published apps". Set the redirect URI to `https:///external-auth/supabase/callback`. ## Usage -### With External Auth +### With Personal Access Token + +```tf +module "supabase" { + source = "registry.coder.com/coder/supabase/coder" + version = "1.0.0" + agent_id = coder_agent.example.id + use_external_auth = false + access_token = var.supabase_token # From Terraform variable or secret +} +``` + +### With External Auth (OAuth) ```tf module "supabase" { - source = "registry.coder.com/coder/supabase/coder" - version = "1.0.0" - agent_id = coder_agent.example.id + source = "registry.coder.com/coder/supabase/coder" + version = "1.0.0" + agent_id = coder_agent.example.id + use_external_auth = true # external_auth_id = "supabase" # Default; change if your provider has a different ID } ``` -### With Personal Access Token +### With Project Dashboard Link ```tf module "supabase" { @@ -43,7 +72,8 @@ module "supabase" { version = "1.0.0" agent_id = coder_agent.example.id use_external_auth = false - access_token = var.supabase_token # From Terraform variable or secret + access_token = var.supabase_token + project_ref = "abcdefghijklmnop" # Links dashboard button directly to this project } ``` @@ -71,14 +101,23 @@ The module supports multiple installation methods to work across different works Detection priority: Homebrew → Scoop → Native packages (deb/rpm/apk) → Binary +## Dashboard App + +The module adds a **Supabase** button to your workspace that links to the Supabase dashboard: + +- **Without `project_ref`**: Links to [supabase.com/dashboard](https://supabase.com/dashboard) (project list) +- **With `project_ref`**: Links directly to your project's dashboard + +Find your project reference in the Supabase dashboard URL: `https://supabase.com/dashboard/project/` + ## Environment Variables The module sets the following environment variables in your workspace: -| Variable | Description | -| ----------------------- | -------------------------------------------- | -| `SUPABASE_ACCESS_TOKEN` | Personal access token for CLI authentication | -| `SUPABASE_DB_PASSWORD` | Database password (if provided) | +| Variable | Description | +| ----------------------- | -------------------------------------------------- | +| `SUPABASE_ACCESS_TOKEN` | Personal access token for CLI authentication | +| `SUPABASE_DB_PASSWORD` | Remote Postgres password for `supabase link` in CI | ## Common CLI Commands diff --git a/registry/coder/modules/supabase/main.tf b/registry/coder/modules/supabase/main.tf index 335d15f02..bb2533b89 100644 --- a/registry/coder/modules/supabase/main.tf +++ b/registry/coder/modules/supabase/main.tf @@ -32,8 +32,8 @@ variable "external_auth_id" { variable "use_external_auth" { type = bool - description = "Use Coder external auth for Supabase authentication. When false, use the access_token variable instead." - default = true + description = "Use Coder external auth for Supabase authentication. Note: The Supabase CLI may reject OAuth tokens due to format validation; if so, set to false and use a Personal Access Token via the access_token variable." + default = false } variable "access_token" { @@ -61,11 +61,17 @@ variable "supabase_version" { variable "db_password" { type = string - description = "Database password for non-interactive db commands (optional). Sets SUPABASE_DB_PASSWORD environment variable." + description = "Remote Postgres database password for non-interactive CLI commands like 'supabase link' (optional). Sets SUPABASE_DB_PASSWORD environment variable." default = "" sensitive = true } +variable "project_ref" { + type = string + description = "Supabase project reference (e.g., 'abcdefghijklmnop'). When set, adds a dashboard link directly to this project." + default = "" +} + variable "pre_install_script" { type = string description = "Custom script to run before installing Supabase CLI. Can be used for dependency ordering between modules." @@ -124,6 +130,16 @@ resource "coder_env" "supabase_db_password" { value = var.db_password } +resource "coder_app" "supabase" { + agent_id = var.agent_id + slug = "supabase" + display_name = "Supabase" + icon = var.icon + url = var.project_ref != "" ? "https://supabase.com/dashboard/project/${var.project_ref}" : "https://supabase.com/dashboard" + external = true +} + + # Pass-through of coder-utils script outputs so upstream modules can serialize # their coder_script resources behind this module's install pipeline using # `coder exp sync want `. diff --git a/registry/coder/modules/supabase/main.tftest.hcl b/registry/coder/modules/supabase/main.tftest.hcl index 160bb0536..bdc7cd12d 100644 --- a/registry/coder/modules/supabase/main.tftest.hcl +++ b/registry/coder/modules/supabase/main.tftest.hcl @@ -21,8 +21,8 @@ run "test_supabase_basic" { } assert { - condition = var.use_external_auth == true - error_message = "use_external_auth should default to true" + condition = var.use_external_auth == false + error_message = "use_external_auth should default to false (PAT method)" } } @@ -173,3 +173,46 @@ run "test_supabase_with_post_install_script" { error_message = "Post-install script should be set" } } + +run "test_supabase_app_default_url" { + command = apply + + variables { + agent_id = "test-agent-app" + } + + assert { + condition = resource.coder_app.supabase.url == "https://supabase.com/dashboard" + error_message = "coder_app URL should default to dashboard when project_ref is empty" + } + + assert { + condition = resource.coder_app.supabase.external == true + error_message = "coder_app should be external" + } + + assert { + condition = resource.coder_app.supabase.slug == "supabase" + error_message = "coder_app slug should be 'supabase'" + } +} + +run "test_supabase_app_with_project_ref" { + command = apply + + variables { + agent_id = "test-agent-project" + project_ref = "abcdefghijklmnop" + } + + assert { + condition = var.project_ref == "abcdefghijklmnop" + error_message = "project_ref should be set correctly" + } + + assert { + condition = resource.coder_app.supabase.url == "https://supabase.com/dashboard/project/abcdefghijklmnop" + error_message = "coder_app URL should include project reference" + } +} + From ae353088cac268fe445db2cd9972216476963aba Mon Sep 17 00:00:00 2001 From: DevelopmentCats Date: Wed, 19 Aug 2026 14:56:38 -0500 Subject: [PATCH 05/20] chore(supabase): use proper SVG format for icon --- .icons/supabase.svg | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.icons/supabase.svg b/.icons/supabase.svg index 46740b0c5..9e31b162a 100644 --- a/.icons/supabase.svg +++ b/.icons/supabase.svg @@ -1 +1,15 @@ - \ No newline at end of file + + + + + + + + + + + + + + + \ No newline at end of file From 149697c5bcb9768b3c208b137246edefa0e07aba Mon Sep 17 00:00:00 2001 From: DevelopmentCats Date: Wed, 19 Aug 2026 15:00:20 -0500 Subject: [PATCH 06/20] feat(supabase): add dashboard_app toggle to disable workspace app - Add dashboard_app variable (default: true) to control app creation - Follows agentapi pattern for app toggle naming - Add test for disabled app state (14 tests total) --- registry/coder/modules/supabase/main.tf | 8 ++++++ .../coder/modules/supabase/main.tftest.hcl | 27 ++++++++++++++++--- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/registry/coder/modules/supabase/main.tf b/registry/coder/modules/supabase/main.tf index bb2533b89..6c70a20a5 100644 --- a/registry/coder/modules/supabase/main.tf +++ b/registry/coder/modules/supabase/main.tf @@ -72,6 +72,13 @@ variable "project_ref" { default = "" } +variable "dashboard_app" { + type = bool + description = "Whether to create the Supabase dashboard workspace app." + default = true +} + + variable "pre_install_script" { type = string description = "Custom script to run before installing Supabase CLI. Can be used for dependency ordering between modules." @@ -131,6 +138,7 @@ resource "coder_env" "supabase_db_password" { } resource "coder_app" "supabase" { + count = var.dashboard_app ? 1 : 0 agent_id = var.agent_id slug = "supabase" display_name = "Supabase" diff --git a/registry/coder/modules/supabase/main.tftest.hcl b/registry/coder/modules/supabase/main.tftest.hcl index bdc7cd12d..011cb10dd 100644 --- a/registry/coder/modules/supabase/main.tftest.hcl +++ b/registry/coder/modules/supabase/main.tftest.hcl @@ -182,17 +182,17 @@ run "test_supabase_app_default_url" { } assert { - condition = resource.coder_app.supabase.url == "https://supabase.com/dashboard" + condition = resource.coder_app.supabase[0].url == "https://supabase.com/dashboard" error_message = "coder_app URL should default to dashboard when project_ref is empty" } assert { - condition = resource.coder_app.supabase.external == true + condition = resource.coder_app.supabase[0].external == true error_message = "coder_app should be external" } assert { - condition = resource.coder_app.supabase.slug == "supabase" + condition = resource.coder_app.supabase[0].slug == "supabase" error_message = "coder_app slug should be 'supabase'" } } @@ -211,8 +211,27 @@ run "test_supabase_app_with_project_ref" { } assert { - condition = resource.coder_app.supabase.url == "https://supabase.com/dashboard/project/abcdefghijklmnop" + condition = resource.coder_app.supabase[0].url == "https://supabase.com/dashboard/project/abcdefghijklmnop" error_message = "coder_app URL should include project reference" } } +run "test_supabase_app_disabled" { + command = apply + + variables { + agent_id = "test-agent-no-app" + dashboard_app = false + } + + assert { + condition = var.dashboard_app == false + error_message = "dashboard_app should be false" + } + + assert { + condition = length(resource.coder_app.supabase) == 0 + error_message = "coder_app should not be created when dashboard_app is false" + } +} + From 5ac6ef0ea35d6b5841dd3e2ab71829cdb0d72485 Mon Sep 17 00:00:00 2001 From: DevelopmentCats Date: Wed, 19 Aug 2026 15:14:34 -0500 Subject: [PATCH 07/20] feat(supabase): auto-link CLI when project_ref is provided --- registry/coder/modules/supabase/main.tf | 1 + .../coder/modules/supabase/scripts/install.sh.tftpl | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/registry/coder/modules/supabase/main.tf b/registry/coder/modules/supabase/main.tf index 6c70a20a5..d8f69f646 100644 --- a/registry/coder/modules/supabase/main.tf +++ b/registry/coder/modules/supabase/main.tf @@ -107,6 +107,7 @@ locals { install_script = templatefile("${path.module}/scripts/install.sh.tftpl", { ARG_INSTALL_METHOD = var.install_method ARG_VERSION = var.supabase_version + ARG_PROJECT_REF = var.project_ref }) } diff --git a/registry/coder/modules/supabase/scripts/install.sh.tftpl b/registry/coder/modules/supabase/scripts/install.sh.tftpl index 562b464ef..c74c40053 100644 --- a/registry/coder/modules/supabase/scripts/install.sh.tftpl +++ b/registry/coder/modules/supabase/scripts/install.sh.tftpl @@ -11,6 +11,7 @@ exec > >(tee -a "$${LOG_DIR}/install.log") 2>&1 INSTALL_METHOD='${ARG_INSTALL_METHOD}' VERSION='${ARG_VERSION}' +PROJECT_REF='${ARG_PROJECT_REF}' echo "Installing Supabase CLI (method: $${INSTALL_METHOD}, version: $${VERSION})..." @@ -263,3 +264,15 @@ else echo "✗ Supabase CLI installation failed" >&2 exit 1 fi + +# === Link to project if project_ref is provided === +if [ -n "$${PROJECT_REF}" ]; then + echo "Linking to Supabase project: $${PROJECT_REF}..." + # supabase link requires being in a directory - use module dir + cd "$${MODULE_DIR}" + if $${SUPABASE_BIN} link --project-ref "$${PROJECT_REF}" 2>&1; then + echo "✓ Linked to project $${PROJECT_REF}" + else + echo "Warning: Failed to link to project $${PROJECT_REF}. You may need to run 'supabase link --project-ref $${PROJECT_REF}' manually." + fi +fi From 05d0d7a177774aee9e00fea1d8b3968c87dd24d1 Mon Sep 17 00:00:00 2001 From: DevelopmentCats Date: Wed, 19 Aug 2026 15:16:12 -0500 Subject: [PATCH 08/20] chore(supabase): clean up script comments --- .../modules/supabase/scripts/install.sh.tftpl | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/registry/coder/modules/supabase/scripts/install.sh.tftpl b/registry/coder/modules/supabase/scripts/install.sh.tftpl index c74c40053..dd3788f73 100644 --- a/registry/coder/modules/supabase/scripts/install.sh.tftpl +++ b/registry/coder/modules/supabase/scripts/install.sh.tftpl @@ -1,7 +1,6 @@ #!/bin/bash set -euo pipefail -# === Module directory setup (per AGENTS.md data layout) === MODULE_DIR="$${HOME}/.coder-modules/coder/supabase" LOG_DIR="$${MODULE_DIR}/logs" BIN_DIR="$${MODULE_DIR}/bin" @@ -15,7 +14,6 @@ PROJECT_REF='${ARG_PROJECT_REF}' echo "Installing Supabase CLI (method: $${INSTALL_METHOD}, version: $${VERSION})..." -# === Platform Detection (pattern from vault-cli/agentapi) === detect_platform() { ARCH=$(uname -m) case "$${ARCH}" in @@ -42,7 +40,6 @@ detect_platform() { echo "Detected platform: $${OS}/$${ARCH}" } -# === Download with fallbacks (pattern from vault-cli) === fetch_to_file() { local dest="$1" url="$2" if command -v curl > /dev/null 2>&1; then @@ -57,11 +54,9 @@ fetch_to_file() { fi } -# === Version resolution (pattern from agentapi) === resolve_version() { local version="$${VERSION}" if [ "$${version}" = "latest" ]; then - # GitHub API to get latest release tag local api_response if command -v curl > /dev/null 2>&1; then api_response=$(curl -fsSL "https://api.github.com/repos/supabase/cli/releases/latest") @@ -71,24 +66,20 @@ resolve_version() { echo "Error: curl or wget required for version resolution" exit 1 fi - # Extract tag_name using head -1 to ensure we only get the first match, and a more precise regex version=$(echo "$${api_response}" | grep -o '"tag_name"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed -E 's/.*"v?([0-9][^"]*).*/\1/') echo "Resolved latest version: $${version}" >&2 fi echo "$${version}" } -# === PATH setup via CODER_SCRIPT_BIN_DIR (pattern from mux/code-server) === setup_path() { local bin_path="$1" - # Primary: Use Coder's native bin directory (preferred) if [ -n "$${CODER_SCRIPT_BIN_DIR:-}" ]; then ln -sf "$${bin_path}" "$${CODER_SCRIPT_BIN_DIR}/supabase" echo "Linked supabase to CODER_SCRIPT_BIN_DIR" fi - # Fallback: Add to shell profiles (pattern from claude-code) local bin_dir bin_dir=$(dirname "$${bin_path}") for profile in "$${HOME}/.profile" "$${HOME}/.bash_profile" "$${HOME}/.bashrc" "$${HOME}/.zprofile" "$${HOME}/.zshrc"; do @@ -100,7 +91,6 @@ setup_path() { fi done - # Fish shell support local fish_config="$${HOME}/.config/fish/config.fish" if [ -f "$${fish_config}" ]; then if ! grep -q "$${bin_dir}" "$${fish_config}" 2>/dev/null; then @@ -110,14 +100,11 @@ setup_path() { fi } -# === Installation Methods === - install_binary() { detect_platform local version version=$(resolve_version) - # Supabase CLI release naming: supabase_linux_amd64.tar.gz local download_url="https://github.com/supabase/cli/releases/download/v$${version}/supabase_$${OS}_$${ARCH}.tar.gz" local tmp_dir @@ -143,9 +130,7 @@ install_native_package() { tmp_dir=$(mktemp -d) trap "rm -rf $${tmp_dir}" EXIT - # Detect package manager and install appropriate package if command -v dpkg > /dev/null 2>&1; then - # Debian/Ubuntu local pkg_url="https://github.com/supabase/cli/releases/download/v$${version}/supabase_$${version}_$${OS}_$${ARCH}.deb" echo "Installing via dpkg..." fetch_to_file "$${tmp_dir}/supabase.deb" "$${pkg_url}" @@ -163,7 +148,6 @@ install_native_package() { } fi elif command -v rpm > /dev/null 2>&1; then - # Fedora/RHEL local pkg_url="https://github.com/supabase/cli/releases/download/v$${version}/supabase_$${version}_$${OS}_$${ARCH}.rpm" echo "Installing via rpm..." fetch_to_file "$${tmp_dir}/supabase.rpm" "$${pkg_url}" @@ -181,7 +165,6 @@ install_native_package() { } fi elif command -v apk > /dev/null 2>&1; then - # Alpine local pkg_url="https://github.com/supabase/cli/releases/download/v$${version}/supabase_$${version}_$${OS}_$${ARCH}.apk" echo "Installing via apk..." fetch_to_file "$${tmp_dir}/supabase.apk" "$${pkg_url}" @@ -214,7 +197,6 @@ install_brew() { } install_scoop() { - # Windows via Scoop if ! command -v scoop > /dev/null 2>&1; then echo "Error: Scoop is required for install_method=scoop" exit 1 @@ -224,7 +206,6 @@ install_scoop() { scoop install supabase || scoop update supabase || true } -# === Detection logic (priority: brew → scoop → native pkg → binary) === if [ "$${INSTALL_METHOD}" = "detect" ]; then if command -v brew > /dev/null 2>&1; then INSTALL_METHOD="brew" @@ -238,7 +219,6 @@ if [ "$${INSTALL_METHOD}" = "detect" ]; then echo "Detected install method: $${INSTALL_METHOD}" fi -# === Execute Installation === case "$${INSTALL_METHOD}" in brew) install_brew ;; scoop) install_scoop ;; @@ -250,7 +230,6 @@ case "$${INSTALL_METHOD}" in ;; esac -# === Verify Installation === SUPABASE_BIN="" if command -v supabase > /dev/null 2>&1; then SUPABASE_BIN="supabase" @@ -265,10 +244,8 @@ else exit 1 fi -# === Link to project if project_ref is provided === if [ -n "$${PROJECT_REF}" ]; then echo "Linking to Supabase project: $${PROJECT_REF}..." - # supabase link requires being in a directory - use module dir cd "$${MODULE_DIR}" if $${SUPABASE_BIN} link --project-ref "$${PROJECT_REF}" 2>&1; then echo "✓ Linked to project $${PROJECT_REF}" From 5ef6acd8a76ade7a735210ef4f45bca6a87a1b52 Mon Sep 17 00:00:00 2001 From: DevelopmentCats Date: Wed, 19 Aug 2026 15:21:34 -0500 Subject: [PATCH 09/20] fix(supabase): link project in home directory instead of module directory --- registry/coder/modules/supabase/scripts/install.sh.tftpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/coder/modules/supabase/scripts/install.sh.tftpl b/registry/coder/modules/supabase/scripts/install.sh.tftpl index dd3788f73..584f4a4d0 100644 --- a/registry/coder/modules/supabase/scripts/install.sh.tftpl +++ b/registry/coder/modules/supabase/scripts/install.sh.tftpl @@ -246,7 +246,7 @@ fi if [ -n "$${PROJECT_REF}" ]; then echo "Linking to Supabase project: $${PROJECT_REF}..." - cd "$${MODULE_DIR}" + cd "$${HOME}" if $${SUPABASE_BIN} link --project-ref "$${PROJECT_REF}" 2>&1; then echo "✓ Linked to project $${PROJECT_REF}" else From 67245d50817eee0c57eaa2ed54bbef376a927bc5 Mon Sep 17 00:00:00 2001 From: DevelopmentCats Date: Wed, 19 Aug 2026 15:25:33 -0500 Subject: [PATCH 10/20] feat(supabase): add project_dir variable to control link directory --- registry/coder/modules/supabase/main.tf | 9 ++++++++- .../coder/modules/supabase/main.tftest.hcl | 20 +++++++++++++++++++ .../modules/supabase/scripts/install.sh.tftpl | 7 +++++-- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/registry/coder/modules/supabase/main.tf b/registry/coder/modules/supabase/main.tf index d8f69f646..23eaf7296 100644 --- a/registry/coder/modules/supabase/main.tf +++ b/registry/coder/modules/supabase/main.tf @@ -68,7 +68,13 @@ variable "db_password" { variable "project_ref" { type = string - description = "Supabase project reference (e.g., 'abcdefghijklmnop'). When set, adds a dashboard link directly to this project." + description = "Supabase project reference (e.g., 'abcdefghijklmnop'). When set, links the CLI to this project and adds a dashboard link." + default = "" +} + +variable "project_dir" { + type = string + description = "Directory to link the Supabase project in. Created if it doesn't exist. Defaults to $HOME if empty." default = "" } @@ -108,6 +114,7 @@ locals { ARG_INSTALL_METHOD = var.install_method ARG_VERSION = var.supabase_version ARG_PROJECT_REF = var.project_ref + ARG_PROJECT_DIR = var.project_dir }) } diff --git a/registry/coder/modules/supabase/main.tftest.hcl b/registry/coder/modules/supabase/main.tftest.hcl index 011cb10dd..7090a220a 100644 --- a/registry/coder/modules/supabase/main.tftest.hcl +++ b/registry/coder/modules/supabase/main.tftest.hcl @@ -216,6 +216,26 @@ run "test_supabase_app_with_project_ref" { } } +run "test_supabase_with_project_dir" { + command = plan + + variables { + agent_id = "test-agent-project-dir" + project_ref = "abcdefghijklmnop" + project_dir = "/home/coder/my-app" + } + + assert { + condition = var.project_dir == "/home/coder/my-app" + error_message = "project_dir should be set correctly" + } + + assert { + condition = var.project_ref == "abcdefghijklmnop" + error_message = "project_ref should be set correctly" + } +} + run "test_supabase_app_disabled" { command = apply diff --git a/registry/coder/modules/supabase/scripts/install.sh.tftpl b/registry/coder/modules/supabase/scripts/install.sh.tftpl index 584f4a4d0..ddcc7537e 100644 --- a/registry/coder/modules/supabase/scripts/install.sh.tftpl +++ b/registry/coder/modules/supabase/scripts/install.sh.tftpl @@ -11,6 +11,7 @@ exec > >(tee -a "$${LOG_DIR}/install.log") 2>&1 INSTALL_METHOD='${ARG_INSTALL_METHOD}' VERSION='${ARG_VERSION}' PROJECT_REF='${ARG_PROJECT_REF}' +PROJECT_DIR='${ARG_PROJECT_DIR}' echo "Installing Supabase CLI (method: $${INSTALL_METHOD}, version: $${VERSION})..." @@ -245,8 +246,10 @@ else fi if [ -n "$${PROJECT_REF}" ]; then - echo "Linking to Supabase project: $${PROJECT_REF}..." - cd "$${HOME}" + LINK_DIR="$${PROJECT_DIR:-$${HOME}}" + mkdir -p "$${LINK_DIR}" + echo "Linking to Supabase project: $${PROJECT_REF} in $${LINK_DIR}..." + cd "$${LINK_DIR}" if $${SUPABASE_BIN} link --project-ref "$${PROJECT_REF}" 2>&1; then echo "✓ Linked to project $${PROJECT_REF}" else From 303069561c158012db83fff3bc065d742151dcdc Mon Sep 17 00:00:00 2001 From: DevelopmentCats Date: Wed, 19 Aug 2026 15:34:38 -0500 Subject: [PATCH 11/20] fix: correct test regex and remove double-logging --- registry/coder/modules/supabase/main.test.ts | 2 +- registry/coder/modules/supabase/scripts/install.sh.tftpl | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/registry/coder/modules/supabase/main.test.ts b/registry/coder/modules/supabase/main.test.ts index 1d0dec417..406221ea9 100644 --- a/registry/coder/modules/supabase/main.test.ts +++ b/registry/coder/modules/supabase/main.test.ts @@ -82,7 +82,7 @@ describe("supabase", async () => { agent_id: "test-agent", install_method: "invalid", }), - ).rejects.toThrow(/install_method must be/); + ).rejects.toThrow(/install_method.*must be/); }); it("sets access_token when use_external_auth is false", async () => { diff --git a/registry/coder/modules/supabase/scripts/install.sh.tftpl b/registry/coder/modules/supabase/scripts/install.sh.tftpl index ddcc7537e..3b791d14c 100644 --- a/registry/coder/modules/supabase/scripts/install.sh.tftpl +++ b/registry/coder/modules/supabase/scripts/install.sh.tftpl @@ -6,8 +6,6 @@ LOG_DIR="$${MODULE_DIR}/logs" BIN_DIR="$${MODULE_DIR}/bin" mkdir -p "$${LOG_DIR}" "$${BIN_DIR}" -exec > >(tee -a "$${LOG_DIR}/install.log") 2>&1 - INSTALL_METHOD='${ARG_INSTALL_METHOD}' VERSION='${ARG_VERSION}' PROJECT_REF='${ARG_PROJECT_REF}' From a1fd47dbf95e65c49de9c15623fcbf9586d85859 Mon Sep 17 00:00:00 2001 From: DevelopmentCats Date: Wed, 19 Aug 2026 15:36:57 -0500 Subject: [PATCH 12/20] style: fix README formatting --- registry/coder/modules/supabase/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/registry/coder/modules/supabase/README.md b/registry/coder/modules/supabase/README.md index e43f33967..34d757e14 100644 --- a/registry/coder/modules/supabase/README.md +++ b/registry/coder/modules/supabase/README.md @@ -48,7 +48,7 @@ module "supabase" { version = "1.0.0" agent_id = coder_agent.example.id use_external_auth = false - access_token = var.supabase_token # From Terraform variable or secret + access_token = var.supabase_token # From Terraform variable or secret } ``` @@ -73,7 +73,7 @@ module "supabase" { agent_id = coder_agent.example.id use_external_auth = false access_token = var.supabase_token - project_ref = "abcdefghijklmnop" # Links dashboard button directly to this project + project_ref = "abcdefghijklmnop" # Links dashboard button directly to this project } ``` @@ -84,7 +84,7 @@ module "supabase" { source = "registry.coder.com/coder/supabase/coder" version = "1.0.0" agent_id = coder_agent.example.id - install_method = "binary" # Force binary install instead of detect + install_method = "binary" # Force binary install instead of detect } ``` From c86733bb17b6ad81a7655d4dab5de5388169dfb0 Mon Sep 17 00:00:00 2001 From: DevelopmentCats Date: Wed, 19 Aug 2026 16:08:08 -0500 Subject: [PATCH 13/20] fix: README structure and test fixes for supabase module - Add tf code block in H1 section (required by readme validation) - Replace Alpine binary test with Debian (Supabase CLI is glibc-linked) - Fix runContainer API usage (returns string, not object) - Fix version regex and base64 script extraction --- registry/coder/modules/supabase/README.md | 8 + registry/coder/modules/supabase/main.test.ts | 284 ++++++++++--------- 2 files changed, 156 insertions(+), 136 deletions(-) diff --git a/registry/coder/modules/supabase/README.md b/registry/coder/modules/supabase/README.md index 34d757e14..dffa1b3ff 100644 --- a/registry/coder/modules/supabase/README.md +++ b/registry/coder/modules/supabase/README.md @@ -10,6 +10,14 @@ tags: [supabase, database, cli, helper] Installs the [Supabase CLI](https://supabase.com/docs/guides/cli) and configures authentication. The CLI is available immediately in your workspace without manual login flows. +```tf +module "supabase" { + source = "registry.coder.com/coder/supabase/coder" + version = "1.0.0" + agent_id = coder_agent.example.id +} +``` + ## Authentication Choose **one** of the following authentication methods: diff --git a/registry/coder/modules/supabase/main.test.ts b/registry/coder/modules/supabase/main.test.ts index 406221ea9..0f824129b 100644 --- a/registry/coder/modules/supabase/main.test.ts +++ b/registry/coder/modules/supabase/main.test.ts @@ -24,13 +24,13 @@ afterEach(async () => { try { await removeContainer(id); } catch { - // Ignore cleanup errors + // ignore cleanup errors } } cleanupContainers = []; }); -describe("supabase", async () => { +describe("supabase", () => { beforeAll(async () => { await runTerraformInit(import.meta.dir); }); @@ -39,14 +39,26 @@ describe("supabase", async () => { agent_id: "test-agent", }); + it("missing variable: agent_id", async () => { + await expect(runTerraformApply(import.meta.dir, {})).rejects.toThrow( + /agent_id/, + ); + }); + it("defaults to detect install method", async () => { const state = await runTerraformApply(import.meta.dir, { agent_id: "test-agent", }); - - // Verify the install script contains the expected ARG_INSTALL_METHOD - const installScript = state.outputs.scripts.value; - expect(installScript).toBeDefined(); + const script = state.resources.find( + (r) => r.type === "coder_script" && r.name === "install_script", + ); + expect(script).toBeDefined(); + // coder-utils wraps our script in base64, decode to check + const wrapperScript = script!.instances[0].attributes.script as string; + const b64Match = wrapperScript.match(/echo -n '([A-Za-z0-9+/=]+)'/); + expect(b64Match).toBeTruthy(); + const decodedScript = Buffer.from(b64Match![1], "base64").toString("utf-8"); + expect(decodedScript).toContain("INSTALL_METHOD='detect'"); }); it("accepts binary install method", async () => { @@ -54,8 +66,13 @@ describe("supabase", async () => { agent_id: "test-agent", install_method: "binary", }); - - expect(state.outputs.scripts.value).toBeDefined(); + const script = state.resources.find( + (r) => r.type === "coder_script" && r.name === "install_script", + ); + const wrapperScript = script!.instances[0].attributes.script as string; + const b64Match = wrapperScript.match(/echo -n '([A-Za-z0-9+/=]+)'/); + const decodedScript = Buffer.from(b64Match![1], "base64").toString("utf-8"); + expect(decodedScript).toContain("INSTALL_METHOD='binary'"); }); it("accepts brew install method", async () => { @@ -63,8 +80,13 @@ describe("supabase", async () => { agent_id: "test-agent", install_method: "brew", }); - - expect(state.outputs.scripts.value).toBeDefined(); + const script = state.resources.find( + (r) => r.type === "coder_script" && r.name === "install_script", + ); + const wrapperScript = script!.instances[0].attributes.script as string; + const b64Match = wrapperScript.match(/echo -n '([A-Za-z0-9+/=]+)'/); + const decodedScript = Buffer.from(b64Match![1], "base64").toString("utf-8"); + expect(decodedScript).toContain("INSTALL_METHOD='brew'"); }); it("accepts scoop install method", async () => { @@ -72,8 +94,13 @@ describe("supabase", async () => { agent_id: "test-agent", install_method: "scoop", }); - - expect(state.outputs.scripts.value).toBeDefined(); + const script = state.resources.find( + (r) => r.type === "coder_script" && r.name === "install_script", + ); + const wrapperScript = script!.instances[0].attributes.script as string; + const b64Match = wrapperScript.match(/echo -n '([A-Za-z0-9+/=]+)'/); + const decodedScript = Buffer.from(b64Match![1], "base64").toString("utf-8"); + expect(decodedScript).toContain("INSTALL_METHOD='scoop'"); }); it("rejects invalid install method", async () => { @@ -91,157 +118,142 @@ describe("supabase", async () => { use_external_auth: "false", access_token: "sbp_test_token_abc123", }); - expect(state.outputs.access_token.value).toBe("sbp_test_token_abc123"); }); it("installs via binary on ubuntu", async () => { - const { id } = await runContainer("ubuntu:22.04"); + const id = await runContainer("ubuntu:22.04"); cleanupContainers.push(id); - // Install curl (required for binary download) await execContainer(id, ["apt-get", "update"]); await execContainer(id, ["apt-get", "install", "-y", "curl", "tar"]); - // Run the install script with binary method - const installScript = ` - set -e - export HOME=/root - export CODER_SCRIPT_BIN_DIR=/tmp/coder-bin - mkdir -p $CODER_SCRIPT_BIN_DIR - - MODULE_DIR="$HOME/.coder-modules/coder/supabase" - LOG_DIR="$MODULE_DIR/logs" - BIN_DIR="$MODULE_DIR/bin" - mkdir -p "$LOG_DIR" "$BIN_DIR" - - INSTALL_METHOD="binary" - VERSION="latest" - - # Platform detection - ARCH=$(uname -m) - case "$ARCH" in - x86_64 | amd64) ARCH="amd64" ;; - aarch64 | arm64) ARCH="arm64" ;; - esac - OS=$(uname -s | tr '[:upper:]' '[:lower:]') - - # Resolve version - API_RESPONSE=$(curl -fsSL "https://api.github.com/repos/supabase/cli/releases/latest") - VERSION=$(echo "$API_RESPONSE" | grep '"tag_name":' | sed -E 's/.*"v?([^"]+)".*/\\1/') - - # Download and install - DOWNLOAD_URL="https://github.com/supabase/cli/releases/download/v$VERSION/supabase_\${OS}_\${ARCH}.tar.gz" - curl -fsSL -o /tmp/supabase.tar.gz "$DOWNLOAD_URL" - tar -xzf /tmp/supabase.tar.gz -C /tmp - mv /tmp/supabase "$BIN_DIR/supabase" - chmod +x "$BIN_DIR/supabase" - - # Verify - "$BIN_DIR/supabase" --version - `; - - const result = await execContainer(id, ["bash", "-c", installScript]); + // Build script as array to avoid template literal escaping issues + const script = [ + "#!/bin/bash", + "set -ex", + "export HOME=/root", + "BIN_DIR=$HOME/.coder-modules/coder/supabase/bin", + "mkdir -p $BIN_DIR", + "ARCH=$(uname -m)", + "case $ARCH in x86_64|amd64) ARCH=amd64 ;; aarch64|arm64) ARCH=arm64 ;; esac", + "OS=$(uname -s | tr A-Z a-z)", + "API_RESPONSE=$(curl -fsSL https://api.github.com/repos/supabase/cli/releases/latest)", + 'VERSION=$(echo "$API_RESPONSE" | grep -o \'"tag_name": *"[^"]*\' | head -1 | sed \'s/.*"v//\' | sed \'s/".*//\')', + "DOWNLOAD_URL=https://github.com/supabase/cli/releases/download/v$VERSION/supabase_${OS}_${ARCH}.tar.gz", + "curl -fsSL -o /tmp/supabase.tar.gz $DOWNLOAD_URL", + "tar -xzf /tmp/supabase.tar.gz -C /tmp", + "mv /tmp/supabase $BIN_DIR/supabase", + "chmod +x $BIN_DIR/supabase", + "$BIN_DIR/supabase --version", + ].join("\n"); + + await execContainer(id, [ + "sh", + "-c", + "cat > /tmp/install.sh << 'SCRIPT'\n" + script + "\nSCRIPT", + ]); + await execContainer(id, ["chmod", "+x", "/tmp/install.sh"]); + const result = await execContainer(id, ["/tmp/install.sh"]); + + if (result.exitCode !== 0) { + console.error("STDOUT:", result.stdout); + console.error("STDERR:", result.stderr); + } expect(result.exitCode).toBe(0); - expect(result.stdout).toContain("Supabase CLI"); + expect(result.stdout).toMatch(/\d+\.\d+\.\d+/); // version number like 2.115.0 }); - it("installs via binary on alpine", async () => { - const { id } = await runContainer("alpine:latest"); + // Note: Binary install on Alpine (musl libc) doesn't work because Supabase CLI + // is compiled for glibc. In production, the module would fall back to apk package. + // This test verifies binary install on a glibc-based distro (Debian). + it("installs via binary on debian", async () => { + const id = await runContainer("debian:bookworm-slim"); cleanupContainers.push(id); - // Install required tools + await execContainer(id, ["apt-get", "update"]); await execContainer(id, [ - "apk", - "add", - "--no-cache", + "apt-get", + "install", + "-y", "curl", - "tar", - "bash", + "ca-certificates", + ]); + + const script = [ + "#!/bin/bash", + "set -ex", + "export HOME=/root", + "BIN_DIR=$HOME/.coder-modules/coder/supabase/bin", + "mkdir -p $BIN_DIR", + "ARCH=$(uname -m)", + "case $ARCH in x86_64|amd64) ARCH=amd64 ;; aarch64|arm64) ARCH=arm64 ;; esac", + "OS=$(uname -s | tr A-Z a-z)", + "API_RESPONSE=$(curl -fsSL https://api.github.com/repos/supabase/cli/releases/latest)", + 'VERSION=$(echo "$API_RESPONSE" | grep -o \'"tag_name": *"[^"]*\' | head -1 | sed \'s/.*"v//\' | sed \'s/".*//\')', + "DOWNLOAD_URL=https://github.com/supabase/cli/releases/download/v$VERSION/supabase_${OS}_${ARCH}.tar.gz", + "curl -fsSL -o /tmp/supabase.tar.gz $DOWNLOAD_URL", + "tar -xzf /tmp/supabase.tar.gz -C /tmp", + "mv /tmp/supabase $BIN_DIR/supabase", + "chmod +x $BIN_DIR/supabase", + "$BIN_DIR/supabase --version", + ].join("\n"); + + await execContainer(id, [ + "sh", + "-c", + "cat > /tmp/install.sh << 'SCRIPT'\n" + script + "\nSCRIPT", ]); + await execContainer(id, ["chmod", "+x", "/tmp/install.sh"]); + const result = await execContainer(id, ["/tmp/install.sh"]); - // Run the install script with binary method - const installScript = ` - set -e - export HOME=/root - export CODER_SCRIPT_BIN_DIR=/tmp/coder-bin - mkdir -p $CODER_SCRIPT_BIN_DIR - - MODULE_DIR="$HOME/.coder-modules/coder/supabase" - BIN_DIR="$MODULE_DIR/bin" - mkdir -p "$BIN_DIR" - - # Platform detection - ARCH=$(uname -m) - case "$ARCH" in - x86_64 | amd64) ARCH="amd64" ;; - aarch64 | arm64) ARCH="arm64" ;; - esac - OS=$(uname -s | tr '[:upper:]' '[:lower:]') - - # Resolve version - API_RESPONSE=$(curl -fsSL "https://api.github.com/repos/supabase/cli/releases/latest") - VERSION=$(echo "$API_RESPONSE" | grep '"tag_name":' | sed -E 's/.*"v?([^"]+)".*/\\1/') - - # Download and install - DOWNLOAD_URL="https://github.com/supabase/cli/releases/download/v$VERSION/supabase_\${OS}_\${ARCH}.tar.gz" - curl -fsSL -o /tmp/supabase.tar.gz "$DOWNLOAD_URL" - tar -xzf /tmp/supabase.tar.gz -C /tmp - mv /tmp/supabase "$BIN_DIR/supabase" - chmod +x "$BIN_DIR/supabase" - - # Verify - "$BIN_DIR/supabase" --version - `; - - const result = await execContainer(id, ["bash", "-c", installScript]); + if (result.exitCode !== 0) { + console.error("Debian STDOUT:", result.stdout); + console.error("Debian STDERR:", result.stderr); + } expect(result.exitCode).toBe(0); - expect(result.stdout).toContain("Supabase CLI"); + expect(result.stdout).toMatch(/\d+\.\d+\.\d+/); // version number like 2.115.0 }); it("creates CODER_SCRIPT_BIN_DIR symlink", async () => { - const { id } = await runContainer("ubuntu:22.04"); + const id = await runContainer("ubuntu:22.04"); cleanupContainers.push(id); await execContainer(id, ["apt-get", "update"]); await execContainer(id, ["apt-get", "install", "-y", "curl", "tar"]); - const installScript = ` - set -e - export HOME=/root - export CODER_SCRIPT_BIN_DIR=/tmp/coder-bin - mkdir -p $CODER_SCRIPT_BIN_DIR - - MODULE_DIR="$HOME/.coder-modules/coder/supabase" - BIN_DIR="$MODULE_DIR/bin" - mkdir -p "$BIN_DIR" - - ARCH=$(uname -m) - case "$ARCH" in - x86_64 | amd64) ARCH="amd64" ;; - aarch64 | arm64) ARCH="arm64" ;; - esac - OS=$(uname -s | tr '[:upper:]' '[:lower:]') - - API_RESPONSE=$(curl -fsSL "https://api.github.com/repos/supabase/cli/releases/latest") - VERSION=$(echo "$API_RESPONSE" | grep '"tag_name":' | sed -E 's/.*"v?([^"]+)".*/\\1/') - - DOWNLOAD_URL="https://github.com/supabase/cli/releases/download/v$VERSION/supabase_\${OS}_\${ARCH}.tar.gz" - curl -fsSL -o /tmp/supabase.tar.gz "$DOWNLOAD_URL" - tar -xzf /tmp/supabase.tar.gz -C /tmp - mv /tmp/supabase "$BIN_DIR/supabase" - chmod +x "$BIN_DIR/supabase" - - # Create symlink in CODER_SCRIPT_BIN_DIR - ln -sf "$BIN_DIR/supabase" "$CODER_SCRIPT_BIN_DIR/supabase" - - # Verify symlink works - ls -la "$CODER_SCRIPT_BIN_DIR/supabase" - "$CODER_SCRIPT_BIN_DIR/supabase" --version - `; - - const result = await execContainer(id, ["bash", "-c", installScript]); + const script = [ + "#!/bin/bash", + "set -ex", + "export HOME=/root", + "export CODER_SCRIPT_BIN_DIR=/tmp/coder-bin", + "mkdir -p $CODER_SCRIPT_BIN_DIR", + "BIN_DIR=$HOME/.coder-modules/coder/supabase/bin", + "mkdir -p $BIN_DIR", + "ARCH=$(uname -m)", + "case $ARCH in x86_64|amd64) ARCH=amd64 ;; aarch64|arm64) ARCH=arm64 ;; esac", + "OS=$(uname -s | tr A-Z a-z)", + "API_RESPONSE=$(curl -fsSL https://api.github.com/repos/supabase/cli/releases/latest)", + 'VERSION=$(echo "$API_RESPONSE" | grep -o \'"tag_name": *"[^"]*\' | head -1 | sed \'s/.*"v//\' | sed \'s/".*//\')', + "DOWNLOAD_URL=https://github.com/supabase/cli/releases/download/v$VERSION/supabase_${OS}_${ARCH}.tar.gz", + "curl -fsSL -o /tmp/supabase.tar.gz $DOWNLOAD_URL", + "tar -xzf /tmp/supabase.tar.gz -C /tmp", + "mv /tmp/supabase $BIN_DIR/supabase", + "chmod +x $BIN_DIR/supabase", + "ln -sf $BIN_DIR/supabase $CODER_SCRIPT_BIN_DIR/supabase", + "ls -la $CODER_SCRIPT_BIN_DIR/supabase", + "$CODER_SCRIPT_BIN_DIR/supabase --version", + ].join("\n"); + + await execContainer(id, [ + "sh", + "-c", + "cat > /tmp/install.sh << 'SCRIPT'\n" + script + "\nSCRIPT", + ]); + await execContainer(id, ["chmod", "+x", "/tmp/install.sh"]); + const result = await execContainer(id, ["/tmp/install.sh"]); + expect(result.exitCode).toBe(0); - expect(result.stdout).toContain("Supabase CLI"); + expect(result.stdout).toMatch(/\d+\.\d+\.\d+/); // version number like 2.115.0 }); }); From b718009f4527b0360a16653de2641ec182e742a8 Mon Sep 17 00:00:00 2001 From: DevelopmentCats Date: Wed, 19 Aug 2026 16:22:23 -0500 Subject: [PATCH 14/20] feat(supabase): improve scorecard with air-gap and egress support - Add skip_install variable for pre-baked images - Add download_base_url variable for internal mirrors - Add Network Egress section documenting external endpoints - Improve Coder-context framing in README intro - Add air-gap and internal mirror usage examples - Add tests for new features (18 TF tests, 14 TS tests pass) --- registry/coder/modules/supabase/README.md | 41 ++++++++++++++- registry/coder/modules/supabase/main.test.ts | 30 +++++++++++ registry/coder/modules/supabase/main.tf | 23 +++++++-- .../coder/modules/supabase/main.tftest.hcl | 51 +++++++++++++++++++ .../modules/supabase/scripts/install.sh.tftpl | 9 ++-- 5 files changed, 144 insertions(+), 10 deletions(-) diff --git a/registry/coder/modules/supabase/README.md b/registry/coder/modules/supabase/README.md index dffa1b3ff..87eb8166a 100644 --- a/registry/coder/modules/supabase/README.md +++ b/registry/coder/modules/supabase/README.md @@ -8,7 +8,9 @@ tags: [supabase, database, cli, helper] # Supabase CLI -Installs the [Supabase CLI](https://supabase.com/docs/guides/cli) and configures authentication. The CLI is available immediately in your workspace without manual login flows. +This module adds the [Supabase CLI](https://supabase.com/docs/guides/cli) to your Coder workspace with pre-configured authentication. Instead of manually installing the CLI and running `supabase login` in each workspace session, the module handles installation and injects credentials via environment variables—so `supabase projects list` and other commands work immediately. + +It integrates with Coder's external auth for OAuth-based login, or accepts a personal access token for simpler setups. When a `project_ref` is provided, the module also links the workspace to your Supabase project and adds a dashboard shortcut to the Coder workspace UI. ```tf module "supabase" { @@ -96,6 +98,29 @@ module "supabase" { } ``` +### Pre-installed Binary (Air-gapped / Golden Image) + +```tf +module "supabase" { + source = "registry.coder.com/coder/supabase/coder" + version = "1.0.0" + agent_id = coder_agent.example.id + skip_install = true # CLI is already in the image + access_token = var.supabase_token +} +``` + +### With Internal Mirror + +```tf +module "supabase" { + source = "registry.coder.com/coder/supabase/coder" + version = "1.0.0" + agent_id = coder_agent.example.id + download_base_url = "https://artifacts.internal.corp/supabase-cli/releases/download" +} +``` + ## Installation Methods The module supports multiple installation methods to work across different workspace environments: @@ -151,6 +176,20 @@ supabase stop # Stop local stack supabase gen types typescript --project-id > types.ts ``` +## Network Egress + +During installation and operation, the module and CLI may connect to these external endpoints: + +| Endpoint | Purpose | When | +| -------------------- | ---------------------------------------- | --------------------------------- | +| `api.github.com` | Resolve latest CLI version | Install (when version = "latest") | +| `github.com` | Download CLI binary/package | Install | +| `api.supabase.com` | OAuth authentication, project operations | Runtime (CLI commands) | +| `supabase.com` | Dashboard links | Workspace app (external link) | +| Homebrew/Scoop repos | Package installation | Install (brew/scoop methods) | + +To use in restricted environments, set `download_base_url` to an internal mirror or use `skip_install = true` with a pre-baked image. + ## Logs Installation logs are stored at: diff --git a/registry/coder/modules/supabase/main.test.ts b/registry/coder/modules/supabase/main.test.ts index 0f824129b..d73f3ccac 100644 --- a/registry/coder/modules/supabase/main.test.ts +++ b/registry/coder/modules/supabase/main.test.ts @@ -112,6 +112,36 @@ describe("supabase", () => { ).rejects.toThrow(/install_method.*must be/); }); + it("supports skip_install option", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "test-agent", + skip_install: "true", + }); + const script = state.resources.find( + (r) => r.type === "coder_script" && r.name === "install_script", + ); + expect(script).toBeDefined(); + const wrapperScript = script!.instances[0].attributes.script as string; + const b64Match = wrapperScript.match(/echo -n '([A-Za-z0-9+/=]+)'/); + expect(b64Match).toBeTruthy(); + const decodedScript = Buffer.from(b64Match![1], "base64").toString("utf-8"); + expect(decodedScript).toContain("Skipping Supabase CLI installation"); + }); + + it("supports custom download_base_url", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "test-agent", + download_base_url: "https://mirror.internal/supabase", + }); + const script = state.resources.find( + (r) => r.type === "coder_script" && r.name === "install_script", + ); + const wrapperScript = script!.instances[0].attributes.script as string; + const b64Match = wrapperScript.match(/echo -n '([A-Za-z0-9+/=]+)'/); + const decodedScript = Buffer.from(b64Match![1], "base64").toString("utf-8"); + expect(decodedScript).toContain("https://mirror.internal/supabase"); + }); + it("sets access_token when use_external_auth is false", async () => { const state = await runTerraformApply(import.meta.dir, { agent_id: "test-agent", diff --git a/registry/coder/modules/supabase/main.tf b/registry/coder/modules/supabase/main.tf index 23eaf7296..6c790b8c3 100644 --- a/registry/coder/modules/supabase/main.tf +++ b/registry/coder/modules/supabase/main.tf @@ -59,6 +59,18 @@ variable "supabase_version" { default = "latest" } +variable "download_base_url" { + type = string + description = "Base URL for downloading Supabase CLI releases. Override to use an internal mirror in restricted environments. The URL should serve the same directory structure as GitHub releases." + default = "https://github.com/supabase/cli/releases/download" +} + +variable "skip_install" { + type = bool + description = "Skip CLI installation (use when supabase is already in the image). Auth environment variables are still configured." + default = false +} + variable "db_password" { type = string description = "Remote Postgres database password for non-interactive CLI commands like 'supabase link' (optional). Sets SUPABASE_DB_PASSWORD environment variable." @@ -110,11 +122,12 @@ locals { access_token = var.use_external_auth ? try(data.coder_external_auth.supabase[0].access_token, "") : var.access_token # Render the install script - install_script = templatefile("${path.module}/scripts/install.sh.tftpl", { - ARG_INSTALL_METHOD = var.install_method - ARG_VERSION = var.supabase_version - ARG_PROJECT_REF = var.project_ref - ARG_PROJECT_DIR = var.project_dir + install_script = var.skip_install ? "echo 'Skipping Supabase CLI installation (skip_install=true)'" : templatefile("${path.module}/scripts/install.sh.tftpl", { + ARG_INSTALL_METHOD = var.install_method + ARG_VERSION = var.supabase_version + ARG_PROJECT_REF = var.project_ref + ARG_PROJECT_DIR = var.project_dir + ARG_DOWNLOAD_BASE_URL = var.download_base_url }) } diff --git a/registry/coder/modules/supabase/main.tftest.hcl b/registry/coder/modules/supabase/main.tftest.hcl index 7090a220a..007232600 100644 --- a/registry/coder/modules/supabase/main.tftest.hcl +++ b/registry/coder/modules/supabase/main.tftest.hcl @@ -255,3 +255,54 @@ run "test_supabase_app_disabled" { } } +run "test_supabase_skip_install" { + command = plan + + variables { + agent_id = "test-agent-skip" + skip_install = true + } + + assert { + condition = var.skip_install == true + error_message = "skip_install should be true" + } +} + +run "test_supabase_custom_download_url" { + command = plan + + variables { + agent_id = "test-agent-mirror" + download_base_url = "https://internal-mirror.corp/supabase/releases" + } + + assert { + condition = var.download_base_url == "https://internal-mirror.corp/supabase/releases" + error_message = "download_base_url should be set to custom mirror" + } +} + +run "test_supabase_skip_install_with_token" { + command = apply + + variables { + agent_id = "test-agent-skip-token" + skip_install = true + use_external_auth = false + access_token = "sbp_test_token_for_skip" + } + + assert { + condition = var.skip_install == true + error_message = "skip_install should be true" + } + + # Env vars should still be set even when skipping install + assert { + condition = length(resource.coder_env.supabase_access_token) == 1 + error_message = "Access token env should be created even with skip_install" + } +} + + diff --git a/registry/coder/modules/supabase/scripts/install.sh.tftpl b/registry/coder/modules/supabase/scripts/install.sh.tftpl index 3b791d14c..5e8aa92ab 100644 --- a/registry/coder/modules/supabase/scripts/install.sh.tftpl +++ b/registry/coder/modules/supabase/scripts/install.sh.tftpl @@ -10,6 +10,7 @@ INSTALL_METHOD='${ARG_INSTALL_METHOD}' VERSION='${ARG_VERSION}' PROJECT_REF='${ARG_PROJECT_REF}' PROJECT_DIR='${ARG_PROJECT_DIR}' +DOWNLOAD_BASE_URL='${ARG_DOWNLOAD_BASE_URL}' echo "Installing Supabase CLI (method: $${INSTALL_METHOD}, version: $${VERSION})..." @@ -104,7 +105,7 @@ install_binary() { local version version=$(resolve_version) - local download_url="https://github.com/supabase/cli/releases/download/v$${version}/supabase_$${OS}_$${ARCH}.tar.gz" + local download_url="$${DOWNLOAD_BASE_URL}/v$${version}/supabase_$${OS}_$${ARCH}.tar.gz" local tmp_dir tmp_dir=$(mktemp -d) @@ -130,7 +131,7 @@ install_native_package() { trap "rm -rf $${tmp_dir}" EXIT if command -v dpkg > /dev/null 2>&1; then - local pkg_url="https://github.com/supabase/cli/releases/download/v$${version}/supabase_$${version}_$${OS}_$${ARCH}.deb" + local pkg_url="$${DOWNLOAD_BASE_URL}/v$${version}/supabase_$${version}_$${OS}_$${ARCH}.deb" echo "Installing via dpkg..." fetch_to_file "$${tmp_dir}/supabase.deb" "$${pkg_url}" if command -v sudo > /dev/null 2>&1; then @@ -147,7 +148,7 @@ install_native_package() { } fi elif command -v rpm > /dev/null 2>&1; then - local pkg_url="https://github.com/supabase/cli/releases/download/v$${version}/supabase_$${version}_$${OS}_$${ARCH}.rpm" + local pkg_url="$${DOWNLOAD_BASE_URL}/v$${version}/supabase_$${version}_$${OS}_$${ARCH}.rpm" echo "Installing via rpm..." fetch_to_file "$${tmp_dir}/supabase.rpm" "$${pkg_url}" if command -v sudo > /dev/null 2>&1; then @@ -164,7 +165,7 @@ install_native_package() { } fi elif command -v apk > /dev/null 2>&1; then - local pkg_url="https://github.com/supabase/cli/releases/download/v$${version}/supabase_$${version}_$${OS}_$${ARCH}.apk" + local pkg_url="$${DOWNLOAD_BASE_URL}/v$${version}/supabase_$${version}_$${OS}_$${ARCH}.apk" echo "Installing via apk..." fetch_to_file "$${tmp_dir}/supabase.apk" "$${pkg_url}" if command -v sudo > /dev/null 2>&1; then From 28d612488c34036fc5e233ec64c89670a5c976e8 Mon Sep 17 00:00:00 2001 From: DevelopmentCats Date: Wed, 19 Aug 2026 16:25:54 -0500 Subject: [PATCH 15/20] fix: pin supabase version in container tests to avoid GitHub API rate limits --- registry/coder/modules/supabase/main.test.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/registry/coder/modules/supabase/main.test.ts b/registry/coder/modules/supabase/main.test.ts index d73f3ccac..025566a0b 100644 --- a/registry/coder/modules/supabase/main.test.ts +++ b/registry/coder/modules/supabase/main.test.ts @@ -158,7 +158,7 @@ describe("supabase", () => { await execContainer(id, ["apt-get", "update"]); await execContainer(id, ["apt-get", "install", "-y", "curl", "tar"]); - // Build script as array to avoid template literal escaping issues + // Use pinned version to avoid GitHub API rate limits in CI (403 on /releases/latest) const script = [ "#!/bin/bash", "set -ex", @@ -168,8 +168,7 @@ describe("supabase", () => { "ARCH=$(uname -m)", "case $ARCH in x86_64|amd64) ARCH=amd64 ;; aarch64|arm64) ARCH=arm64 ;; esac", "OS=$(uname -s | tr A-Z a-z)", - "API_RESPONSE=$(curl -fsSL https://api.github.com/repos/supabase/cli/releases/latest)", - 'VERSION=$(echo "$API_RESPONSE" | grep -o \'"tag_name": *"[^"]*\' | head -1 | sed \'s/.*"v//\' | sed \'s/".*//\')', + "VERSION=2.22.12", "DOWNLOAD_URL=https://github.com/supabase/cli/releases/download/v$VERSION/supabase_${OS}_${ARCH}.tar.gz", "curl -fsSL -o /tmp/supabase.tar.gz $DOWNLOAD_URL", "tar -xzf /tmp/supabase.tar.gz -C /tmp", @@ -219,8 +218,7 @@ describe("supabase", () => { "ARCH=$(uname -m)", "case $ARCH in x86_64|amd64) ARCH=amd64 ;; aarch64|arm64) ARCH=arm64 ;; esac", "OS=$(uname -s | tr A-Z a-z)", - "API_RESPONSE=$(curl -fsSL https://api.github.com/repos/supabase/cli/releases/latest)", - 'VERSION=$(echo "$API_RESPONSE" | grep -o \'"tag_name": *"[^"]*\' | head -1 | sed \'s/.*"v//\' | sed \'s/".*//\')', + "VERSION=2.22.12", "DOWNLOAD_URL=https://github.com/supabase/cli/releases/download/v$VERSION/supabase_${OS}_${ARCH}.tar.gz", "curl -fsSL -o /tmp/supabase.tar.gz $DOWNLOAD_URL", "tar -xzf /tmp/supabase.tar.gz -C /tmp", @@ -263,8 +261,7 @@ describe("supabase", () => { "ARCH=$(uname -m)", "case $ARCH in x86_64|amd64) ARCH=amd64 ;; aarch64|arm64) ARCH=arm64 ;; esac", "OS=$(uname -s | tr A-Z a-z)", - "API_RESPONSE=$(curl -fsSL https://api.github.com/repos/supabase/cli/releases/latest)", - 'VERSION=$(echo "$API_RESPONSE" | grep -o \'"tag_name": *"[^"]*\' | head -1 | sed \'s/.*"v//\' | sed \'s/".*//\')', + "VERSION=2.22.12", "DOWNLOAD_URL=https://github.com/supabase/cli/releases/download/v$VERSION/supabase_${OS}_${ARCH}.tar.gz", "curl -fsSL -o /tmp/supabase.tar.gz $DOWNLOAD_URL", "tar -xzf /tmp/supabase.tar.gz -C /tmp", From ad8163356f04721ee81f58a1b86c52ef7971d6e3 Mon Sep 17 00:00:00 2001 From: DevelopmentCats Date: Wed, 19 Aug 2026 16:29:04 -0500 Subject: [PATCH 16/20] docs: add workflow diagram and clarify token handling in README - Add ASCII diagram showing where Coder fits in the Supabase development workflow - Replace ambiguous comment with explicit variable declaration and security note - Makes it clear tokens should never be hardcoded --- registry/coder/modules/supabase/README.md | 40 ++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/registry/coder/modules/supabase/README.md b/registry/coder/modules/supabase/README.md index 87eb8166a..5667d9323 100644 --- a/registry/coder/modules/supabase/README.md +++ b/registry/coder/modules/supabase/README.md @@ -12,6 +12,37 @@ This module adds the [Supabase CLI](https://supabase.com/docs/guides/cli) to you It integrates with Coder's external auth for OAuth-based login, or accepts a personal access token for simpler setups. When a `project_ref` is provided, the module also links the workspace to your Supabase project and adds a dashboard shortcut to the Coder workspace UI. +### Where Coder Fits + +```text +┌─────────────────────────────────────────────────────────────────────┐ +│ Developer Workflow │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ Local Machine Coder Workspace Supabase │ +│ ───────────── ─────────────── ──────── │ +│ │ +│ ┌───────────┐ ┌──────────────┐ ┌─────────────┐ │ +│ │ Browser │────SSH/────▶│ This Module │─────▶│ Projects │ │ +│ │ or IDE │ Web │ ┌─────────┐ │ API │ Database │ │ +│ └───────────┘ │ │Supabase │ │ │ Edge Funcs │ │ +│ │ │ CLI │ │ │ Storage │ │ +│ │ └─────────┘ │ └─────────────┘ │ +│ │ │ │ +│ │ Pre-authed │ ┌─────────────┐ │ +│ │ via OAuth │◀─────│ Dashboard │ │ +│ │ or Token │ Link └─────────────┘ │ +│ └──────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +The module bridges Coder workspaces and Supabase by: + +1. **Installing the CLI** — Detects the best method for your workspace OS +2. **Injecting credentials** — Sets `SUPABASE_ACCESS_TOKEN` so the CLI authenticates automatically +3. **Adding dashboard access** — Creates a workspace button linking to your Supabase project + ```tf module "supabase" { source = "registry.coder.com/coder/supabase/coder" @@ -53,15 +84,22 @@ Create your OAuth app in the [Supabase Dashboard](https://supabase.com/dashboard ### With Personal Access Token ```tf +variable "supabase_token" { + type = string + sensitive = true +} + module "supabase" { source = "registry.coder.com/coder/supabase/coder" version = "1.0.0" agent_id = coder_agent.example.id use_external_auth = false - access_token = var.supabase_token # From Terraform variable or secret + access_token = var.supabase_token } ``` +> **Note:** Never hardcode tokens in your template. Use a Terraform variable (as shown above) or inject via environment variable (`TF_VAR_supabase_token`). + ### With External Auth (OAuth) ```tf From 593ce11d3430dd7f358873e1b07e34d4879153be Mon Sep 17 00:00:00 2001 From: DevelopmentCats Date: Wed, 19 Aug 2026 16:31:40 -0500 Subject: [PATCH 17/20] docs: simplify intro - remove ASCII diagram, keep it concise --- registry/coder/modules/supabase/README.md | 31 +---------------------- 1 file changed, 1 insertion(+), 30 deletions(-) diff --git a/registry/coder/modules/supabase/README.md b/registry/coder/modules/supabase/README.md index 5667d9323..fb1863fea 100644 --- a/registry/coder/modules/supabase/README.md +++ b/registry/coder/modules/supabase/README.md @@ -12,36 +12,7 @@ This module adds the [Supabase CLI](https://supabase.com/docs/guides/cli) to you It integrates with Coder's external auth for OAuth-based login, or accepts a personal access token for simpler setups. When a `project_ref` is provided, the module also links the workspace to your Supabase project and adds a dashboard shortcut to the Coder workspace UI. -### Where Coder Fits - -```text -┌─────────────────────────────────────────────────────────────────────┐ -│ Developer Workflow │ -├─────────────────────────────────────────────────────────────────────┤ -│ │ -│ Local Machine Coder Workspace Supabase │ -│ ───────────── ─────────────── ──────── │ -│ │ -│ ┌───────────┐ ┌──────────────┐ ┌─────────────┐ │ -│ │ Browser │────SSH/────▶│ This Module │─────▶│ Projects │ │ -│ │ or IDE │ Web │ ┌─────────┐ │ API │ Database │ │ -│ └───────────┘ │ │Supabase │ │ │ Edge Funcs │ │ -│ │ │ CLI │ │ │ Storage │ │ -│ │ └─────────┘ │ └─────────────┘ │ -│ │ │ │ -│ │ Pre-authed │ ┌─────────────┐ │ -│ │ via OAuth │◀─────│ Dashboard │ │ -│ │ or Token │ Link └─────────────┘ │ -│ └──────────────┘ │ -│ │ -└─────────────────────────────────────────────────────────────────────┘ -``` - -The module bridges Coder workspaces and Supabase by: - -1. **Installing the CLI** — Detects the best method for your workspace OS -2. **Injecting credentials** — Sets `SUPABASE_ACCESS_TOKEN` so the CLI authenticates automatically -3. **Adding dashboard access** — Creates a workspace button linking to your Supabase project +**What this module does:** Installs the Supabase CLI in your workspace and wires up authentication through Coder's [external auth](https://coder.com/docs/admin/external-auth) (OAuth) or a personal access token. Once configured, users get a ready-to-use `supabase` command—no manual login required—plus a dashboard button in the workspace UI. ```tf module "supabase" { From 1cdabd76c7dae68b771d02ae9f4e8aadf2a03274 Mon Sep 17 00:00:00 2001 From: DevelopmentCats Date: Wed, 19 Aug 2026 16:44:49 -0500 Subject: [PATCH 18/20] supabase: use supabase login --token instead of env var Per review feedback from @bpmct: setting SUPABASE_ACCESS_TOKEN as an environment variable exposes the token to any process reading the env. Using 'supabase login --token' stores credentials in ~/.supabase/ instead of broadcasting them in the environment. Changes: - Remove coder_env resource for access token - Add supabase login --token call in install script - Pass skip_install flag into script (like claude-code) so auth still works when CLI is pre-installed - Remove Environment Variables section from README (internal detail) - Simplify TF_VAR note in README --- registry/coder/modules/supabase/README.md | 11 +-- registry/coder/modules/supabase/main.tf | 11 +-- .../coder/modules/supabase/main.tftest.hcl | 6 -- .../modules/supabase/scripts/install.sh.tftpl | 70 ++++++++++++------- 4 files changed, 47 insertions(+), 51 deletions(-) diff --git a/registry/coder/modules/supabase/README.md b/registry/coder/modules/supabase/README.md index fb1863fea..3e3de601e 100644 --- a/registry/coder/modules/supabase/README.md +++ b/registry/coder/modules/supabase/README.md @@ -69,7 +69,7 @@ module "supabase" { } ``` -> **Note:** Never hardcode tokens in your template. Use a Terraform variable (as shown above) or inject via environment variable (`TF_VAR_supabase_token`). +> **Note:** Never hardcode tokens in your template. Use a Terraform variable as shown above. ### With External Auth (OAuth) @@ -152,15 +152,6 @@ The module adds a **Supabase** button to your workspace that links to the Supaba Find your project reference in the Supabase dashboard URL: `https://supabase.com/dashboard/project/` -## Environment Variables - -The module sets the following environment variables in your workspace: - -| Variable | Description | -| ----------------------- | -------------------------------------------------- | -| `SUPABASE_ACCESS_TOKEN` | Personal access token for CLI authentication | -| `SUPABASE_DB_PASSWORD` | Remote Postgres password for `supabase link` in CI | - ## Common CLI Commands After workspace start, you can use the Supabase CLI: diff --git a/registry/coder/modules/supabase/main.tf b/registry/coder/modules/supabase/main.tf index 6c790b8c3..b43c0e7ad 100644 --- a/registry/coder/modules/supabase/main.tf +++ b/registry/coder/modules/supabase/main.tf @@ -122,12 +122,14 @@ locals { access_token = var.use_external_auth ? try(data.coder_external_auth.supabase[0].access_token, "") : var.access_token # Render the install script - install_script = var.skip_install ? "echo 'Skipping Supabase CLI installation (skip_install=true)'" : templatefile("${path.module}/scripts/install.sh.tftpl", { + install_script = templatefile("${path.module}/scripts/install.sh.tftpl", { + ARG_SKIP_INSTALL = tostring(var.skip_install) ARG_INSTALL_METHOD = var.install_method ARG_VERSION = var.supabase_version ARG_PROJECT_REF = var.project_ref ARG_PROJECT_DIR = var.project_dir ARG_DOWNLOAD_BASE_URL = var.download_base_url + ARG_ACCESS_TOKEN = base64encode(local.access_token) }) } @@ -144,13 +146,6 @@ module "coder_utils" { post_install_script = var.post_install_script } -resource "coder_env" "supabase_access_token" { - count = local.access_token != "" ? 1 : 0 - agent_id = var.agent_id - name = "SUPABASE_ACCESS_TOKEN" - value = local.access_token -} - resource "coder_env" "supabase_db_password" { count = var.db_password != "" ? 1 : 0 agent_id = var.agent_id diff --git a/registry/coder/modules/supabase/main.tftest.hcl b/registry/coder/modules/supabase/main.tftest.hcl index 007232600..7e84b59ae 100644 --- a/registry/coder/modules/supabase/main.tftest.hcl +++ b/registry/coder/modules/supabase/main.tftest.hcl @@ -297,12 +297,6 @@ run "test_supabase_skip_install_with_token" { condition = var.skip_install == true error_message = "skip_install should be true" } - - # Env vars should still be set even when skipping install - assert { - condition = length(resource.coder_env.supabase_access_token) == 1 - error_message = "Access token env should be created even with skip_install" - } } diff --git a/registry/coder/modules/supabase/scripts/install.sh.tftpl b/registry/coder/modules/supabase/scripts/install.sh.tftpl index 5e8aa92ab..f36efa965 100644 --- a/registry/coder/modules/supabase/scripts/install.sh.tftpl +++ b/registry/coder/modules/supabase/scripts/install.sh.tftpl @@ -6,13 +6,13 @@ LOG_DIR="$${MODULE_DIR}/logs" BIN_DIR="$${MODULE_DIR}/bin" mkdir -p "$${LOG_DIR}" "$${BIN_DIR}" +SKIP_INSTALL='${ARG_SKIP_INSTALL}' INSTALL_METHOD='${ARG_INSTALL_METHOD}' VERSION='${ARG_VERSION}' PROJECT_REF='${ARG_PROJECT_REF}' PROJECT_DIR='${ARG_PROJECT_DIR}' DOWNLOAD_BASE_URL='${ARG_DOWNLOAD_BASE_URL}' - -echo "Installing Supabase CLI (method: $${INSTALL_METHOD}, version: $${VERSION})..." +ACCESS_TOKEN=$(echo -n '${ARG_ACCESS_TOKEN}' | base64 -d) detect_platform() { ARCH=$(uname -m) @@ -206,29 +206,39 @@ install_scoop() { scoop install supabase || scoop update supabase || true } -if [ "$${INSTALL_METHOD}" = "detect" ]; then - if command -v brew > /dev/null 2>&1; then - INSTALL_METHOD="brew" - elif command -v scoop > /dev/null 2>&1; then - INSTALL_METHOD="scoop" - elif command -v dpkg > /dev/null 2>&1 || command -v rpm > /dev/null 2>&1 || command -v apk > /dev/null 2>&1; then - INSTALL_METHOD="native" - else - INSTALL_METHOD="binary" +install_supabase_cli() { + echo "Installing Supabase CLI (method: $${INSTALL_METHOD}, version: $${VERSION})..." + + if [ "$${INSTALL_METHOD}" = "detect" ]; then + if command -v brew > /dev/null 2>&1; then + INSTALL_METHOD="brew" + elif command -v scoop > /dev/null 2>&1; then + INSTALL_METHOD="scoop" + elif command -v dpkg > /dev/null 2>&1 || command -v rpm > /dev/null 2>&1 || command -v apk > /dev/null 2>&1; then + INSTALL_METHOD="native" + else + INSTALL_METHOD="binary" + fi + echo "Detected install method: $${INSTALL_METHOD}" fi - echo "Detected install method: $${INSTALL_METHOD}" -fi -case "$${INSTALL_METHOD}" in - brew) install_brew ;; - scoop) install_scoop ;; - native) install_native_package ;; - binary) install_binary ;; - *) - echo "Error: Unknown install method: $${INSTALL_METHOD}" >&2 - exit 1 - ;; -esac + case "$${INSTALL_METHOD}" in + brew) install_brew ;; + scoop) install_scoop ;; + native) install_native_package ;; + binary) install_binary ;; + *) + echo "Error: Unknown install method: $${INSTALL_METHOD}" >&2 + exit 1 + ;; + esac +} + +if [ "$${SKIP_INSTALL}" != "true" ]; then + install_supabase_cli +else + echo "Skipping Supabase CLI installation (skip_install=true)" +fi SUPABASE_BIN="" if command -v supabase > /dev/null 2>&1; then @@ -237,13 +247,19 @@ elif [ -x "$${BIN_DIR}/supabase" ]; then SUPABASE_BIN="$${BIN_DIR}/supabase" fi -if [ -n "$${SUPABASE_BIN}" ]; then - echo "✓ Supabase CLI installed: $($${SUPABASE_BIN} --version)" -else - echo "✗ Supabase CLI installation failed" >&2 +if [ -z "$${SUPABASE_BIN}" ]; then + echo "Error: Supabase CLI not found. Ensure it is installed or skip_install is false." >&2 exit 1 fi +echo "✓ Supabase CLI available: $($${SUPABASE_BIN} --version)" + +if [ -n "$${ACCESS_TOKEN}" ]; then + echo "Logging in with access token..." + $${SUPABASE_BIN} login --token "$${ACCESS_TOKEN}" + echo "✓ Logged in to Supabase" +fi + if [ -n "$${PROJECT_REF}" ]; then LINK_DIR="$${PROJECT_DIR:-$${HOME}}" mkdir -p "$${LINK_DIR}" From a82699e5ed34e57580a8b6667a5709c033af4a66 Mon Sep 17 00:00:00 2001 From: DevelopmentCats Date: Wed, 19 Aug 2026 16:45:17 -0500 Subject: [PATCH 19/20] supabase: remove redundant note --- registry/coder/modules/supabase/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/registry/coder/modules/supabase/README.md b/registry/coder/modules/supabase/README.md index 3e3de601e..c826113d1 100644 --- a/registry/coder/modules/supabase/README.md +++ b/registry/coder/modules/supabase/README.md @@ -69,8 +69,6 @@ module "supabase" { } ``` -> **Note:** Never hardcode tokens in your template. Use a Terraform variable as shown above. - ### With External Auth (OAuth) ```tf From a0c40000b74230de40620a31187b21e17b35a4c8 Mon Sep 17 00:00:00 2001 From: DevelopmentCats Date: Wed, 19 Aug 2026 16:46:00 -0500 Subject: [PATCH 20/20] supabase: use GFM alert for token note --- registry/coder/modules/supabase/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/registry/coder/modules/supabase/README.md b/registry/coder/modules/supabase/README.md index c826113d1..75b365b21 100644 --- a/registry/coder/modules/supabase/README.md +++ b/registry/coder/modules/supabase/README.md @@ -69,6 +69,9 @@ module "supabase" { } ``` +> [!NOTE] +> Never hardcode tokens in your template. + ### With External Auth (OAuth) ```tf