@@ -71,9 +71,23 @@ async function bootstrapDeployUser(
7171 return true ;
7272}
7373
74+ /**
75+ * Wrap a command so it runs as `user` when set, otherwise as the current SSH user.
76+ * `bash -lc` gives the target user a login shell so $HOME/$PATH resolve to theirs.
77+ */
78+ function asUser ( user : string | null , cmd : string ) : string {
79+ if ( ! user ) return cmd ;
80+ const escaped = cmd . replace ( / ' / g, "'\\''" ) ;
81+ return `SUDO=""; [ "$EUID" -ne 0 ] && SUDO="sudo"; $SUDO -u "${ user } " bash -lc '${ escaped } '` ;
82+ }
83+
7484function buildTasks ( executor : RemoteExecutor , config : ShipnodeConfig , ownerUser : string | null ) {
7585 const nodeVersion = config . nodeVersion === 'lts' ? '24' : config . nodeVersion ;
7686 const mise = `export PATH="$HOME/.local/bin:$HOME/.local/share/mise/shims:$PATH"` ;
87+ // When a deploy user was bootstrapped, install mise/node/pm2 into their home
88+ // so PM2 processes run as that user and `pm2-<user>.service` matches. Without
89+ // this, everything lands in root's home and deploy has no pm2 on their PATH.
90+ const targetHome = ownerUser ? `/home/${ ownerUser } ` : '$HOME' ;
7791
7892 return new Listr (
7993 [
@@ -96,22 +110,25 @@ function buildTasks(executor: RemoteExecutor, config: ShipnodeConfig, ownerUser:
96110 ] , { concurrent : false } ) ,
97111 } ,
98112 {
99- title : ' Mise (version manager)' ,
113+ title : ` Mise (version manager)${ ownerUser ? ` for ${ ownerUser } ` : '' } ` ,
100114 task : ( ) =>
101115 executor . execOrThrow (
102- `if ! command -v mise &>/dev/null; then curl -fsSL https://mise.run | sh; fi` ,
116+ asUser (
117+ ownerUser ,
118+ `if ! command -v mise &>/dev/null && [ ! -x "$HOME/.local/bin/mise" ]; then curl -fsSL https://mise.run | sh; fi` ,
119+ ) ,
103120 ) ,
104121 } ,
105122 {
106123 title : `Node.js ${ config . nodeVersion } ` ,
107124 task : ( _ctx : object , task : any ) => task . newListr ( [
108125 {
109126 title : `Install node@${ nodeVersion } ` ,
110- task : ( ) => executor . execOrThrow ( `${ mise } ; mise install -y "node@${ nodeVersion } "` ) ,
127+ task : ( ) => executor . execOrThrow ( asUser ( ownerUser , `${ mise } ; mise install -y "node@${ nodeVersion } "` ) ) ,
111128 } ,
112129 {
113130 title : `Set node@${ nodeVersion } as global default` ,
114- task : ( ) => executor . execOrThrow ( `${ mise } ; mise use -g -y "node@${ nodeVersion } "` ) ,
131+ task : ( ) => executor . execOrThrow ( asUser ( ownerUser , `${ mise } ; mise use -g -y "node@${ nodeVersion } "` ) ) ,
115132 } ,
116133 ] , { concurrent : false } ) ,
117134 } ,
@@ -121,18 +138,31 @@ function buildTasks(executor: RemoteExecutor, config: ShipnodeConfig, ownerUser:
121138 {
122139 title : 'Install pm2' ,
123140 task : ( ) => executor . execOrThrow (
124- `${ mise } ; ` +
125- `if ! mise exec "node@${ nodeVersion } " -- pm2 --version &>/dev/null; then ` +
126- ` mise exec "node@${ nodeVersion } " -- npm install -g pm2; ` +
127- `fi` ,
141+ asUser (
142+ ownerUser ,
143+ `${ mise } ; ` +
144+ `if ! mise exec "node@${ nodeVersion } " -- pm2 --version &>/dev/null; then ` +
145+ ` mise exec "node@${ nodeVersion } " -- npm install -g pm2; ` +
146+ `fi` ,
147+ ) ,
128148 ) ,
129149 } ,
130150 {
131- title : 'Configure systemd startup' ,
151+ // pm2 startup writes /etc/systemd/system/pm2-<user>.service — needs root
152+ // to write the unit + enable it, but the unit targets the deploy user.
153+ // pm2 save writes ~/.pm2/dump.pm2 for that user and must run as them.
154+ title : `Configure systemd startup (${ ownerUser ?? '$USER' } )` ,
132155 task : ( ) => executor . execOrThrow (
133- `${ mise } ; ` +
134- `mise exec "node@${ nodeVersion } " -- pm2 startup systemd -u $USER --hp $HOME || true; ` +
135- `mise exec "node@${ nodeVersion } " -- pm2 save --force || true` ,
156+ ( ownerUser
157+ ? `SUDO=""; [ "$EUID" -ne 0 ] && SUDO="sudo"; ` +
158+ `PM2_BIN="${ targetHome } /.local/share/mise/shims/pm2"; ` +
159+ `$SUDO env PATH="${ targetHome } /.local/share/mise/shims:$PATH" "$PM2_BIN" startup systemd -u "${ ownerUser } " --hp "${ targetHome } " || true`
160+ : `${ mise } ; mise exec "node@${ nodeVersion } " -- pm2 startup systemd -u $USER --hp $HOME || true` ) +
161+ ` && ` +
162+ asUser (
163+ ownerUser ,
164+ `${ mise } ; mise exec "node@${ nodeVersion } " -- pm2 save --force || true` ,
165+ ) ,
136166 ) ,
137167 } ,
138168 ] , { concurrent : false } ) ,
0 commit comments