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>
108 lines
4.5 KiB
Bash
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
|
|
}
|