@@ -17,6 +17,11 @@ if ! id "$DEV_USER" &>/dev/null; then
1717fi
1818DEV_HOME=$( eval echo " ~$DEV_USER " )
1919
20+ # Fix up plugin paths — skel has __HOMEDIR__ placeholders from build time
21+ for f in " $DEV_HOME /.claude/plugins/known_marketplaces.json" " $DEV_HOME /.claude/plugins/installed_plugins.json" ; do
22+ [ -f " $f " ] && sed -i " s|__HOMEDIR__|$DEV_HOME |g" " $f "
23+ done
24+
2025# ── Set up Claude Code config + credentials in user's home ────
2126log " Setting up credentials..."
2227mkdir -p " $DEV_HOME /.claude"
@@ -29,38 +34,33 @@ elif [ -f "$DEV_HOME/.claude/.credentials.json" ]; then
2934 log " OAuth credentials already exist, skipping"
3035fi
3136
32- # Write .claude.json on first boot only — Claude Code may update it at runtime
33- if [ ! -f " $DEV_HOME /.claude.json" ]; then
34- API_KEY_FIELD=" "
35- if [ -n " $CLAUDE_API_KEY " ]; then
36- API_KEY_FIELD=" \" primaryApiKey\" : \" $CLAUDE_API_KEY \" ,"
37- log " Including API key in config"
38- fi
39- cat > " $DEV_HOME /.claude.json" << CJSON
40- {
41- $API_KEY_FIELD
42- "numStartups": 1,
43- "installMethod": "npm",
44- "autoUpdates": false,
45- "hasCompletedOnboarding": true,
46- "effortCalloutDismissed": true,
47- "bypassPermissionsModeAccepted": true,
48- "projects": {
49- "$APP_DIR ": {
50- "allowedTools": [],
51- "mcpContextUris": [],
52- "mcpServers": {},
53- "enabledMcpjsonServers": [],
54- "disabledMcpjsonServers": [],
55- "hasTrustDialogAccepted": true
56- }
57- }
58- }
59- CJSON
60- log " Wrote .claude.json config"
61- else
62- log " .claude.json already exists, skipping"
63- fi
37+ # Merge required fields into .claude.json (skel provides plugin registrations;
38+ # we overlay credentials, onboarding flags, and project trust on every boot).
39+ log " Updating .claude.json..."
40+ node -e "
41+ const fs = require('fs');
42+ const path = '$DEV_HOME /.claude.json';
43+ let cfg = {};
44+ try { cfg = JSON.parse(fs.readFileSync(path, 'utf-8')); } catch {}
45+ const apiKey = process.env.CLAUDE_API_KEY || '';
46+ if (apiKey) cfg.primaryApiKey = apiKey;
47+ Object.assign(cfg, {
48+ hasCompletedOnboarding: true,
49+ effortCalloutDismissed: true,
50+ bypassPermissionsModeAccepted: true,
51+ });
52+ cfg.projects = cfg.projects || {};
53+ cfg.projects['$APP_DIR '] = Object.assign(cfg.projects['$APP_DIR '] || {}, {
54+ allowedTools: [],
55+ mcpContextUris: [],
56+ mcpServers: {},
57+ enabledMcpjsonServers: [],
58+ disabledMcpjsonServers: [],
59+ hasTrustDialogAccepted: true,
60+ });
61+ fs.writeFileSync(path, JSON.stringify(cfg, null, 2) + '\n');
62+ "
63+ log " .claude.json updated"
6464
6565if [ -z " $CLAUDE_OAUTH_CREDENTIALS " ] && [ -z " $CLAUDE_API_KEY " ]; then
6666 log " WARNING: No Claude credentials found (CLAUDE_OAUTH_CREDENTIALS / CLAUDE_API_KEY not set)"
@@ -77,35 +77,34 @@ if [ ! -f "$APP_DIR/package.json" ]; then
7777 su -s /bin/bash " $DEV_USER " -c " cd '$APP_DIR ' && " $OPFLOW " init '$APP_NAME ' --dir . --no-install"
7878
7979 # Symlink base packages into /data/app/node_modules (one-time, persists on volume).
80- # npm sees symlinks as installed packages — subsequent `npm install <pkg>` only writes
81- # the new package. Uninstalls remove symlinks and they stay gone (not recreated on boot).
82- # Upgrades: npm replaces symlinks with real dirs as needed.
83- log " Symlinking base packages..."
84- mkdir -p " $APP_DIR /node_modules/.bin"
85- for pkg in /node_modules/* ; do
86- name=" $( basename " $pkg " ) "
87- [[ " $name " == .* ]] && continue # skip .bin, .package-lock.json, .cache, etc.
88- if [[ " $name " == @* ]] && [ -d " $pkg " ]; then
89- # Scoped package dir (e.g. @scope) — link individual packages so npm can
90- # still add new packages under the same scope without hitting a symlink.
91- mkdir -p " $APP_DIR /node_modules/$name "
92- for scoped in " $pkg " /* ; do
93- sname=" $( basename " $scoped " ) "
94- [ ! -e " $APP_DIR /node_modules/$name /$sname " ] && \
95- ln -s " $scoped " " $APP_DIR /node_modules/$name /$sname "
96- done
97- else
98- [ ! -e " $APP_DIR /node_modules/$name " ] && \
99- ln -s " $pkg " " $APP_DIR /node_modules/$name "
100- fi
101- done
102- # Symlink .bin entries so npm scripts work without a local install
103- for cmd in /node_modules/.bin/* ; do
104- cname=" $( basename " $cmd " ) "
105- [ ! -e " $APP_DIR /node_modules/.bin/$cname " ] && \
106- ln -s " $cmd " " $APP_DIR /node_modules/.bin/$cname "
107- done
108- chown -R " $DEV_USER :devs" " $APP_DIR /node_modules"
80+ # DISABLED: npm treats symlinks as linked packages and runs their lifecycle scripts,
81+ # which fails (e.g. husky not found). Base packages at /node_modules/ are still
82+ # found by Node.js via parent-directory resolution from /data/app.
83+ # TODO: revisit — either run `npm install` during scaffold or find a symlink-compatible approach.
84+ #
85+ # log " Symlinking base packages..."
86+ # mkdir -p "$APP_DIR/node_modules/.bin"
87+ # for pkg in /node_modules/*; do
88+ # name="$(basename "$pkg")"
89+ # [[ "$name" == .* ]] && continue
90+ # if [[ "$name" == @* ]] && [ -d "$pkg" ]; then
91+ # mkdir -p "$APP_DIR/node_modules/$name"
92+ # for scoped in "$pkg"/*; do
93+ # sname="$(basename "$scoped")"
94+ # [ ! -e "$APP_DIR/node_modules/$name/$sname" ] && \
95+ # ln -s "$scoped" "$APP_DIR/node_modules/$name/$sname"
96+ # done
97+ # else
98+ # [ ! -e "$APP_DIR/node_modules/$name" ] && \
99+ # ln -s "$pkg" "$APP_DIR/node_modules/$name"
100+ # fi
101+ # done
102+ # for cmd in /node_modules/.bin/*; do
103+ # cname="$(basename "$cmd")"
104+ # [ ! -e "$APP_DIR/node_modules/.bin/$cname" ] && \
105+ # ln -s "$cmd" "$APP_DIR/node_modules/.bin/$cname"
106+ # done
107+ # chown -R "$DEV_USER:devs" "$APP_DIR/node_modules"
109108 log " Scaffold complete"
110109fi
111110
@@ -123,14 +122,6 @@ mkdir -p "$APP_DIR/node_modules"
123122ln -sfn /node_modules/0pflow " $APP_DIR /node_modules/0pflow"
124123chown -h " $DEV_USER :devs" " $APP_DIR /node_modules" " $APP_DIR /node_modules/0pflow" 2> /dev/null || true
125124
126- # ── Complete Claude Code native installer migration (suppresses startup prompt) ──
127- log " Completing Claude Code native install for $DEV_USER ..."
128- su -s /bin/bash " $DEV_USER " -c " HOME='$DEV_HOME ' claude install" 2> /dev/null || true
129-
130- # ── Register 0pflow Claude Code plugin for DEV_USER ────────────
131- log " Installing 0pflow plugin for $DEV_USER ..."
132- su -s /bin/bash " $DEV_USER " -c " HOME='$DEV_HOME ' " $OPFLOW " install" 2> /dev/null || true
133-
134125# ── SSH server setup ──────────────────────────────────────────
135126# Persist host keys on volume so they survive redeployments (avoids "host key changed" warnings)
136127if [ ! -f /data/ssh_host_keys/ssh_host_ed25519_key ]; then
0 commit comments