tilt-live 0.1.0 → 0.3.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 +4 -3
- package/bin/tilt-live.js +27 -118
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -6,9 +6,10 @@ See and control any Linux desktop, right in your browser.
|
|
|
6
6
|
npx tilt-live
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Run it on the Linux machine or VM whose screen you want, open the link it prints, click the cursor
|
|
10
|
+
button, and the desktop is yours.
|
|
10
11
|
|
|
12
|
+
- The first run downloads [tilt](https://github.com/cloudycotton/tilt) (checksum-verified). After
|
|
13
|
+
that tilt updates itself whenever it starts; `TILT_NO_UPDATE=1` turns this off.
|
|
11
14
|
- `npx tilt-live --display :1`: every flag goes to [tilt](https://github.com/cloudycotton/tilt/blob/main/docs/guide.md#flags).
|
|
12
|
-
- Always runs the latest release (checksum-verified). Pin one with `TILT_VERSION=0.2.0`, skip the check with `TILT_NO_UPDATE=1`.
|
|
13
|
-
- Keeps its token in `~/.config/tilt-live/token` unless you pass `--token`, `--token-file` or `--no-auth`.
|
|
14
15
|
- Linux on x86_64 or arm64, Node 18+. MIT licensed.
|
package/bin/tilt-live.js
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// tilt-live: runs
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// TILT_VERSION=0.2.0 run that release instead of the latest
|
|
6
|
-
// TILT_NO_UPDATE=1 do not look for updates (run the newest one already downloaded)
|
|
7
|
-
//
|
|
8
|
-
// With no --token, --token-file or --no-auth (nor their TILT_* variables), it makes a token,
|
|
9
|
-
// keeps it in ~/.config/tilt-live/token and prints the link to open.
|
|
2
|
+
// tilt-live: runs tilt (https://github.com/cloudycotton/tilt) with every argument passed through.
|
|
3
|
+
// The first run downloads the latest release (checksum-verified); after that tilt updates itself
|
|
4
|
+
// whenever it starts. Linux on x86_64 or arm64.
|
|
10
5
|
'use strict';
|
|
11
6
|
|
|
12
7
|
const crypto = require('node:crypto');
|
|
@@ -15,130 +10,44 @@ const os = require('node:os');
|
|
|
15
10
|
const path = require('node:path');
|
|
16
11
|
const { spawn } = require('node:child_process');
|
|
17
12
|
|
|
18
|
-
// TILT_RELEASES points elsewhere for tests: a URL with GitHub's /latest
|
|
13
|
+
// TILT_RELEASES points elsewhere for tests: a URL with GitHub's /latest/download layout.
|
|
19
14
|
const RELEASES = process.env.TILT_RELEASES || 'https://github.com/cloudycotton/tilt/releases';
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const TRIPLES = { x64: 'x86_64-unknown-linux-musl', arm64: 'aarch64-unknown-linux-musl' };
|
|
24
|
-
|
|
25
|
-
const home = os.homedir();
|
|
26
|
-
const cacheDir = path.join(process.env.XDG_CACHE_HOME || path.join(home, '.cache'), 'tilt-live');
|
|
27
|
-
const configDir = path.join(process.env.XDG_CONFIG_HOME || path.join(home, '.config'), 'tilt-live');
|
|
15
|
+
const TRIPLE = { x64: 'x86_64-unknown-linux-musl', arm64: 'aarch64-unknown-linux-musl' }[process.arch];
|
|
16
|
+
const cacheDir = path.join(process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache'), 'tilt-live');
|
|
17
|
+
const bin = path.join(cacheDir, 'tilt');
|
|
28
18
|
|
|
29
19
|
function fail(msg) {
|
|
30
20
|
process.stderr.write(`tilt-live: ${msg}\n`);
|
|
31
21
|
process.exit(1);
|
|
32
22
|
}
|
|
33
23
|
|
|
34
|
-
|
|
35
|
-
const pa = a.split(/[.-]/).map(Number);
|
|
36
|
-
const pb = b.split(/[.-]/).map(Number);
|
|
37
|
-
for (let i = 0; i < 3; i++) if ((pa[i] || 0) !== (pb[i] || 0)) return (pa[i] || 0) - (pb[i] || 0);
|
|
38
|
-
return 0;
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
/** Versions already downloaded, newest first. */
|
|
42
|
-
function cached() {
|
|
43
|
-
try {
|
|
44
|
-
return fs.readdirSync(cacheDir)
|
|
45
|
-
.filter((v) => /^\d+\.\d+\.\d+$/.test(v) && fs.existsSync(path.join(cacheDir, v, 'tilt')))
|
|
46
|
-
.sort(cmp)
|
|
47
|
-
.reverse();
|
|
48
|
-
} catch {
|
|
49
|
-
return [];
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** The newest release's version, from the redirect of /releases/latest; null when unknown. */
|
|
54
|
-
async function latest() {
|
|
55
|
-
try {
|
|
56
|
-
const res = await fetch(`${RELEASES}/latest`, {
|
|
57
|
-
redirect: 'manual',
|
|
58
|
-
signal: AbortSignal.timeout(CHECK_TIMEOUT_MS),
|
|
59
|
-
});
|
|
60
|
-
const m = /\/tag\/v?(\d+\.\d+\.\d+)$/.exec(res.headers.get('location') || '');
|
|
61
|
-
return m ? m[1] : null;
|
|
62
|
-
} catch {
|
|
63
|
-
return null;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async function download(version, triple) {
|
|
68
|
-
const base = `${RELEASES}/download/v${version}`;
|
|
24
|
+
async function download() {
|
|
69
25
|
const get = async (name) => {
|
|
70
|
-
const res = await fetch(`${
|
|
26
|
+
const res = await fetch(`${RELEASES}/latest/download/${name}`, { signal: AbortSignal.timeout(120_000) });
|
|
71
27
|
if (!res.ok) throw new Error(`${name}: HTTP ${res.status}`);
|
|
72
28
|
return Buffer.from(await res.arrayBuffer());
|
|
73
29
|
};
|
|
74
|
-
const name = `tilt-${
|
|
75
|
-
const [
|
|
30
|
+
const name = `tilt-${TRIPLE}`;
|
|
31
|
+
const [data, sums] = await Promise.all([get(name), get('SHA256SUMS')]);
|
|
76
32
|
const want = sums.toString().split('\n').map((l) => l.trim().split(/\s+/)).find((f) => f[1] === name)?.[0];
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
fs.
|
|
81
|
-
|
|
82
|
-
fs.writeFileSync(tmp, bin, { mode: 0o755 });
|
|
83
|
-
fs.renameSync(tmp, path.join(dir, 'tilt'));
|
|
84
|
-
for (const old of cached().slice(KEEP_VERSIONS)) fs.rmSync(path.join(cacheDir, old), { recursive: true, force: true });
|
|
33
|
+
if (crypto.createHash('sha256').update(data).digest('hex') !== want) throw new Error(`${name}: checksum mismatch`);
|
|
34
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
35
|
+
const tmp = `${bin}.${process.pid}`;
|
|
36
|
+
fs.writeFileSync(tmp, data, { mode: 0o755 });
|
|
37
|
+
fs.renameSync(tmp, bin);
|
|
85
38
|
}
|
|
86
39
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (!version && !process.env.TILT_NO_UPDATE) version = await latest();
|
|
94
|
-
version ||= have[0];
|
|
95
|
-
if (!version) fail('cannot find the latest tilt release (check the network, or set TILT_VERSION)');
|
|
96
|
-
if (!have.includes(version)) {
|
|
97
|
-
process.stderr.write(`tilt-live: downloading tilt ${version}...\n`);
|
|
98
|
-
try {
|
|
99
|
-
await download(version, triple);
|
|
100
|
-
} catch (e) {
|
|
101
|
-
if (!have[0]) fail(`download failed: ${e.message}`);
|
|
102
|
-
process.stderr.write(`tilt-live: download failed (${e.message}); running ${have[0]}\n`);
|
|
103
|
-
version = have[0];
|
|
104
|
-
}
|
|
40
|
+
async function main() {
|
|
41
|
+
if (process.platform !== 'linux') fail('tilt streams a Linux desktop: run it on the Linux machine or VM whose screen you want');
|
|
42
|
+
if (!TRIPLE) fail('tilt runs on x86_64 or arm64');
|
|
43
|
+
if (!fs.existsSync(bin)) {
|
|
44
|
+
process.stderr.write('tilt-live: downloading tilt...\n');
|
|
45
|
+
await download().catch((e) => fail(`download failed: ${e.message}`));
|
|
105
46
|
}
|
|
106
|
-
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/** Adds --token-file with a kept token when no access control was given, and says where to go. */
|
|
110
|
-
function withAuth(args) {
|
|
111
|
-
const given = args.some((a) => /^--(token|token-file|no-auth)(=|$)/.test(a))
|
|
112
|
-
|| ['TILT_TOKEN', 'TILT_TOKEN_FILE', 'TILT_NO_AUTH'].some((v) => process.env[v]);
|
|
113
|
-
const info = ['-h', '--help', '-V', '--version'].some((a) => args.includes(a));
|
|
114
|
-
if (given || info) return args;
|
|
115
|
-
const file = path.join(configDir, 'token');
|
|
116
|
-
let token = '';
|
|
117
|
-
try { token = fs.readFileSync(file, 'utf8').trim(); } catch { /* none yet */ }
|
|
118
|
-
if (!token) {
|
|
119
|
-
token = crypto.randomBytes(16).toString('hex');
|
|
120
|
-
fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
|
|
121
|
-
fs.writeFileSync(file, `${token}\n`, { mode: 0o600 });
|
|
122
|
-
}
|
|
123
|
-
const bind = (args.find((a) => a.startsWith('--bind='))?.slice(7))
|
|
124
|
-
|| (args.includes('--bind') ? args[args.indexOf('--bind') + 1] : null)
|
|
125
|
-
|| process.env.TILT_BIND || '0.0.0.0:6090';
|
|
126
|
-
const at = bind.lastIndexOf(':');
|
|
127
|
-
const [host, port] = [bind.slice(0, at).replace(/^\[|\]$/g, ''), bind.slice(at + 1)];
|
|
128
|
-
// Bound to every interface: the machine's own addresses; else the one it is bound to.
|
|
129
|
-
const hosts = host === '0.0.0.0' || host === '::'
|
|
130
|
-
? ['localhost', ...Object.values(os.networkInterfaces()).flat()
|
|
131
|
-
.filter((i) => i && i.family === 'IPv4' && !i.internal).map((i) => i.address)]
|
|
132
|
-
: [host.includes(':') ? `[${host}]` : host];
|
|
133
|
-
process.stderr.write(`tilt-live: open ${hosts.map((h) => `http://${h}:${port}/#token=${token}`).join('\n or ')}\n`);
|
|
134
|
-
return [...args, '--token-file', file];
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
(async () => {
|
|
138
|
-
const bin = await binary();
|
|
139
|
-
const args = withAuth(process.argv.slice(2));
|
|
140
|
-
const child = spawn(bin, args, { stdio: 'inherit' });
|
|
47
|
+
const child = spawn(bin, process.argv.slice(2), { stdio: 'inherit' });
|
|
141
48
|
for (const sig of ['SIGINT', 'SIGTERM', 'SIGHUP', 'SIGQUIT']) process.on(sig, () => child.kill(sig));
|
|
142
49
|
child.on('error', (e) => fail(e.message));
|
|
143
|
-
child.on('exit', (code, sig) => process.exit(code ??
|
|
144
|
-
}
|
|
50
|
+
child.on('exit', (code, sig) => process.exit(code ?? 128 + (os.constants.signals[sig] || 0)));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
main();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tilt-live",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "See and control any Linux desktop, right in your browser (
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "See and control any Linux desktop, right in your browser (downloads tilt, which then keeps itself up to date)",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"vnc",
|
|
7
7
|
"remote-desktop",
|