Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit f04d7d2

Browse files
authored
feat: tmux module (#229)
Closes #203 /claim #203 ## Description Introduce the `tmux` module ## Demo https://www.loom.com/share/ec8169d34c3043f7af2163b1a1a14a4b?sid=1ea8bcb2-3db0-43ca-965a-5ed42eec3448 ## Type of Change - [x] New module - [ ] Bug fix - [ ] Feature/enhancement - [ ] Documentation - [ ] Other ## Module Information <!-- Delete this section if not applicable --> **Path:** `registry/anomaly/modules/tmux` **New version:** `v1.0.0` **Breaking change:** [ ] Yes [x] No ## Testing & Validation - [x] Tests pass (`bun test`) - [x] Code formatted (`bun run fmt`) - [x] Changes tested locally ## Related Issues #203
1 parent 4ae6370 commit f04d7d2

File tree

9 files changed

+421
-0
lines changed

9 files changed

+421
-0
lines changed

‎.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,6 @@ dist
145145

146146
# Generated credentials from google-github-actions/auth
147147
gha-creds-*.json
148+
149+
# IDEs
150+
.idea

‎.icons/tmux.svg

Lines changed: 1 addition & 0 deletions
Loading[フレーム]

‎registry/anomaly/.images/avatar.jpeg

8.85 KB
Loading[フレーム]

‎registry/anomaly/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
display_name: "Jay Kumar"
3+
bio: "I'm a Software Engineer :)"
4+
avatar_url: "./.images/avatar.png"
5+
github: "35C4n0r"
6+
linkedin: "https://www.linkedin.com/in/jaykum4r"
7+
support_email: "work.jaykumar@gmail.com"
8+
status: "community"
9+
---
10+
11+
# Your Name
12+
13+
I'm a Software Engineer :)

‎registry/anomaly/modules/tmux/README.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
---
2+
display_name: "Tmux"
3+
description: "Tmux for coder agent :)"
4+
icon: "../../../../.icons/tmux.svg"
5+
verified: false
6+
tags: ["tmux", "terminal", "persistent"]
7+
---
8+
9+
# tmux
10+
11+
This module provisions and configures [tmux](https://github.com/tmux/tmux) with session persistence and plugin support
12+
for a Coder agent. It automatically installs tmux, the Tmux Plugin Manager (TPM), and a set of useful plugins, and sets
13+
up a default or custom tmux configuration with session save/restore capabilities.
14+
15+
```tf
16+
module "tmux" {
17+
source = "registry.coder.com/anomaly/tmux/coder"
18+
version = "1.0.0"
19+
agent_id = coder_agent.example.id
20+
}
21+
```
22+
23+
## Features
24+
25+
- Installs tmux if not already present
26+
- Installs TPM (Tmux Plugin Manager)
27+
- Configures tmux with plugins for sensible defaults, session persistence, and automation:
28+
- `tmux-plugins/tpm`
29+
- `tmux-plugins/tmux-sensible`
30+
- `tmux-plugins/tmux-resurrect`
31+
- `tmux-plugins/tmux-continuum`
32+
- Supports custom tmux configuration
33+
- Enables automatic session save
34+
- Configurable save interval
35+
- **Supports multiple named tmux sessions, each as a separate app in the Coder UI**
36+
37+
## Usage
38+
39+
```tf
40+
module "tmux" {
41+
source = "registry.coder.com/anomaly/tmux/coder"
42+
version = "1.0.0"
43+
agent_id = coder_agent.example.id
44+
tmux_config = "" # Optional: custom tmux.conf content
45+
save_interval = 1 # Optional: save interval in minutes
46+
sessions = ["default", "dev", "ops"] # Optional: list of tmux sessions
47+
order = 1 # Optional: UI order
48+
group = "Terminal" # Optional: UI group
49+
icon = "/icon/tmux.svg" # Optional: app icon
50+
}
51+
```
52+
53+
## Multi-Session Support
54+
55+
This module can provision multiple tmux sessions, each as a separate app in the Coder UI. Use the `sessions` variable to specify a list of session names. For each session, a `coder_app` is created, allowing you to launch or attach to that session directly from the UI.
56+
57+
- **sessions**: List of tmux session names (default: `["default"]`).
58+
59+
## How It Works
60+
61+
- **tmux Installation:**
62+
- Checks if tmux is installed; if not, installs it using the system's package manager (supports apt, yum, dnf,
63+
zypper, apk, brew).
64+
- **TPM Installation:**
65+
- Installs the Tmux Plugin Manager (TPM) to `~/.tmux/plugins/tpm` if not already present.
66+
- **tmux Configuration:**
67+
- If `tmux_config` is provided, writes it to `~/.tmux.conf`.
68+
- Otherwise, generates a default configuration with plugin support and session persistence (using tmux-resurrect and
69+
tmux-continuum).
70+
- Sets up key bindings for quick session save (`Ctrl+s`) and restore (`Ctrl+r`).
71+
- **Plugin Installation:**
72+
- Installs plugins via TPM.
73+
- **Session Persistence:**
74+
- Enables automatic session save/restore at the configured interval.
75+
76+
## Example
77+
78+
```tf
79+
module "tmux" {
80+
source = "registry.coder.com/anomaly/tmux/coder"
81+
version = "1.0.0"
82+
agent_id = var.agent_id
83+
sessions = ["default", "dev", "anomaly"]
84+
tmux_config = <<-EOT
85+
set -g mouse on
86+
set -g history-limit 10000
87+
EOT
88+
group = "Terminal"
89+
order = 2
90+
}
91+
```
92+
93+
> [!IMPORTANT]
94+
>
95+
> - If you provide a custom `tmux_config`, it will completely replace the default configuration. Ensure you include plugin
96+
> and TPM initialization lines if you want plugin support and session persistence.
97+
> - The script will attempt to install dependencies using `sudo` where required.
98+
> - If `git` is not installed, TPM installation will fail.
99+
> - If you are using custom config, you'll be responsible for setting up persistence and plugins.
100+
> - The `order`, `group`, and `icon` variables allow you to customize how tmux apps appear in the Coder UI.
101+
> - In case of session restart or shh reconnection, the tmux session will be automatically restored :)
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { describe, it, expect } from "bun:test";
2+
import {
3+
runTerraformApply,
4+
runTerraformInit,
5+
testRequiredVariables,
6+
findResourceInstance,
7+
} from "~test";
8+
import path from "path";
9+
10+
const moduleDir = path.resolve(__dirname);
11+
12+
const requiredVars = {
13+
agent_id: "dummy-agent-id",
14+
};
15+
16+
describe("tmux module", async () => {
17+
await runTerraformInit(moduleDir);
18+
19+
// 1. Required variables
20+
testRequiredVariables(moduleDir, requiredVars);
21+
22+
// 2. coder_script resource is created
23+
it("creates coder_script resource", async () => {
24+
const state = await runTerraformApply(moduleDir, requiredVars);
25+
const scriptResource = findResourceInstance(state, "coder_script");
26+
expect(scriptResource).toBeDefined();
27+
expect(scriptResource.agent_id).toBe(requiredVars.agent_id);
28+
29+
// check that the script contains expected lines
30+
expect(scriptResource.script).toContain("Installing tmux");
31+
expect(scriptResource.script).toContain("Installing Tmux Plugin Manager (TPM)");
32+
expect(scriptResource.script).toContain("tmux configuration created at");
33+
expect(scriptResource.script).toContain("✅ tmux setup complete!");
34+
});
35+
});

