Skip to content

fix(installer): vendor CLI installs with CONFIG_URL - #225

Merged
pjcdawkins merged 7 commits into
mainfrom
cli-203-installer-vendor-config
Oct 14, 2026
Merged

pjcdawkins merged 7 commits into
mainfrom
cli-203-installer-vendor-config

Conversation

@pjcdawkins

Copy link
Copy Markdown
Contributor

Fixes for installing an alternative CLI with CONFIG_URL (added in #215, not released yet), found by testing in Debian and Alpine containers.

  • Shim on the PATH: config:install now gets --bin-dir set to the directory of the installed upsun, when it is writable. Root installs (e.g. in CI) previously put the shim in ~/.platform-alt/bin, which is not on the PATH. Homebrew's directory is left alone.
  • Shared root installs: as root, outside the home directory, the config goes in /etc/upsun/platform-alt (mode 755, umask 022), so other users can run the shim, e.g. a Dockerfile that installs as root and then switches USER.
  • --force: the installer is explicitly installing this config, so an existing executable with the same name no longer stops it. If another one comes first in the PATH, a warning names it.
  • Outro: names the alternative CLI's executable (read from config:install's output), with its full path if it is not on the PATH.
  • Command failures: call_user returned the status of indent, so failures were never retried with sudo, e.g. moving the binary to /usr/bin as a non-root user. The raw method now also falls back to ~/.local/bin when the default directory is not writable and sudo is not available.
  • Symlinked INSTALL_DIR: resolved before passing --bin-dir, which rejects symlinks.
  • Config updates (Go): skipped when the config's directory is not writable (a shared config is left to root), and the file's mode is kept, so a root update under a restrictive umask does not make a shared config unreadable.

scripts/test/installer-config.sh runs the installer with CONFIG_URL against a local make single build in containers: apt, apk and raw as root, raw as a non-root user with and without sudo, over an existing executable, and with umask 077 and a symlinked INSTALL_DIR. The apt and apk cases install the released package.

🤖 Generated with Claude Code

pjcdawkins and others added 6 commits October 14, 2026 20:42
…ilures

With CONFIG_URL, install_config now runs config:install with --force and,
if the directory of the installed upsun is writable, --bin-dir set to it.
Root installs (e.g. in CI) put the vendor executable in /usr/bin next to
upsun, instead of ~/.platform-alt/bin, which is not on the PATH. An
existing executable with the same name (e.g. from a previous installer)
no longer stops the install; if another one comes first in the PATH, a
warning names it.

The outro names the vendor executable, read from config:install's output,
with its full path if it is not found on the PATH.

call_user returned the status of indent, so a failed command was never
retried with sudo, e.g. moving the binary to /usr/bin as a non-root user.
It now returns the command's status. The raw method installs in
~/.local/bin if the default directory is not writable and sudo is not
available.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
scripts/test/installer-config.sh serves a local build and the test config,
and runs installer.sh with CONFIG_URL in Debian and Alpine containers:
apt, apk and raw installs as root, raw installs as a non-root user with
and without sudo, and an install over an existing executable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
As root, when the vendor executable goes in a system directory such as
/usr/bin, its configuration was saved in root's home directory, which
other users usually cannot read. The installer now passes
--config-dir /etc/platform-alt in that case, e.g. for a Dockerfile that
installs as root and then switches to another user.

Config auto-updates are skipped when the config's directory is not
writable, so other users do not get an error for a config that only root
can update.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
config:install rejects a symlink as --bin-dir, so a symlinked INSTALL_DIR
made the install fail. The directory is now resolved first, and paths are
compared by file rather than by name.

With a umask such as 077, a shared root install created files that other
users could not read or run. The umask is set to 022 for that install,
and /etc/platform-alt is made readable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Config updates wrote a new file with mode 0644 filtered by the umask, and
renamed it over the old one. Under a restrictive umask, a shared config
(e.g. in /etc/platform-alt, updated by root) became unreadable to other
users. Updates now keep the existing file's mode, and alt files are
written with their mode regardless of the umask.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The directory is named after the package that writes it, leaving
/etc/upsun for other system-wide settings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@upsun-dispatch upsun-dispatch Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Changes suggested — 🟡 3 warnings · 🔵 2 minor points

🔍 Full review · 7 files reviewed

Verification
  • call_user now returns sh -c's exit code under set -eu; I ran it in dash and bash: failures reach if !/|| callers, and success does not abort the script.
  • Update registers the deferred LastChecked save before the new writability check, so an unwritable shared config is checked once per interval rather than on every run.
  • writeFile's explicit os.Chmod fixes the mode even when a leftover .tmp file exists, and Update passes the original file's permission bits.
  • config:install defines --bin-dir, --config-dir and --force, and prints Executable: &lt;path> to stderr, which the installer captures with 2>&1 and parses with sed.

New Go tests cover the Go changes: TestUpdateWithUnwritableDir and TestUpdateKeepsMode (unix only), run by the Go tests in ci.yml. The installer changes are only exercised by the manual Docker script scripts/test/installer-config.sh. test-installer.yml runs installer.sh but never sets CONFIG_URL, so no CI job covers the new install_config or check_directories paths.

Review details
  • Commit: 32e8fad
  • Model: claude-opus-5-5

Review 1 of 10 for this pull request · View the full run

Comment thread installer.sh Outdated
Comment thread installer.sh
Comment thread README.md Outdated
Comment thread scripts/test/installer-config.sh Outdated
Comment thread scripts/test/installer-config.sh Outdated
- config:install shortened paths that only share a string prefix with the
  home directory, e.g. "~usr/bin" for HOME=/. It now requires a path
  separator after the home directory.
- The raw method's fallback to ~/.local/bin no longer applies to a
  directory in the home directory that does not exist yet, e.g.
  ~/.global/bin.
- The README says the executable is not put next to upsun with Homebrew.
- The container tests fail when the installer fails, rather than taking
  tee's status, and use the Docker host's architecture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@pjcdawkins
pjcdawkins merged commit 5149f7d into main Oct 14, 2026
14 checks passed
@pjcdawkins
pjcdawkins deleted the cli-203-installer-vendor-config branch October 14, 2026 21:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant