diff --git a/modules.json b/modules.json index 2a27d51..be662e0 100644 --- a/modules.json +++ b/modules.json @@ -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", diff --git a/modules/soar/README.md b/modules/soar/README.md new file mode 100644 index 0000000..44a69f8 --- /dev/null +++ b/modules/soar/README.md @@ -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. + +
+ Uninstallation script + +```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 +``` + +
diff --git a/modules/soar/module.yml b/modules/soar/module.yml new file mode 100644 index 0000000..14f6292 --- /dev/null +++ b/modules/soar/module.yml @@ -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' diff --git a/modules/soar/soar-profile.sh b/modules/soar/soar-profile.sh new file mode 100644 index 0000000..0b5ef0d --- /dev/null +++ b/modules/soar/soar-profile.sh @@ -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 diff --git a/modules/soar/soar-upgrade-packages.service b/modules/soar/soar-upgrade-packages.service new file mode 100644 index 0000000..8a923ea --- /dev/null +++ b/modules/soar/soar-upgrade-packages.service @@ -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 diff --git a/modules/soar/soar-upgrade-packages.timer b/modules/soar/soar-upgrade-packages.timer new file mode 100644 index 0000000..f84897f --- /dev/null +++ b/modules/soar/soar-upgrade-packages.timer @@ -0,0 +1,11 @@ +[Unit] +Description=Timer for upgrading 'soar' packages + +[Timer] +RandomizedDelaySec=10m +OnBootSec=2min +OnUnitInactiveSec=8h +Persistent=true + +[Install] +WantedBy=timers.target \ No newline at end of file diff --git a/modules/soar/soar.sh b/modules/soar/soar.sh new file mode 100644 index 0000000..b71ddc8 --- /dev/null +++ b/modules/soar/soar.sh @@ -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" diff --git a/modules/soar/soar.tsp b/modules/soar/soar.tsp new file mode 100644 index 0000000..114da0e --- /dev/null +++ b/modules/soar/soar.tsp @@ -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; + +}