Files
platform/scripts/setup/officer-setup/lib/layout.sh
T
pastilhasandClaude Opus 5 1e03c600b8 stop naming a branch when pulling the capabilities repository
The plugin and capabilities repositories were standardised on `master`, and
five places said `origin main` — the setup script's sync and the Update-all
path. Swapping them to `master` would be the same mistake wearing a new name.

`HEAD` resolves to whatever the remote's default is, so the question stops
being asked, exactly as it now does for the marketplace icon URLs. Only the
LOCAL branch still needs a name.

Third time today that naming a branch was the bug: the icon URLs 404'd on four
repositories created as `master`, and these would have broken the moment the
rest were renamed to match.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 11:01:46 +00:00

108 lines
4.5 KiB
Bash

#!/bin/bash
# =============================================================================
# officer-setup — the install layout
# =============================================================================
#
# Definitions only.
#
# ── One root, and nothing configurable underneath it ──
#
# $OFFICER_ROOT/
# platform/ the app — the git checkout
# data/ DATA_PATH: managed homes, attachments, job logs
# dockers/ services the app store provisioned
# capabilities/ the file-based item store — skills, tools, tasks, processes
#
# The original asked separately for DATA_PATH and for OFFICER_ITEMS_DIR, and left
# the app store's directory implicit. Three answers that had to agree with each
# other, given by somebody with no reason to know they had to.
#
# Now one question — where the root goes — and the rest follows. Anybody who wants
# data/ on a bigger volume can symlink it; that is a decision about storage, not
# about how Officer is laid out, and it does not need a prompt in a setup script.
#
# This is also what the code already assumes. app-store/paths.ts derives
# OFFICER_ROOT as dirname(DATA_PATH) and DOCKERS_DIR as OFFICER_ROOT/dockers, so
# setting DATA_PATH to <root>/data is the whole of what makes the layout correct.
[[ -n "${OFFICER_SETUP_LAYOUT_LOADED:-}" ]] && return 0
OFFICER_SETUP_LAYOUT_LOADED=1
layout_data_dir() { echo "${OFFICER_ROOT}/data"; }
layout_dockers_dir() { echo "${OFFICER_ROOT}/dockers"; }
layout_items_dir() { echo "${OFFICER_ROOT}/capabilities"; }
layout_dirs() {
echo "$OFFICER_ROOT"
echo "$(layout_data_dir)"
echo "$(layout_dockers_dir)"
echo "$(layout_items_dir)"
}
# Created owned by the account, because everything that writes into them runs as
# the account: the platform under pm2, the app store's compose files, the item
# store the agent authors into.
create_layout() {
local dir
while read -r dir; do
[[ -d "$dir" ]] || install -d -m 0755 -o "$USERNAME" -g "$(user_group)" "$dir"
done < <(layout_dirs)
return 0
}
# A directory that exists but belongs to somebody else is the failure this
# reports: it happens when an earlier run, or a hand-made directory, was created
# as root, and everything written into it afterwards fails in a way that reads as
# a permissions bug in the platform.
layout_wrong_owner() {
local dir
while read -r dir; do
[[ -d "$dir" ]] || continue
[[ "$(stat -c %U "$dir")" == "$USERNAME" ]] || echo "$dir"
done < <(layout_dirs)
}
# -----------------------------------------------------------------------------
# The capabilities Officer ships
# -----------------------------------------------------------------------------
#
# `capabilities/` is the file-based item store. The platform creates the six item
# directories at boot and writes `extensions/tool-loader` from its own source, so
# those are generated and not tracked anywhere. What IS tracked is the small set
# the platform ships — today one affordance, telling an agent that markdown
# images render and that the user's DEVICE fetches the URL.
#
# Without this a fresh install has no affordances at all, and every agent on it
# goes back to guessing what the client on the other end will do.
#
# Cloned rather than copied out of the platform repository, so it can be updated
# on its own — by `git pull` here, or by "Update all" on the marketplace screen.
CAPABILITIES_REPO="https://gitea.officer.dev/officerdev/capabilities.git"
# Three states, because a directory that already exists is the normal one: the
# layout step created it a moment ago, and a re-run finds a checkout.
capabilities_sync() {
local dir; dir="$(layout_items_dir)"
if [[ -d "$dir/.git" ]]; then
# `HEAD` rather than a branch name — it resolves to the remote's default, so standardising the
# repositories on one name does not break this the way naming `main` did.
(cd "$dir" && git pull --ff-only --quiet origin HEAD 2>/dev/null) || return 1
return 0
fi
# An empty directory can be cloned into directly. A non-empty one cannot, and
# it is not empty precisely because the platform has already run and written
# its generated directories — so fetch into place instead of refusing, and let
# the owner's own untracked items sit alongside untouched.
if [[ -z "$(ls -A "$dir" 2>/dev/null)" ]]; then
git clone --quiet "$CAPABILITIES_REPO" "$dir" || return 1
else
(cd "$dir" \
&& git init --quiet \
&& git remote add origin "$CAPABILITIES_REPO" 2>/dev/null \
&& git fetch --quiet origin HEAD \
&& git checkout --quiet -B master FETCH_HEAD) || return 1
fi
}