Add arbitrary mux server command argument parsing (#738)

## Summary
- add a new `additional_arguments` module variable to pass extra
arguments to `mux server`
- parse `additional_arguments` in `run.sh` with quoted-group support so
values like paths with spaces are preserved
- keep existing `add-project` behavior while allowing additional
arbitrary flags
- add Terraform and Bun tests covering `additional_arguments` behavior
- document the new option in the module README and bump example version
references to `1.2.0`

## Why
The module previously only supported the `add-project` flag. This change
lets users pass additional `mux server` arguments without waiting for
new module variables.

## Validation
- `shellcheck --severity=warning --format=gcc
registry/coder/modules/mux/run.sh`
- `terraform -chdir=registry/coder/modules/mux test -verbose`
- `bun test registry/coder/modules/mux/main.test.ts`

## Breaking changes
None.

---
Generated with Mux (exec agent) using GPT-5.
This commit is contained in:
Michael Suchacz
2026-02-25 19:15:33 +01:00
committed by GitHub
parent 480bf4b48c
commit 94e41d3780
5 changed files with 115 additions and 7 deletions
+22 -7
View File
@@ -14,7 +14,7 @@ Automatically install and run [Mux](https://github.com/coder/mux) in a Coder wor
module "mux" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/mux/coder"
version = "1.1.0"
version = "1.2.0"
agent_id = coder_agent.main.id
}
```
@@ -37,7 +37,7 @@ module "mux" {
module "mux" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/mux/coder"
version = "1.1.0"
version = "1.2.0"
agent_id = coder_agent.main.id
}
```
@@ -48,7 +48,7 @@ module "mux" {
module "mux" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/mux/coder"
version = "1.1.0"
version = "1.2.0"
agent_id = coder_agent.main.id
# Default is "latest"; set to a specific version to pin
install_version = "0.4.0"
@@ -63,19 +63,34 @@ Start Mux with `mux server --add-project /path/to/project`:
module "mux" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/mux/coder"
version = "1.1.0"
version = "1.2.0"
agent_id = coder_agent.main.id
add-project = "/path/to/project"
}
```
### Pass Arbitrary `mux server` Arguments
Use `additional_arguments` to append additional arguments to `mux server`.
The module parses quoted values, so grouped arguments remain intact.
```tf
module "mux" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/mux/coder"
version = "1.2.0"
agent_id = coder_agent.main.id
additional_arguments = "--open-mode pinned --add-project '/workspaces/my repo'"
}
```
### Custom Port
```tf
module "mux" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/mux/coder"
version = "1.1.0"
version = "1.2.0"
agent_id = coder_agent.main.id
port = 8080
}
@@ -89,7 +104,7 @@ Run an existing copy of Mux if found, otherwise install from npm:
module "mux" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/mux/coder"
version = "1.1.0"
version = "1.2.0"
agent_id = coder_agent.main.id
use_cached = true
}
@@ -103,7 +118,7 @@ Run without installing from the network (requires Mux to be pre-installed):
module "mux" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/coder/mux/coder"
version = "1.1.0"
version = "1.2.0"
agent_id = coder_agent.main.id
install = false
}
+56
View File
@@ -1,6 +1,11 @@
import { describe, expect, it } from "bun:test";
import {
executeScriptInContainer,
execContainer,
findResourceInstance,
readFileContainer,
removeContainer,
runContainer,
runTerraformApply,
runTerraformInit,
testRequiredVariables,
@@ -40,6 +45,57 @@ describe("mux", async () => {
}
}, 60000);
it("parses custom additional_arguments", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
install: false,
log_path: "/tmp/mux.log",
additional_arguments:
"--open-mode pinned --add-project '/workspaces/my repo'",
});
const instance = findResourceInstance(state, "coder_script");
const id = await runContainer("alpine/curl");
try {
const setup = await execContainer(id, [
"sh",
"-c",
`apk add --no-cache bash >/dev/null
mkdir -p /tmp/mux
cat <<'EOF' > /tmp/mux/mux
#!/usr/bin/env sh
i=1
for arg in "$@"; do
echo "arg$i=$arg"
i=$((i + 1))
done
EOF
chmod +x /tmp/mux/mux`,
]);
expect(setup.exitCode).toBe(0);
const output = await execContainer(id, ["sh", "-c", instance.script]);
if (output.exitCode !== 0) {
console.log("STDOUT:\n" + output.stdout);
console.log("STDERR:\n" + output.stderr);
}
expect(output.exitCode).toBe(0);
await execContainer(id, ["sh", "-c", "sleep 1"]);
const log = await readFileContainer(id, "/tmp/mux.log");
expect(log).toContain("arg1=server");
expect(log).toContain("arg2=--port");
expect(log).toContain("arg3=4000");
expect(log).toContain("arg4=--open-mode");
expect(log).toContain("arg5=pinned");
expect(log).toContain("arg6=--add-project");
expect(log).toContain("arg7=/workspaces/my repo");
} finally {
await removeContainer(id);
}
}, 60000);
it("runs with npm present", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
+7
View File
@@ -55,6 +55,12 @@ variable "add-project" {
default = null
}
variable "additional_arguments" {
type = string
description = "Additional command-line arguments to pass to `mux server` (for example: `--add-project /path --open-mode pinned`)."
default = ""
}
variable "install_version" {
type = string
description = "The version or dist-tag of Mux to install."
@@ -142,6 +148,7 @@ resource "coder_script" "mux" {
PORT : var.port,
LOG_PATH : var.log_path,
ADD_PROJECT : var.add-project == null ? "" : var.add-project,
ADDITIONAL_ARGUMENTS : var.additional_arguments,
INSTALL_PREFIX : var.install_prefix,
OFFLINE : !var.install,
USE_CACHED : var.use_cached,
+14
View File
@@ -79,6 +79,20 @@ run "auth_token_in_url" {
}
}
run "custom_additional_arguments" {
command = plan
variables {
agent_id = "foo"
additional_arguments = "--open-mode pinned --add-project '/workspaces/my repo'"
}
assert {
condition = strcontains(resource.coder_script.mux.script, "--open-mode pinned --add-project '/workspaces/my repo'")
error_message = "mux launch script must include the configured additional arguments"
}
}
run "custom_version" {
command = plan
+16
View File
@@ -20,6 +20,22 @@ function run_mux() {
if [ -n "${ADD_PROJECT}" ]; then
set -- "$@" --add-project "${ADD_PROJECT}"
fi
# Parse additional user-supplied server arguments while preserving quoted groups.
if [ -n "${ADDITIONAL_ARGUMENTS}" ]; then
local parsed_additional_arguments
if ! parsed_additional_arguments="$(printf "%s\n" "${ADDITIONAL_ARGUMENTS}" | xargs -n1 printf "%s\n" 2> /dev/null)"; then
echo "❌ Failed to parse additional_arguments. Ensure quotes are balanced."
exit 1
fi
while IFS= read -r parsed_arg; do
[ -n "$parsed_arg" ] || continue
set -- "$@" "$parsed_arg"
done << EOF
$${parsed_additional_arguments}
EOF
fi
echo "🚀 Starting mux server on port $port_value..."
echo "Check logs at ${LOG_PATH}!"
MUX_SERVER_AUTH_TOKEN="$auth_token_value" PORT="$port_value" "$MUX_BINARY" "$@" > "${LOG_PATH}" 2>&1 &