@gengjiawen/os-init 1.9.1 ā 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 +10 -0
- package/README.md +22 -0
- package/bin/bin.js +14 -0
- package/build/android-setup.js +3 -5
- package/build/dev-setup.js +17 -0
- package/build/fish-shell-utils.js +4 -7
- package/dev-setup/Dockerfile +2 -0
- package/dev-setup/docker-compose.yml +4 -0
- package/dev-setup/init.sh +40 -0
- package/dev-setup/sshd_config +3 -3
- package/libs/android-setup.ts +6 -5
- package/libs/dev-setup.ts +25 -0
- package/libs/fish-shell-utils.ts +6 -9
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
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
|
+
|
|
3
13
|
## [1.9.1](https://github.com/gengjiawen/os-init/compare/v1.9.0...v1.9.1) (2025-11-26)
|
|
4
14
|
|
|
5
15
|
|
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/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)
|
package/build/android-setup.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
}
|
package/build/dev-setup.js
CHANGED
|
@@ -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
|
-
|
|
52
|
-
|
|
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);
|
package/dev-setup/Dockerfile
CHANGED
|
@@ -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
|
package/dev-setup/sshd_config
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Port 22
|
|
2
2
|
Protocol 2
|
|
3
3
|
|
|
4
|
-
# Host keys are
|
|
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
|
package/libs/android-setup.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
package/libs/fish-shell-utils.ts
CHANGED
|
@@ -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.
|
|
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.
|
|
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\"",
|
|
@@ -22,7 +21,8 @@
|
|
|
22
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": "
|
|
39
|
+
"@types/jest": "30.0.0",
|
|
40
40
|
"@types/node": "24.1.0",
|
|
41
41
|
"cpy-cli": "^6.0.0",
|
|
42
42
|
"husky": "9.1.6",
|
|
43
|
-
"jest": "
|
|
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
47
|
"rimraf": "6.1.2",
|
|
48
|
-
"ts-jest": "29.
|
|
48
|
+
"ts-jest": "29.4.6",
|
|
49
49
|
"ts-node": "^10.9.1",
|
|
50
50
|
"typescript": "5.6.3"
|
|
51
51
|
},
|