@gengjiawen/os-init 1.9.0 → 1.10.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/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.10.0](https://github.com/gengjiawen/os-init/compare/v1.9.1...v1.10.0) (2025-12-06)
4
+
5
+
6
+ ### Features
7
+
8
+ * add command to set up Fish shell import script ([8418406](https://github.com/gengjiawen/os-init/commit/841840694783a1264733b7b8415cdb12d2bb21fa))
9
+ * add dev tun to setup ([a373417](https://github.com/gengjiawen/os-init/commit/a373417ca60f0f1554c85c06d73c017bc722fdf3))
10
+ * add ownership fix for gitpod user config and cache in init script ([dd3720c](https://github.com/gengjiawen/os-init/commit/dd3720cd0a57b603d1cc74179187669e3659ae82))
11
+ * persist ssh host keys via init script and mount gitconfig ([dbaabe9](https://github.com/gengjiawen/os-init/commit/dbaabe98ca44aa3e1cdb29deb12f700a4b559fb5))
12
+
13
+ ## [1.9.1](https://github.com/gengjiawen/os-init/compare/v1.9.0...v1.9.1) (2025-11-26)
14
+
15
+
16
+ ### Bug Fixes
17
+
18
+ * update unzip-url package version to 1.1.0 in package.json and pnpm-lock.yaml ([e800522](https://github.com/gengjiawen/os-init/commit/e8005229e0a4d28a91e0c82aceec4e41150ec8dc))
19
+
3
20
  ## [1.9.0](https://github.com/gengjiawen/os-init/compare/v1.8.0...v1.9.0) (2025-11-21)
4
21
 
5
22
 
package/README.md CHANGED
@@ -50,6 +50,28 @@ pnpx @gengjiawen/os-init set-android --android-home ~/my-android-sdk
50
50
  pnpx @gengjiawen/os-init set-android --skip-env-vars
51
51
  ```
52
52
 
53
+ ### Setup Fish Shell Import Script
54
+
55
+ ```bash
56
+ pnpx @gengjiawen/os-init set-fish
57
+ ```
58
+
59
+ Sets up Fish shell to automatically import environment variables from `.bashrc`. This command will:
60
+
61
+ - Detect if Fish shell is installed (by checking for `~/.config/fish/config.fish`)
62
+ - Add an import script to `~/.config/fish/config.fish` that reads environment variables from `~/.bashrc`
63
+ - Automatically convert Bash-style environment variable syntax to Fish shell syntax
64
+ - Handle PATH variables correctly (converting `:` separators to spaces)
65
+ - Skip if the import script already exists
66
+
67
+ This is useful when you have environment variables configured in `.bashrc` but want to use them in Fish shell without duplicating configuration.
68
+
69
+ Example:
70
+
71
+ ```bash
72
+ pnpx @gengjiawen/os-init set-fish
73
+ ```
74
+
53
75
  ---
54
76
 
55
77
  ### Configure Claude Code
package/android-setup.md CHANGED
@@ -58,7 +58,7 @@ os-init set-android --skip-env-vars
58
58
  - Internet connection to download Android SDK
59
59
  - Node.js 22.12.0 or higher
60
60
 
61
- The script uses `@compilets/unzip-url` package to download and extract the Android SDK, so no external `curl` or `unzip` tools are required. No sudo access is needed as the SDK is installed in your home directory.
61
+ The script uses `@gengjiawen/unzip-url` package to download and extract the Android SDK, so no external `curl` or `unzip` tools are required. No sudo access is needed as the SDK is installed in your home directory.
62
62
 
63
63
  ## Environment Variables
64
64
 
@@ -93,11 +93,13 @@ sdkmanager --list
93
93
  ## Platform-Specific Notes
94
94
 
95
95
  ### macOS
96
+
96
97
  - The SDK is downloaded from Google's macOS-specific distribution
97
98
  - Works on both Intel and Apple Silicon Macs
98
99
  - No sudo permissions required
99
100
 
100
101
  ### Linux
102
+
101
103
  - The SDK is downloaded from Google's Linux-specific distribution
102
104
  - Tested on Ubuntu and Debian-based systems
103
105
  - No sudo permissions required when installing to home directory
@@ -124,5 +126,3 @@ If the Android SDK download fails, check your internet connection and try again.
124
126
  - **Build Tools 36.0.0** - Tools for building Android apps
125
127
  - **CMake 3.30.5** - For native code compilation
126
128
  - **NDK 29.0.14206865** - Native Development Kit for C/C++ code
127
-
128
-
package/bin/bin.js CHANGED
@@ -10,6 +10,7 @@ const {
10
10
  setupDevEnvironment,
11
11
  setupAndroidEnvironment,
12
12
  } = require('../build')
13
+ const { appendFishImportScript } = require('../build/fish-shell-utils')
13
14
 
14
15
  const program = new Command()
15
16
 
@@ -128,4 +129,17 @@ program
128
129
  }
129
130
  })
130
131
 
132
+ program
133
+ .command('set-fish')
134
+ .description('setup Fish shell to import environment variables from .bashrc')
135
+ .action(async () => {
136
+ try {
137
+ appendFishImportScript()
138
+ console.log('\nāœ… Fish shell import script setup completed!')
139
+ } catch (err) {
140
+ console.error('Failed to setup Fish shell:', err.message)
141
+ process.exit(1)
142
+ }
143
+ })
144
+
131
145
  program.parse(process.argv)
@@ -52,7 +52,7 @@ function getAndroidEnvVars(androidHome, ndkVersion) {
52
52
  export ANDROID_HOME=${androidHome}
53
53
  export ANDROID_SDK_ROOT=\${ANDROID_HOME}
54
54
  export ANDROID_NDK_HOME=\${ANDROID_HOME}/ndk/${ndkVersion}
55
- export PATH=\${ANDROID_HOME}/cmdline-tools/bin:\${ANDROID_HOME}/cmdline-tools/latest/bin:\${ANDROID_HOME}/emulator:\${ANDROID_HOME}/platform-tools:\${ANDROID_HOME}/tools:\${ANDROID_HOME}/tools/bin:\${PATH}
55
+ export PATH=\${PATH}:\${ANDROID_HOME}/cmdline-tools/bin:\${ANDROID_HOME}/cmdline-tools/latest/bin:\${ANDROID_HOME}/emulator:\${ANDROID_HOME}/platform-tools:\${ANDROID_HOME}/tools:\${ANDROID_HOME}/tools/bin
56
56
  # ===== Android development environment - END =====
57
57
  `;
58
58
  }
@@ -77,7 +77,6 @@ function hasAndroidEnvVars(rcFile) {
77
77
  return content.includes('ANDROID_HOME');
78
78
  }
79
79
  function appendEnvVarsToShellConfig(rcFile, envVars) {
80
- const shell = process.env.SHELL || '';
81
80
  const homeDir = os.homedir();
82
81
  const bashrcFile = path.join(homeDir, '.bashrc');
83
82
  if (!fs.existsSync(bashrcFile) ||
@@ -88,9 +87,7 @@ function appendEnvVarsToShellConfig(rcFile, envVars) {
88
87
  else {
89
88
  console.log(`Environment variables already exist in: ${bashrcFile}`);
90
89
  }
91
- if (shell.includes('fish')) {
92
- (0, fish_shell_utils_1.appendFishImportScript)();
93
- }
90
+ (0, fish_shell_utils_1.appendFishImportScript)();
94
91
  }
95
92
  async function setupAndroidEnvironment(options) {
96
93
  const androidHome = options?.androidHome || getDefaultAndroidHome();
@@ -191,6 +188,7 @@ async function setupAndroidEnvironment(options) {
191
188
  appendEnvVarsToShellConfig(shellRcFile, envVars);
192
189
  envVarsAdded = true;
193
190
  }
191
+ (0, fish_shell_utils_1.appendFishImportScript)();
194
192
  console.log('\nšŸ“‹ To activate the environment in your current shell, run:');
195
193
  console.log(` source ${shellRcFile}`);
196
194
  }
@@ -5,6 +5,7 @@ const fs = require("fs");
5
5
  const path = require("path");
6
6
  const execa_1 = require("execa");
7
7
  const ip = require("ip");
8
+ const yaml = require("yaml");
8
9
  function ensureDir(dirPath) {
9
10
  fs.mkdirSync(dirPath, { recursive: true });
10
11
  }
@@ -53,6 +54,22 @@ async function setupDevEnvironment(sshPublicKey, targetDir) {
53
54
  else {
54
55
  throw new Error(`Dockerfile not found in ${targetPath}`);
55
56
  }
57
+ const tunDevicePath = '/dev/net/tun';
58
+ const dockerComposePath = path.join(targetPath, 'docker-compose.yml');
59
+ if (fs.existsSync(tunDevicePath) && fs.existsSync(dockerComposePath)) {
60
+ const content = fs.readFileSync(dockerComposePath, 'utf-8');
61
+ const doc = yaml.parse(content);
62
+ if (doc.services) {
63
+ const serviceName = Object.keys(doc.services)[0];
64
+ const service = doc.services[serviceName];
65
+ if (!service.devices?.includes('/dev/net/tun:/dev/net/tun')) {
66
+ service.devices = service.devices || [];
67
+ service.devices.push('/dev/net/tun:/dev/net/tun');
68
+ fs.writeFileSync(dockerComposePath, yaml.stringify(doc));
69
+ console.log('Added /dev/net/tun device to docker-compose.yml');
70
+ }
71
+ }
72
+ }
56
73
  const hasDockerCompose = await commandExists('docker-compose');
57
74
  if (hasDockerCompose) {
58
75
  console.log('\n🐳 Docker Compose detected. Building and starting containers...');
@@ -40,17 +40,14 @@ end
40
40
  function appendFishImportScript() {
41
41
  const homeDir = os.homedir();
42
42
  const fishConfigPath = path.join(homeDir, '.config', 'fish', 'config.fish');
43
- if (!fs.existsSync(fishConfigPath)) {
44
- console.log('Fish shell config not found, skipping fish setup');
45
- return;
46
- }
47
43
  const configDir = path.dirname(fishConfigPath);
48
44
  if (!fs.existsSync(configDir)) {
49
45
  fs.mkdirSync(configDir, { recursive: true });
50
46
  }
51
- const fishContent = fs.existsSync(fishConfigPath)
52
- ? fs.readFileSync(fishConfigPath, 'utf-8')
53
- : '';
47
+ if (!fs.existsSync(fishConfigPath)) {
48
+ fs.writeFileSync(fishConfigPath, '', 'utf-8');
49
+ }
50
+ const fishContent = fs.readFileSync(fishConfigPath, 'utf-8');
54
51
  if (!fishContent.includes('Import environment variables from .bashrc - START')) {
55
52
  const fishScript = getFishImportBashExports();
56
53
  fs.appendFileSync(fishConfigPath, fishScript);
@@ -2,6 +2,8 @@ FROM gengjiawen/node-build
2
2
 
3
3
  # SSHD configuration
4
4
  COPY sshd_config /etc/ssh/sshd_config
5
+ COPY init.sh /usr/local/bin/init.sh
6
+ RUN chmod +x /usr/local/bin/init.sh
5
7
 
6
8
  ENV SSH_PUBLIC_KEY="ssh-public-key-placeholder"
7
9
  RUN set -eux; \
@@ -12,5 +12,9 @@ services:
12
12
  - '5173:5173'
13
13
  cap_add:
14
14
  - SYS_PTRACE
15
+ - NET_ADMIN
15
16
  volumes:
16
17
  - .:/pwd
18
+ - ./.ssh_host_keys:/etc/ssh/host_keys
19
+ - ~/.gitconfig:/home/gitpod/.gitconfig:ro
20
+ command: ['/usr/local/bin/init.sh']
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Generate SSH host keys in mounted volume to keep fingerprints stable across rebuilds.
5
+ HOST_KEY_DIR="/etc/ssh/host_keys"
6
+
7
+ mkdir -p "${HOST_KEY_DIR}"
8
+
9
+ for key_type in rsa ed25519; do
10
+ key_path="${HOST_KEY_DIR}/ssh_host_${key_type}_key"
11
+ if [ ! -f "${key_path}" ]; then
12
+ echo "Generating ${key_type} host key..."
13
+ ssh-keygen -q -N "" -t "${key_type}" -f "${key_path}"
14
+ else
15
+ echo "${key_type} host key already exists."
16
+ fi
17
+ done
18
+
19
+ echo "Done. SSH host keys are ready."
20
+
21
+ # Fix ownership for gitpod user config/cache if needed
22
+ TARGET_UID=${TARGET_UID:-33333}
23
+ TARGET_GID=${TARGET_GID:-33333}
24
+ TARGET_HOME=/home/gitpod
25
+
26
+ fix_owner() {
27
+ local dir="$1"
28
+ [ -d "$dir" ] || return 0
29
+ local owner_uid owner_gid
30
+ owner_uid=$(stat -c %u "$dir")
31
+ owner_gid=$(stat -c %g "$dir")
32
+ if [ "$owner_uid" != "$TARGET_UID" ] || [ "$owner_gid" != "$TARGET_GID" ]; then
33
+ chown -R "${TARGET_UID}:${TARGET_GID}" "$dir"
34
+ fi
35
+ }
36
+
37
+ fix_owner "${TARGET_HOME}/.config"
38
+ fix_owner "${TARGET_HOME}/.cache"
39
+
40
+ exec /usr/sbin/sshd -D -e
@@ -1,9 +1,9 @@
1
1
  Port 22
2
2
  Protocol 2
3
3
 
4
- # Host keys are generated at runtime with `ssh-keygen -A`
5
- HostKey /etc/ssh/ssh_host_rsa_key
6
- HostKey /etc/ssh/ssh_host_ed25519_key
4
+ # Host keys are persisted in mounted volume to keep fingerprints stable
5
+ HostKey /etc/ssh/host_keys/ssh_host_rsa_key
6
+ HostKey /etc/ssh/host_keys/ssh_host_ed25519_key
7
7
 
8
8
  SyslogFacility AUTHPRIV
9
9
  LogLevel VERBOSE
@@ -58,7 +58,7 @@ function getAndroidEnvVars(androidHome: string, ndkVersion: string): string {
58
58
  export ANDROID_HOME=${androidHome}
59
59
  export ANDROID_SDK_ROOT=\${ANDROID_HOME}
60
60
  export ANDROID_NDK_HOME=\${ANDROID_HOME}/ndk/${ndkVersion}
61
- export PATH=\${ANDROID_HOME}/cmdline-tools/bin:\${ANDROID_HOME}/cmdline-tools/latest/bin:\${ANDROID_HOME}/emulator:\${ANDROID_HOME}/platform-tools:\${ANDROID_HOME}/tools:\${ANDROID_HOME}/tools/bin:\${PATH}
61
+ export PATH=\${PATH}:\${ANDROID_HOME}/cmdline-tools/bin:\${ANDROID_HOME}/cmdline-tools/latest/bin:\${ANDROID_HOME}/emulator:\${ANDROID_HOME}/platform-tools:\${ANDROID_HOME}/tools:\${ANDROID_HOME}/tools/bin
62
62
  # ===== Android development environment - END =====
63
63
  `
64
64
  }
@@ -88,7 +88,6 @@ function hasAndroidEnvVars(rcFile: string): boolean {
88
88
 
89
89
  /** Append Android environment variables to shell config */
90
90
  function appendEnvVarsToShellConfig(rcFile: string, envVars: string): void {
91
- const shell = process.env.SHELL || ''
92
91
  const homeDir = os.homedir()
93
92
  const bashrcFile = path.join(homeDir, '.bashrc')
94
93
 
@@ -104,9 +103,7 @@ function appendEnvVarsToShellConfig(rcFile: string, envVars: string): void {
104
103
  }
105
104
 
106
105
  // For fish shell, always write to bashrc first, then add import script to fish config
107
- if (shell.includes('fish')) {
108
- appendFishImportScript()
109
- }
106
+ appendFishImportScript()
110
107
  }
111
108
 
112
109
  /** Setup Android development environment */
@@ -265,6 +262,10 @@ export async function setupAndroidEnvironment(options?: {
265
262
  envVarsAdded = true
266
263
  }
267
264
 
265
+ // Always check and add fish import script if fish shell exists
266
+ // This ensures fish users get the import script even if current shell is not fish
267
+ appendFishImportScript()
268
+
268
269
  console.log('\nšŸ“‹ To activate the environment in your current shell, run:')
269
270
  console.log(` source ${shellRcFile}`)
270
271
  }
package/libs/dev-setup.ts CHANGED
@@ -2,6 +2,7 @@ import * as fs from 'fs'
2
2
  import * as path from 'path'
3
3
  import { execa } from 'execa'
4
4
  import * as ip from 'ip'
5
+ import * as yaml from 'yaml'
5
6
 
6
7
  /** Ensure directory exists */
7
8
  function ensureDir(dirPath: string): void {
@@ -68,6 +69,30 @@ export async function setupDevEnvironment(
68
69
  throw new Error(`Dockerfile not found in ${targetPath}`)
69
70
  }
70
71
 
72
+ // Check if /dev/net/tun exists and add devices to docker-compose.yml
73
+ const tunDevicePath = '/dev/net/tun'
74
+ const dockerComposePath = path.join(targetPath, 'docker-compose.yml')
75
+ if (fs.existsSync(tunDevicePath) && fs.existsSync(dockerComposePath)) {
76
+ const content = fs.readFileSync(dockerComposePath, 'utf-8')
77
+ const doc = yaml.parse(content) as {
78
+ services?: { [key: string]: { devices?: string[] } }
79
+ }
80
+
81
+ // Find the first service and add devices
82
+ if (doc.services) {
83
+ const serviceName = Object.keys(doc.services)[0]
84
+ const service = doc.services[serviceName]
85
+
86
+ // Check if devices already contains /dev/net/tun
87
+ if (!service.devices?.includes('/dev/net/tun:/dev/net/tun')) {
88
+ service.devices = service.devices || []
89
+ service.devices.push('/dev/net/tun:/dev/net/tun')
90
+ fs.writeFileSync(dockerComposePath, yaml.stringify(doc))
91
+ console.log('Added /dev/net/tun device to docker-compose.yml')
92
+ }
93
+ }
94
+ }
95
+
71
96
  // Check if docker-compose is available
72
97
  const hasDockerCompose = await commandExists('docker-compose')
73
98
 
@@ -46,22 +46,19 @@ export function appendFishImportScript(): void {
46
46
  const homeDir = os.homedir()
47
47
  const fishConfigPath = path.join(homeDir, '.config', 'fish', 'config.fish')
48
48
 
49
- // Detect if fish shell exists by checking config file
50
- if (!fs.existsSync(fishConfigPath)) {
51
- console.log('Fish shell config not found, skipping fish setup')
52
- return
53
- }
54
-
55
49
  // Ensure fish config directory exists
56
50
  const configDir = path.dirname(fishConfigPath)
57
51
  if (!fs.existsSync(configDir)) {
58
52
  fs.mkdirSync(configDir, { recursive: true })
59
53
  }
60
54
 
55
+ // Create fish config file if it doesn't exist
56
+ if (!fs.existsSync(fishConfigPath)) {
57
+ fs.writeFileSync(fishConfigPath, '', 'utf-8')
58
+ }
59
+
61
60
  // Check if import script already exists
62
- const fishContent = fs.existsSync(fishConfigPath)
63
- ? fs.readFileSync(fishConfigPath, 'utf-8')
64
- : ''
61
+ const fishContent = fs.readFileSync(fishConfigPath, 'utf-8')
65
62
 
66
63
  if (
67
64
  !fishContent.includes('Import environment variables from .bashrc - START')
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@gengjiawen/os-init",
3
3
  "private": false,
4
- "version": "1.9.0",
4
+ "version": "1.10.0",
5
5
  "description": "",
6
6
  "main": "index.js",
7
7
  "bin": {
@@ -9,7 +9,6 @@
9
9
  },
10
10
  "scripts": {
11
11
  "dev": "tsc -w",
12
- "server": "nodemon --exec ts-node libs/index.ts",
13
12
  "prepare": "husky",
14
13
  "clean": "rimraf build",
15
14
  "format": "prettier --write \"{examples,libs,script,bin}/**/*.{js,ts}\" \"**/*.yml\"",
@@ -19,10 +18,11 @@
19
18
  "postbuild": "cpy '**/*' '!**/*.ts' ../build/ --cwd=libs --parents"
20
19
  },
21
20
  "dependencies": {
22
- "@gengjiawen/unzip-url": "^1.0.0",
21
+ "@gengjiawen/unzip-url": "^1.1.0",
23
22
  "commander": "^12.1.0",
24
23
  "ip": "^2.0.1",
25
- "execa": "^8.0.1"
24
+ "execa": "^8.0.1",
25
+ "yaml": "^2.8.2"
26
26
  },
27
27
  "publishConfig": {
28
28
  "access": "public"
@@ -36,16 +36,16 @@
36
36
  },
37
37
  "devDependencies": {
38
38
  "@types/ip": "^1.1.3",
39
- "@types/jest": "29.5.12",
39
+ "@types/jest": "30.0.0",
40
40
  "@types/node": "24.1.0",
41
- "cpy-cli": "^4.2.0",
41
+ "cpy-cli": "^6.0.0",
42
42
  "husky": "9.1.6",
43
- "jest": "29.7.0",
43
+ "jest": "30.2.0",
44
44
  "lint-staged": "^15.2.2",
45
45
  "nodemon": "3.1.4",
46
46
  "prettier": "3.5.3",
47
- "rimraf": "5.0.5",
48
- "ts-jest": "29.2.4",
47
+ "rimraf": "6.1.2",
48
+ "ts-jest": "29.4.6",
49
49
  "ts-node": "^10.9.1",
50
50
  "typescript": "5.6.3"
51
51
  },