Skip to content

Commit 1f6eb16

Browse files
committed
fix: wrap generated scripts in main() for safe curl|bash piping
When bash reads a script from a pipe (curl | bash), exec < /dev/tty at the top level redirects stdin away from the pipe mid-read, causing bash to lose the rest of the script and hang. Wrapping in main() forces bash to parse the entire script before executing, matching the pattern used by scripts/install.sh, rustup, and other curl|bash installers.
1 parent 13f21bc commit 1f6eb16

File tree

2 files changed

+14
-2
lines changed

2 files changed

+14
-2
lines changed

src/lib/server/install-script.test.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ import { describe, it, expect } from 'vitest';
22
import { generatePrivateInstallScript, generateInstallScript } from './install-script';
33

44
describe('generatePrivateInstallScript', () => {
5-
it('should reopen stdin from /dev/tty for curl|bash', () => {
5+
it('should wrap everything in main() for safe curl|bash piping', () => {
66
const script = generatePrivateInstallScript(
77
'https://openboot.dev',
88
'testuser',
99
'my-config'
1010
);
1111

12+
expect(script).toContain('main()');
13+
expect(script).toMatch(/main "\$@"\s*$/);
1214
expect(script).toContain('exec < /dev/tty');
1315
});
1416

@@ -110,9 +112,11 @@ describe('generatePrivateInstallScript', () => {
110112
});
111113

112114
describe('generateInstallScript', () => {
113-
it('should reopen stdin from /dev/tty for curl|bash', () => {
115+
it('should wrap everything in main() for safe curl|bash piping', () => {
114116
const script = generateInstallScript('testuser', 'my-config', '', '');
115117

118+
expect(script).toContain('main()');
119+
expect(script).toMatch(/main "\$@"\s*$/);
116120
expect(script).toContain('exec < /dev/tty');
117121
});
118122

src/lib/server/install-script.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export function generatePrivateInstallScript(
1313
return `#!/bin/bash
1414
set -e
1515
16+
main() {
1617
# When run via "curl | bash", stdin is the script content, not the terminal.
1718
# Reopen stdin from /dev/tty so interactive prompts (sudo, Homebrew) work.
1819
if [ ! -t 0 ] && [ -e /dev/tty ]; then
@@ -83,6 +84,9 @@ echo "Authorized! Fetching install script..."
8384
echo ""
8485
8586
exec bash <(curl -fsSL -H "Authorization: Bearer \$TOKEN" "\$APP_URL/${safeUsername}/${safeSlug}/install")
87+
}
88+
89+
main "\$@"
8690
`;
8791
}
8892

@@ -98,6 +102,7 @@ export function generateInstallScript(
98102
return `#!/bin/bash
99103
set -e
100104
105+
main() {
101106
# When run via "curl | bash", stdin is the script content, not the terminal.
102107
# Reopen stdin from /dev/tty so interactive prompts (sudo, Homebrew) work.
103108
if [ ! -t 0 ] && [ -e /dev/tty ]; then
@@ -218,5 +223,8 @@ done
218223
219224
echo ""
220225
echo "Installation complete!"
226+
}
227+
228+
main "\$@"
221229
`;
222230
}

0 commit comments

Comments
 (0)