#!/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 /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 }