Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .icons/zellij.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added registry/jang2162/.images/avatar.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions registry/jang2162/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
display_name: "Byeong-Hyun"
bio: "ㅎㅇ means Hi"
avatar: "./.images/avatar.png"
github: "jang2162"
status: "community"
---

# Byeong-Hyun

ㅎㅇ means "Hi"
113 changes: 113 additions & 0 deletions registry/jang2162/modules/zellij/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
---
display_name: Zellij
description: Modern terminal workspace with session management
icon: ../../../../.icons/zellij.svg
verified: false
tags: [zellij, terminal, multiplexer]
---

# Zellij

Automatically install and configure [zellij](https://github.com/zellij-org/zellij), a modern terminal workspace with session management. Supports terminal and web modes, custom configuration, and session persistence.

```tf
module "zellij" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/jang2162/zellij/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
}
```

## Features

- Installs zellij if not already present (version configurable, default `0.43.1`)
- Configures zellij with sensible defaults
- Supports custom configuration (KDL format)
- Session serialization enabled by default
- **Two modes**: `terminal` (Coder built-in terminal) and `web` (browser-based via subdomain proxy)
- Cross-platform architecture support (x86_64, aarch64)

## Examples

### Basic Usage (Terminal Mode)

```tf
module "zellij" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/jang2162/zellij/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
}
```

### Web Mode

```tf
module "zellij" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/jang2162/zellij/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
mode = "web"
web_port = 8082
group = "Terminal"
order = 1
}
```

### Custom Configuration

```tf
module "zellij" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/jang2162/zellij/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
zellij_config = <<-EOT
keybinds {
normal {
bind "Ctrl t" { NewTab; }
}
}
theme "dracula"
EOT
}
```

## How It Works

### Installation & Setup (scripts/run.sh)

1. **Version Check**: Checks if zellij is already installed with the correct version
2. **Architecture Detection**: Detects system architecture (x86_64 or aarch64)
3. **Download**: Downloads the appropriate zellij binary from GitHub releases
4. **Installation**: Installs zellij to `/usr/local/bin/zellij`
5. **Configuration**: Creates default or custom configuration at `~/.config/zellij/config.kdl`
6. **Web Mode Only**:
- Prepends a `TERM` fix to `~/.bashrc` (sets `TERM=xterm-256color` inside zellij when `TERM=dumb`)
- Starts the zellij web server as a daemon and creates an authentication token

### Session Access

- **Terminal mode**: Opens zellij in the Coder built-in terminal via `zellij attach --create default`
- **Web mode**: Accesses zellij through a subdomain proxy in the browser (authentication token required on first visit)

## Default Configuration

The default configuration includes:

- Session serialization enabled for persistence
- 10,000 line scroll buffer
- Copy on select enabled (system clipboard)
- Rounded pane frames
- Key bindings: `Ctrl+s` (new pane), `Ctrl+q` (quit)
- Default theme
- Web mode: web server IP/port automatically appended

> [!IMPORTANT]
>
> - Custom `zellij_config` replaces the default configuration entirely
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The IMPORTANT note says "Custom zellij_config replaces the default configuration entirely" but does not warn that in web mode, the user must also include web_server_ip and web_server_port in their custom config, since those are only appended to the default config. Without these settings, the zellij web server won't listen on the configured port when using a custom config. This is a significant omission that should be documented here.

Suggested change
> - Custom `zellij_config` replaces the default configuration entirely
> - Custom `zellij_config` replaces the default configuration entirely
> - When using `mode = "web"`, your custom configuration **must** also define `web_server_ip` and `web_server_port`, since these are only appended to the default config. Omitting them will prevent the zellij web server from listening on the expected port.

Copilot uses AI. Check for mistakes.
> - Requires `curl` and `tar` for installation
> - Uses `sudo` to install to `/usr/local/bin/`
> - Supported architectures: x86_64, aarch64
63 changes: 63 additions & 0 deletions registry/jang2162/modules/zellij/main.test.ts
Original file line number Diff line number Diff line change
@@ -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");
});
});
104 changes: 104 additions & 0 deletions registry/jang2162/modules/zellij/main.tf
Original file line number Diff line number Diff line change
@@ -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
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The coder_app.zellij_web resource does not include a healthcheck block. Other web-mode modules in this registry (e.g., filebrowser, jupyterlab, code-server) include a healthcheck so Coder can display the correct app status. If the Zellij web server exposes any health or status endpoint, consider adding a healthcheck block to give users visibility into whether the web server is running.

Suggested change
subdomain = true
healthcheck {
url = "/"
interval = "10s"
timeout = "5s"
}
subdomain = true

Copilot uses AI. Check for mistakes.
}

# 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"
}
Comment on lines +80 to +104
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both coder_app resources hardcode slug and display_name, and the web app (zellij_web) also lacks a configurable share variable. Other modules in this registry (e.g., code-server, filebrowser, kasmvnc) expose slug, display_name, and share (with a default of "owner") as configurable variables. Without slug being configurable, users cannot run multiple instances of this module in the same workspace. Consider adding slug, display_name, and share variables to match the established convention.

Copilot uses AI. Check for mistakes.
Loading