Skip to content

Commit b5a9295

Browse files
feat(service): add interactive PM2 service wizard (#57)
## Summary Add `termbeam service` subcommand with a fully interactive wizard for installing and managing TermBeam as a PM2 background service. ### ✨ New Features **PM2 Service Wizard** (`termbeam service install`) - Interactive setup with arrow-key navigation, progress bar, and live configuration preview - Configures: service name, password, port, access mode (DevTunnel/LAN/localhost), working directory, log level, and boot persistence - Auto-detects PM2 and offers to install it globally if missing - Runs `pm2 startup` automatically for boot persistence - Shows QR code + connection URL after install for immediate use **Service Lifecycle Commands** - `termbeam service status` — PM2 process details - `termbeam service logs` — tail service logs - `termbeam service restart` — restart service - `termbeam service uninstall` — remove from PM2 with cleanup **Login Page Redesign** - Matches app theme with CSS variables (dark/light mode) - Theme toggle with localStorage persistence - Blue accent button, proper TermBeam branding, mobile-optimized **Responsive Terminal Font** - Default font size adapts to screen width (12px mobile → 15px desktop) - User's saved preference in localStorage takes priority ### 📊 Test Coverage | Metric | Before | After | |--------|--------|-------| | Tests | 257 | 325 | | Coverage | ~93% | 95.24% | | service.js | 0% (new) | 98.76% | ### 📝 Documentation - Enhanced `docs/running-in-background.md` with full wizard walkthrough - Added Subcommands section to `docs/configuration.md` - Added "Running as a Service" to `docs/getting-started.md` - Updated README CLI reference ### Files Changed | File | Change | |------|--------| | `src/service.js` | **New** — Interactive PM2 service manager (~700 lines) | | `test/service.test.js` | **New** — 67 unit tests | | `test/service-interactive.test.js` | **New** — 24 interactive prompt tests | | `bin/termbeam.js` | Subcommand dispatch + PM2-compatible server start | | `src/server.js` | Simplified auto-start guard | | `src/auth.js` | Login page redesign | | `src/cli.js` | Service subcommand help text | | `public/terminal.html` | Responsive default font size | | `docs/*.md`, `README.md` | Documentation updates | Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 18e829a commit b5a9295

12 files changed

Lines changed: 2281 additions & 28 deletions

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,11 @@ termbeam [shell] [args...] # start with a specific shell (default: auto-d
106106
termbeam --port 8080 # custom port (default: 3456)
107107
termbeam --host 0.0.0.0 # allow LAN access (default: 127.0.0.1)
108108
termbeam --lan # shortcut for --host 0.0.0.0
109+
termbeam service install # interactive PM2 service setup wizard
110+
termbeam service uninstall # stop & remove PM2 service
111+
termbeam service status # show PM2 service status
112+
termbeam service logs # tail PM2 service logs
113+
termbeam service restart # restart PM2 service
109114
```
110115

111116
| Flag | Description | Default |
@@ -124,6 +129,14 @@ termbeam --lan # shortcut for --host 0.0.0.0
124129
| `-h, --help` | Show help ||
125130
| `-v, --version` | Show version ||
126131

132+
| Subcommand | Description |
133+
| ------------------- | ----------------------------- |
134+
| `service install` | Interactive PM2 service setup |
135+
| `service uninstall` | Stop & remove from PM2 |
136+
| `service status` | Show PM2 service status |
137+
| `service logs` | Tail PM2 service logs |
138+
| `service restart` | Restart PM2 service |
139+
127140
Environment variables: `PORT`, `TERMBEAM_PASSWORD`, `TERMBEAM_CWD`, `TERMBEAM_LOG_LEVEL`, `SHELL` (Unix fallback), `COMSPEC` (Windows fallback). See [Configuration docs](https://dorlugasigal.github.io/TermBeam/configuration/).
128141

129142
## Security

bin/termbeam.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,27 @@
11
#!/usr/bin/env node
2-
require('../src/server.js');
2+
3+
// Dispatch subcommands before loading the server
4+
const subcommand = (process.argv[2] || '').toLowerCase();
5+
if (subcommand === 'service') {
6+
const { run } = require('../src/service');
7+
run(process.argv.slice(3)).catch((err) => {
8+
console.error(err.message);
9+
process.exit(1);
10+
});
11+
} else {
12+
const { createTermBeamServer } = require('../src/server.js');
13+
const instance = createTermBeamServer();
14+
15+
process.on('SIGINT', () => {
16+
console.log('\n[termbeam] Shutting down...');
17+
instance.shutdown();
18+
setTimeout(() => process.exit(0), 500).unref();
19+
});
20+
process.on('SIGTERM', () => {
21+
console.log('\n[termbeam] Shutting down...');
22+
instance.shutdown();
23+
setTimeout(() => process.exit(0), 500).unref();
24+
});
25+
26+
instance.start();
27+
}

docs/configuration.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,26 @@ description: All TermBeam CLI flags and options — ports, passwords, tunnels, s
4646
!!! info "Legacy Variables"
4747
The environment variables `PTY_PASSWORD` and `PTY_CWD` are also supported as fallbacks for `TERMBEAM_PASSWORD` and `TERMBEAM_CWD` respectively.
4848

49+
## Subcommands
50+
51+
### `termbeam service`
52+
53+
TermBeam includes a `service` subcommand for managing a PM2-based background service. Run `termbeam service install` to launch an interactive wizard that configures and starts the service.
54+
55+
| Subcommand | Description |
56+
| ------------------- | --------------------------------------------------------------------- |
57+
| `service install` | Interactive wizard — configures password, port, access mode, and more |
58+
| `service uninstall` | Stops the PM2 process, removes it, and deletes the ecosystem config |
59+
| `service status` | Shows detailed PM2 process status (uptime, memory, restarts) |
60+
| `service logs` | Tails PM2 logs (last 200 lines + live stream) |
61+
| `service restart` | Restarts the PM2 process |
62+
63+
<!-- prettier-ignore -->
64+
!!! info "Ecosystem config"
65+
The wizard saves its configuration to `~/.termbeam/ecosystem.config.js`. You can edit this file manually and run `termbeam service restart` to apply changes.
66+
67+
For a full walkthrough of the wizard steps and each subcommand, see [Running in Background](running-in-background.md#interactive-setup-easiest).
68+
4969
## Examples
5070

5171
### Basic Usage

docs/getting-started.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,13 @@ The bottom touch bar provides quick access to:
118118
| ^C | Ctrl+C (interrupt process) |
119119

120120
Font size can be adjusted with **** / **+** buttons in the top toolbar.
121+
122+
## Running as a Service
123+
124+
Want TermBeam always available in the background? The built-in service installer configures [PM2](https://pm2.keymetrics.io/) for you with an interactive wizard:
125+
126+
```bash
127+
termbeam service install
128+
```
129+
130+
After installation, manage the service with `termbeam service status`, `logs`, `restart`, or `uninstall`. For the full setup guide and alternative methods (systemd, launchd, Windows), see [Running in Background](running-in-background.md).

docs/running-in-background.md

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,75 @@ kill $(cat ~/.termbeam.pid)
2727

2828
[PM2](https://pm2.keymetrics.io/) is the most popular Node.js process manager. It handles restarts, logging, and monitoring out of the box.
2929

30-
### Setup
30+
### Interactive Setup (Easiest)
31+
32+
TermBeam includes a built-in interactive installer that configures PM2 for you:
33+
34+
```bash
35+
termbeam service install
36+
```
37+
38+
The wizard checks if PM2 is installed (and offers to install it globally if not), then walks you through 7 configuration steps:
39+
40+
| Step | Question | Options / Default |
41+
| ------------------------ | -------------------------- | ----------------------------------------------------------------- |
42+
| 1. **Service name** | Name for the PM2 process | Default: `termbeam` |
43+
| 2. **Password** | How to protect the service | Auto-generate (recommended), enter custom, or no password |
44+
| 3. **Port** | Server port | Default: `3456` |
45+
| 4. **Access mode** | How to reach the service | DevTunnel (from anywhere), LAN (local network), or Localhost only |
46+
| 5. **Working directory** | Default terminal directory | Default: current directory |
47+
| 6. **Log level** | Logging verbosity | `info` (default), `debug`, `warn`, or `error` |
48+
| 7. **Boot auto-start** | Start on system boot? | Default: Yes — runs `pm2 startup` |
49+
50+
If you choose **DevTunnel** access, a follow-up question asks whether the tunnel should be **private** (Microsoft login required) or **public** (anyone with the link). Choosing public with no password will auto-generate one for safety.
51+
52+
After confirming, the wizard generates an ecosystem config file, starts the PM2 process, and saves the process list.
53+
54+
<!-- prettier-ignore -->
55+
!!! tip "Ecosystem config location"
56+
The wizard saves the PM2 ecosystem file to `~/.termbeam/ecosystem.config.js`. This file contains all the CLI flags and environment variables for your service. You can edit it manually and run `termbeam service restart` to apply changes.
57+
58+
### Service Subcommands
59+
60+
After installation, manage the service with these subcommands:
61+
62+
#### `termbeam service status`
63+
64+
Shows detailed PM2 process information (equivalent to `pm2 describe <name>`), including uptime, restarts, memory usage, and log file paths.
65+
66+
```bash
67+
termbeam service status
68+
```
69+
70+
#### `termbeam service logs`
71+
72+
Tails the PM2 log output, showing the last 200 lines and streaming new output in real time. Press `Ctrl+C` to stop.
73+
74+
```bash
75+
termbeam service logs
76+
```
77+
78+
#### `termbeam service restart`
79+
80+
Restarts the PM2 process. Useful after editing the ecosystem config file or updating TermBeam.
81+
82+
```bash
83+
termbeam service restart
84+
```
85+
86+
#### `termbeam service uninstall`
87+
88+
Stops the PM2 process, removes it from PM2, and deletes the ecosystem config file. Prompts for confirmation before proceeding.
89+
90+
```bash
91+
termbeam service uninstall
92+
```
93+
94+
<!-- prettier-ignore -->
95+
!!! warning
96+
`uninstall` removes the service from PM2 and deletes the ecosystem config at `~/.termbeam/ecosystem.config.js`. If you've customized the config, back it up first.
97+
98+
### Manual Setup
3199

32100
```bash
33101
# Install PM2 globally

public/terminal.html

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2050,7 +2050,17 @@ <h3>
20502050
// ===== Zoom =====
20512051
const MIN_FONT = 2,
20522052
MAX_FONT = 28;
2053-
let fontSize = parseInt(localStorage.getItem('termbeam-fontsize') || '8', 10);
2053+
function defaultFontSize() {
2054+
const w = window.innerWidth;
2055+
if (w <= 480) return 12;
2056+
if (w <= 768) return 13;
2057+
if (w <= 1280) return 14;
2058+
return 15;
2059+
}
2060+
let fontSize = parseInt(
2061+
localStorage.getItem('termbeam-fontsize') || String(defaultFontSize()),
2062+
10,
2063+
);
20542064

20552065
function applyZoom(size) {
20562066
fontSize = Math.max(MIN_FONT, Math.min(MAX_FONT, size));

src/auth.js

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,41 +5,70 @@ const LOGIN_HTML = `<!DOCTYPE html>
55
<html lang="en">
66
<head>
77
<meta charset="UTF-8" />
8-
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
9-
<meta name="theme-color" content="#1a1a2e" />
8+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
9+
<meta name="apple-mobile-web-app-capable" content="yes" />
10+
<meta name="mobile-web-app-capable" content="yes" />
11+
<meta name="theme-color" content="#1e1e1e" />
1012
<title>TermBeam — Login</title>
1113
<style>
12-
* { margin: 0; padding: 0; box-sizing: border-box; }
13-
html, body { height: 100%; background: #1a1a2e; color: #e0e0e0;
14-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
15-
display: flex; align-items: center; justify-content: center; }
16-
.card { background: #16213e; border: 1px solid #0f3460; border-radius: 16px;
17-
padding: 32px 24px; width: 320px; text-align: center; }
18-
h1 { font-size: 20px; margin-bottom: 8px; }
19-
h1 span { color: #533483; }
20-
p { font-size: 13px; color: #888; margin-bottom: 24px; }
21-
input { width: 100%; padding: 12px; background: #1a1a2e; border: 1px solid #0f3460;
22-
border-radius: 8px; color: #e0e0e0; font-size: 16px; outline: none;
23-
text-align: center; letter-spacing: 2px; }
24-
input:focus { border-color: #533483; }
25-
button { width: 100%; padding: 12px; margin-top: 16px; background: #533483;
26-
color: white; border: none; border-radius: 8px; font-size: 16px;
27-
font-weight: 600; cursor: pointer; }
28-
button:active { background: #6a42a8; }
29-
.error { color: #e74c3c; font-size: 13px; margin-top: 12px; display: none; }
14+
:root { --bg:#1e1e1e; --surface:#252526; --border:#3c3c3c; --border-subtle:#474747;
15+
--text:#d4d4d4; --text-secondary:#858585; --text-dim:#6e6e6e;
16+
--accent:#0078d4; --accent-hover:#1a8ae8; --accent-active:#005a9e;
17+
--danger:#f14c4c; --shadow:rgba(0,0,0,0.15); }
18+
[data-theme='light'] { --bg:#ffffff; --surface:#f3f3f3; --border:#e0e0e0;
19+
--border-subtle:#d0d0d0; --text:#1e1e1e; --text-secondary:#616161;
20+
--text-dim:#767676; --accent:#0078d4; --accent-hover:#106ebe;
21+
--accent-active:#005a9e; --danger:#e51400; --shadow:rgba(0,0,0,0.06); }
22+
* { margin:0; padding:0; box-sizing:border-box; }
23+
html, body { height:100%; background:var(--bg); color:var(--text);
24+
font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
25+
display:flex; flex-direction:column; align-items:center; justify-content:center;
26+
transition:background 0.3s,color 0.3s;
27+
padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); }
28+
.theme-toggle { position:fixed; top:16px; right:16px; background:none;
29+
border:1px solid var(--border); color:var(--text-dim); width:32px; height:32px;
30+
border-radius:8px; cursor:pointer; display:flex; align-items:center;
31+
justify-content:center; font-size:16px; transition:color 0.15s,border-color 0.15s,background 0.15s;
32+
-webkit-tap-highlight-color:transparent; z-index:10; }
33+
.theme-toggle:hover { color:var(--text); border-color:var(--border-subtle); background:var(--border); }
34+
.card { background:var(--surface); border:1px solid var(--border); border-radius:12px;
35+
padding:32px 24px; width:320px; max-width:calc(100vw - 32px); text-align:center;
36+
box-shadow:0 2px 8px var(--shadow); transition:background 0.3s,border-color 0.3s,box-shadow 0.3s; }
37+
h1 { font-size:22px; font-weight:700; margin-bottom:4px; }
38+
h1 span { color:var(--accent); }
39+
.subtitle { font-size:13px; color:var(--text-secondary); margin-bottom:24px; }
40+
input { width:100%; padding:12px; background:var(--bg); border:1px solid var(--border);
41+
border-radius:8px; color:var(--text); font-size:16px; outline:none;
42+
text-align:center; letter-spacing:2px; transition:border-color 0.15s,background 0.3s,color 0.3s; }
43+
input:focus { border-color:var(--accent); }
44+
.btn { width:100%; padding:12px; margin-top:16px; background:var(--accent);
45+
color:#fff; border:none; border-radius:8px; font-size:16px;
46+
font-weight:600; cursor:pointer; transition:background 0.15s; }
47+
.btn:hover { background:var(--accent-hover); }
48+
.btn:active { background:var(--accent-active); }
49+
.error { color:var(--danger); font-size:13px; margin-top:12px; display:none; transition:color 0.3s; }
50+
.tagline { margin-top:24px; font-size:12px; color:var(--text-dim); transition:color 0.3s; }
3051
</style>
3152
</head>
3253
<body>
54+
<button class="theme-toggle" id="themeBtn" aria-label="Toggle theme">🌙</button>
3355
<div class="card">
3456
<h1>📡 Term<span>Beam</span></h1>
35-
<p>Enter the access password</p>
57+
<p class="subtitle">Enter the access password</p>
3658
<form id="form">
3759
<input type="password" id="pw" placeholder="Password" autocomplete="off" autofocus />
38-
<button type="submit">Unlock</button>
60+
<button type="submit" class="btn">Unlock</button>
3961
</form>
4062
<div class="error" id="err">Incorrect password</div>
4163
</div>
64+
<p class="tagline">Beam your terminal to any device</p>
4265
<script>
66+
const t=document.getElementById('themeBtn'), h=document.documentElement;
67+
function applyTheme(light){h.setAttribute('data-theme',light?'light':'');t.textContent=light?'☀️':'🌙';
68+
document.querySelector('meta[name=theme-color]').content=light?'#ffffff':'#1e1e1e';}
69+
applyTheme(localStorage.getItem('theme')==='light');
70+
t.addEventListener('click',()=>{const light=h.getAttribute('data-theme')!=='light';
71+
localStorage.setItem('theme',light?'light':'dark');applyTheme(light);});
4372
document.getElementById('form').addEventListener('submit', async (e) => {
4473
e.preventDefault();
4574
const pw = document.getElementById('pw').value;

src/cli.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ termbeam — Beam your terminal to any device
1010
1111
Usage:
1212
termbeam [options] [shell] [args...]
13+
termbeam service <action> Manage as a background service (PM2)
14+
15+
Actions (service):
16+
install Interactive setup & start as PM2 service
17+
uninstall Stop & remove from PM2
18+
status Show service status
19+
logs Tail service logs
20+
restart Restart the service
1321
1422
Options:
1523
--password <pw> Set access password (or TERMBEAM_PASSWORD env var)
@@ -39,6 +47,7 @@ Examples:
3947
termbeam --password secret Start with specific password
4048
termbeam --persisted-tunnel Stable tunnel URL across restarts
4149
termbeam /bin/bash Use bash instead of default shell
50+
termbeam service install Set up as background service (PM2)
4251
4352
Environment:
4453
PORT Server port (default: 3456)

src/server.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -233,9 +233,8 @@ function createTermBeamServer(overrides = {}) {
233233

234234
module.exports = { createTermBeamServer, getLocalIP };
235235

236-
// Auto-start when run directly (CLI entry point)
237-
const _entryBase = path.basename(process.argv[1] || '');
238-
if (require.main === module || _entryBase === 'termbeam' || _entryBase === 'termbeam.js') {
236+
// Auto-start when run directly (e.g. `node src/server.js`)
237+
if (require.main === module) {
239238
const instance = createTermBeamServer();
240239

241240
process.on('SIGINT', () => {

0 commit comments

Comments
 (0)