‎registry/anomaly/modules/tmux/main.tf

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
terraform {
2+
required_version = ">= 1.0"
3+
4+
required_providers {
5+
coder = {
6+
source = "coder/coder"
7+
version = ">= 2.5"
8+
}
9+
}
10+
}
11+
12+
variable "agent_id" {
13+
type = string
14+
description = "The ID of a Coder agent."
15+
}
16+
17+
variable "tmux_config" {
18+
type = string
19+
description = "Custom tmux configuration to apply."
20+
default = ""
21+
}
22+
23+
variable "save_interval" {
24+
type = number
25+
description = "Save interval (in minutes)."
26+
default = 1
27+
}
28+
29+
variable "order" {
30+
type = number
31+
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)."
32+
default = null
33+
}
34+
35+
variable "group" {
36+
type = string
37+
description = "The name of a group that this app belongs to."
38+
default = null
39+
}
40+
41+
variable "icon" {
42+
type = string
43+
description = "The icon to use for the app."
44+
default = "/icon/tmux.svg"
45+
}
46+
47+
variable "sessions" {
48+
type = list(string)
49+
description = "List of tmux sessions to create or start."
50+
default = ["default"]
51+
}
52+
53+
resource "coder_script" "tmux" {
54+
agent_id = var.agent_id
55+
display_name = "tmux"
56+
icon = "/icon/terminal.svg"
57+
script = templatefile("${path.module}/scripts/run.sh", {
58+
TMUX_CONFIG = var.tmux_config
59+
SAVE_INTERVAL = var.save_interval
60+
})
61+
run_on_start = true
62+
run_on_stop = false
63+
}
64+
65+
resource "coder_app" "tmux_sessions" {
66+
for_each = toset(var.sessions)
67+
68+
agent_id = var.agent_id
69+
slug = "tmux-${each.value}"
70+
display_name = "tmux - ${each.value}"
71+
icon = var.icon
72+
order = var.order
73+
group = var.group
74+
75+
command = templatefile("${path.module}/scripts/start.sh", {
76+
SESSION_NAME = each.value
77+
})
78+
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
#!/usr/bin/env bash
2+
3+
BOLD='033円[0;1m'
4+
5+
# Convert templated variables to shell variables
6+
SAVE_INTERVAL="${SAVE_INTERVAL}"
7+
TMUX_CONFIG="${TMUX_CONFIG}"
8+
9+
# Function to install tmux
10+
install_tmux() {
11+
printf "Checking for tmux installation\n"
12+
13+
if command -v tmux &> /dev/null; then
14+
printf "tmux is already installed \n\n"
15+
return 0
16+
fi
17+
18+
printf "Installing tmux \n\n"
19+
20+
# Detect package manager and install tmux
21+
if command -v apt-get &> /dev/null; then
22+
sudo apt-get update
23+
sudo apt-get install -y tmux
24+
elif command -v yum &> /dev/null; then
25+
sudo yum install -y tmux
26+
elif command -v dnf &> /dev/null; then
27+
sudo dnf install -y tmux
28+
elif command -v zypper &> /dev/null; then
29+
sudo zypper install -y tmux
30+
elif command -v apk &> /dev/null; then
31+
sudo apk add tmux
32+
elif command -v brew &> /dev/null; then
33+
brew install tmux
34+
else
35+
printf "No supported package manager found. Please install tmux manually. \n"
36+
exit 1
37+
fi
38+
39+
printf "tmux installed successfully \n"
40+
}
41+
42+
# Function to install Tmux Plugin Manager (TPM)
43+
install_tpm() {
44+
local tpm_dir="$HOME/.tmux/plugins/tpm"
45+
46+
if [ -d "$tpm_dir" ]; then
47+
printf "TPM is already installed"
48+
return 0
49+
fi
50+
51+
printf "Installing Tmux Plugin Manager (TPM) \n"
52+
53+
# Create plugins directory
54+
mkdir -p "$HOME/.tmux/plugins"
55+
56+
# Clone TPM repository
57+
if command -v git &> /dev/null; then
58+
git clone https://github.com/tmux-plugins/tpm "$tpm_dir"
59+
printf "TPM installed successfully"
60+
else
61+
printf "Git is not installed. Please install git to use tmux plugins. \n"
62+
exit 1
63+
fi
64+
}
65+
66+
# Function to create tmux configuration
67+
setup_tmux_config() {
68+
printf "Setting up tmux configuration \n"
69+
70+
local config_dir="$HOME/.tmux"
71+
local config_file="$HOME/.tmux.conf"
72+
73+
mkdir -p "$config_dir"
74+
75+
if [ -n "$TMUX_CONFIG" ]; then
76+
printf "$TMUX_CONFIG" > "$config_file"
77+
printf "$${BOLD}Custom tmux configuration applied at {$config_file} \n\n"
78+
else
79+
cat > "$config_file" << EOF
80+
# Tmux Configuration File
81+
82+
# =============================================================================
83+
# PLUGIN CONFIGURATION
84+
# =============================================================================
85+
86+
# List of plugins
87+
set -g @plugin 'tmux-plugins/tpm'
88+
set -g @plugin 'tmux-plugins/tmux-sensible'
89+
set -g @plugin 'tmux-plugins/tmux-resurrect'
90+
set -g @plugin 'tmux-plugins/tmux-continuum'
91+
92+
# tmux-continuum configuration
93+
set -g @continuum-restore 'on'
94+
set -g @continuum-save-interval '$${SAVE_INTERVAL}'
95+
set -g @continuum-boot 'on'
96+
set -g status-right 'Continuum status: #{continuum_status}'
97+
98+
# =============================================================================
99+
# KEY BINDINGS FOR SESSION MANAGEMENT
100+
# =============================================================================
101+
102+
# Quick session save and restore
103+
bind C-s run-shell "~/.tmux/plugins/tmux-resurrect/scripts/save.sh"
104+
bind C-r run-shell "~/.tmux/plugins/tmux-resurrect/scripts/restore.sh"
105+
106+
# Initialize TMUX plugin manager (keep this line at the very bottom of tmux.conf)
107+
run '~/.tmux/plugins/tpm/tpm'
108+
EOF
109+
printf "tmux configuration created at {$config_file} \n\n"
110+
fi
111+
}
112+
113+
# Function to install tmux plugins
114+
install_plugins() {
115+
printf "Installing tmux plugins"
116+
117+
# Check if TPM is installed
118+
if [ ! -d "$HOME/.tmux/plugins/tpm" ]; then
119+
printf "TPM is not installed. Cannot install plugins. \n"
120+
return 1
121+
fi
122+
123+
# Install plugins using TPM
124+
"$HOME/.tmux/plugins/tpm/bin/install_plugins"
125+
126+
printf "tmux plugins installed successfully \n"
127+
}
128+
129+
# Main execution
130+
main() {
131+
printf "$${BOLD} 🛠️Setting up tmux with session persistence! \n\n"
132+
printf ""
133+
134+
# Install dependencies
135+
install_tmux
136+
install_tpm
137+
138+
# Setup tmux configuration
139+
setup_tmux_config
140+
141+
# Install plugins
142+
install_plugins
143+
144+
printf "$${BOLD}✅ tmux setup complete! \n\n"
145+
146+
printf "$${BOLD} Attempting to restore sessions\n"
147+
tmux new-session -d \; source-file ~/.tmux.conf \; run-shell '~/.tmux/plugins/tmux-resurrect/scripts/restore.sh'
148+
printf "$${BOLD} Sessions restored: -> %s\n" "$(tmux ls)"
149+
150+
}
151+
152+
# Run main function
153+
main

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /