Skip to content

Commit 84b1051

Browse files
author
test2
committed
fix: sourcing login shell for linux breaking for installs. Apt install held by other process bug.
1 parent 0ac4013 commit 84b1051

4 files changed

Lines changed: 56 additions & 15 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@codifycli/plugin-core",
3-
"version": "1.2.6",
3+
"version": "1.2.7",
44
"description": "TypeScript library for building Codify plugins to manage system resources (applications, CLI tools, settings) through infrastructure-as-code",
55
"main": "dist/index.js",
66
"typings": "dist/index.d.ts",

src/pty/background-pty.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@ EventEmitter.defaultMaxListeners = 1000;
2121
*/
2222
export class BackgroundPty implements IPty {
2323
private historyIgnore = Utils.getShell() === Shell.ZSH ? { HISTORY_IGNORE: '*' } : { HISTIGNORE: '*' };
24-
// Login + interactive (-l -i) so the user's rc files are sourced. GUI launches
25-
// (e.g. the Codify desktop app) do not inherit env like TART_HOME / PATH additions
26-
// from the parent process; these live in ~/.zshrc / ~/.zprofile.
27-
private basePty = pty.spawn(this.getDefaultShell(), ['-l', '-i'], {
24+
// Add -l (login shell) only for GUI launches (e.g. the Codify desktop app),
25+
// detected via the absence of process.env.SHELL, so rc files sourced via
26+
// ~/.zshrc/~/.zprofile are picked up. On terminal/CI launches, avoid -l:
27+
// it sources ~/.profile/~/.bash_profile instead of ~/.bashrc on Linux,
28+
// hiding PATH exports written by FileUtils.addToShellRc().
29+
private basePty = pty.spawn(this.getDefaultShell(), Utils.needsLoginShell() ? ['-l', '-i'] : ['-i'], {
2830
env: { ...process.env, ...this.historyIgnore },
2931
cols: 10_000, // Set to a really large value to prevent wrapping
3032
name: nanoid(6),

src/pty/seqeuntial-pty.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,11 +92,13 @@ export class SequentialPty implements IPty {
9292
// the pty waiting for interactive input. Can't be disabled via env var; must unset the options explicitly.
9393
const disableAutocorrect = Utils.getShell() === Shell.ZSH ? 'unsetopt CORRECT CORRECT_ALL 2>/dev/null; ' : '';
9494
const wrappedCmd = `${disableAutocorrect}${cmd}`;
95-
// Use a login + interactive shell (-l -i) so the user's rc files are sourced.
96-
// This matters for GUI launches (e.g. the Codify desktop app) where env vars
97-
// like TART_HOME and PATH additions live in ~/.zshrc/~/.zprofile and are not
98-
// inherited from the parent process.
99-
const args = options?.interactive ? ['-l', '-i', '-c', wrappedCmd] : ['-c', wrappedCmd]
95+
// Add -l (login shell) only for GUI launches (e.g. the Codify desktop app),
96+
// detected via the absence of process.env.SHELL, so rc files sourced via
97+
// ~/.zshrc/~/.zprofile are picked up. On terminal/CI launches, avoid -l:
98+
// it sources ~/.profile/~/.bash_profile instead of ~/.bashrc on Linux,
99+
// hiding PATH exports written by FileUtils.addToShellRc().
100+
const loginFlag = Utils.needsLoginShell() ? ['-l'] : [];
101+
const args = options?.interactive ? [...loginFlag, '-i', '-c', wrappedCmd] : ['-c', wrappedCmd]
100102

101103
// Run the command in a pty for interactivity
102104
const mPty = pty.spawn(this.getDefaultShell(), args, {

src/utils/index.ts

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as fs from 'node:fs/promises';
44
import os from 'node:os';
55
import path from 'node:path';
66

7-
import { getPty, SpawnStatus } from '../pty/index.js';
7+
import { getPty, SpawnResult, SpawnStatus } from '../pty/index.js';
88

99
export function isDebug(): boolean {
1010
return process.env.DEBUG != null && process.env.DEBUG.includes('codify'); // TODO: replace with debug library
@@ -146,6 +146,22 @@ export const Utils = {
146146
return process.env.SHELL || os.userInfo().shell || '/bin/zsh';
147147
},
148148

149+
/**
150+
* Whether interactive spawns should add `-l` (login shell) on top of `-i`.
151+
* Only needed when `process.env.SHELL` is unset — the signal that Codify was
152+
* launched outside a terminal (e.g. the desktop app via launchd on macOS),
153+
* where GUI-launched processes don't inherit the user's rc-file env (e.g.
154+
* TART_HOME, PATH additions).
155+
*
156+
* When `SHELL` *is* set (normal terminal/CI launches), a login shell must be
157+
* avoided: on Linux, `-l` sources `~/.profile`/`~/.bash_profile` instead of
158+
* `~/.bashrc` (often not sourcing `.bashrc` at all, since CI images
159+
* typically ship no `~/.bash_profile`), so PATH exports written by
160+
* `FileUtils.addToShellRc()` become invisible to later interactive spawns.
161+
*/
162+
needsLoginShell(): boolean {
163+
return !process.env.SHELL;
164+
},
149165

150166
getPrimaryShellRc(): string {
151167
return this.getShellRcFiles()[0];
@@ -274,10 +290,25 @@ Brew can be installed using Codify:
274290
if (isAptInstalled.status === SpawnStatus.SUCCESS) {
275291
await $.spawn('apt-get update', { requiresRoot: true });
276292
const flagStr = extraFlags.length > 0 ? `${extraFlags.join(' ')} ` : '';
277-
const { status, data } = await $.spawnSafe(`apt-get -y -qq install -o Dpkg::Use-Pty=0 -o Dpkg::Progress-Fancy=0 ${flagStr}${packageName}`, {
278-
requiresRoot: true,
279-
env: { DEBIAN_FRONTEND: 'noninteractive', NEEDRESTART_MODE: 'a' }
280-
});
293+
294+
let status: SpawnResult['status'] = SpawnStatus.ERROR;
295+
let data = '';
296+
const maxLockRetries = 5;
297+
for (let attempt = 0; attempt <= maxLockRetries; attempt++) {
298+
({ status, data } = await $.spawnSafe(`apt-get -y -qq install -o Dpkg::Use-Pty=0 -o Dpkg::Progress-Fancy=0 ${flagStr}${packageName}`, {
299+
requiresRoot: true,
300+
env: { DEBIAN_FRONTEND: 'noninteractive', NEEDRESTART_MODE: 'a' }
301+
}));
302+
303+
// dpkg/apt lock is held by another process (e.g. unattended-upgrades on a fresh VM).
304+
// This isn't a broken-dependency condition, so back off and retry rather than falling
305+
// through to `apt-get install -f`, which will just hit the same lock and fail too.
306+
const isLockContention = status === SpawnStatus.ERROR
307+
&& (data.includes('Could not get lock') || data.includes('dpkg frontend lock'));
308+
if (!isLockContention || attempt === maxLockRetries) break;
309+
310+
await new Promise((resolve) => setTimeout(resolve, 5000 * (attempt + 1)));
311+
}
281312

282313
if (status === SpawnStatus.ERROR && data.includes('E: dpkg was interrupted, you must manually run \'sudo dpkg --configure -a\' to correct the problem.')) {
283314
await $.spawn('dpkg --configure -a', { requiresRoot: true });
@@ -288,6 +319,12 @@ Brew can be installed using Codify:
288319
return;
289320
}
290321

322+
const isLockContention = status === SpawnStatus.ERROR
323+
&& (data.includes('Could not get lock') || data.includes('dpkg frontend lock'));
324+
if (isLockContention) {
325+
throw new Error(`Failed to install package ${packageName} via apt: dpkg/apt lock held by another process after ${maxLockRetries} retries: ${data}`);
326+
}
327+
291328
if (status === SpawnStatus.ERROR) {
292329
// Attempt to fix broken dependencies then retry
293330
const fixResult = await $.spawnSafe('apt-get install -f -y -o Dpkg::Use-Pty=0 -o Dpkg::Progress-Fancy=0', {

0 commit comments

Comments
 (0)