From 7f07e851be3b1edceea4093f5b61b4cd9c2b68d1 Mon Sep 17 00:00:00 2001 From: GigabiteStudios Date: Sat, 20 Jun 2026 21:05:52 -0500 Subject: [PATCH 1/8] fix(ci): normalize container image names --- .gitea/workflows/release.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 972fd64..f8e555c 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -37,22 +37,25 @@ jobs: short_sha="$(printf '%s' "$GITEA_SHA" | cut -c1-7)" repo_owner="${GITEA_REPOSITORY%%/*}" registry_host="${GITEA_REGISTRY}" + app_name="$(printf '%s' "$APP_NAME" | tr '[:upper:]' '[:lower:]')" if [ -z "$registry_host" ]; then registry_host="$(printf '%s' "$GITEA_SERVER_URL" | sed -E 's#^https?://##; s#/$##')" fi + registry_host="$(printf '%s' "$registry_host" | tr '[:upper:]' '[:lower:]')" package_namespace="${GITEA_PACKAGE_NAMESPACE}" if [ -z "$package_namespace" ]; then package_namespace="${repo_owner}" fi + package_namespace="$(printf '%s' "$package_namespace" | tr '[:upper:]' '[:lower:]')" registry_username="${GITEA_REGISTRY_USERNAME}" if [ -z "$registry_username" ]; then registry_username="${GITEA_ACTOR}" fi - image_ref="${registry_host}/${package_namespace}/${APP_NAME}" + image_ref="${registry_host}/${package_namespace}/${app_name}" echo "SHORT_SHA=${short_sha}" >> "$GITHUB_ENV" echo "RELEASE_TAG=release-${short_sha}" >> "$GITHUB_ENV" From a95271c516429533c62c13e016969a3bd80c6e1d Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sun, 21 Jun 2026 02:13:09 +0000 Subject: [PATCH 2/8] deps: Update golang Docker tag to v1.26 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 245e617..1b8a48c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.25-bookworm AS build +FROM golang:1.26-bookworm@sha256:5f68ec6805843bd3981a951ffada82a26a0bd2631045c8f7dba483fa868f5ec5 AS build WORKDIR /src From 993fc60cf365b0260a781c35be11a6c5a932e2c0 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sun, 21 Jun 2026 02:25:34 +0000 Subject: [PATCH 3/8] deps: Pin dependencies --- .gitea/workflows/release.yml | 2 +- .gitea/workflows/verify.yml | 4 ++-- Dockerfile | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index f8e555c..c92d831 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -25,7 +25,7 @@ jobs: steps: - name: Check out repository - uses: https://dock-it.dev/actions/checkout@v4 + uses: https://dock-it.dev/actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: fetch-depth: 0 diff --git a/.gitea/workflows/verify.yml b/.gitea/workflows/verify.yml index 7e62001..0181bb7 100644 --- a/.gitea/workflows/verify.yml +++ b/.gitea/workflows/verify.yml @@ -12,10 +12,10 @@ jobs: steps: - name: Checkout code - uses: https://dock-it.dev/actions/checkout@v4 + uses: https://dock-it.dev/actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Set up Go - uses: https://dock-it.dev/actions/setup-go@v5 + uses: https://dock-it.dev/actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5 with: go-version-file: go.mod cache: true diff --git a/Dockerfile b/Dockerfile index 1b8a48c..081028c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ COPY web ./web RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o /out/maintainarr ./cmd/maintainarr -FROM debian:bookworm-slim +FROM debian:bookworm-slim@sha256:96e378d7e6531ac9a15ad505478fcc2e69f371b10f5cdf87857c4b8188404716 RUN apt-get update \ && apt-get install -y --no-install-recommends ca-certificates tzdata \ From 8a2eb31baa76d767a422e5ae8c52b4e185e56803 Mon Sep 17 00:00:00 2001 From: GigabiteStudios Date: Sat, 20 Jun 2026 21:33:37 -0500 Subject: [PATCH 4/8] fix(ci): require explicit registry credentials --- .gitea/workflows/release.yml | 21 +++++++++++++++++---- README.md | 4 +++- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index f8e555c..ae247ce 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -17,10 +17,10 @@ jobs: GITEA_SERVER_URL: ${{ gitea.server_url }} GITEA_REPOSITORY: ${{ gitea.repository }} GITEA_SHA: ${{ gitea.sha }} - GITEA_ACTOR: ${{ gitea.actor }} GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} GITEA_REGISTRY: ${{ secrets.GITEA_REGISTRY }} GITEA_REGISTRY_USERNAME: ${{ secrets.GITEA_REGISTRY_USERNAME }} + GITEA_REGISTRY_TOKEN: ${{ secrets.GITEA_REGISTRY_TOKEN }} GITEA_PACKAGE_NAMESPACE: ${{ secrets.GITEA_PACKAGE_NAMESPACE }} steps: @@ -52,7 +52,8 @@ jobs: registry_username="${GITEA_REGISTRY_USERNAME}" if [ -z "$registry_username" ]; then - registry_username="${GITEA_ACTOR}" + echo "The repository secret GITEA_REGISTRY_USERNAME is required for container registry login." + exit 1 fi image_ref="${registry_host}/${package_namespace}/${app_name}" @@ -71,10 +72,22 @@ jobs: set -euo pipefail if [ -z "$GITEA_TOKEN" ]; then - echo "The repository secret GITEA_TOKEN is required to publish releases and packages." + echo "The repository secret GITEA_TOKEN is required to publish releases." exit 1 fi + registry_token="${GITEA_REGISTRY_TOKEN}" + if [ -z "$registry_token" ]; then + registry_token="${GITEA_TOKEN}" + fi + + if [ -z "$GITEA_REGISTRY_USERNAME" ]; then + echo "The repository secret GITEA_REGISTRY_USERNAME is required to publish container packages." + exit 1 + fi + + echo "REGISTRY_TOKEN=${registry_token}" >> "$GITHUB_ENV" + - name: Install release dependencies shell: bash run: | @@ -86,7 +99,7 @@ jobs: shell: bash run: | set -euo pipefail - printf '%s' "$GITEA_TOKEN" | docker login "$REGISTRY_HOST" --username "$REGISTRY_USERNAME" --password-stdin + printf '%s' "$REGISTRY_TOKEN" | docker login "$REGISTRY_HOST" --username "$REGISTRY_USERNAME" --password-stdin - name: Build container image shell: bash diff --git a/README.md b/README.md index 9fac299..308e14b 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,9 @@ MAINTAINARR_REFRESH_CRON=@every 5s - Optional secret: `GITEA_REGISTRY` Defaults to the host from `gitea.server_url` - Optional secret: `GITEA_REGISTRY_USERNAME` - Defaults to `gitea.actor` + Required for container registry login +- Optional secret: `GITEA_REGISTRY_TOKEN` + Defaults to `GITEA_TOKEN` - Optional secret: `GITEA_PACKAGE_NAMESPACE` Defaults to the repository owner from `gitea.repository` From f07be8a531340f327257fd3221d60b46f8219566 Mon Sep 17 00:00:00 2001 From: GigabiteStudios Date: Sat, 20 Jun 2026 21:42:34 -0500 Subject: [PATCH 5/8] feat(install): add bootstrap installer and simplify README --- README.md | 175 +++++----------------- install.sh | 416 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 450 insertions(+), 141 deletions(-) create mode 100755 install.sh diff --git a/README.md b/README.md index 308e14b..d0a0b6d 100644 --- a/README.md +++ b/README.md @@ -1,152 +1,45 @@ +![Maintainarr Banner](web/static/img/maintainarr_banner.png) + # Maintainarr -Maintainarr is a self-hosted Go application for managing a single organization's Linux VM fleet from one dashboard. +Maintainarr is a self-hosted dashboard for managing Linux VMs from one place. -It ships with: +## Install -- SQLite-backed persistence -- Daily compressed command log archives -- User registration and login -- OTP-based 2FA setup and verification -- Role model with `admin`, `editor`, and `viewer` -- Sidebar dashboard with theme tokens compatible with [UI Colors](https://uicolors.app/generate) -- VM chip grid with distro icon, CPU/RAM bars, IP, and uptime -- Node overview pages with action buttons and SSH web console -- Wake-on-LAN, restart, shutdown, stat refresh, and apt-upgrade action hooks -- VM groups and recurring/triggered automation job scaffolding -- Go-first backend structure intended for adding more fleet features without rewrites - -## Branch Model - -- `main`: stable branch -- `dev`: active feature branch - -The repository has been initialized with both `main` and `dev`. Work should land in `dev` first. - -## Stack - -- Go `1.25` -- `chi` router -- `modernc.org/sqlite` for embedded SQLite -- `gorilla/sessions` for cookie sessions -- `pquerna/otp` for TOTP 2FA -- `golang.org/x/crypto/ssh` for remote command and console transport -- Embedded Go HTML templates plus local static assets - -## Project Layout - -```text -cmd/maintainarr entrypoint -internal/app app bootstrap and routing -internal/config environment configuration -internal/db sqlite connection and schema bootstrap -internal/handlers HTTP handlers and websocket console -internal/middleware auth and role enforcement -internal/models domain models -internal/services auth, crypto, nodes, scheduler, repository -internal/views embedded templates and view helpers -web/static CSS and browser-side JS +```bash +curl -fsSL https://dock-it.dev/Idea-Studios/Maintainarr/raw/branch/main/install.sh | sudo bash ``` -## Run +The installer will: -```powershell +- install missing system packages +- install Go if needed +- download or reuse Maintainarr under `/opt/maintainarr` +- create config in `/etc/maintainarr/maintainarr.env` +- create data in `/var/lib/maintainarr` +- ask about auto-updates with `cron` +- ask about starting at boot with `systemd` + +## Open + +After install, open: + +```text +http://your-server-ip:8080 +``` + +The first registered user becomes the admin. + +## Manual Run + +```bash +cd /opt/maintainarr/src go run ./cmd/maintainarr ``` -Default address: `http://localhost:8080` +## Important Paths -## Container - -```powershell -docker build -t maintainarr . -docker run --rm -p 8080:8080 -v ${PWD}/data:/app/data maintainarr -``` - -## First User - -The first registered user becomes the initial `admin`. - -No example users, groups, nodes, or jobs are seeded. - -## Environment - -```env -MAINTAINARR_ADDR=:8080 -MAINTAINARR_DB_PATH=data/maintainarr.db -MAINTAINARR_LOG_ARCHIVE_DIR=data/log-archives -MAINTAINARR_SESSION_KEY=change-me-session-key-please -MAINTAINARR_ENCRYPTION_KEY=change-me-encryption-key-32bytes -MAINTAINARR_ORG_NAME=Maintainarr -MAINTAINARR_BASE_URL=http://localhost:8080 -MAINTAINARR_THEME=blue -MAINTAINARR_THEME_MODE=dark -MAINTAINARR_REFRESH_CRON=@every 5s -``` - -## Release Automation - -- Push or merge into `main` to trigger `.gitea/workflows/release.yml` -- The workflow builds a Docker image, publishes it to the Gitea container registry, and creates or updates a Gitea release -- It tags the image as `latest`, `main`, and the short commit SHA -- Required secret: `GITEA_TOKEN` -- Optional secret: `GITEA_REGISTRY` - Defaults to the host from `gitea.server_url` -- Optional secret: `GITEA_REGISTRY_USERNAME` - Required for container registry login -- Optional secret: `GITEA_REGISTRY_TOKEN` - Defaults to `GITEA_TOKEN` -- Optional secret: `GITEA_PACKAGE_NAMESPACE` - Defaults to the repository owner from `gitea.repository` - -## Roles - -- `admin`: full access, intended for user management and future organization settings -- `editor`: can operate nodes and create automation jobs -- `viewer`: read-only access - -## Theme System - -The dashboard is styled around `uicolors.app` style primary scale variables: - -```css ---color-primary-50 ---color-primary-100 ---color-primary-200 ---color-primary-300 ---color-primary-400 ---color-primary-500 ---color-primary-600 ---color-primary-700 ---color-primary-800 ---color-primary-900 ---color-primary-950 -``` - -Replace those values in `web/static/css/app.css` or feed them through a future admin theme editor to recolor the interface. - -## Current Scope - -This repository now includes the base architecture for: - -- auth and sessions -- role-aware route protection -- dashboard rendering -- node actions -- SSH-backed console transport -- SQLite schema for future fleet features -- recurring automation scheduling - -## Next Expansions - -The backend shape is ready for: - -- user management screens -- node create/edit flows -- SSH key auth -- richer live metrics collection -- audit logs -- package policy models -- triggered event ingestion -- per-group maintenance windows -- multi-step provisioning workflows +- config: `/etc/maintainarr/maintainarr.env` +- data: `/var/lib/maintainarr` +- source: `/opt/maintainarr/src` +- binary: `/opt/maintainarr/bin/maintainarr` diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..3403a0e --- /dev/null +++ b/install.sh @@ -0,0 +1,416 @@ +#!/usr/bin/env bash + +set -euo pipefail + +REPO_URL="https://dock-it.dev/Idea-Studios/Maintainarr.git" +REPO_BRANCH="${MAINTAINARR_BRANCH:-main}" +APP_USER="${MAINTAINARR_USER:-maintainarr}" +INSTALL_ROOT="${MAINTAINARR_INSTALL_ROOT:-/opt/maintainarr}" +SRC_DIR="$INSTALL_ROOT/src" +BIN_DIR="$INSTALL_ROOT/bin" +BIN_PATH="$BIN_DIR/maintainarr" +DATA_DIR="${MAINTAINARR_DATA_DIR:-/var/lib/maintainarr}" +CONFIG_DIR="${MAINTAINARR_CONFIG_DIR:-/etc/maintainarr}" +ENV_FILE="$CONFIG_DIR/maintainarr.env" +SYSTEMD_UNIT="/etc/systemd/system/maintainarr.service" +UPDATE_SCRIPT="/usr/local/bin/maintainarr-update" +CRON_FILE="/etc/cron.d/maintainarr-update" +GO_VERSION="${MAINTAINARR_GO_VERSION:-1.26.4}" + +log() { + printf '[maintainarr] %s\n' "$*" +} + +fail() { + printf '[maintainarr] ERROR: %s\n' "$*" >&2 + exit 1 +} + +require_root() { + if [[ "${EUID}" -ne 0 ]]; then + fail "Run this installer as root, for example: curl -fsSL ... | sudo bash" + fi +} + +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +apt_package_installed() { + dpkg -s "$1" >/dev/null 2>&1 +} + +version_ge() { + [[ "$(printf '%s\n%s\n' "$2" "$1" | sort -V | head -n1)" == "$2" ]] +} + +detect_arch() { + case "$(uname -m)" in + x86_64|amd64) + echo "amd64" + ;; + aarch64|arm64) + echo "arm64" + ;; + *) + fail "Unsupported architecture: $(uname -m)" + ;; + esac +} + +pkg_install() { + if command_exists apt-get; then + export DEBIAN_FRONTEND=noninteractive + apt-get install -y "$@" + return + fi + + if command_exists dnf; then + dnf install -y "$@" + return + fi + + if command_exists yum; then + yum install -y "$@" + return + fi + + if command_exists pacman; then + pacman -Sy --noconfirm "$@" + return + fi + + if command_exists zypper; then + zypper --non-interactive install "$@" + return + fi + + if command_exists apk; then + apk add --no-cache "$@" + return + fi + + fail "No supported package manager found" +} + +install_base_packages() { + if command_exists apt-get; then + local packages=() + local package + + for package in ca-certificates curl git tar cron; do + if ! apt_package_installed "$package"; then + packages+=("$package") + fi + done + + if [[ "${#packages[@]}" -eq 0 ]]; then + log "System packages already present: ca-certificates curl git tar cron" + return + fi + + log "Installing missing system packages: ${packages[*]}" + export DEBIAN_FRONTEND=noninteractive + apt-get update + apt-get install -y "${packages[@]}" + return + fi + + if command_exists dnf; then + pkg_install ca-certificates curl git tar cronie + return + fi + + if command_exists yum; then + pkg_install ca-certificates curl git tar cronie + return + fi + + if command_exists pacman; then + pkg_install ca-certificates curl git tar cronie + return + fi + + if command_exists zypper; then + pkg_install ca-certificates curl git tar cron + return + fi + + if command_exists apk; then + pkg_install ca-certificates curl git tar dcron + return + fi + + fail "Unable to install prerequisites for this distribution" +} + +install_go() { + local current_version="" + + if command_exists go; then + current_version="$(go version | awk '{print $3}' | sed 's/^go//')" + elif [[ -x /usr/local/go/bin/go ]]; then + current_version="$(/usr/local/go/bin/go version | awk '{print $3}' | sed 's/^go//')" + fi + + if [[ -n "$current_version" ]] && version_ge "$current_version" "$GO_VERSION"; then + log "Go $current_version already satisfies the requirement" + return + fi + + local arch + arch="$(detect_arch)" + local archive="/tmp/go${GO_VERSION}.linux-${arch}.tar.gz" + + log "Installing Go $GO_VERSION" + curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-${arch}.tar.gz" -o "$archive" + rm -rf /usr/local/go + tar -C /usr/local -xzf "$archive" + ln -sf /usr/local/go/bin/go /usr/local/bin/go + ln -sf /usr/local/go/bin/gofmt /usr/local/bin/gofmt +} + +tty_available() { + [[ -r /dev/tty && -w /dev/tty ]] +} + +prompt_yes_no() { + local prompt="$1" + local default_answer="$2" + local reply="" + local suffix="[y/N]" + + if [[ "$default_answer" == "y" ]]; then + suffix="[Y/n]" + fi + + if ! tty_available; then + [[ "$default_answer" == "y" ]] + return + fi + + while true; do + printf '%s %s ' "$prompt" "$suffix" > /dev/tty + IFS= read -r reply < /dev/tty || true + reply="${reply:-$default_answer}" + + case "$reply" in + y|Y|yes|YES) + return 0 + ;; + n|N|no|NO) + return 1 + ;; + *) + printf 'Please answer yes or no.\n' > /dev/tty + ;; + esac + done +} + +ensure_user() { + if id -u "$APP_USER" >/dev/null 2>&1; then + return + fi + + local nologin_shell + nologin_shell="$(command -v nologin || true)" + nologin_shell="${nologin_shell:-/usr/sbin/nologin}" + useradd --system --home "$INSTALL_ROOT" --create-home --shell "$nologin_shell" "$APP_USER" +} + +clone_or_update_repo() { + mkdir -p "$INSTALL_ROOT" "$BIN_DIR" + + if [[ -d "$SRC_DIR/.git" ]]; then + log "Updating source in $SRC_DIR" + git -C "$SRC_DIR" fetch --tags origin + git -C "$SRC_DIR" checkout "$REPO_BRANCH" + git -C "$SRC_DIR" pull --ff-only origin "$REPO_BRANCH" + return + fi + + if [[ -f "$SRC_DIR/go.mod" && -d "$SRC_DIR/cmd/maintainarr" ]]; then + log "Existing Maintainarr source found in $SRC_DIR; skipping download" + return + fi + + log "Cloning source into $SRC_DIR" + git clone --branch "$REPO_BRANCH" --single-branch "$REPO_URL" "$SRC_DIR" +} + +build_binary() { + log "Building Maintainarr" + mkdir -p "$BIN_DIR" + ( + cd "$SRC_DIR" + export PATH="/usr/local/go/bin:/usr/local/bin:$PATH" + export CGO_ENABLED=0 + go build -o "$BIN_PATH" ./cmd/maintainarr + ) + chmod 755 "$BIN_PATH" +} + +random_hex() { + local bytes="$1" + od -An -N"$bytes" -tx1 /dev/urandom | tr -d ' \n' +} + +write_env_file() { + mkdir -p "$CONFIG_DIR" "$DATA_DIR/log-archives" + + if [[ -f "$ENV_FILE" ]]; then + log "Keeping existing environment file at $ENV_FILE" + return + fi + + local session_key + local encryption_key + session_key="$(random_hex 32)" + encryption_key="$(random_hex 16)" + + cat > "$ENV_FILE" < "$UPDATE_SCRIPT" <&2 + exit 1 +fi + +git -C "\$SRC_DIR" fetch --tags origin +git -C "\$SRC_DIR" checkout "\$REPO_BRANCH" +git -C "\$SRC_DIR" pull --ff-only origin "\$REPO_BRANCH" + +( + cd "\$SRC_DIR" + export CGO_ENABLED=0 + go build -o "\$BIN_PATH" ./cmd/maintainarr +) + +chown "\$APP_USER:\$APP_USER" "\$BIN_PATH" + +if command -v systemctl >/dev/null 2>&1 && systemctl is-enabled --quiet maintainarr 2>/dev/null; then + systemctl restart maintainarr +fi +EOF + + chmod 755 "$UPDATE_SCRIPT" +} + +write_systemd_service() { + cat > "$SYSTEMD_UNIT" < "$CRON_FILE" <> /var/log/maintainarr-update.log 2>&1 +EOF + + chmod 644 "$CRON_FILE" + + if command_exists systemctl; then + if systemctl list-unit-files | grep -q '^cron\.service'; then + systemctl enable --now cron >/dev/null 2>&1 || true + elif systemctl list-unit-files | grep -q '^crond\.service'; then + systemctl enable --now crond >/dev/null 2>&1 || true + fi + fi + + log "Installed daily update job in $CRON_FILE" +} + +main() { + require_root + install_base_packages + install_go + ensure_user + clone_or_update_repo + write_env_file + build_binary + write_update_script + + mkdir -p "$DATA_DIR" + chown -R "$APP_USER:$APP_USER" "$INSTALL_ROOT" "$DATA_DIR" + chown root:"$APP_USER" "$ENV_FILE" + + if prompt_yes_no "Enable automatic daily updates with cron?" "n"; then + configure_auto_updates + else + rm -f "$CRON_FILE" + log "Skipped cron auto-update setup" + fi + + if prompt_yes_no "Start Maintainarr at boot with systemd?" "y"; then + enable_start_on_boot + else + log "Skipped boot-start setup" + log "Run it manually with: $BIN_PATH" + fi + + log "Install complete" + log "Config: $ENV_FILE" + log "Data: $DATA_DIR" + log "Source: $SRC_DIR" + log "Binary: $BIN_PATH" +} + +main "$@" From f52dfc9c2ba5147b9282d9cc32a40d6c6bbe2e7c Mon Sep 17 00:00:00 2001 From: GigabiteStudios Date: Sat, 20 Jun 2026 21:47:38 -0500 Subject: [PATCH 6/8] feat(install): print access details after setup --- install.sh | 88 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 83 insertions(+), 5 deletions(-) diff --git a/install.sh b/install.sh index 3403a0e..597db3a 100755 --- a/install.sh +++ b/install.sh @@ -16,6 +16,7 @@ SYSTEMD_UNIT="/etc/systemd/system/maintainarr.service" UPDATE_SCRIPT="/usr/local/bin/maintainarr-update" CRON_FILE="/etc/cron.d/maintainarr-update" GO_VERSION="${MAINTAINARR_GO_VERSION:-1.26.4}" +INSTALL_COMMAND="curl -fsSL https://dock-it.dev/Idea-Studios/Maintainarr/raw/branch/main/install.sh | sudo bash" log() { printf '[maintainarr] %s\n' "$*" @@ -256,6 +257,77 @@ random_hex() { od -An -N"$bytes" -tx1 /dev/urandom | tr -d ' \n' } +env_value() { + local key="$1" + awk -F= -v target="$key" '$1 == target { sub(/^[^=]+=*/, "", $0); print $0; exit }' "$ENV_FILE" +} + +extract_port() { + local addr="$1" + + if [[ "$addr" =~ :([0-9]+)$ ]]; then + echo "${BASH_REMATCH[1]}" + return + fi + + echo "8080" +} + +detect_local_ip() { + local ip="" + + if command_exists hostname; then + ip="$(hostname -I 2>/dev/null | awk '{print $1}')" + fi + + if [[ -z "$ip" ]] && command_exists ip; then + ip="$(ip route get 1.1.1.1 2>/dev/null | awk '/src/ {for (i=1;i<=NF;i++) if ($i=="src") {print $(i+1); exit}}')" + fi + + if [[ -z "$ip" ]]; then + ip="127.0.0.1" + fi + + echo "$ip" +} + +print_summary() { + local local_ip="$1" + local port="$2" + local auto_updates="$3" + local start_on_boot="$4" + local addr base_url + + addr="$(env_value "MAINTAINARR_ADDR")" + base_url="$(env_value "MAINTAINARR_BASE_URL")" + + printf '\n' + printf 'Maintainarr install complete\n' + printf 'Local URL: http://%s:%s\n' "$local_ip" "$port" + printf 'Base URL: %s\n' "$base_url" + printf '\n' + printf 'Settings\n' + printf 'MAINTAINARR_ADDR=%s\n' "$addr" + printf 'MAINTAINARR_DB_PATH=%s\n' "$(env_value "MAINTAINARR_DB_PATH")" + printf 'MAINTAINARR_LOG_ARCHIVE_DIR=%s\n' "$(env_value "MAINTAINARR_LOG_ARCHIVE_DIR")" + printf 'MAINTAINARR_ORG_NAME=%s\n' "$(env_value "MAINTAINARR_ORG_NAME")" + printf 'MAINTAINARR_BASE_URL=%s\n' "$base_url" + printf 'MAINTAINARR_THEME=%s\n' "$(env_value "MAINTAINARR_THEME")" + printf 'MAINTAINARR_THEME_MODE=%s\n' "$(env_value "MAINTAINARR_THEME_MODE")" + printf 'MAINTAINARR_REFRESH_CRON=%s\n' "$(env_value "MAINTAINARR_REFRESH_CRON")" + printf 'AUTO_UPDATES=%s\n' "$auto_updates" + printf 'START_ON_BOOT=%s\n' "$start_on_boot" + printf '\n' + printf 'Paths\n' + printf 'Config: %s\n' "$ENV_FILE" + printf 'Data: %s\n' "$DATA_DIR" + printf 'Source: %s\n' "$SRC_DIR" + printf 'Binary: %s\n' "$BIN_PATH" + printf '\n' + printf 'Re-run installer\n' + printf '%s\n' "$INSTALL_COMMAND" +} + write_env_file() { mkdir -p "$CONFIG_DIR" "$DATA_DIR/log-archives" @@ -379,6 +451,10 @@ EOF } main() { + local auto_updates="disabled" + local start_on_boot="disabled" + local addr port local_ip + require_root install_base_packages install_go @@ -394,6 +470,7 @@ main() { if prompt_yes_no "Enable automatic daily updates with cron?" "n"; then configure_auto_updates + auto_updates="enabled" else rm -f "$CRON_FILE" log "Skipped cron auto-update setup" @@ -401,16 +478,17 @@ main() { if prompt_yes_no "Start Maintainarr at boot with systemd?" "y"; then enable_start_on_boot + start_on_boot="enabled" else log "Skipped boot-start setup" log "Run it manually with: $BIN_PATH" fi - log "Install complete" - log "Config: $ENV_FILE" - log "Data: $DATA_DIR" - log "Source: $SRC_DIR" - log "Binary: $BIN_PATH" + addr="$(env_value "MAINTAINARR_ADDR")" + port="$(extract_port "$addr")" + local_ip="$(detect_local_ip)" + + print_summary "$local_ip" "$port" "$auto_updates" "$start_on_boot" } main "$@" From bc3d8bf1cee05174b1441e4fa66db204659c2bfb Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sun, 21 Jun 2026 02:52:31 +0000 Subject: [PATCH 7/8] deps: Update https://dock-it.dev/actions/setup-go action to v6 --- .gitea/workflows/verify.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/verify.yml b/.gitea/workflows/verify.yml index 0181bb7..c23a59e 100644 --- a/.gitea/workflows/verify.yml +++ b/.gitea/workflows/verify.yml @@ -15,7 +15,7 @@ jobs: uses: https://dock-it.dev/actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Set up Go - uses: https://dock-it.dev/actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5 + uses: https://dock-it.dev/actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6 with: go-version-file: go.mod cache: true From 3371bb0b3255f101f02392b16c35a6a3f705ce5a Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Sun, 21 Jun 2026 11:06:03 +0000 Subject: [PATCH 8/8] deps: Update module modernc.org/sqlite to v1.53.0 --- go.mod | 4 ++-- go.sum | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 2d0b2d8..0738d33 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/robfig/cron/v3 v3.0.1 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e golang.org/x/crypto v0.53.0 - modernc.org/sqlite v1.52.0 + modernc.org/sqlite v1.53.0 ) require ( @@ -22,7 +22,7 @@ require ( github.com/ncruces/go-strftime v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect golang.org/x/sys v0.46.0 // indirect - modernc.org/libc v1.72.3 // indirect + modernc.org/libc v1.73.4 // indirect modernc.org/mathutil v1.7.1 // indirect modernc.org/memory v1.11.0 // indirect ) diff --git a/go.sum b/go.sum index f6706bc..c178a5a 100644 --- a/go.sum +++ b/go.sum @@ -35,9 +35,13 @@ golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= modernc.org/libc v1.72.3 h1:ZnDF4tXn4NBXFutMMQC4vtbTFSXhhKzR73fv0beZEAU= modernc.org/libc v1.72.3/go.mod h1:dn0dZNnnn1clLyvRxLxYExxiKRZIRENOfqQ8XEeg4Qs= +modernc.org/libc v1.73.4 h1:+ra4Ui8ngyt8HDcO1FTDPWlkAh6yOdaO2yAoh8MddQA= +modernc.org/libc v1.73.4/go.mod h1:DXZ3eO8qMCNn2SnmTNCiC71nJ9Rcq3PsnpU6Vc4rWK8= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= modernc.org/sqlite v1.52.0 h1:p4dhYh2tXZCiyaqHwRVJDjIGKWyXayiQpThxgDzJaxo= modernc.org/sqlite v1.52.0/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM= +modernc.org/sqlite v1.53.0 h1:20WG8N9q4ji/dEqGk4uiI0c6OPjSeLTNYGFCc3+7c1M= +modernc.org/sqlite v1.53.0/go.mod h1:xoEpOIpGrgT48H5iiyt/YXPCZPEzlfmfFwtk8Lklw8s=