From 1f14f867ce234047514e44056bfd0f84a5c7d130 Mon Sep 17 00:00:00 2001 From: jang2162 Date: Mon, 2 Mar 2026 19:15:27 +0900 Subject: [PATCH] feat: add Zellij module --- .icons/zellij.svg | 1 + registry/jang2162/.images/avatar.png | Bin 0 -> 14713 bytes registry/jang2162/README.md | 11 + registry/jang2162/modules/zellij/README.md | 113 +++++++++ registry/jang2162/modules/zellij/main.test.ts | 63 +++++ registry/jang2162/modules/zellij/main.tf | 104 ++++++++ .../jang2162/modules/zellij/scripts/run.sh | 239 ++++++++++++++++++ .../jang2162/modules/zellij/zellij.tftest.hcl | 136 ++++++++++ 8 files changed, 667 insertions(+) create mode 100644 .icons/zellij.svg create mode 100644 registry/jang2162/.images/avatar.png create mode 100644 registry/jang2162/README.md create mode 100644 registry/jang2162/modules/zellij/README.md create mode 100644 registry/jang2162/modules/zellij/main.test.ts create mode 100644 registry/jang2162/modules/zellij/main.tf create mode 100644 registry/jang2162/modules/zellij/scripts/run.sh create mode 100644 registry/jang2162/modules/zellij/zellij.tftest.hcl diff --git a/.icons/zellij.svg b/.icons/zellij.svg new file mode 100644 index 000000000..ef77c1dd2 --- /dev/null +++ b/.icons/zellij.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/registry/jang2162/.images/avatar.png b/registry/jang2162/.images/avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..331be9af95243681151a6684437e75fd6b7fb89d GIT binary patch literal 14713 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 rza3)#?GXE)?-09sK}0}!d3mh-Fp>1G3hT9{5 [!IMPORTANT] +> +> - Custom `zellij_config` replaces the default configuration entirely +> - Requires `curl` and `tar` for installation +> - Uses `sudo` to install to `/usr/local/bin/` +> - Supported architectures: x86_64, aarch64 diff --git a/registry/jang2162/modules/zellij/main.test.ts b/registry/jang2162/modules/zellij/main.test.ts new file mode 100644 index 000000000..687f7ce92 --- /dev/null +++ b/registry/jang2162/modules/zellij/main.test.ts @@ -0,0 +1,63 @@ +import { describe, expect, it } from "bun:test"; +import { + runTerraformApply, + runTerraformInit, + testRequiredVariables, +} from "~test"; + +describe("zellij", async () => { + await runTerraformInit(import.meta.dir); + + testRequiredVariables(import.meta.dir, { + agent_id: "foo", + }); + + it("default mode should be terminal", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + }); + const terminalApp = state.resources.find( + (r) => r.type === "coder_app" && r.name === "zellij_terminal", + ); + const webApp = state.resources.find( + (r) => r.type === "coder_app" && r.name === "zellij_web", + ); + expect(terminalApp).toBeDefined(); + expect(terminalApp!.instances.length).toBe(1); + expect(terminalApp!.instances[0].attributes.command).toBe( + "zellij attach --create default", + ); + expect(webApp).toBeUndefined(); + }); + + it("web mode should create web app", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + mode: "web", + }); + const webApp = state.resources.find( + (r) => r.type === "coder_app" && r.name === "zellij_web", + ); + const terminalApp = state.resources.find( + (r) => r.type === "coder_app" && r.name === "zellij_terminal", + ); + expect(webApp).toBeDefined(); + expect(webApp!.instances.length).toBe(1); + expect(webApp!.instances[0].attributes.subdomain).toBe(true); + expect(webApp!.instances[0].attributes.url).toBe("http://localhost:8082"); + expect(terminalApp).toBeUndefined(); + }); + + it("web mode should use custom port", async () => { + const state = await runTerraformApply(import.meta.dir, { + agent_id: "foo", + mode: "web", + web_port: 9090, + }); + const webApp = state.resources.find( + (r) => r.type === "coder_app" && r.name === "zellij_web", + ); + expect(webApp).toBeDefined(); + expect(webApp!.instances[0].attributes.url).toBe("http://localhost:9090"); + }); +}); diff --git a/registry/jang2162/modules/zellij/main.tf b/registry/jang2162/modules/zellij/main.tf new file mode 100644 index 000000000..86712c910 --- /dev/null +++ b/registry/jang2162/modules/zellij/main.tf @@ -0,0 +1,104 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + coder = { + source = "coder/coder" + version = ">= 2.5" + } + } +} + +variable "agent_id" { + type = string + description = "The ID of a Coder agent." +} + +variable "zellij_version" { + type = string + description = "The version of zellij to install." + default = "0.43.1" +} + +variable "zellij_config" { + type = string + description = "Custom zellij configuration to apply." + default = "" +} + +variable "order" { + type = number + description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)." + default = null +} + +variable "group" { + type = string + description = "The name of a group that this app belongs to." + default = null +} + +variable "icon" { + type = string + description = "The icon to use for the app." + default = "/icon/zellij.svg" +} + +variable "mode" { + type = string + description = "How to run zellij: 'web' for web client with subdomain proxy, 'terminal' for Coder built-in terminal." + default = "terminal" + + validation { + condition = contains(["web", "terminal"], var.mode) + error_message = "mode must be 'web' or 'terminal'." + } +} + +variable "web_port" { + type = number + description = "The port for the zellij web server. Only used when mode is 'web'." + default = 8082 +} + + +resource "coder_script" "zellij" { + agent_id = var.agent_id + display_name = "Zellij" + icon = "/icon/zellij.svg" + script = templatefile("${path.module}/scripts/run.sh", { + ZELLIJ_VERSION = var.zellij_version + ZELLIJ_CONFIG = var.zellij_config + MODE = var.mode + WEB_PORT = var.web_port + }) + run_on_start = true + run_on_stop = false +} + +# Web mode: subdomain proxy to zellij web server +resource "coder_app" "zellij_web" { + count = var.mode == "web" ? 1 : 0 + + agent_id = var.agent_id + slug = "zellij" + display_name = "Zellij" + icon = var.icon + order = var.order + group = var.group + url = "http://localhost:${var.web_port}" + subdomain = true +} + +# Terminal mode: run zellij in Coder built-in terminal +resource "coder_app" "zellij_terminal" { + count = var.mode == "terminal" ? 1 : 0 + + agent_id = var.agent_id + slug = "zellij" + display_name = "Zellij" + icon = var.icon + order = var.order + group = var.group + command = "zellij attach --create default" +} diff --git a/registry/jang2162/modules/zellij/scripts/run.sh b/registry/jang2162/modules/zellij/scripts/run.sh new file mode 100644 index 000000000..14ebf69fc --- /dev/null +++ b/registry/jang2162/modules/zellij/scripts/run.sh @@ -0,0 +1,239 @@ +#!/usr/bin/env bash + +BOLD='\033[0;1m' + +# Convert templated variables to shell variables +ZELLIJ_VERSION="${ZELLIJ_VERSION}" +ZELLIJ_CONFIG="${ZELLIJ_CONFIG}" +MODE="${MODE}" +WEB_PORT="${WEB_PORT}" + +# Function to check if zellij is already installed +is_installed() { + command -v zellij > /dev/null 2>&1 +} + +# Function to get installed version +get_installed_version() { + if is_installed; then + zellij --version | grep -oP 'zellij \K[0-9]+\.[0-9]+\.[0-9]+' + else + echo "" + fi +} + +# Function to install zellij +install_zellij() { + printf "Checking for zellij installation\n" + + INSTALLED_VERSION=$(get_installed_version) + + if [ -n "$INSTALLED_VERSION" ]; then + if [ "$INSTALLED_VERSION" = "$ZELLIJ_VERSION" ]; then + printf "zellij version $ZELLIJ_VERSION is already installed \n\n" + return 0 + else + printf "zellij version $INSTALLED_VERSION is installed, but version $ZELLIJ_VERSION is required\n" + fi + fi + + printf "Installing zellij version $ZELLIJ_VERSION \n\n" + + # Detect architecture + ARCH=$(uname -m) + case "$ARCH" in + x86_64) + ARCH="x86_64" + ;; + aarch64 | arm64) + ARCH="aarch64" + ;; + *) + printf "ERROR: Unsupported architecture: $ARCH\n" + exit 1 + ;; + esac + + # Download and install zellij + DOWNLOAD_URL="https://github.com/zellij-org/zellij/releases/download/v$${ZELLIJ_VERSION}/zellij-$${ARCH}-unknown-linux-musl.tar.gz" + TEMP_DIR=$(mktemp -d) + + printf "Downloading zellij version $ZELLIJ_VERSION for $ARCH...\n" + printf "URL: $DOWNLOAD_URL\n" + + if ! curl -fsSL "$DOWNLOAD_URL" -o "$TEMP_DIR/zellij.tar.gz"; then + printf "ERROR: Failed to download zellij\n" + rm -rf "$TEMP_DIR" + exit 1 + fi + + printf "Extracting zellij...\n" + tar -xzf "$TEMP_DIR/zellij.tar.gz" -C "$TEMP_DIR" + + printf "Installing zellij to /usr/local/bin...\n" + sudo mv "$TEMP_DIR/zellij" /usr/local/bin/zellij + sudo chmod +x /usr/local/bin/zellij + + # Cleanup + rm -rf "$TEMP_DIR" + + # Verify installation + if is_installed; then + FINAL_VERSION=$(get_installed_version) + printf "✓ zellij version $FINAL_VERSION installed successfully\n" + else + printf "ERROR: zellij installation failed\n" + exit 1 + fi +} + +# Function to setup zellij configuration +setup_zellij_config() { + printf "Setting up zellij configuration \n" + + local config_dir="$HOME/.config/zellij" + local config_file="$config_dir/config.kdl" + + mkdir -p "$config_dir" + + if [ -n "$ZELLIJ_CONFIG" ]; then + printf "$ZELLIJ_CONFIG" > "$config_file" + printf "$${BOLD}Custom zellij configuration applied at $config_file \n\n" + else + cat > "$config_file" << 'CONFIGEOF' +// Zellij Configuration File + +keybinds { + normal { + // Session management + bind "Ctrl s" { NewPane; } + bind "Ctrl q" { Quit; } + } +} + +// UI configuration +ui { + pane_frames { + rounded_corners true + } +} + +// Session configuration +session_serialization true +pane_frames true +simplified_ui false + +// Scroll settings +scroll_buffer_size 10000 +copy_on_select true +copy_clipboard "system" + +// Theme +theme "default" +CONFIGEOF + + # Append web server config only in web mode + if [ "$MODE" = "web" ]; then + cat >> "$config_file" << EOF + +// Web server configuration +web_server_ip "127.0.0.1" +web_server_port $WEB_PORT +EOF + fi + printf "zellij configuration created at $config_file \n\n" + fi +} + +# Function to fix TERM for zellij web client (sets TERM=dumb) +# Must be prepended to .bashrc so it runs BEFORE prompt color detection +fix_term_for_web() { + local bashrc="$HOME/.bashrc" + local marker="# zellij-term-fix" + + if ! grep -q "$marker" "$bashrc" 2> /dev/null; then + printf "Prepending TERM fix for Zellij web client to $bashrc\n" + local fix + fix=$( + cat << 'TERMFIX' +# Fix TERM for Zellij web client (TERM=dumb breaks colors) # zellij-term-fix +if [ -n "$ZELLIJ" ] && [ "$TERM" = "dumb" ]; then + export TERM=xterm-256color +fi + +TERMFIX + ) + # Prepend to .bashrc so TERM is set before prompt color detection + if [ -f "$bashrc" ]; then + local tmp + tmp=$(mktemp) + printf '%s\n' "$fix" | cat - "$bashrc" > "$tmp" && mv "$tmp" "$bashrc" + else + printf '%s\n' "$fix" > "$bashrc" + fi + fi +} + +# Function to start zellij web server and create auth token +start_web_server() { + printf "Starting zellij web server on port $WEB_PORT...\n" + + # Stop any existing web server + zellij web --stop 2> /dev/null || true + + # Start web server in daemon mode + zellij web -d + + # Wait for web server to be ready + sleep 2 + + # Create auth token if not exists or invalid + local token_file="$HOME/.zellij-web-token" + local need_token=false + + if [ ! -f "$token_file" ]; then + need_token=true + elif ! grep -qP '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$' "$token_file"; then + printf "Invalid token file detected, regenerating...\n" + rm -f "$token_file" + need_token=true + fi + + if [ "$need_token" = true ]; then + printf "Creating authentication token...\n" + # Extract UUID token from output (format: "token_N: ") + TOKEN=$(zellij web --create-token 2>&1 | grep -oP '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}') + echo "$TOKEN" > "$token_file" + printf "$${BOLD}===========================================\n" + printf "$${BOLD} Zellij Web Token: $TOKEN\n" + printf "$${BOLD} Saved to: ~/.zellij-web-token\n" + printf "$${BOLD} Enter this token on first browser visit.\n" + printf "$${BOLD}===========================================\n\n" + else + printf "Auth token: $(cat "$token_file")\n\n" + fi +} + +# Main execution +main() { + printf "$${BOLD}🛠️ Setting up zellij! \n\n" + printf "" + + # Install zellij + install_zellij + + # Setup zellij configuration + setup_zellij_config + + # Web mode: fix TERM and start web server + if [ "$MODE" = "web" ]; then + fix_term_for_web + start_web_server + fi + + printf "$${BOLD}✅ zellij setup complete! \n\n" + printf "$${BOLD}Access zellij via the Coder dashboard.\n" +} + +# Run main function +main diff --git a/registry/jang2162/modules/zellij/zellij.tftest.hcl b/registry/jang2162/modules/zellij/zellij.tftest.hcl new file mode 100644 index 000000000..8acdda0c0 --- /dev/null +++ b/registry/jang2162/modules/zellij/zellij.tftest.hcl @@ -0,0 +1,136 @@ +run "required_variables" { + command = plan + + variables { + agent_id = "test-agent-id" + } +} + +run "default_terminal_mode" { + command = plan + + variables { + agent_id = "test-agent-id" + } + + assert { + condition = resource.coder_app.zellij_terminal[0].command == "zellij attach --create default" + error_message = "Terminal mode should use 'zellij attach --create default' command" + } + + assert { + condition = resource.coder_app.zellij_terminal[0].slug == "zellij" + error_message = "Terminal app slug should be 'zellij'" + } + + assert { + condition = resource.coder_app.zellij_terminal[0].display_name == "Zellij" + error_message = "Terminal app display name should be 'Zellij'" + } + + assert { + condition = length(resource.coder_app.zellij_web) == 0 + error_message = "Web app should not be created in terminal mode" + } +} + +run "web_mode" { + command = plan + + variables { + agent_id = "test-agent-id" + mode = "web" + } + + assert { + condition = resource.coder_app.zellij_web[0].url == "http://localhost:8082" + error_message = "Web app should use default port 8082" + } + + assert { + condition = resource.coder_app.zellij_web[0].subdomain == true + error_message = "Web app should use subdomain" + } + + assert { + condition = resource.coder_app.zellij_web[0].slug == "zellij" + error_message = "Web app slug should be 'zellij'" + } + + assert { + condition = length(resource.coder_app.zellij_terminal) == 0 + error_message = "Terminal app should not be created in web mode" + } +} + +run "web_mode_custom_port" { + command = plan + + variables { + agent_id = "test-agent-id" + mode = "web" + web_port = 9090 + } + + assert { + condition = resource.coder_app.zellij_web[0].url == "http://localhost:9090" + error_message = "Web app should use custom port 9090" + } +} + +run "custom_order_and_group" { + command = plan + + variables { + agent_id = "test-agent-id" + order = 3 + group = "Terminal" + } + + assert { + condition = resource.coder_app.zellij_terminal[0].order == 3 + error_message = "App order should be 3" + } + + assert { + condition = resource.coder_app.zellij_terminal[0].group == "Terminal" + error_message = "App group should be 'Terminal'" + } +} + +run "custom_icon" { + command = plan + + variables { + agent_id = "test-agent-id" + icon = "/icon/custom.svg" + } + + assert { + condition = resource.coder_app.zellij_terminal[0].icon == "/icon/custom.svg" + error_message = "App should use custom icon" + } +} + +run "coder_script_config" { + command = plan + + variables { + agent_id = "test-agent-id" + } + + assert { + condition = resource.coder_script.zellij.display_name == "Zellij" + error_message = "Script display name should be 'Zellij'" + } + + assert { + condition = resource.coder_script.zellij.run_on_start == true + error_message = "Script should run on start" + } + + assert { + condition = resource.coder_script.zellij.run_on_stop == false + error_message = "Script should not run on stop" + } +}