officer-setup section 2: the repository
Clones from ssh://git@gitea.officer.dev:2222/officerdev/platform.git, or uses the checkout already at $OFFICER_ROOT/platform. Cloned as the account, never as root. A repository owned by root is one the owner cannot pull, cannot commit in, and whose node_modules they cannot write — and every later section in this script writes into that directory as them. Three things it refuses to do quietly: It does not repoint an existing remote. This checkout points at gitea.pastilhas.dev rather than the new gitea.officer.dev; that is reported with the command to change it, because where somebody's work pushes to is their decision. It does not pull over uncommitted changes. A dirty tree means the pull is skipped and said so, rather than failing halfway or burying the work. It pulls with --ff-only, so a failure means the branch has diverged rather than that the network was down, and the message says which. SSH reachability is checked before the clone, not after. An ssh URL with no usable key does not fail cleanly: git prompts for a password nobody is there to type, or stops on host-key verification. BatchMode turns both into an immediate answer, and the check reads the server's response rather than the exit code — Gitea greets a successful authentication and then exits 1, so exit status alone reports success as failure. When the key is missing it offers the https form of the same URL, which works without a key if the repository is readable anonymously, and otherwise stops and says to add the key. Verified against the new host: ssh authentication from this account already works. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,8 @@ done
|
|||||||
source "$SCRIPT_DIR/officer-setup/lib/base.sh"
|
source "$SCRIPT_DIR/officer-setup/lib/base.sh"
|
||||||
# shellcheck source=officer-setup/lib/preflight.sh
|
# shellcheck source=officer-setup/lib/preflight.sh
|
||||||
source "$SCRIPT_DIR/officer-setup/lib/preflight.sh"
|
source "$SCRIPT_DIR/officer-setup/lib/preflight.sh"
|
||||||
|
# shellcheck source=officer-setup/lib/repo.sh
|
||||||
|
source "$SCRIPT_DIR/officer-setup/lib/repo.sh"
|
||||||
|
|
||||||
trap 'echo ""; echo -e "${RED}╔══════════════════════════════════════════════════╗${NC}"; echo -e "${RED}║ OFFICER SETUP FAILED${NC}"; echo -e "${RED}║ Step: ${CURRENT_STEP:-unknown}${NC}"; echo -e "${RED}║ Line: $LINENO${NC}"; echo -e "${RED}║ Command: $BASH_COMMAND${NC}"; echo -e "${RED}╚══════════════════════════════════════════════════╝${NC}"' ERR
|
trap 'echo ""; echo -e "${RED}╔══════════════════════════════════════════════════╗${NC}"; echo -e "${RED}║ OFFICER SETUP FAILED${NC}"; echo -e "${RED}║ Step: ${CURRENT_STEP:-unknown}${NC}"; echo -e "${RED}║ Line: $LINENO${NC}"; echo -e "${RED}║ Command: $BASH_COMMAND${NC}"; echo -e "${RED}╚══════════════════════════════════════════════════╝${NC}"' ERR
|
||||||
|
|
||||||
@@ -153,11 +155,113 @@ else
|
|||||||
echo " steps are remembered and skipped."
|
echo " steps are remembered and skipped."
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# 2. Repository
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
step "Repository"
|
||||||
|
if ! skip; then
|
||||||
|
PLATFORM_DIR="$(platform_dir)"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
info "Repository — where the platform's code lives"
|
||||||
|
echo " path: ${PLATFORM_DIR}"
|
||||||
|
|
||||||
|
if repo_exists; then
|
||||||
|
echo " remote: $(repo_remote)"
|
||||||
|
echo " branch: $(repo_branch)"
|
||||||
|
echo " working: $(repo_is_dirty && echo 'has uncommitted changes' || echo 'clean')"
|
||||||
|
|
||||||
|
# Reported, never silently corrected. Repointing somebody's remote is a
|
||||||
|
# decision about where their work goes, and this script is not entitled to
|
||||||
|
# make it quietly.
|
||||||
|
if [[ -n "$(repo_remote)" && "$(repo_remote)" != "$OFFICER_REPO" ]]; then
|
||||||
|
echo ""
|
||||||
|
warn "this checkout points somewhere other than ${OFFICER_REPO}"
|
||||||
|
echo " Left alone. To move it:"
|
||||||
|
echo " git -C ${PLATFORM_DIR} remote set-url origin ${OFFICER_REPO}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if repo_is_dirty; then
|
||||||
|
echo ""
|
||||||
|
echo " not pulling — there are uncommitted changes here, and a pull"
|
||||||
|
echo " would either fail or bury them"
|
||||||
|
SUMMARY+=("Repository: present at ${PLATFORM_DIR}, left alone (uncommitted changes)")
|
||||||
|
elif confirm "Pull the latest changes?"; then
|
||||||
|
if pull_repo; then
|
||||||
|
ok "up to date on $(repo_branch)"
|
||||||
|
SUMMARY+=("Repository: pulled, on $(repo_branch)")
|
||||||
|
else
|
||||||
|
# --ff-only, so this means the branch has diverged rather than that the
|
||||||
|
# network failed. Saying which matters.
|
||||||
|
warn "could not fast-forward — the local branch has diverged from the remote"
|
||||||
|
ERRORS+=("Repository: pull refused, branch diverged")
|
||||||
|
SUMMARY+=("Repository: present, pull refused (diverged)")
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
SUMMARY+=("Repository: present at ${PLATFORM_DIR}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
else
|
||||||
|
echo " nothing there yet"
|
||||||
|
echo ""
|
||||||
|
info "Clone from ${OFFICER_REPO}?"
|
||||||
|
echo " Cloned as ${USERNAME}, not as root — a repository owned by root is"
|
||||||
|
echo " one you cannot pull, commit in, or install into."
|
||||||
|
|
||||||
|
CLONE_URL="$OFFICER_REPO"
|
||||||
|
|
||||||
|
# Checked before cloning. An ssh URL with no usable key does not fail
|
||||||
|
# cleanly: git prompts for a password nobody is there to type, or stops on
|
||||||
|
# host-key verification.
|
||||||
|
if [[ "$CLONE_URL" == ssh://* ]] && ! repo_ssh_ok "$CLONE_URL"; then
|
||||||
|
echo ""
|
||||||
|
warn "${USERNAME} cannot authenticate to $(repo_ssh_host "$CLONE_URL") over ssh"
|
||||||
|
echo " Either add that account's public key to the git server, or clone"
|
||||||
|
echo " over https instead — which works without a key if the repository"
|
||||||
|
echo " is readable anonymously."
|
||||||
|
echo ""
|
||||||
|
echo " [1] https — $(repo_https_url "$CLONE_URL")"
|
||||||
|
echo " [2] ssh anyway — will fail if the key is genuinely missing"
|
||||||
|
echo " [3] stop here, and add the key first"
|
||||||
|
echo ""
|
||||||
|
REPO_PICK=""
|
||||||
|
while [[ -z "$REPO_PICK" ]]; do
|
||||||
|
if ! read -rp " Which one? (1/2/3) [1]: " REPO_CHOICE; then
|
||||||
|
echo ""
|
||||||
|
fail "No answer."
|
||||||
|
fi
|
||||||
|
case "${REPO_CHOICE:-1}" in
|
||||||
|
1)
|
||||||
|
CLONE_URL="$(repo_https_url "$CLONE_URL")"
|
||||||
|
REPO_PICK=go
|
||||||
|
;;
|
||||||
|
2) REPO_PICK=go ;;
|
||||||
|
3) fail "Stopped. Add ${USERNAME}'s public key to the git server and run this again." ;;
|
||||||
|
*) warn "Pick 1, 2 or 3." ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if confirm "Clone it now?"; then
|
||||||
|
if clone_repo "$CLONE_URL"; then
|
||||||
|
ok "cloned to ${PLATFORM_DIR} on $(repo_branch)"
|
||||||
|
SUMMARY+=("Repository: cloned from ${CLONE_URL}")
|
||||||
|
else
|
||||||
|
warn "the clone did not complete"
|
||||||
|
ERRORS+=("Repository: clone failed")
|
||||||
|
fail "Nothing below can run without the repository."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
fail "Nothing below can run without the repository."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
step_ok
|
||||||
|
fi
|
||||||
|
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
# NOT BUILT YET
|
# NOT BUILT YET
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
#
|
|
||||||
# 2 Repository clone or locate the platform at $OFFICER_ROOT/platform
|
|
||||||
# 3 Dependencies bun install
|
# 3 Dependencies bun install
|
||||||
# 4 Database Postgres in docker, or one you already run
|
# 4 Database Postgres in docker, or one you already run
|
||||||
# 5 Environment .env
|
# 5 Environment .env
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
#!/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
|
||||||
|
|
||||||
|
OFFICER_REPO="${OFFICER_REPO:-ssh://git@gitea.officer.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?
|
||||||
|
#
|
||||||
|
# Checked before the clone rather than after, because an ssh URL with no usable
|
||||||
|
# key does not fail cleanly — git either prompts for a password nobody is there
|
||||||
|
# to type, or hangs on host-key verification. BatchMode turns both into an
|
||||||
|
# immediate non-zero.
|
||||||
|
#
|
||||||
|
# Gitea answers a successful auth with a message and exit 1, so the test is
|
||||||
|
# whether the SERVER recognised us, not the exit code.
|
||||||
|
repo_ssh_ok() {
|
||||||
|
local url="$1" host port out
|
||||||
|
host="$(repo_ssh_host "$url")"
|
||||||
|
port="$(repo_ssh_port "$url")"
|
||||||
|
[[ -n "$host" ]] || return 1
|
||||||
|
|
||||||
|
out="$(as_owner "ssh -T -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ConnectTimeout=8 ${port:+-p $port} git@${host} 2>&1" || true)"
|
||||||
|
grep -qiE "authenticated|successfully|welcome|does not provide shell access" <<<"$out"
|
||||||
|
}
|
||||||
|
|
||||||
|
# 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 clone '${url}' '${dest}'" /
|
||||||
|
}
|
||||||
|
|
||||||
|
pull_repo() { as_owner "git -C '$(platform_dir)' pull --ff-only" /; }
|
||||||
Reference in New Issue
Block a user