An HTTPS clone of a private repo prompts for a username, and under sudo with no interactive terminal that hangs or dies with "could not read Username" — which is what gitea.pastilhas.dev does right now. ssh://git@gitea.pastilhas.dev:2222/officerdev/platform.git instead, temporarily. Back to HTTPS when it is public; nothing else in the script cares which. Tested the path the script actually takes, not just the URL: the clone runs as the OWNER rather than root, and sudo drops SSH_AUTH_SOCK, so there is no agent to answer a passphrase. `sudo -u pastilhas env -u SSH_AUTH_SOCK git ls-remote` returns HEAD, so the key works unaided on this machine. A passphrase-protected key that relies on an agent would not. Still overridable with OFFICER_REPO. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
93 lines
4.2 KiB
Bash
93 lines
4.2 KiB
Bash
#!/bin/bash
|
|
# =============================================================================
|
|
# officer-setup — the repository
|
|
# =============================================================================
|
|
#
|
|
# Definitions only.
|
|
#
|
|
# ── Cloned as the owner, never as root ──
|
|
#
|
|
# A repository cloned by root is one the owner cannot pull, cannot commit in, and
|
|
# whose node_modules they cannot write. Every git operation here runs as the
|
|
# account, from a directory that account can stat.
|
|
|
|
[[ -n "${OFFICER_SETUP_REPO_LOADED:-}" ]] && return 0
|
|
OFFICER_SETUP_REPO_LOADED=1
|
|
|
|
# SSH rather than HTTPS, temporarily: the repository is private, and an HTTPS
|
|
# clone of a private repo prompts for a username — which under sudo, with no
|
|
# interactive terminal, hangs or dies with "could not read Username".
|
|
#
|
|
# The clone runs as the OWNER, not as root (see the note above), so it uses their
|
|
# ~/.ssh key. A passphrase-protected key normally answered by ssh-agent will not
|
|
# work here: sudo drops SSH_AUTH_SOCK, so there is no agent to ask. An unencrypted
|
|
# key, or one already accepted by the host, is what this expects.
|
|
#
|
|
# Back to HTTPS when the repository is public — nothing else here cares which.
|
|
OFFICER_REPO="${OFFICER_REPO:-ssh://git@gitea.pastilhas.dev:2222/officerdev/platform.git}"
|
|
|
|
platform_dir() { echo "${OFFICER_ROOT}/platform"; }
|
|
|
|
repo_exists() { [[ -d "$(platform_dir)/.git" ]]; }
|
|
|
|
repo_remote() { (cd "$(platform_dir)" 2>/dev/null && git remote get-url origin 2>/dev/null) || true; }
|
|
repo_branch() { (cd "$(platform_dir)" 2>/dev/null && git branch --show-current 2>/dev/null) || true; }
|
|
repo_is_dirty() { [[ -n "$(cd "$(platform_dir)" 2>/dev/null && git status --porcelain 2>/dev/null)" ]]; }
|
|
|
|
# Split an ssh:// URL into host and port, for the reachability check below.
|
|
repo_ssh_host() { sed -E 's|^ssh://[^@]*@([^:/]+).*|\1|' <<<"$1"; }
|
|
repo_ssh_port() { sed -nE 's|^ssh://[^@]*@[^:]+:([0-9]+)/.*|\1|p' <<<"$1"; }
|
|
|
|
# Can this account actually clone it?
|
|
#
|
|
# `git ls-remote` is the real question — not "does the host answer" but "can this
|
|
# account read this repository". Both prompts are disabled, because neither fails
|
|
# cleanly on its own: over https git asks for a username nobody is there to type,
|
|
# and over ssh it asks for a password or stops on host-key verification. With
|
|
# both off, an unreachable or unreadable repository is an immediate non-zero
|
|
# instead of a hang.
|
|
repo_reachable() {
|
|
as_owner "GIT_TERMINAL_PROMPT=0 \
|
|
GIT_SSH_COMMAND='ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ConnectTimeout=8' \
|
|
timeout 20 git ls-remote '$1' >/dev/null 2>&1" /
|
|
}
|
|
|
|
# The https form of the same repository, for a machine with no key.
|
|
repo_https_url() {
|
|
sed -E 's|^ssh://[^@]*@([^:/]+)(:[0-9]+)?/|https://\1/|' <<<"$1"
|
|
}
|
|
|
|
clone_repo() {
|
|
local url="$1" dest
|
|
dest="$(platform_dir)"
|
|
install -d -m 0755 -o "$USERNAME" -g "$(user_group)" "$OFFICER_ROOT"
|
|
as_owner "GIT_TERMINAL_PROMPT=0 git clone '${url}' '${dest}'" /
|
|
}
|
|
|
|
pull_repo() { as_owner "git -C '$(platform_dir)' pull --ff-only" /; }
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Dependencies
|
|
# -----------------------------------------------------------------------------
|
|
#
|
|
# ── The lockfile is frozen, and that is the point ──
|
|
#
|
|
# bunfig.toml sets [install] frozenLockfile = true, so `bun install` resolves from
|
|
# bun.lock and nothing else. A package.json that disagrees with the lockfile is a
|
|
# hard failure rather than a quiet resolution — which is deliberate: the friction
|
|
# exists so that an unexplained lockfile change shows up in a diff. See the
|
|
# supply-chain note in CLAUDE.md.
|
|
#
|
|
# So a failure here is usually one of two things, and they need different
|
|
# answers: the lockfile genuinely disagrees with package.json, or node-pty failed
|
|
# to build. Both are reported as such rather than as "install failed".
|
|
|
|
deps_installed() { [[ -d "$(platform_dir)/node_modules" ]]; }
|
|
|
|
# node-pty has no Linux prebuild, so `bun install` compiles it every time. This is
|
|
# the artefact that proves it worked, and its absence is why the terminal sidecar
|
|
# would not start.
|
|
node_pty_built() { compgen -G "$(platform_dir)/node_modules/node-pty/build/Release/*.node" >/dev/null 2>&1; }
|
|
|
|
install_deps() { as_owner "cd '$(platform_dir)' && bun install 2>&1"; }
|