diff --git a/registry/coder/modules/mux/README.md b/registry/coder/modules/mux/README.md index 2f4155e29..977cbbaad 100644 --- a/registry/coder/modules/mux/README.md +++ b/registry/coder/modules/mux/README.md @@ -8,13 +8,13 @@ tags: [ai, agents, development, multiplexer] # Mux -Automatically install and run [Mux](https://github.com/coder/mux) in a Coder workspace. By default, the module auto-detects an available package manager (`npm`, `pnpm`, or `bun`) to install `mux@next` (with a fallback to downloading the npm tarball if none is found). You can also force a specific package manager via `package_manager` and point to a custom registry with `registry_url`. The launcher keeps watching the mux process after startup, appends signal/exit-code diagnostics to the mux log when the server is killed outside the Node runtime, and can optionally wait a few seconds, remove the stale server lock, and restart Mux after any exit until an optional restart-attempt cap is reached. Mux is a desktop application for parallel agentic development that enables developers to run multiple AI agents simultaneously across isolated workspaces. +Automatically install and run [Mux](https://github.com/coder/xum) in a Coder workspace. By default, the module auto-detects an available package manager (`npm`, `pnpm`, or `bun`) to install `@coder/xum@next`, the npm package that ships the `mux` CLI (with a fallback to downloading the npm tarball if none is found). You can also force a specific package manager via `package_manager` and point to a custom registry with `registry_url`. The launcher keeps watching the mux process after startup, appends signal/exit-code diagnostics to the mux log when the server is killed outside the Node runtime, and can optionally wait a few seconds, remove the stale server lock, and restart Mux after any exit until an optional restart-attempt cap is reached. Mux is a desktop application for parallel agentic development that enables developers to run multiple AI agents simultaneously across isolated workspaces. ```tf module "mux" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/mux/coder" - version = "1.5.0" + version = "2.0.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.5.0" + version = "2.0.0" agent_id = coder_agent.main.id } ``` @@ -48,10 +48,12 @@ module "mux" { module "mux" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/mux/coder" - version = "1.5.0" + version = "2.0.0" agent_id = coder_agent.main.id - # Default is "latest"; set to a specific version to pin - install_version = "0.4.0" + # Default is "next"; set to a specific version to pin. + # Only versions published as @coder/xum are available: 0.28.3 or newer, + # or prereleases from 0.28.2-next.24. + install_version = "0.28.4" } ``` @@ -63,7 +65,7 @@ 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.5.0" + version = "2.0.0" agent_id = coder_agent.main.id add_project = "/path/to/project" } @@ -78,7 +80,7 @@ The module parses quoted values, so grouped arguments remain intact. module "mux" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/mux/coder" - version = "1.5.0" + version = "2.0.0" agent_id = coder_agent.main.id additional_arguments = "--open-mode pinned --add-project '/workspaces/my repo'" } @@ -92,7 +94,7 @@ Enable automatic restarts after Mux exits, including clean exits and intentional module "mux" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/mux/coder" - version = "1.5.0" + version = "2.0.0" agent_id = coder_agent.main.id restart_on_kill = true restart_delay_seconds = 3 @@ -106,7 +108,7 @@ module "mux" { module "mux" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/mux/coder" - version = "1.5.0" + version = "2.0.0" agent_id = coder_agent.main.id port = 8080 } @@ -120,7 +122,7 @@ Force a specific package manager instead of auto-detection: module "mux" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/mux/coder" - version = "1.5.0" + version = "2.0.0" agent_id = coder_agent.main.id package_manager = "pnpm" # or "npm", "bun" } @@ -128,13 +130,13 @@ module "mux" { ### Custom Registry -Use a private or mirrored npm registry: +Use a private or mirrored npm registry. The registry must serve the scoped `@coder/xum` package: ```tf module "mux" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/mux/coder" - version = "1.5.0" + version = "2.0.0" agent_id = coder_agent.main.id registry_url = "https://npm.pkg.github.com" } @@ -148,7 +150,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.5.0" + version = "2.0.0" agent_id = coder_agent.main.id use_cached = true } @@ -156,13 +158,13 @@ module "mux" { ### Skip Install -Run without installing from the network (requires Mux to be pre-installed): +Run without installing from the network (requires a `mux` binary at `/mux`, by default `~/.coder-modules/coder/mux/mux`): ```tf module "mux" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/mux/coder" - version = "1.5.0" + version = "2.0.0" agent_id = coder_agent.main.id install = false } @@ -177,8 +179,10 @@ module "mux" { - Mux is currently in preview and you may encounter bugs - Requires internet connectivity for agent operations (unless `install` is set to false) - Auto-detects `npm`, `pnpm`, or `bun` by default; set `package_manager` to force a specific one -- Requires a Node.js runtime; if `node` is not on the workspace `PATH`, the module bootstraps a pinned Node.js runtime into `~/.local/share/coder-mux` (override the version with the `MUX_NODE_VERSION` environment variable) -- Installs `mux@next` from the npm registry by default; set `registry_url` to use a private or mirrored registry +- Requires a Node.js runtime; if `node` is not on the workspace `PATH`, the module bootstraps a pinned Node.js runtime into `~/.coder-modules/coder/mux` (override the version with the `MUX_NODE_VERSION` environment variable) +- Installs `@coder/xum@next` from the npm registry by default (this package ships the `mux` binary); set `registry_url` to use a private or mirrored registry +- `install_version` must be a version or dist-tag published as `@coder/xum` (0.28.3 or newer, or a prerelease from 0.28.2-next.24); older releases were only published under the legacy `mux` package name +- Installs into `~/.coder-modules/coder/mux` and logs to `~/.coder-modules/coder/mux/logs/mux.log` by default, so the install survives restarts that clear `/tmp`; override with `install_prefix` and `log_path` - Falls back to a direct tarball download when no package manager is found - Appends best-effort signal and external-kill diagnostics to `log_path` if the mux process dies after startup - Set `restart_on_kill = true` to wait `restart_delay_seconds`, remove `~/.mux/server.lock`, and restart Mux after it exits diff --git a/registry/coder/modules/mux/main.test.ts b/registry/coder/modules/mux/main.test.ts index a8944deec..7d26cebb5 100644 --- a/registry/coder/modules/mux/main.test.ts +++ b/registry/coder/modules/mux/main.test.ts @@ -1,6 +1,5 @@ import { describe, expect, it } from "bun:test"; import { - executeScriptInContainer, execContainer, findResourceInstance, readFileContainer, @@ -8,9 +7,38 @@ import { runContainer, runTerraformApply, runTerraformInit, + type scriptOutput, + type TerraformState, testRequiredVariables, } from "~test"; +// Default install_prefix and log_path with HOME=/root inside the test containers. +const MODULE_ROOT = "/root/.coder-modules/coder/mux"; +const LOG_PATH = `${MODULE_ROOT}/logs/mux.log`; + +// Like executeScriptInContainer, but removes the container inside the test: +// deleting a container that holds a full @coder/xum install takes longer than +// the few seconds the global afterAll cleanup hook allows. +const executeInstallScriptInContainer = async ( + state: TerraformState, + image: string, + before: string, +): Promise => { + const instance = findResourceInstance(state, "coder_script"); + const id = await runContainer(image); + try { + await execContainer(id, ["sh", "-c", before]); + const resp = await execContainer(id, ["sh", "-c", instance.script]); + return { + exitCode: resp.exitCode, + stdout: resp.stdout.trim().split("\n"), + stderr: resp.stderr.trim().split("\n"), + }; + } finally { + await removeContainer(id); + } +}; + describe("mux", async () => { await runTerraformInit(import.meta.dir); @@ -23,10 +51,9 @@ describe("mux", async () => { agent_id: "foo", }); - const output = await executeScriptInContainer( + const output = await executeInstallScriptInContainer( state, "alpine/curl", - "sh", "apk add --no-cache bash tar gzip ca-certificates findutils nodejs && update-ca-certificates", ); if (output.exitCode !== 0) { @@ -36,9 +63,9 @@ describe("mux", async () => { expect(output.exitCode).toBe(0); const expectedLines = [ "📥 No package manager found; downloading tarball from registry...", - "🥳 mux has been installed in /tmp/mux", + `🥳 mux has been installed in ${MODULE_ROOT}`, "🚀 Starting mux server on port 4000...", - "Check logs at /tmp/mux.log!", + `Check logs at ${LOG_PATH}!`, ]; for (const line of expectedLines) { expect(output.stdout).toContain(line); @@ -49,7 +76,6 @@ describe("mux", 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'", }); @@ -62,8 +88,8 @@ describe("mux", async () => { "sh", "-c", `apk add --no-cache bash >/dev/null -mkdir -p /tmp/mux -cat <<'EOF' > /tmp/mux/mux +mkdir -p ${MODULE_ROOT} +cat <<'EOF' > ${MODULE_ROOT}/mux #!/usr/bin/env sh i=1 for arg in "$@"; do @@ -71,7 +97,7 @@ for arg in "$@"; do i=$((i + 1)) done EOF -chmod +x /tmp/mux/mux`, +chmod +x ${MODULE_ROOT}/mux`, ]); expect(setup.exitCode).toBe(0); @@ -83,7 +109,7 @@ chmod +x /tmp/mux/mux`, expect(output.exitCode).toBe(0); await execContainer(id, ["sh", "-c", "sleep 1"]); - const log = await readFileContainer(id, "/tmp/mux.log"); + const log = await readFileContainer(id, LOG_PATH); expect(log).toContain("arg1=server"); expect(log).toContain("arg2=--port"); expect(log).toContain("arg3=4000"); @@ -100,7 +126,6 @@ chmod +x /tmp/mux/mux`, const state = await runTerraformApply(import.meta.dir, { agent_id: "foo", install: false, - log_path: "/tmp/mux.log", }); const instance = findResourceInstance(state, "coder_script"); @@ -111,8 +136,8 @@ chmod +x /tmp/mux/mux`, "sh", "-c", `apk add --no-cache bash >/dev/null -mkdir -p /tmp/mux -cat <<'EOF' > /tmp/mux/mux +mkdir -p ${MODULE_ROOT} +cat <<'EOF' > ${MODULE_ROOT}/mux #!/usr/bin/env sh target_pid="$$" ( @@ -123,7 +148,7 @@ while true; do sleep 1 done EOF -chmod +x /tmp/mux/mux`, +chmod +x ${MODULE_ROOT}/mux`, ]); expect(setup.exitCode).toBe(0); @@ -135,7 +160,7 @@ chmod +x /tmp/mux/mux`, expect(output.exitCode).toBe(0); await execContainer(id, ["sh", "-c", "sleep 2"]); - const log = await readFileContainer(id, "/tmp/mux.log"); + const log = await readFileContainer(id, LOG_PATH); expect(log).toContain("shell exit code 137"); expect(log).toContain( "SIGKILL usually means the process was killed externally or by the OOM killer.", @@ -149,7 +174,6 @@ chmod +x /tmp/mux/mux`, const state = await runTerraformApply(import.meta.dir, { agent_id: "foo", install: false, - log_path: "/tmp/mux.log", restart_on_kill: true, restart_delay_seconds: 1, max_restart_attempts: 1, @@ -163,10 +187,10 @@ chmod +x /tmp/mux/mux`, "sh", "-c", `apk add --no-cache bash >/dev/null -mkdir -p /tmp/mux -cat <<'EOF' > /tmp/mux/mux +mkdir -p ${MODULE_ROOT} +cat <<'EOF' > ${MODULE_ROOT}/mux #!/usr/bin/env sh -run_count_file="/tmp/mux-run-count" +run_count_file="${MODULE_ROOT}/run-count" run_count=0 if [ -f "$run_count_file" ]; then run_count=$(cat "$run_count_file") @@ -186,7 +210,7 @@ else fi exit 0 EOF -chmod +x /tmp/mux/mux`, +chmod +x ${MODULE_ROOT}/mux`, ]); expect(setup.exitCode).toBe(0); @@ -198,8 +222,8 @@ chmod +x /tmp/mux/mux`, expect(output.exitCode).toBe(0); await execContainer(id, ["sh", "-c", "sleep 4"]); - const log = await readFileContainer(id, "/tmp/mux.log"); - const runCount = await readFileContainer(id, "/tmp/mux-run-count"); + const log = await readFileContainer(id, LOG_PATH); + const runCount = await readFileContainer(id, `${MODULE_ROOT}/run-count`); expect(log).toContain("run=1"); expect(log).toContain("mux server exited cleanly."); expect(log).toContain( @@ -223,7 +247,6 @@ chmod +x /tmp/mux/mux`, const state = await runTerraformApply(import.meta.dir, { agent_id: "foo", install: false, - log_path: "/tmp/mux.log", restart_on_kill: true, restart_delay_seconds: 1, max_restart_attempts: 1, @@ -237,10 +260,10 @@ chmod +x /tmp/mux/mux`, "sh", "-c", `apk add --no-cache bash >/dev/null -mkdir -p /tmp/mux -cat <<'EOF' > /tmp/mux/mux +mkdir -p ${MODULE_ROOT} +cat <<'EOF' > ${MODULE_ROOT}/mux #!/usr/bin/env sh -run_count_file="/tmp/mux-run-count" +run_count_file="${MODULE_ROOT}/run-count" run_count=0 if [ -f "$run_count_file" ]; then run_count=$(cat "$run_count_file") @@ -253,7 +276,7 @@ if [ "$run_count" -eq 1 ]; then fi exit 0 EOF -chmod +x /tmp/mux/mux`, +chmod +x ${MODULE_ROOT}/mux`, ]); expect(setup.exitCode).toBe(0); @@ -265,8 +288,8 @@ chmod +x /tmp/mux/mux`, expect(output.exitCode).toBe(0); await execContainer(id, ["sh", "-c", "sleep 4"]); - const log = await readFileContainer(id, "/tmp/mux.log"); - const runCount = await readFileContainer(id, "/tmp/mux-run-count"); + const log = await readFileContainer(id, LOG_PATH); + const runCount = await readFileContainer(id, `${MODULE_ROOT}/run-count`); expect(log).toContain("run=1"); expect(log).toContain("signal TERM (15); shell exit code 143."); expect(log).toContain( @@ -287,20 +310,19 @@ chmod +x /tmp/mux/mux`, agent_id: "foo", }); - const output = await executeScriptInContainer( + const output = await executeInstallScriptInContainer( state, "node:20-alpine", - "sh", "apk add bash", ); expect(output.exitCode).toBe(0); const expectedLines = [ - "📦 Installing mux via npm into /tmp/mux...", + `📦 Installing @coder/xum via npm into ${MODULE_ROOT}...`, "⏭️ Skipping lifecycle scripts with --ignore-scripts", - "🥳 mux has been installed in /tmp/mux", + `🥳 mux has been installed in ${MODULE_ROOT}`, "🚀 Starting mux server on port 4000...", - "Check logs at /tmp/mux.log!", + `Check logs at ${LOG_PATH}!`, ]; for (const line of expectedLines) { expect(output.stdout).toContain(line); diff --git a/registry/coder/modules/mux/main.tf b/registry/coder/modules/mux/main.tf index f80b8b3ff..787df6934 100644 --- a/registry/coder/modules/mux/main.tf +++ b/registry/coder/modules/mux/main.tf @@ -39,14 +39,14 @@ variable "slug" { variable "install_prefix" { type = string - description = "The prefix to install Mux to." - default = "/tmp/mux" + description = "The directory to install Mux into." + default = "$HOME/.coder-modules/coder/mux" } variable "log_path" { type = string description = "The path for Mux logs." - default = "/tmp/mux.log" + default = "$HOME/.coder-modules/coder/mux/logs/mux.log" } variable "restart_on_kill" { @@ -91,7 +91,7 @@ variable "additional_arguments" { variable "install_version" { type = string - description = "The version or dist-tag of Mux to install." + description = "The version or dist-tag of the @coder/xum npm package to install. Only versions published as @coder/xum are available: 0.28.3 or newer, or prereleases from 0.28.2-next.24." default = "next" } @@ -107,7 +107,7 @@ variable "package_manager" { variable "registry_url" { type = string - description = "The npm-compatible registry URL to install Mux from. Override this for private registries or mirrors." + description = "The npm-compatible registry URL to install @coder/xum from. Override this for private registries or mirrors." default = "https://registry.npmjs.org" } diff --git a/registry/coder/modules/mux/mux.tftest.hcl b/registry/coder/modules/mux/mux.tftest.hcl index af4cbfe23..4a0db7edc 100644 --- a/registry/coder/modules/mux/mux.tftest.hcl +++ b/registry/coder/modules/mux/mux.tftest.hcl @@ -221,7 +221,7 @@ run "custom_version" { variables { agent_id = "foo" - install_version = "0.3.0" + install_version = "0.28.4" } } @@ -245,6 +245,87 @@ run "use_cached_only_success" { } } +# Module-controlled paths default to the per-module root (AGENTS.md Module +# Data Layout) so the install and logs survive restarts that clear /tmp. +run "default_paths_under_module_root" { + command = plan + + variables { + agent_id = "foo" + } + + assert { + condition = strcontains(resource.coder_script.mux.script, "MUX_BINARY=\"$HOME/.coder-modules/coder/mux/mux\"") + error_message = "mux must install under $HOME/.coder-modules/coder/mux by default" + } + + assert { + condition = strcontains(resource.coder_script.mux.script, "LOG_PATH=\"$HOME/.coder-modules/coder/mux/logs/mux.log\"") + error_message = "mux must log to $HOME/.coder-modules/coder/mux/logs/mux.log by default" + } + + assert { + condition = strcontains(resource.coder_script.mux.script, "node_dir=\"$HOME/.coder-modules/coder/mux/node-v") + error_message = "the Node.js bootstrap must live under the module root" + } + + assert { + condition = !strcontains(resource.coder_script.mux.script, "/tmp/mux") + error_message = "mux script must not default any module path to /tmp" + } +} + +run "custom_install_prefix_and_log_path" { + command = plan + + variables { + agent_id = "foo" + install_prefix = "/opt/mux" + log_path = "/var/log/mux.log" + } + + assert { + condition = strcontains(resource.coder_script.mux.script, "MUX_BINARY=\"/opt/mux/mux\"") + error_message = "mux must honor a custom install_prefix" + } + + assert { + condition = strcontains(resource.coder_script.mux.script, "LOG_PATH=\"/var/log/mux.log\"") + error_message = "mux must honor a custom log_path" + } +} + +# The installed npm package must be @coder/xum (it ships the mux bin); +# the legacy mux package is only a compat shim with an exact-pinned +# @coder/xum dependency that is published separately. +run "installs_coder_xum_package" { + command = plan + + variables { + agent_id = "foo" + } + + assert { + condition = strcontains(resource.coder_script.mux.script, "PKG=\"@coder/xum\"") + error_message = "mux script must install the @coder/xum npm package" + } + + assert { + condition = strcontains(resource.coder_script.mux.script, "https://registry.npmjs.org/@coder%2Fxum/") + error_message = "tarball fallback must fetch @coder/xum metadata with the URL-encoded scoped name" + } + + assert { + condition = strcontains(resource.coder_script.mux.script, "https://registry.npmjs.org/@coder/xum/-/xum-") + error_message = "tarball fallback must construct the scoped @coder/xum tarball URL" + } + + assert { + condition = !strcontains(resource.coder_script.mux.script, "PKG=\"mux\"") + error_message = "mux script must not install the legacy mux compat package" + } +} + # Custom package_manager should appear in generated script run "custom_package_manager_npm" { command = plan @@ -332,7 +413,7 @@ run "registry_url_trailing_slash" { } assert { - condition = strcontains(resource.coder_script.mux.script, "https://npm.example.com/mux/") + condition = strcontains(resource.coder_script.mux.script, "https://npm.example.com/@coder%2Fxum/") error_message = "registry URL trailing slash must be stripped to avoid double slashes" } } diff --git a/registry/coder/modules/mux/run.sh b/registry/coder/modules/mux/run.sh index 04431e02f..839a07c21 100644 --- a/registry/coder/modules/mux/run.sh +++ b/registry/coder/modules/mux/run.sh @@ -196,7 +196,7 @@ EOF_LAUNCHER } # Ensure a Node.js runtime is available (mux is a Node application launched # via "#!/usr/bin/env node"). When the workspace image does not provide node, -# bootstrap a pinned runtime into $HOME so it persists across restarts. +# bootstrap a pinned runtime into the module root so it persists across restarts. ensure_node() { if command -v node > /dev/null 2>&1; then return 0 @@ -213,7 +213,7 @@ ensure_node() { ;; esac - node_dir="$HOME/.local/share/coder-mux/node-v$node_version-linux-$node_arch" + node_dir="$HOME/.coder-modules/coder/mux/node-v$node_version-linux-$node_arch" if [ ! -x "$node_dir/bin/node" ]; then echo "⚠️ node not found on PATH; bootstrapping Node.js v$node_version into $node_dir..." mkdir -p "$(dirname "$node_dir")" @@ -272,8 +272,11 @@ if [ ! -f "$MUX_BINARY" ] || [ "${USE_CACHED}" != true ]; then fi fi + # @coder/xum is the package that ships the mux CLI (bins: mux, xum). + PKG="@coder/xum" + if [ -n "$PM_CMD" ]; then - echo "📦 Installing mux via $PM_CMD into ${INSTALL_PREFIX}..." + echo "📦 Installing $PKG via $PM_CMD into ${INSTALL_PREFIX}..." NPM_WORKDIR="${INSTALL_PREFIX}/npm" mkdir -p "$NPM_WORKDIR" cd "$NPM_WORKDIR" || exit 1 @@ -281,7 +284,6 @@ if [ ! -f "$MUX_BINARY" ] || [ "${USE_CACHED}" != true ]; then echo '{}' > package.json fi echo "⏭️ Skipping lifecycle scripts with --ignore-scripts" - PKG="mux" if [ -z "${VERSION}" ] || [ "${VERSION}" = "latest" ]; then PKG_SPEC="$PKG@latest" else @@ -306,7 +308,7 @@ if [ ! -f "$MUX_BINARY" ] || [ "${USE_CACHED}" != true ]; then ;; esac if [ "$INSTALL_OK" != true ]; then - echo "❌ Failed to install mux via $PM_CMD" + echo "❌ Failed to install $PKG via $PM_CMD" exit 1 fi # Determine the installed binary path @@ -324,7 +326,8 @@ if [ ! -f "$MUX_BINARY" ] || [ "${USE_CACHED}" != true ]; then if [ -z "$VERSION_TO_USE" ]; then VERSION_TO_USE="next" fi - META_URL="${REGISTRY_URL}/mux/$VERSION_TO_USE" + # Scoped package names are URL-encoded in npm registry metadata paths. + META_URL="${REGISTRY_URL}/@coder%2Fxum/$VERSION_TO_USE" META_JSON="$(curl -fsSL "$META_URL" || true)" if [ -z "$META_JSON" ]; then echo "❌ Failed to fetch npm metadata: $META_URL" @@ -360,10 +363,11 @@ if [ ! -f "$MUX_BINARY" ] || [ "${USE_CACHED}" != true ]; then VERSION_TO_USE="$RESOLVED_VERSION" fi if [ -z "$VERSION_TO_USE" ]; then - echo "❌ Could not determine version for mux" + echo "❌ Could not determine version for $PKG" exit 1 fi - TARBALL_URL="${REGISTRY_URL}/mux/-/mux-$VERSION_TO_USE.tgz" + # Registry tarball layout for scoped packages: /@scope/name/-/name-.tgz + TARBALL_URL="${REGISTRY_URL}/@coder/xum/-/xum-$VERSION_TO_USE.tgz" fi TMP_DIR="$(mktemp -d)" TAR_PATH="$TMP_DIR/mux.tgz"