rms-devremote 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +154 -0
- package/dist/commands/attach.d.ts +2 -0
- package/dist/commands/attach.js +10 -0
- package/dist/commands/check.d.ts +2 -0
- package/dist/commands/check.js +210 -0
- package/dist/commands/clean.d.ts +2 -0
- package/dist/commands/clean.js +177 -0
- package/dist/commands/dashboard.d.ts +2 -0
- package/dist/commands/dashboard.js +57 -0
- package/dist/commands/link.d.ts +2 -0
- package/dist/commands/link.js +112 -0
- package/dist/commands/ping.d.ts +2 -0
- package/dist/commands/ping.js +21 -0
- package/dist/commands/setup.d.ts +2 -0
- package/dist/commands/setup.js +54 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +65 -0
- package/dist/commands/unlink.d.ts +2 -0
- package/dist/commands/unlink.js +53 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +55 -0
- package/dist/server/auth.d.ts +6 -0
- package/dist/server/auth.js +32 -0
- package/dist/server/frontend.d.ts +4 -0
- package/dist/server/frontend.js +886 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +283 -0
- package/dist/server/terminal.d.ts +14 -0
- package/dist/server/terminal.js +43 -0
- package/dist/services/battery-worker.d.ts +1 -0
- package/dist/services/battery-worker.js +2 -0
- package/dist/services/battery.d.ts +27 -0
- package/dist/services/battery.js +152 -0
- package/dist/services/config.d.ts +63 -0
- package/dist/services/config.js +84 -0
- package/dist/services/docker.d.ts +25 -0
- package/dist/services/docker.js +75 -0
- package/dist/services/hooks.d.ts +15 -0
- package/dist/services/hooks.js +111 -0
- package/dist/services/ntfy.d.ts +19 -0
- package/dist/services/ntfy.js +63 -0
- package/dist/services/process.d.ts +30 -0
- package/dist/services/process.js +90 -0
- package/dist/services/proxy-worker.d.ts +1 -0
- package/dist/services/proxy-worker.js +12 -0
- package/dist/services/proxy.d.ts +4 -0
- package/dist/services/proxy.js +195 -0
- package/dist/services/shell.d.ts +22 -0
- package/dist/services/shell.js +47 -0
- package/dist/services/tmux.d.ts +30 -0
- package/dist/services/tmux.js +74 -0
- package/dist/services/ttyd.d.ts +28 -0
- package/dist/services/ttyd.js +71 -0
- package/dist/setup-server/routes.d.ts +4 -0
- package/dist/setup-server/routes.js +177 -0
- package/dist/setup-server/server.d.ts +4 -0
- package/dist/setup-server/server.js +32 -0
- package/docker/docker-compose.yml +24 -0
- package/docker/ntfy/server.yml +6 -0
- package/package.json +61 -0
- package/scripts/claude-remote.sh +583 -0
- package/scripts/hooks/notify.sh +68 -0
- package/scripts/notify.sh +54 -0
- package/scripts/startup.sh +29 -0
- package/scripts/update-check.sh +25 -0
- package/src/setup-server/public/index.html +21 -0
- package/src/setup-server/public/setup.css +475 -0
- package/src/setup-server/public/setup.js +687 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { fork } from 'child_process';
|
|
2
|
+
import { resolve, dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { isSetupDone, readConfig, BATTERY_PID_PATH } from '../services/config.js';
|
|
6
|
+
import { isServerRunning, checkPortAvailable, startServer } from '../services/ttyd.js';
|
|
7
|
+
import { sessionExists, createSession, attachSession } from '../services/tmux.js';
|
|
8
|
+
import { isDockerRunning, isComposeUp, composeUp } from '../services/docker.js';
|
|
9
|
+
import { mergeHooks } from '../services/hooks.js';
|
|
10
|
+
import { inhibitSleep, getBatteryInfo } from '../services/battery.js';
|
|
11
|
+
import { writePid } from '../services/process.js';
|
|
12
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
13
|
+
const __dirname = dirname(__filename);
|
|
14
|
+
export async function link() {
|
|
15
|
+
// 1. Guard: setup must be done
|
|
16
|
+
if (!isSetupDone()) {
|
|
17
|
+
console.error(chalk.red('✖ rms-devremote n\'est pas encore configure. Lance: rms-devremote setup'));
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
20
|
+
const config = readConfig();
|
|
21
|
+
// 2. Guard: already linked
|
|
22
|
+
if (isServerRunning()) {
|
|
23
|
+
console.log(chalk.yellow('⚠ Session déjà linkée.'));
|
|
24
|
+
console.log(chalk.white(' URL: ') + chalk.blue(config.domains.terminal));
|
|
25
|
+
console.log(chalk.gray(' → rms-devremote unlink pour stopper'));
|
|
26
|
+
console.log(chalk.gray(' → rms-devremote attach pour rejoindre'));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
// 3. Check Docker
|
|
30
|
+
if (!isDockerRunning()) {
|
|
31
|
+
console.error(chalk.red('✖ Docker n\'est pas en cours d\'exécution. Démarre Docker et réessaie.'));
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
if (!isComposeUp()) {
|
|
35
|
+
console.log(chalk.cyan(' Démarrage du stack Docker Compose…'));
|
|
36
|
+
try {
|
|
37
|
+
composeUp();
|
|
38
|
+
console.log(chalk.green('✔ Docker Compose démarré.'));
|
|
39
|
+
}
|
|
40
|
+
catch (err) {
|
|
41
|
+
console.error(chalk.red('✖ Échec du démarrage Docker Compose.'), err);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// 4. Check/create tmux session
|
|
46
|
+
if (!sessionExists()) {
|
|
47
|
+
console.log(chalk.cyan(' Création de la session tmux…'));
|
|
48
|
+
createSession();
|
|
49
|
+
console.log(chalk.green('✔ Session tmux créée.'));
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.log(chalk.green('✔ Session tmux existante.'));
|
|
53
|
+
}
|
|
54
|
+
// 5. Check port availability
|
|
55
|
+
const portCheck = checkPortAvailable();
|
|
56
|
+
if (!portCheck.available) {
|
|
57
|
+
console.error(chalk.red(`✖ Le port ${config.ttyd.port} est bloqué par le processus PID ${portCheck.blockingPid ?? 'inconnu'}.`));
|
|
58
|
+
console.error(chalk.gray(` Arrête ce processus ou change le port dans la config.`));
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
// 6. Start devremote terminal server
|
|
62
|
+
console.log(chalk.cyan(' Démarrage du serveur terminal…'));
|
|
63
|
+
let serverPid;
|
|
64
|
+
try {
|
|
65
|
+
serverPid = startServer();
|
|
66
|
+
console.log(chalk.green(`✔ Serveur terminal démarré (PID ${serverPid}).`));
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
console.error(chalk.red('✖ Impossible de démarrer le serveur terminal.'), err);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
// 7. Merge hooks
|
|
73
|
+
try {
|
|
74
|
+
mergeHooks();
|
|
75
|
+
console.log(chalk.green('✔ Hooks Claude configurés.'));
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
console.log(chalk.yellow('⚠ Impossible de configurer les hooks Claude.'));
|
|
79
|
+
}
|
|
80
|
+
// 8. Inhibit sleep
|
|
81
|
+
try {
|
|
82
|
+
inhibitSleep();
|
|
83
|
+
console.log(chalk.green('✔ Mise en veille désactivée.'));
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
console.log(chalk.yellow('⚠ Impossible de désactiver la mise en veille.'));
|
|
87
|
+
}
|
|
88
|
+
// 9. Battery warning
|
|
89
|
+
const battery = getBatteryInfo();
|
|
90
|
+
if (battery.present && !battery.charging) {
|
|
91
|
+
console.log(chalk.yellow(`⚠ Batterie non branchée — ${battery.percent}% restant. Pense à brancher le chargeur.`));
|
|
92
|
+
}
|
|
93
|
+
// 10. Fork battery worker if battery present
|
|
94
|
+
if (battery.present) {
|
|
95
|
+
const workerPath = resolve(__dirname, '..', 'services', 'battery-worker.js');
|
|
96
|
+
const worker = fork(workerPath, [], { detached: true, stdio: 'ignore' });
|
|
97
|
+
if (worker.pid !== undefined) {
|
|
98
|
+
writePid(BATTERY_PID_PATH, worker.pid);
|
|
99
|
+
}
|
|
100
|
+
worker.unref();
|
|
101
|
+
console.log(chalk.green('✔ Surveillance batterie démarrée.'));
|
|
102
|
+
}
|
|
103
|
+
// 11. Success
|
|
104
|
+
console.log();
|
|
105
|
+
console.log(chalk.bold.green('✔ Terminal linked!'));
|
|
106
|
+
console.log(chalk.white(' URL: ') + chalk.blue(config.domains.terminal));
|
|
107
|
+
console.log();
|
|
108
|
+
// 12. Attach tmux session (blocks until detach/exit)
|
|
109
|
+
// tmux + server continue in background if terminal is closed
|
|
110
|
+
attachSession();
|
|
111
|
+
}
|
|
112
|
+
export default link;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { isSetupDone } from '../services/config.js';
|
|
3
|
+
import { sendNotification } from '../services/ntfy.js';
|
|
4
|
+
export async function ping() {
|
|
5
|
+
if (!isSetupDone()) {
|
|
6
|
+
console.log(chalk.yellow('⚠ rms-devremote n\'est pas encore configuré. Lance: rms-devremote setup'));
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
console.log(chalk.cyan(' Envoi d\'une notification de test…'));
|
|
10
|
+
const ok = await sendNotification('rms-devremote ping — connexion OK ✓', 'default');
|
|
11
|
+
if (ok) {
|
|
12
|
+
console.log(chalk.green('✔ Notification envoyée avec succès.'));
|
|
13
|
+
console.log(chalk.gray(' Vérifie ton appareil pour la notification ntfy.'));
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
console.log(chalk.red('✖ Échec de l\'envoi de la notification.'));
|
|
17
|
+
console.log(chalk.gray(' Assure-toi que le conteneur ntfy est en cours d\'exécution.'));
|
|
18
|
+
console.log(chalk.gray(' Lance: rms-devremote status'));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export default ping;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { isSetupDone } from '../services/config.js';
|
|
3
|
+
import { startSetupServer, getSetupUrl } from '../setup-server/server.js';
|
|
4
|
+
import { run } from '../services/shell.js';
|
|
5
|
+
export async function setup() {
|
|
6
|
+
// Guard: already configured
|
|
7
|
+
if (isSetupDone()) {
|
|
8
|
+
console.log(chalk.yellow('Deja configure. Pour reconfigurer, supprime ~/.rms-devremote/ et relance.'));
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
// Start the setup HTTP + WebSocket server
|
|
12
|
+
const { close } = await startSetupServer();
|
|
13
|
+
const url = getSetupUrl();
|
|
14
|
+
// Open browser (ignore errors — may not have a display)
|
|
15
|
+
try {
|
|
16
|
+
const platform = process.platform;
|
|
17
|
+
if (platform === 'darwin') {
|
|
18
|
+
run('open', [url]);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
run('xdg-open', [url]);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Silently ignore — user can open manually
|
|
26
|
+
}
|
|
27
|
+
console.log();
|
|
28
|
+
console.log(chalk.bold.cyan('Setup en cours sur ' + url + '...'));
|
|
29
|
+
console.log(chalk.gray('Ouvrez l\'URL ci-dessus dans votre navigateur si elle ne s\'est pas ouverte automatiquement.'));
|
|
30
|
+
console.log(chalk.gray('Ctrl+C pour annuler'));
|
|
31
|
+
console.log();
|
|
32
|
+
// Poll until setup is done (config.json + .env written)
|
|
33
|
+
await new Promise((resolve) => {
|
|
34
|
+
const interval = setInterval(() => {
|
|
35
|
+
if (isSetupDone()) {
|
|
36
|
+
clearInterval(interval);
|
|
37
|
+
resolve();
|
|
38
|
+
}
|
|
39
|
+
}, 1000);
|
|
40
|
+
// Allow Ctrl+C to cancel cleanly
|
|
41
|
+
process.once('SIGINT', () => {
|
|
42
|
+
clearInterval(interval);
|
|
43
|
+
close();
|
|
44
|
+
console.log(chalk.gray('\nSetup annule.'));
|
|
45
|
+
process.exit(0);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
// Teardown server
|
|
49
|
+
close();
|
|
50
|
+
console.log();
|
|
51
|
+
console.log(chalk.bold.green('Setup termine ! Lance: rms-devremote link'));
|
|
52
|
+
console.log();
|
|
53
|
+
}
|
|
54
|
+
export default setup;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { isSetupDone, readConfig } from '../services/config.js';
|
|
3
|
+
import { getContainerStatus } from '../services/docker.js';
|
|
4
|
+
import { healthCheck } from '../services/ntfy.js';
|
|
5
|
+
import { isTtydRunning } from '../services/ttyd.js';
|
|
6
|
+
import { sessionExists } from '../services/tmux.js';
|
|
7
|
+
import { hasDevremoteHooks } from '../services/hooks.js';
|
|
8
|
+
import { getBatteryInfo } from '../services/battery.js';
|
|
9
|
+
function statusIcon(ok) {
|
|
10
|
+
return ok ? chalk.green('●') : chalk.red('●');
|
|
11
|
+
}
|
|
12
|
+
function statusLabel(ok, onLabel, offLabel) {
|
|
13
|
+
return ok ? chalk.green(onLabel) : chalk.red(offLabel);
|
|
14
|
+
}
|
|
15
|
+
export async function status() {
|
|
16
|
+
if (!isSetupDone()) {
|
|
17
|
+
console.log(chalk.yellow('⚠ rms-devremote n\'est pas encore configuré. Lance: rms-devremote setup'));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const config = readConfig();
|
|
21
|
+
// Gather status info
|
|
22
|
+
const tunnelStatus = getContainerStatus('devremote-tunnel');
|
|
23
|
+
const ntfyStatus = getContainerStatus('devremote-ntfy');
|
|
24
|
+
const ntfyHealth = await healthCheck();
|
|
25
|
+
const ttydRunning = isTtydRunning();
|
|
26
|
+
const tmuxSession = sessionExists();
|
|
27
|
+
const hooksConfigured = hasDevremoteHooks();
|
|
28
|
+
const battery = getBatteryInfo();
|
|
29
|
+
console.log();
|
|
30
|
+
console.log(chalk.bold.cyan('rms-devremote — Status'));
|
|
31
|
+
console.log(chalk.cyan('─────────────────────────────────────────'));
|
|
32
|
+
// Docker services
|
|
33
|
+
console.log(chalk.bold.white('Docker:'));
|
|
34
|
+
const tunnelOk = tunnelStatus === 'running';
|
|
35
|
+
const ntfyOk = ntfyStatus === 'running';
|
|
36
|
+
console.log(` ${statusIcon(tunnelOk)} Tunnel (Cloudflare) ${statusLabel(tunnelOk, 'running', tunnelStatus)}`);
|
|
37
|
+
console.log(` ${statusIcon(ntfyOk)} ntfy ${statusLabel(ntfyOk, 'running', ntfyStatus)}`);
|
|
38
|
+
console.log(` ${statusIcon(ntfyHealth)} ntfy health ${statusLabel(ntfyHealth, 'healthy', 'unreachable')}`);
|
|
39
|
+
console.log();
|
|
40
|
+
console.log(chalk.bold.white('Services:'));
|
|
41
|
+
console.log(` ${statusIcon(ttydRunning)} terminal server ${statusLabel(ttydRunning, 'running', 'stopped')}`);
|
|
42
|
+
console.log(` ${statusIcon(tmuxSession)} tmux session ${statusLabel(tmuxSession, 'active', 'no session')}`);
|
|
43
|
+
console.log(` ${statusIcon(hooksConfigured)} Claude hooks ${statusLabel(hooksConfigured, 'configured', 'not configured')}`);
|
|
44
|
+
console.log();
|
|
45
|
+
console.log(chalk.bold.white('Battery:'));
|
|
46
|
+
if (battery.present) {
|
|
47
|
+
const chargingIcon = battery.charging ? chalk.green('⚡ charging') : chalk.yellow('🔋 discharging');
|
|
48
|
+
const percentColor = battery.percent > 50
|
|
49
|
+
? chalk.green(`${battery.percent}%`)
|
|
50
|
+
: battery.percent > 20
|
|
51
|
+
? chalk.yellow(`${battery.percent}%`)
|
|
52
|
+
: chalk.red(`${battery.percent}%`);
|
|
53
|
+
console.log(` ${percentColor} ${chargingIcon}`);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
console.log(` ${chalk.gray('No battery detected (desktop or AC only)')}`);
|
|
57
|
+
}
|
|
58
|
+
console.log();
|
|
59
|
+
console.log(chalk.bold.white('Domains:'));
|
|
60
|
+
console.log(` Terminal : ${chalk.blue(config.domains.terminal)}`);
|
|
61
|
+
console.log(` Notify : ${chalk.blue(config.domains.notify)}`);
|
|
62
|
+
console.log(chalk.cyan('─────────────────────────────────────────'));
|
|
63
|
+
console.log();
|
|
64
|
+
}
|
|
65
|
+
export default status;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { BATTERY_PID_PATH, INHIBIT_PID_PATH } from '../services/config.js';
|
|
3
|
+
import { isTtydRunning, stopTtyd } from '../services/ttyd.js';
|
|
4
|
+
import { sessionExists, attachSession } from '../services/tmux.js';
|
|
5
|
+
import { removeHooks } from '../services/hooks.js';
|
|
6
|
+
import { releaseSleep } from '../services/battery.js';
|
|
7
|
+
import { killByPidFile, cleanupPidFile } from '../services/process.js';
|
|
8
|
+
export async function unlink() {
|
|
9
|
+
// 1. Guard: must be linked
|
|
10
|
+
if (!isTtydRunning()) {
|
|
11
|
+
console.log(chalk.yellow('⚠ Pas de session linkée en cours.'));
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
// 2. Stop ttyd
|
|
15
|
+
console.log(chalk.cyan(' Arrêt de ttyd…'));
|
|
16
|
+
try {
|
|
17
|
+
stopTtyd();
|
|
18
|
+
console.log(chalk.green('✔ ttyd arrêté.'));
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
console.log(chalk.yellow('⚠ Impossible d\'arrêter ttyd proprement.'));
|
|
22
|
+
}
|
|
23
|
+
// 3. Remove hooks
|
|
24
|
+
try {
|
|
25
|
+
removeHooks();
|
|
26
|
+
console.log(chalk.green('✔ Hooks Claude supprimés.'));
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
console.log(chalk.yellow('⚠ Impossible de supprimer les hooks Claude.'));
|
|
30
|
+
}
|
|
31
|
+
// 4. Release sleep inhibitor
|
|
32
|
+
try {
|
|
33
|
+
releaseSleep();
|
|
34
|
+
console.log(chalk.green('✔ Mise en veille réactivée.'));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
console.log(chalk.yellow('⚠ Impossible de réactiver la mise en veille.'));
|
|
38
|
+
}
|
|
39
|
+
// 5. Kill battery watcher
|
|
40
|
+
killByPidFile(BATTERY_PID_PATH);
|
|
41
|
+
// 6. Clean PID files
|
|
42
|
+
cleanupPidFile(BATTERY_PID_PATH);
|
|
43
|
+
cleanupPidFile(INHIBIT_PID_PATH);
|
|
44
|
+
console.log();
|
|
45
|
+
console.log(chalk.bold.green('✔ Terminal unlinked.'));
|
|
46
|
+
console.log();
|
|
47
|
+
// 7. Attach tmux session if it exists
|
|
48
|
+
if (sessionExists()) {
|
|
49
|
+
console.log(chalk.gray(' La session tmux est toujours active. Connexion…'));
|
|
50
|
+
attachSession();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export default unlink;
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
const program = new Command();
|
|
4
|
+
program
|
|
5
|
+
.name('rms-devremote')
|
|
6
|
+
.description('Share your local terminal remotely with push notifications')
|
|
7
|
+
.version('2.0.0');
|
|
8
|
+
// Helper: lazy-load a command module by path (relative to dist/)
|
|
9
|
+
async function runCommand(modulePath) {
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
11
|
+
const mod = await import(modulePath);
|
|
12
|
+
await mod.default();
|
|
13
|
+
}
|
|
14
|
+
program
|
|
15
|
+
.command('setup')
|
|
16
|
+
.description('Configure the remote server (install ttyd, nginx, ntfy)')
|
|
17
|
+
.action(() => runCommand('./commands/setup.js'));
|
|
18
|
+
program
|
|
19
|
+
.command('link')
|
|
20
|
+
.description('Link this machine to a remote server')
|
|
21
|
+
.action(() => runCommand('./commands/link.js'));
|
|
22
|
+
program
|
|
23
|
+
.command('unlink')
|
|
24
|
+
.description('Unlink this machine from the remote server')
|
|
25
|
+
.action(() => runCommand('./commands/unlink.js'));
|
|
26
|
+
program
|
|
27
|
+
.command('attach')
|
|
28
|
+
.description('Attach to a remote terminal session')
|
|
29
|
+
.action(() => runCommand('./commands/attach.js'));
|
|
30
|
+
program
|
|
31
|
+
.command('status')
|
|
32
|
+
.description('Show the status of the remote connection')
|
|
33
|
+
.action(() => runCommand('./commands/status.js'));
|
|
34
|
+
program
|
|
35
|
+
.command('ping')
|
|
36
|
+
.description('Ping the remote server to check connectivity')
|
|
37
|
+
.action(() => runCommand('./commands/ping.js'));
|
|
38
|
+
program
|
|
39
|
+
.command('check')
|
|
40
|
+
.description('Check system requirements and dependencies')
|
|
41
|
+
.action(() => runCommand('./commands/check.js'));
|
|
42
|
+
program
|
|
43
|
+
.command('clean')
|
|
44
|
+
.description('Remove all rms-devremote configuration and data')
|
|
45
|
+
.action(() => runCommand('./commands/clean.js'));
|
|
46
|
+
// Default action: show dashboard when no command is provided
|
|
47
|
+
if (process.argv.length <= 2) {
|
|
48
|
+
runCommand('./commands/dashboard.js').catch((err) => {
|
|
49
|
+
console.error(err);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
program.parse(process.argv);
|
|
55
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
2
|
+
/**
|
|
3
|
+
* Express middleware: HTTP Basic Authentication.
|
|
4
|
+
* Credentials come from ~/.rms-devremote/.env (TTYD_USER / TTYD_PASSWORD).
|
|
5
|
+
*/
|
|
6
|
+
export declare function basicAuth(req: Request, res: Response, next: NextFunction): void;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { readEnv } from '../services/config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Express middleware: HTTP Basic Authentication.
|
|
4
|
+
* Credentials come from ~/.rms-devremote/.env (TTYD_USER / TTYD_PASSWORD).
|
|
5
|
+
*/
|
|
6
|
+
export function basicAuth(req, res, next) {
|
|
7
|
+
const authHeader = req.headers.authorization;
|
|
8
|
+
if (!authHeader || !authHeader.startsWith('Basic ')) {
|
|
9
|
+
res.setHeader('WWW-Authenticate', 'Basic realm="devremote"');
|
|
10
|
+
res.status(401).end('Authentication required');
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const encoded = authHeader.slice(6);
|
|
14
|
+
const decoded = Buffer.from(encoded, 'base64').toString('utf8');
|
|
15
|
+
const colonIdx = decoded.indexOf(':');
|
|
16
|
+
if (colonIdx === -1) {
|
|
17
|
+
res.status(401).end('Invalid credentials');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const user = decoded.slice(0, colonIdx);
|
|
21
|
+
const pass = decoded.slice(colonIdx + 1);
|
|
22
|
+
const env = readEnv();
|
|
23
|
+
const expectedUser = env.TTYD_USER ?? 'admin';
|
|
24
|
+
const expectedPass = env.TTYD_PASSWORD ?? 'changeme';
|
|
25
|
+
if (user === expectedUser && pass === expectedPass) {
|
|
26
|
+
next();
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
res.setHeader('WWW-Authenticate', 'Basic realm="devremote"');
|
|
30
|
+
res.status(401).end('Invalid credentials');
|
|
31
|
+
}
|
|
32
|
+
}
|