Skip to content
Closed
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
10 changes: 5 additions & 5 deletions src/gmux/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# gmux (ghcr.io/gmuxapp/features/gmux)

Installs [gmux](https://gmux.app) and gmuxd into a dev container. gmuxd starts automatically with a [network listener](https://gmux.app/develop/network-listener) so it's accessible from the host via port forwarding.
Installs [gmux](https://gmux.app) and gmuxd into a dev container. gmuxd starts automatically with `GMUXD_LISTEN=0.0.0.0` so it's accessible from the host via port forwarding.

## Usage

Expand All @@ -13,15 +13,15 @@ Installs [gmux](https://gmux.app) and gmuxd into a dev container. gmuxd starts a
}
```

Port 8791 is automatically forwarded to the host. Open the forwarded URL and authenticate with the bearer token.
Port 8790 is automatically forwarded to the host. After the container starts, a clickable auth link appears in the terminal.

### Finding the auth token

```bash
docker exec <container> gmuxd auth-link
gmuxd auth
```

Prints a ready-to-use URL with the token. Open it in a browser to authenticate.
Prints the listen address and a ready-to-use URL with the token. Open it in a browser to authenticate.

## Options

Expand All @@ -31,7 +31,7 @@ Prints a ready-to-use URL with the token. Open it in a browser to authenticate.

## Security

The network listener uses bearer token authentication. The token is auto-generated on first start and stored inside the container at `~/.local/state/gmux/auth-token`.
All TCP connections use bearer token authentication. The token is auto-generated on first start and stored inside the container at `~/.local/state/gmux/auth-token`.

Devcontainer-aware tooling (VS Code, Codespaces) forwards the port to `localhost` on the host, so only local processes can reach it. The bearer token provides a second layer of protection on the Docker bridge network.

Expand Down
7 changes: 4 additions & 3 deletions src/gmux/devcontainer-feature.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@
"description": "gmux version to install (e.g. '0.8.0' or 'latest')"
}
},
"forwardPorts": [8791],
"forwardPorts": [8790],
"portsAttributes": {
"8791": {
"8790": {
"label": "gmux",
"onAutoForward": "silent"
}
},
"entrypoint": "/usr/local/bin/gmuxd-start.sh"
"entrypoint": "/usr/local/bin/gmuxd-start.sh",
"postAttachCommand": "/usr/local/bin/gmuxd-auth-notice.sh"
}
64 changes: 44 additions & 20 deletions src/gmux/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,31 +36,55 @@ fi

echo "gmux ${VERSION} installed successfully"

# Generate config for the network listener.
# Tailscale is intentionally disabled; container gmuxd is accessed
# via port forwarding (browser) or Docker network (peer discovery).
mkdir -p /usr/local/share/gmux
cat > /usr/local/share/gmux/config.toml << EOF
[network]
listen = "0.0.0.0:8791"
EOF
# Entrypoint: starts gmuxd with GMUXD_LISTEN=0.0.0.0 so the container
# is reachable via port forwarding. No config file needed.
# _REMOTE_USER and _REMOTE_USER_HOME are available at build time (install.sh)
# but NOT at container start. Bake them into the entrypoint script now.
REMOTE_USER="${_REMOTE_USER:-root}"
REMOTE_HOME="${_REMOTE_USER_HOME:-/root}"

# Entrypoint: copies config if user hasn't provided their own, starts gmuxd.
cat > /usr/local/bin/gmuxd-start.sh << 'SCRIPT'
cat > /usr/local/bin/gmuxd-start.sh << SCRIPT
#!/bin/bash
GMUX_CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/gmux"
if [ ! -f "$GMUX_CONFIG_DIR/config.toml" ]; then
mkdir -p "$GMUX_CONFIG_DIR"
cp /usr/local/share/gmux/config.toml "$GMUX_CONFIG_DIR/config.toml"
fi
TARGET_USER="${REMOTE_USER}"
TARGET_HOME="${REMOTE_HOME}"
export GMUXD_LISTEN="0.0.0.0"

# Start gmuxd as the remote user so state (auth token, db) lives in their home.
start_gmuxd() {
if [ "\$(id -u)" = "0" ] && [ "\$TARGET_USER" != "root" ] && id "\$TARGET_USER" &>/dev/null; then
su - "\$TARGET_USER" -c "GMUXD_LISTEN=0.0.0.0 gmuxd start" >/tmp/gmuxd.log 2>&1 &
else
gmuxd start >/tmp/gmuxd.log 2>&1 &
fi
}

if ! curl -fsS http://localhost:8790/ >/dev/null 2>&1; then
gmuxd start >/tmp/gmuxd.log 2>&1 &
for i in $(seq 1 30); do
curl -fsS http://localhost:8790/ >/dev/null 2>&1 && break
SOCK="\${TARGET_HOME}/.local/state/gmux/gmuxd.sock"
if ! curl -fsS --unix-socket "\$SOCK" http://localhost/v1/health >/dev/null 2>&1; then
start_gmuxd
for i in \$(seq 1 30); do
curl -fsS --unix-socket "\$SOCK" http://localhost/v1/health >/dev/null 2>&1 && break
sleep 0.5
done
fi
exec "$@"
exec "\$@"
SCRIPT
chmod +x /usr/local/bin/gmuxd-start.sh

# Post-attach notice: prints a localhost auth URL that's clickable in the
# VS Code terminal. Runs as the remote user after VS Code attaches.
cat > /usr/local/bin/gmuxd-auth-notice.sh << 'NOTICE'
#!/bin/bash
TOKEN_FILE="${XDG_STATE_HOME:-$HOME/.local/state}/gmux/auth-token"
if [ -f "$TOKEN_FILE" ]; then
TOKEN=$(cat "$TOKEN_FILE")
URL="http://localhost:8790/auth/login?token=${TOKEN}"
VERSION=$(gmuxd version 2>/dev/null || echo "unknown")
CHANGELOG="https://gmux.app/changelog"
echo ""
printf ' \e[1;36m\e]8;;%s\007Open gmux\e]8;;\007\e[0m\n' "$URL"
echo ""
printf ' Version %s \xc2\xb7 \e]8;;%s\007Changelog\e]8;;\007\n' "$VERSION" "$CHANGELOG"
echo ""
fi
NOTICE
chmod +x /usr/local/bin/gmuxd-auth-notice.sh
15 changes: 12 additions & 3 deletions test/gmux/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,20 @@ command -v gmuxd || { echo "FAIL: gmuxd not found"; exit 1; }
# Entrypoint script exists
test -x /usr/local/bin/gmuxd-start.sh || { echo "FAIL: gmuxd-start.sh not found"; exit 1; }

# Default config was generated with network listener
grep -q 'listen = "0.0.0.0:8791"' /usr/local/share/gmux/config.toml \
|| { echo "FAIL: default config missing or wrong"; exit 1; }
# Entrypoint sets GMUXD_LISTEN for container use
grep -q 'GMUXD_LISTEN' /usr/local/bin/gmuxd-start.sh \
|| { echo "FAIL: entrypoint missing GMUXD_LISTEN"; exit 1; }

# gmuxd can report its version
gmuxd version || { echo "FAIL: gmuxd version failed"; exit 1; }

# Entrypoint bakes in the remote user, not root (unless root IS the remote user)
if grep -q 'TARGET_USER="root"' /usr/local/bin/gmuxd-start.sh; then
# Only acceptable if _REMOTE_USER was actually root
echo "WARN: TARGET_USER is root; verify this is intentional"
else
grep -q 'TARGET_USER=' /usr/local/bin/gmuxd-start.sh \
|| { echo "FAIL: entrypoint missing TARGET_USER"; exit 1; }
fi

echo "All tests passed!"
Loading