9.5 KiB
Skill: Formatting a Repository as a MiniServer Sibling Project
Overview
A sibling project is a git repository that builds on MiniServer. Users install
one with MiniServer's sibling command, and from then on MiniServer's
update command keeps it up to date.
Follow this document when you are working in a sibling repository and need it to install and update correctly under MiniServer. A repository needs no special files to be installable. It becomes useful by adding these optional files at its top level:
| File | When it's used |
|---|---|
install.sh |
Runs once, when the user runs sibling add, and again if they re-run it |
update.sh |
Runs on every update, right after the repo has been pulled |
firewall.conf |
Read on sibling add, sibling remove and every update to open the project's ports |
Don't also copy a hook into MiniServer's updates.d/ for the same project, or
it updates twice.
What MiniServer Does
sibling add <url> (or sibling, which asks for the URL)
- Works out the project name from the last part of the URL, dropping
.git:git@gitea.young.computer:david/my-tools.git→my-tools. - Clones the repo into
~/.local/share/siblings/<name>, or, if that folder already holds a clone of the same URL, runsgit pull --ff-onlythere. - If
install.shexists, runsbash install.shwith the repo folder as the working directory. Its input and output are the user's terminal. - If
firewall.confexists, opens the ports it lists (see Firewall). - Reports success only if every step above succeeded.
update
MiniServer runs its own update hooks in order (10-pacman.sh … 30-pm2.sh).
Next, updates.d/71-siblings.sh takes each sibling in alphabetical order and:
- Runs
git pull --ff-only --quietin its folder. - If the pull succeeded and
update.shexists, runsbash update.shwith the repo folder as the working directory.
A failed pull or a non-zero exit from update.sh is printed and listed in
update's "Done, with errors in: …" summary. Other siblings still update.
MiniServer's end-of-run hooks (firewall, cleanup, reboot check) run after all
siblings, so a port added in the latest commit opens in the same update.
sibling remove <name>
This deletes ~/.local/share/siblings/<name> and closes the ports that only
its firewall.conf asked for. MiniServer has no other uninstall step for
siblings, so anything install.sh set up elsewhere stays.
Rules for the Repository
Layout
- Put
install.shandupdate.shat the top level of the repo. Other names or locations are ignored. - Both scripts are run with
bash, so they don't need to be executable, but a#!/bin/bashline keeps them runnable by hand. - Locate files relative to the script, not to a hard-coded path. The user may
also clone the repo somewhere else and run
install.shby hand.SELF="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
Environment
-
The scripts run as separate
bashprocesses, so they inherit no variables or functions from MiniServer. In particular$MINISERVERis not set. Use the path directly:MINISERVER="$HOME/.local/share/MiniServer" -
MiniServer's helpers are available by sourcing
utils.sh:Function What it does install_packages ...Installs pacman/AUR packages via yay, skipping installed onesremove_packages ...Removes installed pacman/AUR packages enable_services ...Enables systemd services not yet enabled install_script src dstCopies a file atomically and makes it executable is_installed pkgTrue if a pacman package is installed -
sudoprompts for a password as normal. MiniServer does not grant passwordless sudo to siblings.
install.sh
- Exit non-zero on failure.
set -enear the top is the simplest way.siblingthen tells the user it failed and how to retry. - Make it safe to re-run. Re-running
sibling addis how users repair an install, so skip work that is already done instead of failing on it. - It may prompt the user, since it runs in their terminal.
- Install commands into
~/.local/bin(already onPATHunder MiniServer) withinstall_script.
update.sh
- Make it idempotent. It runs on every
update, usually with nothing to do. - Don't prompt. Users expect
updateto run through without questions, apart fromsudopasswords. - Exit non-zero only for real failures, so that
update's error summary stays meaningful. - Print what it's doing. MiniServer prints only the project's name before it runs.
- The repo has already been pulled, so
update.shalways runs its newest version. Use it to apply changes made since the last update: new packages, updated commands in~/.local/bin, config migrations. - It's fine to omit
update.shif pulling the repo is enough, for example when~/.local/binholds symlinks into the repo.
Firewall
MiniServer runs firewalld, which blocks incoming connections except for the
services it allows (SSH, mDNS, Cockpit and a few ports). If the project serves
anything other machines connect to, list its ports in firewall.conf at the
top of the repo:
# Ports other machines need to reach. Format: <port>/<protocol> or
# <first>-<last>/<protocol>, protocol tcp, udp, sctp or dccp.
FIREWALL_PORTS=(
5000/tcp
)
# Optional: firewalld service names (see `firewall-cmd --get-services`)
FIREWALL_SERVICES=(
)
- Don't open ports from
install.shorupdate.shwithfirewall-cmd. MiniServer can't close those onsibling remove. - Only assign the two arrays. The file is sourced by bash, but it's read on
every
updateandsiblingcommand, so it must not do anything else. - Removing an entry closes that port on the next
update, unless another project still lists it or it was already open before MiniServer opened it. - Containers' published ports with Docker don't need listing: Docker opens them itself.
Keep pulls working
update pulls with --ff-only and never merges or resets. A pull fails if:
- History on the default branch is rewritten (force-pushed). Never do this.
- Tracked files are edited in place in the clone and a new commit touches
them. Scripts must not write into tracked files. Write generated files,
caches and per-machine state outside the repo (e.g.
~/.config/<name>/), or into paths listed in.gitignore.
Authentication
sibling clones with plain git. A private repo works only if the user can
already clone it (SSH key, credential helper, or a password typed at the
prompt). Every later update needs the same access.
Templates
Share the install logic between the two scripts so that packages or commands
added later reach existing machines through update, not just new installs.
install.sh:
#!/bin/bash
#
# Purpose : Install <project> (run by MiniServer's `sibling add`)
set -e
SELF="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MINISERVER="$HOME/.local/share/MiniServer"
source "$MINISERVER/utils.sh"
# One-time setup: prompts, accounts, initial config...
mkdir -p "$HOME/.config/<project>"
# Everything that update.sh also needs to keep current
bash "$SELF/update.sh"
echo "<project> installed."
update.sh:
#!/bin/bash
#
# Purpose : Keep <project> current (run by MiniServer's `update`)
set -e
SELF="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MINISERVER="$HOME/.local/share/MiniServer"
source "$MINISERVER/utils.sh"
PACKAGES=(
jq
ripgrep
)
install_packages "${PACKAGES[@]}"
mkdir -p "$HOME/.local/bin"
install_script "$SELF/bin/<project>" "$HOME/.local/bin/<project>"
Testing
Test in a throwaway home folder so nothing touches the real one. Point
sibling at a local clone; it accepts any URL or path that git clone does.
export HOME="$(mktemp -d)"
mkdir -p "$HOME/.local/share"
ln -s /path/to/real/MiniServer "$HOME/.local/share/MiniServer"
# Install
bash "$HOME/.local/share/MiniServer/sibling" add /path/to/sibling-repo
# Commit a change in /path/to/sibling-repo, then run the update hook the way
# `update` does
( source "$HOME/.local/share/MiniServer/updates.d/71-siblings.sh"; is_installed && run_update )
# Re-run the install to check it is safe to repeat
bash "$HOME/.local/share/MiniServer/sibling" add /path/to/sibling-repo
Scripts that call sudo, yay or systemctl still change the real system
even with a temporary HOME. Test those on a machine or VM you don't mind
changing.
Checklist
- The repo can be cloned by URL by the people who will install it
install.sh(optional) is at the top level, exits non-zero on failure, and is safe to re-runupdate.sh(optional) is at the top level, idempotent, doesn't prompt, and prints what it's doing- Every port other machines connect to is in
firewall.conf, and no script runsfirewall-cmditself - Scripts find their own files relative to the script and set
MINISERVER="$HOME/.local/share/MiniServer"themselves - Nothing writes to tracked files in the clone, and the default branch is never force-pushed
- The project doesn't also copy a hook into MiniServer's
updates.d/ - The README tells users to install with
sibling add <url>