feat: soar module (#413)

* feat: `soar` module

* fix(soar): Inverted `unlock-repos` logic

* chore(soar): Add `soar` to modules.json

* fix(soar): Logic for detecting if `unlock-repos` is disabled

* fix(docs): Wrong `/usr/` systemd path reference

* chore(soar): Add log for `unlock_repos`

* chore(soar): Add `finished=false` at the beggining of `soar` shell profile

* fix(soar): `/root` being `$HOME` in config

* fix(soar): Wrong `PATH`

I forgot to add `bin` directory

* docs(soar): Clarify that `PATH` needs to be exported only outside of standard config dir

* chore(soar): Unlock extra repos if `unlock-repos` is enabled

* docs(soar): Mention of external repos

* chore(soar): Simplify getting `XDG` directories

* chore(soar): Add removing config in uninstallation script

* chore(soar): Remove whole config directory instead of file in uninstallation script

* fix(soar): Missing quote in uninstallation script

* chore(soar): Make uninstallation script POSIX

* fix(soar): Bad syntax for `soar-upgrade-packages` service `exec`

It's `ExecStart`, not `Exec`.

* fix(soar): Set `auto-upgrade` timer as `--user` instead of `--system`

* chore(soar): Shorten the boot delay & randomize it for `auto-upgrades`

* chore(soar): Add `network-online.target` in `Wants` to systemd service for `auto-upgrades`

* chore(soar): Remove `upgrade-wait-after-boot`, as good defaults are set with no need for changing

* chore(soar): Add option for unlocking the `PATH` in all conditions

* chore(soar): Add `unlock-path` option to typespec

* docs(soar): Add `unlock-path` to `module.yml` example

* chore(soar): Remove condition for checking the `soar.sh` file

If another `soar` module overwrites the previous one, it should be able to overwrite `soar.sh` shell profile too with custom option if set.

* chore(soar): Remove `unlock-path`, as it's too dangerous

* chore(soar): Don't copy update timer & service if `auto-upgrade` is false

* chore(soar): Style fix

* chore(soar): Don't execute `upgrade` when soar is already running

* chore(soar): Use environment variable for `SOAR_CONFIG` to simplify things

* docs(sour): Fix typo

* chore(soar): Use native `soar` command for setting `bincache` repo only

* fix(soar): Make `soar` condition check for  auto-upgrades more reliable

* chore(soar): Some more improvements to auto-update condition check

False positive can happen if some text editor edits some file like `soar-something.txt` or similar.

This assures more that only executed binary is detected.

* chore(soar): Remove duplicate code for setting config

* docs(soar): Add full example of options in `module.yml`

* chore(soar): Rename `unlock-repos` to `additional-repos` & add docs about repo info

* chore(soar): Enable `pkforge-cargo` & `pkgforge-go` repos

* docs: rewordings, run through languagetool

* chore(soar): Add condition in shell profile to check if `soar` PATH already exists

* chore(soar): Disable redundant shellcheck SC2076 in shell profile

* docs: final reword

---------

Co-authored-by: xyny <git@xyny.anonaddy.me>
Co-authored-by: xyny <60004820+xynydev@users.noreply.github.com>
This commit is contained in:
fiftydinar 2025-07-26 16:10:21 +02:00 committed by GitHub
parent a639f1f64f
commit 23f020a5b3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 186 additions and 0 deletions

View file

@ -4,6 +4,7 @@
"https://raw.githubusercontent.com/blue-build/modules/main/modules/chezmoi/module.yml",
"https://raw.githubusercontent.com/blue-build/modules/main/modules/bling/module.yml",
"https://raw.githubusercontent.com/blue-build/modules/main/modules/brew/module.yml",
"https://raw.githubusercontent.com/blue-build/modules/main/modules/soar/module.yml",
"https://raw.githubusercontent.com/blue-build/modules/main/modules/default-flatpaks/module.yml",
"https://raw.githubusercontent.com/blue-build/modules/main/modules/fonts/module.yml",
"https://raw.githubusercontent.com/blue-build/modules/main/modules/gnome-extensions/module.yml",

64
modules/soar/README.md Normal file
View file

@ -0,0 +1,64 @@
# soar
The `soar` module installs & integrates the [`soar`](https://github.com/pkgforge/soar) package manager as an alternative to [Homebrew / Linuxbrew](https://brew.sh/).
[`soar`](https://github.com/pkgforge/soar) is a package manager, which manages the installation of portable & static binaries.
[PkgForge's](https://github.com/pkgforge) `bincache`, `pkgforge-cargo` & `pkgforge-go` repos are used by default for the binaries.
Other default & external repos that contain AppImages & other similar formats are disabled to make `soar` focused on CLI binaries only.
This is configurable if you wish to have a package manager for GUI applications, see [`Configuration options`](#configuration-options).
The repositories with prebuilt binaries use the GitHub Container registry as their backend and all their packages are published there.
Compared to [Homebrew / Linuxbrew](https://brew.sh/):
- there are no managed dependencies for packages by design (single package = single binary).
- no conflicting system packages in the repo (like `systemd`, `dbus` or similar).
- it's simpler in design, with respect for Linux folder structuring
For more information, please see the [official documentation of `soar`](https://soar.qaidvoid.dev/).
## Features
- Downloads & installs `soar`.
- Sets up systemd timer for auto-upgrading `soar` packages.
- Sets up shell profile for automatically adding the directory containing `soar` binaries to `PATH`.
## Repos
To see the useful information about source, reliability, trust & security of all `soar` repos, including external ones, you can open the links below:
- https://docs.pkgforge.dev/repositories
- https://docs.pkgforge.dev/repositories/external
## Local modification
By default, `soar` utilizes BlueBuild's config (`/usr/share/bluebuild/soar/config.toml`).
End-users can use custom a `soar` configuration by creating it at `~/.config/soar/config.toml`, or in a custom directory while making sure to supply it to `soar` by providing `SOAR_CONFIG` the environment variable in shell profile.
If you specify the custom `bin_path` directory for `soar` packages, you also need to export that directory to `PATH` manually in the shell profile.
## Uninstallation
Removing the `soar` module from the recipe is not enough to get it completely removed.
On a booted system, it's also necessary to run the `soar` uninstallation script to uninstall config & installed packages in the `${HOME}` directory.
Either a local-user can execute this script manually, or the image-maintainer may make it automatic through a custom systemd service.
<details>
<summary>Uninstallation script</summary>
```sh
#!/bin/sh
if [ -f "${XDG_CONFIG_HOME:-$HOME/.config}/soar/config.toml" ]; then
echo "Removing soar config in '${XDG_CONFIG_HOME:-$HOME/.config}/soar/' directory"
rm -r "${XDG_CONFIG_HOME:-$HOME/.config}/soar/"
else
echo "'${XDG_CONFIG_HOME:-$HOME/.config}/soar/config.toml' file is already removed"
fi
if [ -d "${XDG_DATA_HOME:-$HOME/.local/share}/soar/" ]; then
echo "Removing '${XDG_DATA_HOME:-$HOME/.local/share}/soar/' directory"
rm -r "${XDG_DATA_HOME:-$HOME/.local/share}/soar/"
else
echo "'${XDG_DATA_HOME:-$HOME/.local/share}/soar/' directory is already removed"
fi
```
</details>

7
modules/soar/module.yml Normal file
View file

@ -0,0 +1,7 @@
name: soar
shortdesc: The soar module installs & integrates the soar package manager, as an alternative to Homebrew / Linuxbrew.
example: |
type: soar
additional-repos: true
auto-upgrade: true
upgrade-interval: '3h'

View file

@ -0,0 +1,8 @@
#!/bin/bash
# Don't source PATH for soar packages when it's not an interactive terminal session & when it's a root user
if [[ ${-} == *i* && "$(/bin/id -u)" != 0 ]]; then
# shellcheck disable=SC2076
if ! [[ "$PATH" =~ "${XDG_DATA_HOME:-$HOME/.local/share}/soar/bin:" ]]; then
export PATH="${XDG_DATA_HOME:-$HOME/.local/share}/soar/bin:${PATH}"
fi
fi

View file

@ -0,0 +1,9 @@
[Unit]
Description=Auto-upgrade 'soar' packages
Wants=network-online.target
After=network-online.target
[Service]
Type=oneshot
ExecCondition=/bin/bash -c 'if ps aux | grep -v grep | grep -E -q " /sbin/soar | /bin/soar | /usr/sbin/soar | /usr/bin/soar | soar "; then exit 1; else exit 0; fi'
ExecStart=/bin/soar update

View file

@ -0,0 +1,11 @@
[Unit]
Description=Timer for upgrading 'soar' packages
[Timer]
RandomizedDelaySec=10m
OnBootSec=2min
OnUnitInactiveSec=8h
Persistent=true
[Install]
WantedBy=timers.target

61
modules/soar/soar.sh Normal file
View file

@ -0,0 +1,61 @@
#!/usr/bin/env bash
set -euo pipefail
# Install 'soar'
echo "Downloading & installing 'soar' package manager"
REPO="pkgforge/soar"
LATEST_VER="$(basename $(curl -Ls -o /dev/null -w %{url_effective} https://github.com/${REPO}/releases/latest))"
# Assuming that ARM64 custom images will be built from ARM64 runners for this working detection
ARCH="$(uname -m)"
curl -fLs --create-dirs "https://github.com/${REPO}/releases/download/${LATEST_VER}/soar-${ARCH}-linux" -o "/usr/bin/soar"
chmod +x "/usr/bin/soar"
# Configuration values for package auto-upgrades (using upgrade term here from brew)
AUTO_UPGRADE=$(echo "${1}" | jq -r 'try .["auto-upgrade"]')
if [[ -z "${AUTO_UPGRADE}" || "${AUTO_UPGRADE}" == "null" ]]; then
AUTO_UPGRADE=true
fi
UPGRADE_INTERVAL=$(echo "${1}" | jq -r 'try .["upgrade-interval"]')
if [[ -z "${UPGRADE_INTERVAL}" || "${UPGRADE_INTERVAL}" == "null" ]]; then
UPGRADE_INTERVAL="8h"
fi
# Configuration for enabling additional repos (outside of 'bincache')
ADDITIONAL_REPOS=$(echo "${1}" | jq -r 'try .["additional-repos"]')
mkdir -p "/usr/share/bluebuild/soar"
if [[ "${ADDITIONAL_REPOS}" == "true" ]]; then
echo "Enabling all additional 'soar' repos in config, including external ones"
soar defconfig --external -c "/usr/share/bluebuild/soar/config.toml"
else
echo "Using the default 'bincache', 'pkgforge-cargo' & 'pkgforge-go' repositories in config"
soar -c /usr/share/bluebuild/soar/config.toml defconfig -r bincache -r pkgforge-cargo -r pkgforge-go
fi
# Fix /root being ${HOME}
sed -i 's|/root|~|g' "/usr/share/bluebuild/soar/config.toml"
# Add soar config to environment
echo "SOAR_CONFIG=/usr/share/bluebuild/soar/config.toml" >> /etc/environment
if [[ "${AUTO_UPGRADE}" == true ]]; then
echo "Configuring auto-upgrades of 'soar' packages"
echo "Copying soar-upgrade-packages service"
cp "${MODULE_DIRECTORY}/soar/soar-upgrade-packages.service" "/usr/lib/systemd/user/soar-upgrade-packages.service"
if [[ -n "${UPGRADE_INTERVAL}" ]] && [[ "${UPGRADE_INTERVAL}" != "8h" ]]; then
echo "Applying custom 'upgrade-interval' value in '${UPGRADE_INTERVAL}' time interval for soar-upgrade-packages timer"
sed -i "s/^OnUnitInactiveSec=.*/OnUnitInactiveSec=${UPGRADE_INTERVAL}/" "${MODULE_DIRECTORY}/soar/soar-upgrade-packages.timer"
fi
echo "Copying soar-upgrade-packages timer"
cp "${MODULE_DIRECTORY}/soar/soar-upgrade-packages.timer" "/usr/lib/systemd/user/soar-upgrade-packages.timer"
echo "Enabling auto-upgrades for 'soar' packages"
systemctl --global enable soar-upgrade-packages.timer
else
echo "Auto-upgrades for 'soar' packages are disabled"
fi
# Add 'soar' packages to path only when it's interactive terminal session & non-root user, similar to brew
if [[ ! -d "/etc/profile.d/" ]]; then
mkdir -p "/etc/profile.d/"
fi
echo "Applying shell profile for exporting 'soar' packages directory to PATH"
cp "${MODULE_DIRECTORY}/soar/soar-profile.sh" "/etc/profile.d/soar.sh"

25
modules/soar/soar.tsp Normal file
View file

@ -0,0 +1,25 @@
import "@typespec/json-schema";
using TypeSpec.JsonSchema;
@jsonSchema("/modules/soar-latest.json")
model SoarModuleLatest {
...SoarModuleV1;
}
@jsonSchema("/modules/soar-v1.json")
model SoarModuleV1 {
/** The soar module installs & integrates soar package manager, as an alternative to Homebrew / Linuxbrew.
* https://blue-build.org/reference/modules/soar/
*/
type: "soar" | "soar@v1" | "soar@latest";
/** Whether to auto-upgrade all installed `soar` packages using a systemd service. */
`auto-upgrade`?: boolean = true;
/** Defines how often the `soar` upgrade service should run. The string is passed directly to `OnUnitInactiveSec` in systemd timer. (Syntax: ['1d', '6h', '10m']). */
`upgrade-interval`?: string = "8h";
/** Whether to enable all additional repos, including official `soar` & external repos like `AM`, for installing portable AppImages & other similar formats. */
`additional-repos`?: boolean = false;
}