change to update format
This commit is contained in:
@@ -3,3 +3,33 @@ Installs the bare necessities to turn a newly minted Arch Linux server into some
|
|||||||
```
|
```
|
||||||
wget -qO- https://gitea.young.computer/david/MiniServer/raw/branch/main/install.sh | bash
|
wget -qO- https://gitea.young.computer/david/MiniServer/raw/branch/main/install.sh | bash
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Keep your server up to date
|
||||||
|
|
||||||
|
```
|
||||||
|
update
|
||||||
|
```
|
||||||
|
|
||||||
|
This pulls the latest MiniServer into `~/.local/share/MiniServer`, then runs each step in `updates.d/` in order: the Arch keyring and system packages (plus any packages newly added to `packages.conf`), AUR packages, pm2, sibling projects, cache cleanup, and a reboot check. If a step fails, the others still run and the failed ones are listed at the end.
|
||||||
|
|
||||||
|
## Refresh
|
||||||
|
|
||||||
|
```
|
||||||
|
refresh
|
||||||
|
```
|
||||||
|
|
||||||
|
Re-runs `install.sh` to pick up anything new in the installer (services, config). Run `update` afterwards.
|
||||||
|
|
||||||
|
## Sibling projects
|
||||||
|
|
||||||
|
A sibling project is another git repository that builds on MiniServer. To install one:
|
||||||
|
|
||||||
|
```
|
||||||
|
sibling
|
||||||
|
```
|
||||||
|
|
||||||
|
It asks for the repository's git URL, clones it into `~/.local/share/siblings/`, and runs the project's `install.sh` (if it has one). After that, every `update` pulls each sibling and runs its `update.sh` (if it has one).
|
||||||
|
|
||||||
|
Use `sibling list` to see what's installed and `sibling remove {name}` to uninstall one.
|
||||||
|
|
||||||
|
To make your own repository work as a sibling project, see [skills/sibling-project-format.md](skills/sibling-project-format.md).
|
||||||
|
|||||||
+23
-9
@@ -2,6 +2,10 @@
|
|||||||
#
|
#
|
||||||
# Purpose : MiniServer installer
|
# Purpose : MiniServer installer
|
||||||
|
|
||||||
|
# Wrapped in { ...; exit; } so bash reads the whole script up front; the git
|
||||||
|
# pull below can replace this file while it runs.
|
||||||
|
{
|
||||||
|
|
||||||
# Print the logo
|
# Print the logo
|
||||||
print_logo() {
|
print_logo() {
|
||||||
cat << "EOF"
|
cat << "EOF"
|
||||||
@@ -61,15 +65,26 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "Getting the latest version of MiniServer..."
|
echo "Getting the latest version of MiniServer..."
|
||||||
rm -rf ~/.local/share/MiniServer
|
if [ -d "$HOME/.local/share/MiniServer/.git" ]; then
|
||||||
git clone https://gitea.young.computer/david/MiniServer.git "$HOME/.local/share/MiniServer" > /dev/null
|
git -C "$HOME/.local/share/MiniServer" pull
|
||||||
|
else
|
||||||
mkdir -p "$HOME/.local/bin"
|
git clone https://gitea.young.computer/david/MiniServer.git "$HOME/.local/share/MiniServer" > /dev/null
|
||||||
cp "$HOME/.local/share/MiniServer/miniserverrefresh" "$HOME/.local/bin/miniserverrefresh"
|
fi
|
||||||
cp "$HOME/.local/share/MiniServer/miniserverupdate" "$HOME/.local/bin/miniserverupdate"
|
|
||||||
|
|
||||||
cd ~/.local/share/MiniServer
|
cd ~/.local/share/MiniServer
|
||||||
|
|
||||||
|
# Source utility functions (provides install_packages, install_script and other helpers)
|
||||||
|
source utils.sh
|
||||||
|
|
||||||
|
# Install the refresh, update and sibling management scripts to the user's bin
|
||||||
|
mkdir -p "$HOME/.local/bin"
|
||||||
|
install_script "$HOME/.local/share/MiniServer/refresh" "$HOME/.local/bin/refresh"
|
||||||
|
install_script "$HOME/.local/share/MiniServer/update" "$HOME/.local/bin/update"
|
||||||
|
install_script "$HOME/.local/share/MiniServer/sibling" "$HOME/.local/bin/sibling"
|
||||||
|
|
||||||
|
# Replaced by refresh and update
|
||||||
|
rm -f "$HOME/.local/bin/miniserverrefresh" "$HOME/.local/bin/miniserverupdate"
|
||||||
|
|
||||||
echo "Updating system..."
|
echo "Updating system..."
|
||||||
sudo pacman -Syu --noconfirm
|
sudo pacman -Syu --noconfirm
|
||||||
|
|
||||||
@@ -94,9 +109,6 @@ if ! yay --version &> /dev/null; then
|
|||||||
install_yay
|
install_yay
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Source utility functions
|
|
||||||
source utils.sh
|
|
||||||
|
|
||||||
# Source package list
|
# Source package list
|
||||||
if [ ! -f "packages.conf" ]; then
|
if [ ! -f "packages.conf" ]; then
|
||||||
echo "Error: packages.conf not found!"
|
echo "Error: packages.conf not found!"
|
||||||
@@ -171,3 +183,5 @@ if ! ls /usr/lib/modules/$(uname -r) > /dev/null 2>&1; then
|
|||||||
else
|
else
|
||||||
echo "Kernel is up to date, no reboot needed. You may want to consider a reboot if other significant software was updated or installed."
|
echo "Kernel is up to date, no reboot needed. You may want to consider a reboot if other significant software was updated or installed."
|
||||||
fi
|
fi
|
||||||
|
exit
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Print the logo
|
|
||||||
print_logo() {
|
|
||||||
cat << "EOF"
|
|
||||||
|
|
||||||
███╗ ███╗██╗███╗ ██╗██╗
|
|
||||||
████╗ ████║██║████╗ ██║██║
|
|
||||||
██╔████╔██║██║██╔██╗ ██║██║
|
|
||||||
██║╚██╔╝██║██║██║╚██╗██║██║
|
|
||||||
██║ ╚═╝ ██║██║██║ ╚████║██║
|
|
||||||
╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═╝
|
|
||||||
|
|
||||||
███████╗███████╗██████╗ ██╗ ██╗███████╗██████╗
|
|
||||||
██╔════╝██╔════╝██╔══██╗██║ ██║██╔════╝██╔══██╗
|
|
||||||
███████╗█████╗ ██████╔╝██║ ██║█████╗ ██████╔╝
|
|
||||||
╚════██║██╔══╝ ██╔══██╗╚██╗ ██╔╝██╔══╝ ██╔══██╗
|
|
||||||
███████║███████╗██║ ██║ ╚████╔╝ ███████╗██║ ██║
|
|
||||||
╚══════╝╚══════╝╚═╝ ╚═╝ ╚═══╝ ╚══════╝╚═╝ ╚═╝
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
# Clear screen and show logo
|
|
||||||
clear
|
|
||||||
print_logo
|
|
||||||
|
|
||||||
echo "Updating Arch..."
|
|
||||||
sudo pacman -Syu
|
|
||||||
|
|
||||||
echo "Updating AUR..."
|
|
||||||
|
|
||||||
if ! yay -Sua 2>/dev/null; then
|
|
||||||
echo "yay -Sua returned an error. Let's rebuild YAY."
|
|
||||||
sudo pacman -R yay
|
|
||||||
cd /tmp
|
|
||||||
git clone https://aur.archlinux.org/yay.git
|
|
||||||
cd yay
|
|
||||||
makepkg -si
|
|
||||||
cd ..
|
|
||||||
rm -rf yay
|
|
||||||
cd
|
|
||||||
yay -Sua --noconfirm
|
|
||||||
else
|
|
||||||
yay -Sua --noconfirm
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Updating the repo and installing...
|
|
||||||
echo "Updating MiniServer..."
|
|
||||||
|
|
||||||
REPO_DIR="$HOME/.local/share/MiniServer"
|
|
||||||
|
|
||||||
echo "Pulling latest MiniServer repo..."
|
|
||||||
git -C "$REPO_DIR" pull
|
|
||||||
|
|
||||||
echo "Running install.sh..."
|
|
||||||
bash "$REPO_DIR/install.sh"
|
|
||||||
|
|
||||||
# Check whether the running kernel matches the installed modules — if not, a
|
|
||||||
# newer kernel was installed during this run and a reboot is required
|
|
||||||
if ! ls /usr/lib/modules/$(uname -r) > /dev/null 2>&1; then
|
|
||||||
echo "A newer kernel has been installed, a reboot is recommended."
|
|
||||||
read -rp "Reboot now? [y/N] " response
|
|
||||||
case "$response" in
|
|
||||||
[yY][eE][sS]|[yY])
|
|
||||||
echo "Rebooting..."
|
|
||||||
sudo reboot
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Reboot skipped. Please remember to reboot when convenient."
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
else
|
|
||||||
echo "Kernel is up to date, no reboot needed. You may want to consider a reboot if other significant software was updated or installed."
|
|
||||||
fi
|
|
||||||
+10
-20
@@ -1,5 +1,9 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Wrapped in { ...; exit; } so bash reads the whole script up front; install.sh
|
||||||
|
# replaces this file while it runs.
|
||||||
|
{
|
||||||
|
|
||||||
# Print the logo
|
# Print the logo
|
||||||
print_logo() {
|
print_logo() {
|
||||||
cat << "EOF"
|
cat << "EOF"
|
||||||
@@ -27,26 +31,12 @@ EOF
|
|||||||
clear
|
clear
|
||||||
print_logo
|
print_logo
|
||||||
|
|
||||||
echo "Let's get the lastest changes to MiniServer..."
|
echo "Let's get the latest changes to MiniServer..."
|
||||||
|
|
||||||
wget -qO- https://gitea.young.computer/david/MiniServer/raw/branch/main/install.sh | bash
|
if [ -f "$HOME/.local/share/MiniServer/install.sh" ]; then
|
||||||
|
bash "$HOME/.local/share/MiniServer/install.sh"
|
||||||
# Check whether the running kernel matches the installed modules — if not, a
|
|
||||||
# newer kernel was installed during this run and a reboot is required
|
|
||||||
|
|
||||||
if ! ls /usr/lib/modules/$(uname -r) > /dev/null 2>&1; then
|
|
||||||
echo "A newer kernel has been installed, a reboot is recommended."
|
|
||||||
read -rp "Reboot now? [y/N] " response
|
|
||||||
case "$response" in
|
|
||||||
[yY][eE][sS]|[yY])
|
|
||||||
echo "Rebooting..."
|
|
||||||
sudo reboot
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Reboot skipped. Please remember to reboot when convenient."
|
|
||||||
exit 0
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
else
|
else
|
||||||
echo "Kernel is up to date, no reboot needed. You may want to consider a reboot if other significant software was updated or installed."
|
wget -qO- https://gitea.young.computer/david/MiniServer/raw/branch/main/install.sh | bash
|
||||||
fi
|
fi
|
||||||
|
exit
|
||||||
|
}
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Purpose : Install and manage MiniServer sibling projects
|
||||||
|
#
|
||||||
|
# A sibling is a git repository cloned into ~/.local/share/siblings/<name>.
|
||||||
|
# If it has an install.sh, that runs once when the sibling is added.
|
||||||
|
# Every `update` pulls each sibling and runs its update.sh, if it has one
|
||||||
|
# (see updates.d/71-siblings.sh).
|
||||||
|
|
||||||
|
SIBLINGS="$HOME/.local/share/siblings"
|
||||||
|
|
||||||
|
usage() {
|
||||||
|
cat << EOF
|
||||||
|
Usage: sibling [command]
|
||||||
|
|
||||||
|
add [url] Install a sibling project from a git repository (default)
|
||||||
|
list Show installed sibling projects
|
||||||
|
remove <name> Uninstall a sibling project
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Derive a directory name from a git URL, e.g.
|
||||||
|
# https://host/user/my-project.git -> my-project
|
||||||
|
sibling_name() {
|
||||||
|
local name="${1%/}"
|
||||||
|
name="${name##*/}"
|
||||||
|
name="${name##*:}"
|
||||||
|
echo "${name%.git}"
|
||||||
|
}
|
||||||
|
|
||||||
|
add_sibling() {
|
||||||
|
local url="$1" name dir
|
||||||
|
|
||||||
|
if [ -z "$url" ]; then
|
||||||
|
read -rp "Git repository URL for the sibling project: " url
|
||||||
|
fi
|
||||||
|
if [ -z "$url" ]; then
|
||||||
|
echo "No URL given."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
name="$(sibling_name "$url")"
|
||||||
|
if [[ -z "$name" || "$name" == .* ]]; then
|
||||||
|
echo "Could not work out a project name from: $url"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
dir="$SIBLINGS/$name"
|
||||||
|
|
||||||
|
mkdir -p "$SIBLINGS"
|
||||||
|
if [ -d "$dir/.git" ]; then
|
||||||
|
local existing
|
||||||
|
existing="$(git -C "$dir" remote get-url origin 2>/dev/null)"
|
||||||
|
if [ "$existing" != "$url" ]; then
|
||||||
|
echo "A different sibling named '$name' is already installed from: $existing"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
echo "$name is already installed; pulling the latest version..."
|
||||||
|
git -C "$dir" pull --ff-only || return 1
|
||||||
|
else
|
||||||
|
echo "Cloning $name..."
|
||||||
|
git clone "$url" "$dir" || return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "$dir/install.sh" ]; then
|
||||||
|
echo "Running $name's install.sh..."
|
||||||
|
if ! (cd "$dir" && bash install.sh); then
|
||||||
|
echo "$name's install.sh failed. Fix the problem and run 'sibling add $url' again."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Confirm the install
|
||||||
|
if [ -d "$dir/.git" ] && git -C "$dir" rev-parse --verify --quiet HEAD > /dev/null; then
|
||||||
|
echo "Installed $name at $dir ($(git -C "$dir" log -1 --format='%h %s'))."
|
||||||
|
echo "'update' will keep it up to date."
|
||||||
|
else
|
||||||
|
echo "$name does not look installed correctly at $dir."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
list_siblings() {
|
||||||
|
local dir found=0
|
||||||
|
for dir in "$SIBLINGS"/*/; do
|
||||||
|
[ -d "$dir/.git" ] || continue
|
||||||
|
found=1
|
||||||
|
printf '%-24s %s (%s)\n' "$(basename "$dir")" \
|
||||||
|
"$(git -C "$dir" remote get-url origin 2>/dev/null)" \
|
||||||
|
"$(git -C "$dir" log -1 --format='%h, %cr' 2>/dev/null)"
|
||||||
|
done
|
||||||
|
[ $found -eq 1 ] || echo "No sibling projects installed."
|
||||||
|
}
|
||||||
|
|
||||||
|
remove_sibling() {
|
||||||
|
local name="$1"
|
||||||
|
if [[ -z "$name" || "$name" == */* || "$name" == .* ]]; then
|
||||||
|
usage
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if [ ! -d "$SIBLINGS/$name" ]; then
|
||||||
|
echo "No sibling named '$name' is installed."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
read -rp "Remove $SIBLINGS/$name? [y/N] " response
|
||||||
|
case "$response" in
|
||||||
|
[yY][eE][sS]|[yY])
|
||||||
|
rm -rf "${SIBLINGS:?}/$name"
|
||||||
|
echo "Removed $name. Anything its install.sh set up outside that folder is still there."
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Nothing removed."
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
""|add) add_sibling "$2" ;;
|
||||||
|
list|ls) list_siblings ;;
|
||||||
|
remove|rm) remove_sibling "$2" ;;
|
||||||
|
-h|--help|help) usage ;;
|
||||||
|
*) usage; exit 1 ;;
|
||||||
|
esac
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
# 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 two optional
|
||||||
|
scripts at its top level:
|
||||||
|
|
||||||
|
| File | When it runs |
|
||||||
|
|--------------|--------------------------------------------------------------|
|
||||||
|
| `install.sh` | Once, when the user runs `sibling add`, and again if they re-run it |
|
||||||
|
| `update.sh` | On every `update`, right after the repo has been pulled |
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
1. Works out the project name from the last part of the URL, dropping `.git`:
|
||||||
|
`git@gitea.young.computer:david/my-tools.git` → `my-tools`.
|
||||||
|
2. Clones the repo into `~/.local/share/siblings/<name>`, or, if that folder
|
||||||
|
already holds a clone of the same URL, runs `git pull --ff-only` there.
|
||||||
|
3. If `install.sh` exists, runs `bash install.sh` with the repo folder as the
|
||||||
|
working directory. Its input and output are the user's terminal.
|
||||||
|
4. 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:
|
||||||
|
|
||||||
|
1. Runs `git pull --ff-only --quiet` in its folder.
|
||||||
|
2. If the pull succeeded and `update.sh` exists, runs `bash update.sh` with 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 (cleanup, reboot check) run after all
|
||||||
|
siblings.
|
||||||
|
|
||||||
|
### `sibling remove <name>`
|
||||||
|
|
||||||
|
This only deletes `~/.local/share/siblings/<name>`. MiniServer has no uninstall
|
||||||
|
step for siblings, so anything `install.sh` set up elsewhere stays.
|
||||||
|
|
||||||
|
## Rules for the Repository
|
||||||
|
|
||||||
|
### Layout
|
||||||
|
|
||||||
|
- Put `install.sh` and `update.sh` at 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/bash` line 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.sh` by hand.
|
||||||
|
```bash
|
||||||
|
SELF="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment
|
||||||
|
|
||||||
|
- The scripts run as separate `bash` processes, so they inherit **no**
|
||||||
|
variables or functions from MiniServer. In particular `$MINISERVER` is
|
||||||
|
**not** set. Use the path directly:
|
||||||
|
```bash
|
||||||
|
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 ones |
|
||||||
|
| `remove_packages ...` | Removes installed pacman/AUR packages |
|
||||||
|
| `enable_services ...` | Enables systemd services not yet enabled |
|
||||||
|
| `install_script src dst` | Copies a file atomically and makes it executable |
|
||||||
|
| `is_installed pkg` | True if a pacman package is installed |
|
||||||
|
|
||||||
|
- `sudo` prompts for a password as normal. MiniServer does not grant
|
||||||
|
passwordless sudo to siblings.
|
||||||
|
|
||||||
|
### `install.sh`
|
||||||
|
|
||||||
|
- **Exit non-zero on failure.** `set -e` near the top is the simplest way.
|
||||||
|
`sibling` then tells the user it failed and how to retry.
|
||||||
|
- **Make it safe to re-run.** Re-running `sibling add` is 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 on `PATH` under MiniServer)
|
||||||
|
with `install_script`.
|
||||||
|
|
||||||
|
### `update.sh`
|
||||||
|
|
||||||
|
- **Make it idempotent.** It runs on every `update`, usually with nothing to do.
|
||||||
|
- **Don't prompt.** Users expect `update` to run through without questions,
|
||||||
|
apart from `sudo` passwords.
|
||||||
|
- **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.sh` always 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.sh` if pulling the repo is enough, for example when
|
||||||
|
`~/.local/bin` holds symlinks into the repo.
|
||||||
|
|
||||||
|
### 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`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/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`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/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.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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-run
|
||||||
|
- [ ] `update.sh` (optional) is at the top level, idempotent, doesn't prompt,
|
||||||
|
and prints what it's doing
|
||||||
|
- [ ] 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>`
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# The whole script is wrapped in { ...; exit; } so bash parses all of it before
|
||||||
|
# running anything. That way, replacing this file mid-run (e.g. by git pull and
|
||||||
|
# the copy below) can't make the running copy read a mix of old and new text.
|
||||||
|
{
|
||||||
|
|
||||||
|
# Install a script atomically (copy to a temp file, then rename over the target)
|
||||||
|
install_script() {
|
||||||
|
local tmp
|
||||||
|
tmp="$(mktemp "$2.XXXXXX")" && cp "$1" "$tmp" && chmod 755 "$tmp" && mv -f "$tmp" "$2"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print the logo
|
||||||
|
print_logo() {
|
||||||
|
cat << "EOF"
|
||||||
|
|
||||||
|
███╗ ███╗██╗███╗ ██╗██╗
|
||||||
|
████╗ ████║██║████╗ ██║██║
|
||||||
|
██╔████╔██║██║██╔██╗ ██║██║
|
||||||
|
██║╚██╔╝██║██║██║╚██╗██║██║
|
||||||
|
██║ ╚═╝ ██║██║██║ ╚████║██║
|
||||||
|
╚═╝ ╚═╝╚═╝╚═╝ ╚═══╝╚═╝
|
||||||
|
|
||||||
|
███████╗███████╗██████╗ ██╗ ██╗███████╗██████╗
|
||||||
|
██╔════╝██╔════╝██╔══██╗██║ ██║██╔════╝██╔══██╗
|
||||||
|
███████╗█████╗ ██████╔╝██║ ██║█████╗ ██████╔╝
|
||||||
|
╚════██║██╔══╝ ██╔══██╗╚██╗ ██╔╝██╔══╝ ██╔══██╗
|
||||||
|
███████║███████╗██║ ██║ ╚████╔╝ ███████╗██║ ██║
|
||||||
|
╚══════╝╚══════╝╚═╝ ╚═╝ ╚═══╝ ╚══════╝╚═╝ ╚═╝
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
# Clear screen and show logo
|
||||||
|
clear
|
||||||
|
print_logo
|
||||||
|
|
||||||
|
MINISERVER="$HOME/.local/share/MiniServer"
|
||||||
|
|
||||||
|
echo "Updating MiniServer..."
|
||||||
|
if [[ -z "$UPDATE_REEXECED" ]] && ! git -C "$MINISERVER" pull; then
|
||||||
|
echo "WARNING: could not pull the latest MiniServer; running the update hooks already on disk."
|
||||||
|
export UPDATE_PULL_FAILED=1
|
||||||
|
fi
|
||||||
|
mkdir -p "$HOME/.local/bin" "$MINISERVER/updates.d"
|
||||||
|
install_script "$MINISERVER/update" ~/.local/bin/update
|
||||||
|
install_script "$MINISERVER/refresh" ~/.local/bin/refresh
|
||||||
|
install_script "$MINISERVER/sibling" ~/.local/bin/sibling
|
||||||
|
|
||||||
|
if [[ -z "$UPDATE_REEXECED" ]]; then
|
||||||
|
export UPDATE_REEXECED=1
|
||||||
|
exec ~/.local/bin/update
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Ask for the password once, then keep sudo's cached credentials fresh until
|
||||||
|
# update exits, so long-running hooks don't stop for a password again.
|
||||||
|
sudo -v || exit 1
|
||||||
|
while kill -0 $$ 2>/dev/null; do sudo -n -v; sleep 60; done 2>/dev/null &
|
||||||
|
SUDO_KEEPALIVE_PID=$!
|
||||||
|
trap 'kill "$SUDO_KEEPALIVE_PID" 2>/dev/null' EXIT
|
||||||
|
|
||||||
|
FAILED=()
|
||||||
|
[[ -n "$UPDATE_PULL_FAILED" ]] && FAILED+=("git pull")
|
||||||
|
|
||||||
|
# Each hook runs in its own subshell so a cd, set -e, exit, or stray variable
|
||||||
|
# in one hook can't leak into or abort the hooks after it.
|
||||||
|
for plugin in "$MINISERVER/updates.d/"*.sh; do
|
||||||
|
[ -f "$plugin" ] || continue
|
||||||
|
(
|
||||||
|
unset -f is_installed run_update
|
||||||
|
source "$plugin"
|
||||||
|
if declare -f run_update > /dev/null; then
|
||||||
|
if ! declare -f is_installed > /dev/null || is_installed; then
|
||||||
|
run_update
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
)
|
||||||
|
# Checked separately: under `( ... ) ||`, bash would ignore set -e inside the hook
|
||||||
|
[ $? -eq 0 ] || FAILED+=("$(basename "$plugin")")
|
||||||
|
done
|
||||||
|
|
||||||
|
if (( ${#FAILED[@]} > 0 )); then
|
||||||
|
echo "Done, with errors in: ${FAILED[*]}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Done."
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
Executable
+31
@@ -0,0 +1,31 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
MINISERVER="$HOME/.local/share/MiniServer"
|
||||||
|
|
||||||
|
source "$MINISERVER/utils.sh"
|
||||||
|
source "$MINISERVER/packages.conf"
|
||||||
|
|
||||||
|
STATUS=0
|
||||||
|
|
||||||
|
# A machine that hasn't updated in a while can have a keyring too old to verify
|
||||||
|
# the new packages. Update it first; the -Su below completes this -Sy.
|
||||||
|
echo "Updating Arch keyring..."
|
||||||
|
sudo pacman -Sy --needed --noconfirm archlinux-keyring || STATUS=1
|
||||||
|
|
||||||
|
# script(1) keeps pacman's progress bars while logging its output for the
|
||||||
|
# initramfs check
|
||||||
|
echo "Updating Arch..."
|
||||||
|
mkdir -p "$(dirname "$PACMAN_UPDATE_LOG")"
|
||||||
|
: > "$PACMAN_UPDATE_LOG"
|
||||||
|
sudo script -qefc "pacman -Su --noconfirm" "$PACMAN_UPDATE_LOG" || STATUS=1
|
||||||
|
|
||||||
|
if initramfs_build_failed; then
|
||||||
|
echo -e "\e[31mRebuilding the initramfs failed. Don't reboot until it's fixed - see the output above or $PACMAN_UPDATE_LOG.\e[0m"
|
||||||
|
STATUS=1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Checking for new packages..."
|
||||||
|
install_packages "${SERVERS[@]}"
|
||||||
|
install_packages "${UTILITIES[@]}"
|
||||||
|
|
||||||
|
exit $STATUS
|
||||||
Executable
+23
@@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
echo "Updating AUR..."
|
||||||
|
|
||||||
|
YAY_FLAGS="--noconfirm --answerdiff=None --answeredit=None --answerclean=None"
|
||||||
|
|
||||||
|
# Only rebuild yay when yay itself is broken (e.g. a pacman upgrade bumped
|
||||||
|
# libalpm). A failing AUR package is not fixed by a rebuild, so let that error
|
||||||
|
# show and fail the hook instead.
|
||||||
|
if ! yay --version &>/dev/null; then
|
||||||
|
echo "yay is broken (likely a libalpm bump). Rebuilding yay..."
|
||||||
|
sudo pacman -R --noconfirm yay
|
||||||
|
cd /tmp
|
||||||
|
rm -rf yay
|
||||||
|
git clone https://aur.archlinux.org/yay.git
|
||||||
|
cd yay
|
||||||
|
makepkg -si --noconfirm
|
||||||
|
cd ..
|
||||||
|
rm -rf yay
|
||||||
|
cd
|
||||||
|
fi
|
||||||
|
|
||||||
|
yay -Sua $YAY_FLAGS
|
||||||
Executable
+11
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# pm2 is installed by install.sh with npm into ~/.local
|
||||||
|
is_installed() {
|
||||||
|
[ -x "$HOME/.local/bin/pm2" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
run_update() {
|
||||||
|
echo "Updating pm2..."
|
||||||
|
npm update -g pm2 --prefix "$HOME/.local"
|
||||||
|
}
|
||||||
Executable
+27
@@ -0,0 +1,27 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Sibling projects installed with the `sibling` command
|
||||||
|
SIBLINGS="$HOME/.local/share/siblings"
|
||||||
|
|
||||||
|
is_installed() {
|
||||||
|
compgen -G "$SIBLINGS/*/.git" > /dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
run_update() {
|
||||||
|
echo "Updating sibling projects..."
|
||||||
|
local dir name failed=0
|
||||||
|
for dir in "$SIBLINGS"/*/; do
|
||||||
|
[ -d "$dir/.git" ] || continue
|
||||||
|
name="$(basename "$dir")"
|
||||||
|
echo " $name"
|
||||||
|
if ! git -C "$dir" pull --ff-only --quiet; then
|
||||||
|
echo " Could not pull $name."
|
||||||
|
failed=1
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if [ -f "$dir/update.sh" ]; then
|
||||||
|
(cd "$dir" && bash update.sh) || { echo " $name's update.sh failed."; failed=1; }
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return $failed
|
||||||
|
}
|
||||||
Executable
+16
@@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if command -v paccache &>/dev/null; then
|
||||||
|
echo "Trimming pacman package cache..."
|
||||||
|
sudo paccache -rk2
|
||||||
|
sudo paccache -ruk0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Report packages that were installed as dependencies and are no longer needed.
|
||||||
|
# Not removed automatically: some may be tools you use directly.
|
||||||
|
ORPHANS="$(pacman -Qtdq 2>/dev/null)"
|
||||||
|
if [ -n "$ORPHANS" ]; then
|
||||||
|
echo "Packages no longer needed by anything:"
|
||||||
|
echo "$ORPHANS" | sed 's/^/ /'
|
||||||
|
echo "To remove them: sudo pacman -Rns \$(pacman -Qtdq)"
|
||||||
|
fi
|
||||||
Executable
+10
@@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
source "$HOME/.local/share/MiniServer/utils.sh"
|
||||||
|
|
||||||
|
if initramfs_build_failed; then
|
||||||
|
echo -e "\e[31mDon't reboot yet: the initramfs failed to rebuild (see $PACMAN_UPDATE_LOG).\e[0m"
|
||||||
|
# If the running kernel's modules are gone, a newer kernel was installed
|
||||||
|
elif [ ! -d "/usr/lib/modules/$(uname -r)" ]; then
|
||||||
|
echo "A newer kernel has been installed. Please reboot when convenient."
|
||||||
|
fi
|
||||||
@@ -23,6 +23,53 @@ install_packages() {
|
|||||||
|
|
||||||
if [ ${#to_install[@]} -ne 0 ]; then
|
if [ ${#to_install[@]} -ne 0 ]; then
|
||||||
echo "Installing: ${to_install[*]}"
|
echo "Installing: ${to_install[*]}"
|
||||||
yay -S --noconfirm "${to_install[@]}"
|
yay -S --noconfirm --answerdiff=None --answeredit=None --answerclean=None "${to_install[@]}"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Function to remove pacman/AUR packages if installed
|
||||||
|
remove_packages() {
|
||||||
|
local packages=("$@")
|
||||||
|
local to_remove=()
|
||||||
|
|
||||||
|
for pkg in "${packages[@]}"; do
|
||||||
|
if is_installed "$pkg"; then
|
||||||
|
to_remove+=("$pkg")
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ${#to_remove[@]} -ne 0 ]; then
|
||||||
|
echo "Removing: ${to_remove[*]}"
|
||||||
|
sudo pacman -Rns --noconfirm "${to_remove[@]}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Function to enable systemd services if not already enabled
|
||||||
|
enable_services() {
|
||||||
|
for service in "$@"; do
|
||||||
|
if ! systemctl is-enabled "$service" &> /dev/null; then
|
||||||
|
echo "Enabling $service..."
|
||||||
|
sudo systemctl enable "$service"
|
||||||
|
else
|
||||||
|
echo "$service is already enabled."
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# Output of the last update's pacman -Su, written by updates.d/10-pacman.sh
|
||||||
|
PACMAN_UPDATE_LOG="$HOME/.local/state/miniserver/pacman-update.log"
|
||||||
|
|
||||||
|
# True if the last update rebuilt the initramfs and mkinitcpio reported a
|
||||||
|
# failure. Rebooting then can leave the machine unbootable.
|
||||||
|
initramfs_build_failed() {
|
||||||
|
[ -f "$PACMAN_UPDATE_LOG" ] || return 1
|
||||||
|
grep -q "Updating linux initcpios" "$PACMAN_UPDATE_LOG" || return 1
|
||||||
|
grep -q "==> ERROR" "$PACMAN_UPDATE_LOG" || ! grep -q "Image generation successful" "$PACMAN_UPDATE_LOG"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Install a script atomically (copy to a temp file, then rename over the target)
|
||||||
|
# so a copy of it that is running right now keeps reading the old file
|
||||||
|
install_script() {
|
||||||
|
local tmp
|
||||||
|
tmp="$(mktemp "$2.XXXXXX")" && cp "$1" "$tmp" && chmod 755 "$tmp" && mv -f "$tmp" "$2"
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user