Add reproducible build system, native Unraid plugin, and WebUI
- versions.env pins podman, conmon, crun, netavark, aardvark-dns, passt, and fuse-overlayfs to verified upstream source checksums; SlackBuild recipes, scripts/build-packages.sh, checksums.sh, release.sh, and update-versions.sh implement the reproducible pipeline; GitHub Actions workflows build in a Slackware container and publish releases without committing any binaries. - plugin/podman.plg installs/updates/removes all eight packages (the seven components plus the plugin's own unraid-podman scaffolding package) via upgradepkg, using the official Unraid array-event hook mechanism (event/disks_mounted, event/stopping) instead of editing /boot/config/go. rc.podman and the sbin/ helper scripts implement storage creation, config seeding/sync, preflight checks, autostart with per-container Safe-Mode, and package verify/update/rollback. - webui/plugins/podman implements the Dashboard, Containers, Pods, Images, Volumes, Networks, Logs, Terminal, Compose, and Settings panels against the approved mockup (webui/mockups/prototype.html), talking to podman system service exclusively via PodmanClient.php (libpod REST API over the Unix socket), with two documented exceptions: Terminal's one-shot exec model and Compose's use of the podman compose CLI, since libpod has no REST equivalent for either. - docs/ARCHITECTURE.md and docs/ROADMAP.md record the design decisions and honest current status (syntax-checked, unit- and integration-tested against fake sockets/servers; not yet run against a real Unraid/Podman/Slackware system). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,21 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.sh]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.SlackBuild]
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[Makefile]
|
||||||
|
indent_style = tab
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
# Normalize line endings for all text files; Slackware/Unraid scripts and the
|
||||||
|
# .plg manifest must stay LF, never CRLF.
|
||||||
|
* text=auto eol=lf
|
||||||
|
|
||||||
|
*.sh text eol=lf
|
||||||
|
*.SlackBuild text eol=lf
|
||||||
|
*.plg text eol=lf
|
||||||
|
rc.* text eol=lf
|
||||||
|
|
||||||
|
*.png binary
|
||||||
|
*.jpg binary
|
||||||
|
*.ico binary
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Default owners for everything in the repo, until per-area owners are named.
|
||||||
|
# See https://docs.github.com/articles/about-codeowners for syntax.
|
||||||
|
|
||||||
|
* @OWNER
|
||||||
|
|
||||||
|
# Example of future area-based ownership once the team grows:
|
||||||
|
# /packages/ @OWNER @packaging-maintainer
|
||||||
|
# /webui/ @OWNER @webui-maintainer
|
||||||
|
# /plugin/ @OWNER
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
# Contributor Covenant Code of Conduct
|
||||||
|
|
||||||
|
## Our Pledge
|
||||||
|
|
||||||
|
We as members, contributors, and leaders pledge to make participation in our
|
||||||
|
community a harassment-free experience for everyone, regardless of age, body
|
||||||
|
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||||
|
identity and expression, level of experience, education, socio-economic status,
|
||||||
|
nationality, personal appearance, race, religion, or sexual identity and
|
||||||
|
orientation.
|
||||||
|
|
||||||
|
## Our Standards
|
||||||
|
|
||||||
|
Examples of behavior that contributes to a positive environment:
|
||||||
|
|
||||||
|
- Demonstrating empathy and kindness toward other people
|
||||||
|
- Being respectful of differing opinions, viewpoints, and experiences
|
||||||
|
- Giving and gracefully accepting constructive feedback
|
||||||
|
- Focusing on what is best for the community
|
||||||
|
|
||||||
|
Examples of unacceptable behavior:
|
||||||
|
|
||||||
|
- The use of sexualized language or imagery, and unwelcome sexual attention
|
||||||
|
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||||
|
- Public or private harassment
|
||||||
|
- Publishing others' private information without explicit permission
|
||||||
|
|
||||||
|
## Enforcement Responsibilities
|
||||||
|
|
||||||
|
Project maintainers are responsible for clarifying and enforcing standards of
|
||||||
|
acceptable behavior and will take appropriate corrective action in response to
|
||||||
|
any behavior deemed inappropriate, threatening, offensive, or harmful.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This Code of Conduct applies within all project spaces (issues, pull requests,
|
||||||
|
discussions) and when an individual is officially representing the project in
|
||||||
|
public spaces.
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||||
|
reported to the maintainers via the contact listed in
|
||||||
|
[SECURITY.md](SECURITY.md). All complaints will be reviewed and investigated
|
||||||
|
promptly and fairly.
|
||||||
|
|
||||||
|
## Attribution
|
||||||
|
|
||||||
|
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org),
|
||||||
|
version 2.1, available at
|
||||||
|
https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Report a problem with the plugin
|
||||||
|
title: "[Bug] "
|
||||||
|
labels: bug
|
||||||
|
assignees: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
A clear description of what went wrong.
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
|
||||||
|
- Unraid version:
|
||||||
|
- unraid-podman plugin version:
|
||||||
|
- Podman version (`podman version`, if available):
|
||||||
|
- Storage backend (Cache pool / Array disk), pool filesystem (XFS/BTRFS/ZFS):
|
||||||
|
- Docker also installed/running? (yes/no):
|
||||||
|
|
||||||
|
## Steps to reproduce
|
||||||
|
|
||||||
|
1. ...
|
||||||
|
2. ...
|
||||||
|
|
||||||
|
## Expected behavior
|
||||||
|
|
||||||
|
## Actual behavior
|
||||||
|
|
||||||
|
## Relevant logs
|
||||||
|
|
||||||
|
Attach or paste relevant excerpts from:
|
||||||
|
- `/mnt/*/system/podman/logs/podman-service.log`
|
||||||
|
- Unraid System Log (`Tools -> System Log`)
|
||||||
|
- `rc.podman status` output
|
||||||
|
|
||||||
|
Please redact any secrets (registry credentials, tokens) before posting.
|
||||||
|
|
||||||
|
## Additional context
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Security vulnerability
|
||||||
|
url: https://github.com/OWNER/unraid-podman/security/advisories/new
|
||||||
|
about: Please report security issues privately — see SECURITY.md, not a public issue.
|
||||||
|
- name: General discussion / questions
|
||||||
|
url: https://github.com/OWNER/unraid-podman/discussions
|
||||||
|
about: Usage questions and design discussions that aren't a concrete bug or feature request.
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an enhancement or new capability
|
||||||
|
title: "[Feature] "
|
||||||
|
labels: enhancement
|
||||||
|
assignees: ""
|
||||||
|
---
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
What are you trying to do that isn't currently possible or is unnecessarily hard?
|
||||||
|
|
||||||
|
## Proposed solution
|
||||||
|
|
||||||
|
## Alternatives considered
|
||||||
|
|
||||||
|
## Which architecture phase does this fit?
|
||||||
|
|
||||||
|
See [docs/ROADMAP.md](../../docs/ROADMAP.md) — e.g. MVP / WebUI / Rootless /
|
||||||
|
Docker-optional / Pods. If unsure, leave blank; this helps triage against the
|
||||||
|
existing phased plan rather than scope-creeping the current phase.
|
||||||
|
|
||||||
|
## Additional context
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
## Summary
|
||||||
|
|
||||||
|
<!-- What does this PR change and why? -->
|
||||||
|
|
||||||
|
## Related issue(s)
|
||||||
|
|
||||||
|
<!-- Closes #... -->
|
||||||
|
|
||||||
|
## Type of change
|
||||||
|
|
||||||
|
- [ ] Documentation
|
||||||
|
- [ ] Packaging (`packages/`)
|
||||||
|
- [ ] Plugin core (`.plg`, `rc.podman`, `sbin/*`)
|
||||||
|
- [ ] WebUI (`webui/`)
|
||||||
|
- [ ] CI / tooling (`.github/`, `scripts/`)
|
||||||
|
- [ ] Other
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
- [ ] Changes follow the design in [docs/ARCHITECTURE.md](../docs/ARCHITECTURE.md);
|
||||||
|
any deliberate deviation is explained below
|
||||||
|
- [ ] Persistence discipline respected (state written under `/etc`, `/usr`, `/var`
|
||||||
|
at runtime is also mirrored to `/boot/config/plugins/podman/` or array/cache)
|
||||||
|
- [ ] No systemd usage introduced
|
||||||
|
- [ ] Docker coexistence preserved (no shared storage/network/iptables-chain names)
|
||||||
|
- [ ] `CHANGELOG.md` updated under `[Unreleased]` (if user-facing)
|
||||||
|
- [ ] Relevant docs under `docs/` updated
|
||||||
|
- [ ] Tested on a real or virtualized Unraid instance (describe below), where applicable
|
||||||
|
|
||||||
|
## How was this tested?
|
||||||
|
|
||||||
|
## Notes for reviewers
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Threat model context
|
||||||
|
|
||||||
|
This plugin runs **rootful Podman**. Its API socket (`/var/run/podman/podman.sock`)
|
||||||
|
is root-equivalent on the host, in the same way Docker's `docker.sock` is. See
|
||||||
|
[docs/ARCHITECTURE.md](../docs/ARCHITECTURE.md#19-sicherheitsbetrachtungen-phase-1-rootful)
|
||||||
|
for the full rationale. Treat any bug that affects socket permissions, WebUI
|
||||||
|
authentication, or container-to-host isolation as security-sensitive by default.
|
||||||
|
|
||||||
|
## Reporting a vulnerability
|
||||||
|
|
||||||
|
Please **do not** open a public GitHub issue for security vulnerabilities.
|
||||||
|
|
||||||
|
Instead, use one of:
|
||||||
|
|
||||||
|
- GitHub [private security advisories](https://github.com/OWNER/unraid-podman/security/advisories/new)
|
||||||
|
for this repository, or
|
||||||
|
- Email the maintainers at `security@OWNER-DOMAIN` (placeholder — update once a
|
||||||
|
contact address exists).
|
||||||
|
|
||||||
|
Please include:
|
||||||
|
|
||||||
|
- A description of the issue and its potential impact.
|
||||||
|
- Steps to reproduce (Unraid version, plugin version, storage backend).
|
||||||
|
- Whether the issue requires local access, network access, or a malicious
|
||||||
|
container image to trigger.
|
||||||
|
|
||||||
|
## Supported versions
|
||||||
|
|
||||||
|
This project has not yet cut a stable release. Until a `1.0.0` release, only the
|
||||||
|
latest `main` branch / most recent tag is supported with security fixes.
|
||||||
|
|
||||||
|
## Disclosure process
|
||||||
|
|
||||||
|
We aim to acknowledge reports within 5 business days and to agree on a
|
||||||
|
coordinated disclosure timeline before any public write-up.
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
name: Build Packages
|
||||||
|
|
||||||
|
# Builds the seven Slackware .txz packages defined under packages/
|
||||||
|
# (podman, conmon, crun, netavark, aardvark-dns, passt, fuse-overlayfs)
|
||||||
|
# inside a Slackware container, verifies + consolidates their checksums, and
|
||||||
|
# uploads the result as a workflow artifact.
|
||||||
|
#
|
||||||
|
# Intentionally does NOT commit any built binary back to the repository —
|
||||||
|
# packages/**, *.txz, dist/ are all git-ignored (see .gitignore). Artifacts
|
||||||
|
# only ever leave this workflow via the "Upload build artifacts" step below
|
||||||
|
# (retained by GitHub Actions, not the repo) or, for tagged releases, via
|
||||||
|
# release.yml attaching them to a GitHub Release.
|
||||||
|
#
|
||||||
|
# See docs/ARCHITECTURE.md section 5 (Paketmanagement).
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
paths:
|
||||||
|
- "packages/**"
|
||||||
|
- "versions.env"
|
||||||
|
- "scripts/**"
|
||||||
|
- ".github/workflows/build-packages.yml"
|
||||||
|
pull_request:
|
||||||
|
paths:
|
||||||
|
- "packages/**"
|
||||||
|
- "versions.env"
|
||||||
|
- "scripts/**"
|
||||||
|
- ".github/workflows/build-packages.yml"
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
packages:
|
||||||
|
description: >
|
||||||
|
Space-separated package names to build (default: all seven).
|
||||||
|
Example: "podman conmon"
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
|
workflow_call:
|
||||||
|
outputs:
|
||||||
|
artifact-name:
|
||||||
|
description: "Name of the uploaded dist/ artifact"
|
||||||
|
value: ${{ jobs.build.outputs.artifact-name }}
|
||||||
|
|
||||||
|
# Pin the Slackware build image by tag here. vbatts/slackware is a
|
||||||
|
# long-standing, widely used Slackware Docker image; swap this (and ideally
|
||||||
|
# pin by digest) if the project standardizes on a different/self-hosted
|
||||||
|
# base image. See scripts/ci/setup-slackware-buildenv.sh for how missing
|
||||||
|
# build dependencies are bootstrapped on top of whatever this image ships.
|
||||||
|
env:
|
||||||
|
SLACKWARE_IMAGE: "vbatts/slackware:15.0"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build .txz packages
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: ${{ env.SLACKWARE_IMAGE }}
|
||||||
|
outputs:
|
||||||
|
artifact-name: ${{ steps.artifact-name.outputs.value }}
|
||||||
|
steps:
|
||||||
|
- name: Install git and tar (needed before actions/checkout can run)
|
||||||
|
run: |
|
||||||
|
(command -v git && command -v tar) || \
|
||||||
|
(echo "!! base image is missing git/tar — see SLACKWARE_IMAGE in this workflow" && exit 1)
|
||||||
|
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Slackware build environment
|
||||||
|
run: scripts/ci/setup-slackware-buildenv.sh
|
||||||
|
|
||||||
|
- name: Build packages
|
||||||
|
run: scripts/build-packages.sh ${{ github.event.inputs.packages }}
|
||||||
|
|
||||||
|
- name: Verify and consolidate checksums
|
||||||
|
run: scripts/checksums.sh
|
||||||
|
|
||||||
|
- name: Compute artifact name
|
||||||
|
id: artifact-name
|
||||||
|
run: echo "value=podman-packages-${{ github.sha }}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Upload build artifacts
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ steps.artifact-name.outputs.value }}
|
||||||
|
path: |
|
||||||
|
dist/*.txz
|
||||||
|
dist/*.sha256
|
||||||
|
dist/*.md5
|
||||||
|
dist/CHECKSUMS.sha256
|
||||||
|
dist/CHECKSUMS.md5
|
||||||
|
if-no-files-found: error
|
||||||
|
retention-days: 14
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
name: Lint
|
||||||
|
|
||||||
|
# Static analysis over shell scripts (ShellCheck) and the .plg/XML manifest
|
||||||
|
# (xmllint) on every push and pull request. Mirrors scripts/dev/lint.sh for
|
||||||
|
# local use.
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
shellcheck:
|
||||||
|
name: ShellCheck
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install ShellCheck
|
||||||
|
run: |
|
||||||
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get install -y --no-install-recommends shellcheck
|
||||||
|
|
||||||
|
- name: Run ShellCheck
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
# shellcheck disable=SC2038 (find | xargs is fine here, filenames
|
||||||
|
# in this repo never contain spaces/newlines)
|
||||||
|
find plugin/rc.d plugin/sbin scripts -type f \
|
||||||
|
\( -name '*.sh' -o -name 'rc.*' -o -name '*.SlackBuild' \) \
|
||||||
|
-print0 \
|
||||||
|
| xargs -0 shellcheck --severity=warning --external-sources
|
||||||
|
|
||||||
|
xmllint:
|
||||||
|
name: Validate .plg XML
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install libxml2-utils
|
||||||
|
run: |
|
||||||
|
sudo apt-get update -qq
|
||||||
|
sudo apt-get install -y --no-install-recommends libxml2-utils
|
||||||
|
|
||||||
|
- name: Validate podman.plg is well-formed XML
|
||||||
|
run: xmllint --noout plugin/podman.plg
|
||||||
|
|
||||||
|
editorconfig:
|
||||||
|
name: EditorConfig
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: editorconfig-checker/action-editorconfig-checker@main
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
name: Release
|
||||||
|
|
||||||
|
# Publishes a GitHub Release for a version tag (vX.Y.Z).
|
||||||
|
#
|
||||||
|
# By design, this workflow does NOT bump versions or modify podman.plg
|
||||||
|
# itself — that happens locally via `scripts/release.sh <version>`, which a
|
||||||
|
# maintainer reviews, commits, and tags *before* pushing the tag (see that
|
||||||
|
# script's own printed instructions). This workflow's only job is to:
|
||||||
|
# 1. Rebuild all packages from the tagged commit in a clean Slackware
|
||||||
|
# container (reproducibility check + provenance — we don't trust
|
||||||
|
# whatever a maintainer happened to have in their local dist/).
|
||||||
|
# 2. Verify checksums match what's already committed in plugin/podman.plg
|
||||||
|
# at this tag (catches a release.sh run that wasn't followed by a
|
||||||
|
# matching commit — see the "Verify plg matches build" step).
|
||||||
|
# 3. Create the GitHub Release and attach the .txz packages, checksum
|
||||||
|
# manifests, and podman.plg.
|
||||||
|
#
|
||||||
|
# See docs/ARCHITECTURE.md section 13 (Updates).
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*.*.*"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build release packages
|
||||||
|
uses: ./.github/workflows/build-packages.yml
|
||||||
|
|
||||||
|
publish:
|
||||||
|
name: Publish GitHub Release
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Download built packages
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ needs.build.outputs.artifact-name }}
|
||||||
|
path: dist
|
||||||
|
|
||||||
|
- name: Re-verify checksums
|
||||||
|
run: scripts/checksums.sh dist
|
||||||
|
|
||||||
|
- name: Verify plugin/podman.plg matches these artifacts
|
||||||
|
# scripts/release.sh should already have been run locally, and its
|
||||||
|
# resulting podman.plg changes committed as part of this tag, before
|
||||||
|
# the tag was pushed. This step fails the release loudly if that
|
||||||
|
# didn't happen, instead of publishing a release whose plg points at
|
||||||
|
# MD5s that don't match the .txz files actually attached below.
|
||||||
|
run: |
|
||||||
|
set -eu
|
||||||
|
for f in dist/*.txz.md5; do
|
||||||
|
expected_md5=$(awk '{print $1}' "$f")
|
||||||
|
txz_name=$(basename "${f%.md5}")
|
||||||
|
if ! grep -qF "$expected_md5" plugin/podman.plg; then
|
||||||
|
echo "!! $txz_name's checksum ($expected_md5) is not referenced in plugin/podman.plg." >&2
|
||||||
|
echo "!! Did you forget to run scripts/release.sh and commit its changes before tagging?" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo "All package checksums are referenced in plugin/podman.plg — OK."
|
||||||
|
|
||||||
|
- name: Extract version from tag
|
||||||
|
id: version
|
||||||
|
run: echo "value=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Extract changelog section for this version
|
||||||
|
id: changelog
|
||||||
|
run: |
|
||||||
|
awk -v ver="${{ steps.version.outputs.value }}" '
|
||||||
|
$0 ~ "^## \\[" ver "\\]" { found=1; print; next }
|
||||||
|
found && /^## \[/ { exit }
|
||||||
|
found { print }
|
||||||
|
' CHANGELOG.md > /tmp/release-notes.md
|
||||||
|
echo "path=/tmp/release-notes.md" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Create GitHub Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
name: "unraid-podman v${{ steps.version.outputs.value }}"
|
||||||
|
body_path: ${{ steps.changelog.outputs.path }}
|
||||||
|
# v0.x tags are treated as pre-releases until the plugin reaches a
|
||||||
|
# first stable 1.0.0 — see docs/ROADMAP.md.
|
||||||
|
prerelease: ${{ startsWith(steps.version.outputs.value, '0.') }}
|
||||||
|
files: |
|
||||||
|
dist/*.txz
|
||||||
|
dist/*.sha256
|
||||||
|
dist/*.md5
|
||||||
|
dist/CHECKSUMS.sha256
|
||||||
|
dist/CHECKSUMS.md5
|
||||||
|
plugin/podman.plg
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
# --- Slackware package build artifacts ---
|
||||||
|
packages/**/src/
|
||||||
|
packages/**/pkg/
|
||||||
|
packages/**/work/
|
||||||
|
packages/**/*.txz
|
||||||
|
packages/**/*.tgz
|
||||||
|
packages/**/*.txz.md5
|
||||||
|
packages/**/*.tgz.md5
|
||||||
|
|
||||||
|
# --- Build / release output ---
|
||||||
|
/dist/
|
||||||
|
/build/
|
||||||
|
/release/
|
||||||
|
*.plg.bak
|
||||||
|
|
||||||
|
# --- Logs ---
|
||||||
|
*.log
|
||||||
|
/scripts/dev/*.log
|
||||||
|
|
||||||
|
# --- Editor / IDE ---
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*~
|
||||||
|
|
||||||
|
# --- OS cruft ---
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
|
# --- WebUI tooling (once JS/PHP tooling is added) ---
|
||||||
|
node_modules/
|
||||||
|
vendor/
|
||||||
|
composer.lock
|
||||||
|
package-lock.json
|
||||||
|
|
||||||
|
# --- Local/dev config overrides, never commit real secrets ---
|
||||||
|
*.local
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
config/*.secret
|
||||||
|
|
||||||
|
# --- Downloaded upstream sources used during package builds ---
|
||||||
|
packages/**/*.tar.gz
|
||||||
|
packages/**/*.tar.xz
|
||||||
|
packages/**/*.tar.bz2
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
|
||||||
|
for the plugin version (independent of the bundled Podman/upstream package versions,
|
||||||
|
see [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md#52-build-strategie)).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Initial repository scaffolding: directory structure, documentation skeleton,
|
||||||
|
CI workflow stubs, and community health files.
|
||||||
|
- Architecture documentation (`docs/ARCHITECTURE.md`).
|
||||||
|
|
||||||
|
[Unreleased]: https://github.com/OWNER/unraid-podman/compare/HEAD...HEAD
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# Contributing to unraid-podman
|
||||||
|
|
||||||
|
Thanks for your interest in contributing. This project is in an early,
|
||||||
|
architecture-first stage — please read [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
|
||||||
|
before proposing structural changes, so new work stays consistent with the agreed
|
||||||
|
design (RAM-root persistence model, no systemd, Docker coexistence, etc.).
|
||||||
|
|
||||||
|
## Ground rules
|
||||||
|
|
||||||
|
- Discuss non-trivial changes in an issue before opening a PR — this repo makes
|
||||||
|
deliberate, sometimes non-obvious trade-offs (see the "Offene Fragen / Risiken"
|
||||||
|
section of the architecture doc) and we'd rather align early than rework a PR.
|
||||||
|
- Keep persistence discipline: anything written under `/etc`, `/usr`, `/var` on a
|
||||||
|
running Unraid system is lost on reboot. Any new config or state must be mirrored
|
||||||
|
to `/boot/config/plugins/podman/` or an array/cache path — see
|
||||||
|
[docs/ARCHITECTURE.md](docs/ARCHITECTURE.md#7-persistenz-strategie-zusammenfassung-der-prinzipien).
|
||||||
|
- No systemd. Init/process management goes through `plugin/rc.d/rc.podman` in the
|
||||||
|
classic BSD `start|stop|restart|status` style.
|
||||||
|
- Don't introduce changes that could destabilize a coexisting Docker installation
|
||||||
|
(shared storage paths, shared bridge/network names, shared iptables chains).
|
||||||
|
|
||||||
|
## Repository layout
|
||||||
|
|
||||||
|
See the "Repository structure" section in [README.md](README.md). In short:
|
||||||
|
|
||||||
|
| Directory | What goes here |
|
||||||
|
|---|---|
|
||||||
|
| `packages/` | One subdirectory per Slackware `.txz` package (podman, conmon, crun, netavark, aardvark-dns, containers-common, fuse-overlayfs, passt) |
|
||||||
|
| `plugin/` | The `.plg` manifest, `rc.d/rc.podman`, `sbin/` helper scripts, and default files staged for `/boot/config/plugins/podman/` |
|
||||||
|
| `webui/` | Dynamix-style GUI pages under `plugins/podman/` (later phase) |
|
||||||
|
| `config/` | Default `containers.conf` / `storage.conf` / `registries.conf` / `policy.json` templates |
|
||||||
|
| `scripts/` | Build and release tooling, not shipped to end users |
|
||||||
|
| `docs/` | Architecture, install guide, FAQ, troubleshooting, roadmap |
|
||||||
|
|
||||||
|
## Development setup
|
||||||
|
|
||||||
|
Podman package builds must target Slackware compatibility with Unraid's current
|
||||||
|
base — this generally means building inside a Slackware-compatible container
|
||||||
|
rather than on your own distro. Run `scripts/build-packages.sh` (optionally
|
||||||
|
`scripts/build-packages.sh <name>` for a single package) inside such a
|
||||||
|
container; `scripts/ci/setup-slackware-buildenv.sh` bootstraps the toolchain
|
||||||
|
and C library dependencies the build needs. `.github/workflows/build-packages.yml`
|
||||||
|
runs the same scripts in CI and is the reference for the exact container image
|
||||||
|
and setup sequence.
|
||||||
|
|
||||||
|
For anything touching `plugin/rc.d/rc.podman`, `plugin/sbin/*.sh`, or `podman.plg`,
|
||||||
|
test on a real or virtualized Unraid instance — behavior around RAM-root, `/boot`
|
||||||
|
persistence, and array/cache availability at boot cannot be fully validated on a
|
||||||
|
generic Linux box.
|
||||||
|
|
||||||
|
## Coding conventions
|
||||||
|
|
||||||
|
- **Shell scripts** (`rc.podman`, `sbin/*.sh`, `scripts/*.sh`): POSIX-compatible
|
||||||
|
where practical, `set -eu` at minimum, and must pass
|
||||||
|
[ShellCheck](https://www.shellcheck.net/) (see `.github/workflows/lint.yml`).
|
||||||
|
- **Slackware package recipes** (`packages/*`): follow standard `SlackBuild`
|
||||||
|
conventions (`slack-desc` limited to 70 columns / 11 lines, `doinst.sh` for
|
||||||
|
post-install steps).
|
||||||
|
- **PHP/JS in `webui/`**: follow the conventions of Unraid's existing Dynamix
|
||||||
|
plugins for consistency with the rest of the GUI.
|
||||||
|
- Commit messages: [Conventional Commits](https://www.conventionalcommits.org/)
|
||||||
|
(`feat:`, `fix:`, `docs:`, `build:`, `ci:`, ...) so changelog generation and
|
||||||
|
history stay readable.
|
||||||
|
|
||||||
|
## Pull requests
|
||||||
|
|
||||||
|
1. Fork, branch off `main`, keep PRs focused on a single concern.
|
||||||
|
2. Update `CHANGELOG.md` under `[Unreleased]` for any user-facing change.
|
||||||
|
3. Update relevant docs under `docs/` if behavior or structure changes.
|
||||||
|
4. Make sure CI (lint / package build checks) passes.
|
||||||
|
5. Describe how you tested the change, especially for anything touching boot
|
||||||
|
behavior, persistence, or Docker coexistence.
|
||||||
|
|
||||||
|
## Reporting bugs / requesting features
|
||||||
|
|
||||||
|
Use the issue templates under `.github/ISSUE_TEMPLATE/`. For security-relevant
|
||||||
|
reports (rootful socket exposure, privilege escalation, etc.), follow
|
||||||
|
[SECURITY.md](.github/SECURITY.md) instead of a public issue.
|
||||||
|
|
||||||
|
## Code of Conduct
|
||||||
|
|
||||||
|
This project follows the [Code of Conduct](.github/CODE_OF_CONDUCT.md). By
|
||||||
|
participating, you agree to uphold it.
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 unraid-podman contributors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# assets/
|
||||||
|
|
||||||
|
Branding and imagery used by the plugin manifest and (later) the WebUI.
|
||||||
|
|
||||||
|
```
|
||||||
|
assets/
|
||||||
|
├── icons/ Plugin icon(s) referenced by plugin/podman.plg (icon="podman")
|
||||||
|
│ and by webui/plugins/podman/. Unraid convention: PNG, square,
|
||||||
|
│ ~64x64 or SVG.
|
||||||
|
└── screenshots/ Screenshots used in README.md / docs/ once a WebUI exists.
|
||||||
|
```
|
||||||
|
|
||||||
|
No binary assets are checked in yet — this is scaffolding only. When adding the
|
||||||
|
real plugin icon, keep a source (SVG) alongside the exported PNG(s) so it can be
|
||||||
|
re-exported at other sizes later.
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
# Default containers.conf template for unraid-podman.
|
||||||
|
#
|
||||||
|
# This is a source template shipped in the repository/package. At install/boot
|
||||||
|
# time it is copied to /boot/config/plugins/podman/containers.conf (if not
|
||||||
|
# already present, never overwritten on update) and from there synced to
|
||||||
|
# /etc/containers/containers.conf by rc.podman start.
|
||||||
|
#
|
||||||
|
# See docs/ARCHITECTURE.md section 6.1 (Start-/Stop-Skripte) and section 7
|
||||||
|
# (Persistenz-Strategie). Placeholder — real defaults to be tuned during MVP
|
||||||
|
# implementation.
|
||||||
|
#
|
||||||
|
# Full reference: https://github.com/containers/common/blob/main/docs/containers.conf.5.md
|
||||||
|
|
||||||
|
[containers]
|
||||||
|
# TODO: default ulimits, log driver (k8s-file, see docs/ARCHITECTURE.md section 15), etc.
|
||||||
|
|
||||||
|
[engine]
|
||||||
|
# TODO: static_dir / volume_path / runroot overrides pointing at the cache-pool
|
||||||
|
# backed storage location instead of RAM-root defaults.
|
||||||
|
|
||||||
|
[network]
|
||||||
|
# TODO: default network backend (netavark), default subnet range distinct from
|
||||||
|
# Docker's docker0 range — see docs/ARCHITECTURE.md section 8.
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Example plugin settings file for unraid-podman.
|
||||||
|
#
|
||||||
|
# The real, user-editable copy of this file lives on the flash device at
|
||||||
|
# /boot/config/plugins/podman/podman.cfg and is the source of truth read by
|
||||||
|
# rc.podman at every start. See docs/ARCHITECTURE.md section 4.1 and 7.
|
||||||
|
#
|
||||||
|
# Format: simple KEY="value" pairs (bash-sourceable), matching the convention
|
||||||
|
# used by Unraid's own /boot/config/docker.cfg.
|
||||||
|
|
||||||
|
# Path to the directory holding podman.img (cache pool or array disk).
|
||||||
|
# TODO: finalize default; must NOT be a path under /mnt/user.
|
||||||
|
STORAGE_PATH="/mnt/cache/system/podman"
|
||||||
|
|
||||||
|
# Size of the podman.img loopback image, in GiB, used only on first creation.
|
||||||
|
STORAGE_IMAGE_SIZE_GB="20"
|
||||||
|
|
||||||
|
# Whether rc.podman should start automatically when the array starts.
|
||||||
|
PODMAN_ENABLED="yes"
|
||||||
|
|
||||||
|
# Per-container stop timeout, in seconds, used by `rc.podman stop`.
|
||||||
|
STOP_TIMEOUT="10"
|
||||||
|
|
||||||
|
# Config schema version, used by the migration logic described in
|
||||||
|
# docs/ARCHITECTURE.md section 13.1.
|
||||||
|
CONFIG_SCHEMA_VERSION="1"
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"_comment": "Default container image signature verification policy. Placeholder: accepts all images without signature verification, matching Docker's default trust model on Unraid today. See docs/ARCHITECTURE.md section 10 (Images). Revisit before a 1.0 release if signed-image verification becomes a goal.",
|
||||||
|
"default": [
|
||||||
|
{ "type": "insecureAcceptAnything" }
|
||||||
|
],
|
||||||
|
"transports": {
|
||||||
|
"docker-daemon": {
|
||||||
|
"": [{ "type": "insecureAcceptAnything" }]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Default registries.conf template for unraid-podman.
|
||||||
|
#
|
||||||
|
# Pre-configures unqualified-search registries explicitly, since there is no
|
||||||
|
# interactive TTY to prompt the user in a boot-script context.
|
||||||
|
# See docs/ARCHITECTURE.md section 10 (Images).
|
||||||
|
#
|
||||||
|
# Full reference: https://github.com/containers/image/blob/main/docs/containers-registries.conf.5.md
|
||||||
|
|
||||||
|
unqualified-search-registries = ["docker.io"]
|
||||||
|
|
||||||
|
# TODO: consider pre-listing common registries (ghcr.io, quay.io, lscr.io) as
|
||||||
|
# short-name aliases, matching what Unraid Community Applications users expect
|
||||||
|
# from Docker Hub-centric templates today.
|
||||||
|
|
||||||
|
[[registry]]
|
||||||
|
prefix = "docker.io"
|
||||||
|
location = "docker.io"
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Default storage.conf template for unraid-podman.
|
||||||
|
#
|
||||||
|
# Defines the Podman storage graph root/driver. On Unraid this MUST point at
|
||||||
|
# the mounted podman.img loopback (or equivalent cache-pool/disk path), never
|
||||||
|
# at a path under /mnt/user (FUSE) or RAM-root /var/lib/containers directly.
|
||||||
|
# See docs/ARCHITECTURE.md section 4.3 and section 10 (Images).
|
||||||
|
#
|
||||||
|
# Full reference: https://github.com/containers/storage/blob/main/docs/containers-storage.conf.5.md
|
||||||
|
|
||||||
|
[storage]
|
||||||
|
driver = "overlay"
|
||||||
|
# runroot and graphroot are set at runtime by rc.podman based on
|
||||||
|
# /boot/config/plugins/podman/podman.cfg (configurable storage location),
|
||||||
|
# not hardcoded here. TODO: document the exact substitution mechanism once
|
||||||
|
# rc.podman is implemented.
|
||||||
|
# graphroot = "/var/lib/containers/storage"
|
||||||
|
# runroot = "/var/run/containers/storage"
|
||||||
|
|
||||||
|
[storage.options]
|
||||||
|
# TODO: overlay-specific mount options once the loopback filesystem
|
||||||
|
# (XFS with ftype=1, or BTRFS) is finalized.
|
||||||
@@ -0,0 +1,656 @@
|
|||||||
|
# unraid-podman — Architekturplanung
|
||||||
|
|
||||||
|
Natives Unraid-Plugin zur vollständigen Integration von Podman (rootful, Phase 1) als
|
||||||
|
gleichberechtigte Alternative zu Docker, mit dem langfristigen Ziel, Docker optional
|
||||||
|
abschaltbar zu machen.
|
||||||
|
|
||||||
|
Status: Planungsdokument, kein Code. Dient als Grundlage für Implementierungs-Tickets.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. Ziele & Nicht-Ziele
|
||||||
|
|
||||||
|
**Ziele (Phase 1 / MVP):**
|
||||||
|
- Podman rootful lauffähig unter Unraid, überlebt Reboots (Config, Images, Container, Volumes, Netzwerke).
|
||||||
|
- Koexistenz mit Docker ohne Konflikte (Storage, Netzwerk, Ports, iptables-Ketten).
|
||||||
|
- Start/Stop/Autostart analog zum bestehenden `rc.docker`-Muster, ohne systemd.
|
||||||
|
- Sauberer Update-/Rollback-Pfad für das Plugin selbst.
|
||||||
|
- Grundlegendes Logging & Fehlerdiagnose, das GUI-Meldungen von Unraid nutzt.
|
||||||
|
- Vorbereitung (nicht Umsetzung) einer WebUI-Anbindung.
|
||||||
|
|
||||||
|
**Nicht-Ziele (bewusst verschoben):**
|
||||||
|
- Rootless Podman (Phase 2+, andere Storage-/UID-Mapping-Anforderungen).
|
||||||
|
- Podman Pods / Kubernetes-YAML-Support (Phase 3, siehe Roadmap).
|
||||||
|
- 1:1-Ersatz der Community-Applications-Vorlagen (erfordert eigenen Übersetzungslayer, separat geplant).
|
||||||
|
- Abschalten von Docker (erst wenn Podman-Pfad stabil ist, siehe Abschnitt 17).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Rahmenbedingungen von Unraid, die die Architektur erzwingen
|
||||||
|
|
||||||
|
| Eigenschaft | Konsequenz für das Plugin |
|
||||||
|
|---|---|
|
||||||
|
| Unraid läuft komplett im RAM (SquashFS + tmpfs-Overlay via `unionfs`) | Alles, was `/`, `/etc`, `/usr`, `/var` betrifft, ist bei jedem Reboot verloren. Nichts darf implizit dort persistiert werden. |
|
||||||
|
| Persistenter Speicher nur unter `/boot` (USB-Stick, FAT32, klein, langsam, schreibintensiv riskant) und `/mnt/*` (Array/Cache/Pools) | Konfiguration klein & textbasiert auf `/boot/config/plugins/...`; große/volatile Daten (Images, Container-Storage, Logs) auf Array/Cache. |
|
||||||
|
| Kein systemd, sondern BSD-artige `/etc/rc.d/rc.*`-Skripte, angestoßen über `/boot/config/go` | Eigenes `rc.podman`-Init-Skript im klassischen `start|stop|restart|status`-Stil, keine `.service`-Units, keine `systemctl`-Aufrufe irgendwo im Code. |
|
||||||
|
| Slackware-Basis, Pakete als `.txz` (pkgtools: `installpkg`/`upgradepkg`/`removepkg`) | Podman + Abhängigkeiten müssen als Slackware-kompatible `.txz` gebaut werden, nicht als RPM/DEB. |
|
||||||
|
| Plugins werden über `.plg`-XML installiert (lädt Pakete, führt `<FILE>`-Blöcke mit Pre-/Post-Scripts aus) | Das `.plg` ist der zentrale Installations-/Update-/Deinstallations-Mechanismus, vergleichbar mit einem deklarativen Installer. |
|
||||||
|
| `/mnt/user` (shfs/FUSE) hat Einschränkungen für Overlay-Filesysteme (fehlendes `d_type` in manchen Konstellationen, FUSE-Overhead) | Podman-Storage (`overlay`-Driver) nicht direkt auf `/mnt/user/...` legen, sondern auf ein dediziertes Loopback-Image oder einen direkt gemounteten Pool/Disk-Pfad (Analogie zu `docker.img` bzw. Docker-„Directory“-Modus seit 6.12). |
|
||||||
|
| Array kann offline sein / erst spät im Boot verfügbar (`emhttpd`, Array-Start ist ein User-Trigger, kein automatischer Boot-Schritt) | `rc.podman` darf nicht blind beim Boot starten, sondern muss auf Array-/Pool-Verfügbarkeit warten bzw. vom `dynamix`-Event „array started“ getriggert werden, wie es `rc.docker` heute schon tut. |
|
||||||
|
| Unraid pflegt eigene Firewall-/NAT-Logik (`iptables`) für Docker-Netzwerke (`docker0`, custom bridges, macvlan) | Podman-Netzwerke (netavark) müssen eigene, klar abgegrenzte iptables-Ketten/Chains verwenden, um nicht mit Dockers Regeln zu kollidieren. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Gesamtarchitektur (Überblick)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌───────────────────────────────────────────┐
|
||||||
|
│ /boot (USB, FAT32) │
|
||||||
|
│ config/plugins/podman/ │
|
||||||
|
│ ├─ podman.cfg (Settings) │
|
||||||
|
│ ├─ autostart (Flat-File) │
|
||||||
|
│ ├─ network.json │
|
||||||
|
│ ├─ containers.conf / storage.conf (Vorlagen)
|
||||||
|
│ └─ backup/ (vorherige Paketversionen) │
|
||||||
|
└───────────────────┬─────────────────────────┘
|
||||||
|
│ beim Boot kopiert nach /etc, /var/lib
|
||||||
|
▼
|
||||||
|
┌────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ RAM-Root (tmpfs/unionfs) │
|
||||||
|
│ /etc/rc.d/rc.podman (Init-Skript) │
|
||||||
|
│ /etc/containers/* (aus /boot kopiert) │
|
||||||
|
│ /usr/bin/podman, conmon, crun, netavark, aardvark-dns, ... │
|
||||||
|
│ /usr/local/emhttp/plugins/podman/ (WebUI-Stub, PHP) │
|
||||||
|
└───────────────────────────┬───────────────────────────────────────┘
|
||||||
|
│ bind-mount / Storage-Root zeigt auf
|
||||||
|
▼
|
||||||
|
┌────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Persistenter Bereich (Cache-Pool empfohlen) │
|
||||||
|
│ /mnt/cache/system/podman/ │
|
||||||
|
│ ├─ podman.img (Loopback, XFS/BTRFS, für overlay-Storage) │
|
||||||
|
│ │ → gemountet auf /var/lib/containers/storage │
|
||||||
|
│ ├─ logs/ │
|
||||||
|
│ └─ networks/ (netavark state, falls nicht im Image) │
|
||||||
|
└────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
Kernprinzip: **Trennung von „Konfiguration“ (klein, Text, `/boot`) und „Nutzdaten“
|
||||||
|
(groß, binär, Array/Cache)** — exakt das bestehende Unraid-Muster, das auch
|
||||||
|
`dynamix.docker.manager` verwendet.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Pluginstruktur
|
||||||
|
|
||||||
|
### 3.1 Repository-Layout (Build-/Source-Repo, nicht das, was auf Unraid landet)
|
||||||
|
|
||||||
|
```
|
||||||
|
unraid-podman/
|
||||||
|
├── podman.plg # Haupt-Plugin-Manifest (XML)
|
||||||
|
├── packages/ # Slackware .txz Build-Rezepte
|
||||||
|
│ ├── podman/
|
||||||
|
│ ├── conmon/
|
||||||
|
│ ├── crun/
|
||||||
|
│ ├── netavark/
|
||||||
|
│ ├── aardvark-dns/
|
||||||
|
│ ├── containers-common/
|
||||||
|
│ ├── fuse-overlayfs/
|
||||||
|
│ └── passt/
|
||||||
|
├── source/
|
||||||
|
│ ├── rc.podman # Init-Skript
|
||||||
|
│ ├── podman-preflight.sh # Startup-Checks
|
||||||
|
│ ├── podman-autostart.sh
|
||||||
|
│ ├── podman-backup.sh
|
||||||
|
│ └── config-templates/
|
||||||
|
│ ├── containers.conf
|
||||||
|
│ ├── storage.conf
|
||||||
|
│ └── registries.conf
|
||||||
|
├── emhttp/
|
||||||
|
│ └── plugins/podman/ # WebUI (PHP/JS), Phase 2+
|
||||||
|
├── scripts/
|
||||||
|
│ ├── build-packages.sh
|
||||||
|
│ └── release.sh
|
||||||
|
└── CHANGELOG.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 `.plg`-Manifest — Verantwortlichkeiten
|
||||||
|
|
||||||
|
Das `.plg` ist eine deklarative XML-Datei, die von Unraids `plugin`-Kommando
|
||||||
|
interpretiert wird. Verantwortlich für:
|
||||||
|
|
||||||
|
- **Metadaten**: `<plugin name="podman" version="..." pluginURL="..." min="..." />` inkl.
|
||||||
|
Mindest-Unraid-Version (Kernel-/glibc-Kompatibilität der Podman-Binaries).
|
||||||
|
- **Entity-Variablen** (XML-Entities) für Basis-URL, Version, MD5-Summen — ermöglicht
|
||||||
|
Update-Checks über den Plugin-Manager der GUI.
|
||||||
|
- **`<FILE>`-Blöcke**:
|
||||||
|
1. Download & Installation aller acht `.txz`-Pakete (die sieben Komponenten
|
||||||
|
plus das plugin-eigene `unraid-podman`-Scaffolding-Paket) einheitlich per
|
||||||
|
`upgradepkg --install-new --reinstall` — funktioniert unverändert für
|
||||||
|
Erstinstallation und Update (bestätigtes Muster echter Unraid-Plugins,
|
||||||
|
siehe unten).
|
||||||
|
2. `<INLINE>`-Postinstall-Skript: Setzen von Berechtigungen, Anlegen der
|
||||||
|
Verzeichnisstruktur unter `/boot/config/plugins/podman/` (nur beim ersten
|
||||||
|
Install, idempotent; Configs nur kopiert, wenn nicht vorhanden — kein
|
||||||
|
Überschreiben bestehender Nutzerkonfiguration bei Updates), Schreiben der
|
||||||
|
Versions-Manifest-Datei, erster `rc.podman start`.
|
||||||
|
- **Array-Start/Stop-Hook**: **kein** Eintrag in `/boot/config/go`. Stattdessen
|
||||||
|
der offizielle Unraid-Plugin-Event-Mechanismus:
|
||||||
|
`/usr/local/emhttp/plugins/podman/event/disks_mounted` (startet `rc.podman`,
|
||||||
|
sobald Cache-Pools/Disks gemountet sind) und `.../event/stopping` (stoppt
|
||||||
|
`rc.podman` synchron als allererster Schritt der Shutdown-Sequenz, bevor
|
||||||
|
Unraid die zugrundeliegende Disk unmountet). Dieses Verzeichnis-Konvention
|
||||||
|
wurde gegen den echten Quellcode etablierter Plugins verifiziert (u. a.
|
||||||
|
`unassigned.devices`, das exakt dieselben `event/`-Hooks für
|
||||||
|
`disks_mounted`/`stopping_svcs` verwendet) — kein `/boot/config/go`-Edit
|
||||||
|
nötig, dadurch auch beim Deinstallieren nichts manuell rückgängig zu machen.
|
||||||
|
- **`<FILE Run="/bin/bash" Method="remove">`-Block für Deinstallation**: ruft
|
||||||
|
zuerst `podman-uninstall-cleanup.sh` auf (stoppt `rc.podman`, räumt
|
||||||
|
Laufzeitzustand auf), dann `removepkg` für alle acht Pakete. Lässt Nutzdaten
|
||||||
|
unter `/mnt/*` und `/boot/config/plugins/podman/` standardmäßig **stehen**
|
||||||
|
(explizite Option „Restlose Deinstallation inkl. Daten“ via
|
||||||
|
`--purge-data`-Flag, kein automatisches Löschen von Nutzdaten).
|
||||||
|
|
||||||
|
### 3.3 Namens- und Versionskonventionen
|
||||||
|
|
||||||
|
- Plugin-Name: `podman` (Namespace-Kollisionen mit CA-Plugins prüfen).
|
||||||
|
- Paket-Versionierung getrennt von Plugin-Versionierung: `podman.plg` trägt eine eigene
|
||||||
|
SemVer (`1.2.0`), referenziert aber exakte Upstream-Versionen der Binärpakete
|
||||||
|
(`podman-5.x.x`, `netavark-1.x.x`, ...) — erlaubt Plugin-Patches (z. B. am
|
||||||
|
Init-Skript) ohne Podman-Versionssprung.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Verzeichnislayout (vollständig)
|
||||||
|
|
||||||
|
### 4.1 Auf dem Flash-Device (`/boot`, persistent, klein)
|
||||||
|
|
||||||
|
```
|
||||||
|
/boot/config/plugins/podman/
|
||||||
|
├── podman.cfg # Key=Value, analog docker.cfg (Storage-Pfad, Enable, Optionen)
|
||||||
|
├── autostart # eine Container-ID/-Name pro Zeile, Reihenfolge = Startreihenfolge
|
||||||
|
├── autostart-delay # optionale Wartezeiten pro Container (Key=Sekunden)
|
||||||
|
├── containers.conf # Vorlage, wird nach /etc/containers/ kopiert
|
||||||
|
├── storage.conf # Vorlage, referenziert podman.img-Pfad
|
||||||
|
├── registries.conf
|
||||||
|
├── policy.json
|
||||||
|
├── networks/ # persistierte netavark-Netzwerkdefinitionen (JSON)
|
||||||
|
├── backup/
|
||||||
|
│ ├── packages/<version>/*.txz # vorherige Paketversionen für Rollback
|
||||||
|
│ └── config/<timestamp>/ # Config-Snapshots vor Updates
|
||||||
|
└── plugin.log # Install-/Update-Log des Plugins selbst
|
||||||
|
```
|
||||||
|
|
||||||
|
`/boot/config/plugins/podman/` ist bewusst analog zu
|
||||||
|
`/boot/config/plugins/dynamix.docker.manager/` gehalten (Wiedererkennung für
|
||||||
|
erfahrene Unraid-Nutzer, Support-Vergleichbarkeit).
|
||||||
|
|
||||||
|
### 4.2 Laufzeit (RAM-Root, bei jedem Boot neu aufgebaut)
|
||||||
|
|
||||||
|
```
|
||||||
|
/etc/rc.d/rc.podman # Init-Skript (Symlink-Ziel oder direkt kopiert)
|
||||||
|
/etc/containers/{containers,storage,registries}.conf # aus /boot kopiert
|
||||||
|
/etc/containers/policy.json
|
||||||
|
/etc/cni/ (falls CNI-Fallback statt netavark benötigt)
|
||||||
|
/usr/bin/{podman,conmon,crun,fuse-overlayfs,passt,pasta}
|
||||||
|
/usr/libexec/podman/{netavark,aardvark-dns} # matches upstream's own layout
|
||||||
|
/usr/local/emhttp/plugins/podman/ # WebUI-Stub (statisch aus Paket)
|
||||||
|
/var/run/podman/podman.sock # API-Socket (rootful)
|
||||||
|
/var/log/podman -> /mnt/cache/system/podman/logs # Symlink, siehe Abschnitt 13
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 Persistente Nutzdaten (Cache-Pool bevorzugt, Array als Fallback)
|
||||||
|
|
||||||
|
```
|
||||||
|
/mnt/cache/system/podman/ # analog /mnt/user/system/docker/
|
||||||
|
├── podman.img # Loopback-Datei, XFS (ftype=1) oder BTRFS
|
||||||
|
│ # → gemountet auf /var/lib/containers/storage
|
||||||
|
├── logs/
|
||||||
|
│ ├── containers/<id>.log
|
||||||
|
│ └── podman-service.log
|
||||||
|
└── networks/ # falls netavark-State nicht im Image liegt
|
||||||
|
```
|
||||||
|
|
||||||
|
**Warum Cache-Pool statt `/mnt/user`:** Der `overlay`-Storage-Driver benötigt native
|
||||||
|
Dateisystemsemantik (d_type, xattrs, Hardlinks) ohne den FUSE-Layer von `shfs`. Das
|
||||||
|
Docker-Vorbild löst das identisch mit `docker.img`/Directory-Modus auf einem echten
|
||||||
|
Mountpoint. Nutzer ohne Cache-Pool müssen einen Array-Disk-Pfad (`/mnt/diskX/system/podman`)
|
||||||
|
wählen können — mit deutlicher GUI-Warnung bzgl. Performance und Spin-up-Verhalten.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Paketmanagement
|
||||||
|
|
||||||
|
### 5.1 Zu paketierende Komponenten
|
||||||
|
|
||||||
|
| Paket | Zweck | Bemerkung |
|
||||||
|
|---|---|---|
|
||||||
|
| `podman` | Kern-Binary | Statisch gegen möglichst wenige glibc-Versionen bauen oder gegen Unraid-Slackware-Base kompilieren |
|
||||||
|
| `conmon` | Container-Monitor-Prozess | Pflicht |
|
||||||
|
| `crun` (empfohlen) / `runc` | OCI-Runtime | `crun` bevorzugt (leichter, cgroup v2-freundlich, kompatibel zu Unraids Kernel-Config) |
|
||||||
|
| `netavark` + `aardvark-dns` | Netzwerk-Backend + DNS | Ersetzt CNI-Plugins als Default seit Podman 4.x |
|
||||||
|
| `containers-common` | Default-Configs (`containers.conf`, `seccomp.json`, `registries.conf`) | Wird als Vorlage übernommen, nicht als aktive Config |
|
||||||
|
| `fuse-overlayfs` | Fallback-Storage-Driver | Für Rootless-Phase 2 vorbereitet, in Phase 1 optional |
|
||||||
|
| `passt`/`pasta` | Rootless-Networking | Nachfolger von slirp4netns, Phase 2, aber Paket schon mitbauen (geringe Kosten) |
|
||||||
|
| `catatonit` oder `tini` | Init-Prozess in Containern (optional, falls von Templates genutzt) | |
|
||||||
|
|
||||||
|
### 5.2 Build-Strategie
|
||||||
|
|
||||||
|
- Eigene Build-Pipeline (containerisiert, z. B. in einem Slackware-kompatiblen
|
||||||
|
Build-Container) statt manuellem Cross-Compile auf laufenden Unraid-Systemen.
|
||||||
|
- Zielarchitektur: `x86_64` (Unraid unterstützt aktuell keine anderen Architekturen
|
||||||
|
produktiv) — vereinfacht Matrix erheblich.
|
||||||
|
- Statisch oder minimal dynamisch gelinkt (Go-Binaries von Podman/netavark/aardvark-dns
|
||||||
|
sind ohnehin größtenteils statisch), um Abhängigkeits-Drift gegenüber der jeweils
|
||||||
|
aktuellen Unraid-Slackware-Basis zu minimieren.
|
||||||
|
- Jedes Paket als eigenständiges `.txz` mit Slackware-Standard-Metadaten
|
||||||
|
(`slack-desc`, `doinst.sh`), damit `removepkg`/`upgradepkg` korrekt funktionieren.
|
||||||
|
- Versions-Pinning: Das `.plg` referenziert exakte Paketversionen + MD5, kein
|
||||||
|
„latest“-Pull zur Laufzeit — reproduzierbare Installationen, Voraussetzung für Rollback.
|
||||||
|
|
||||||
|
### 5.3 Abhängigkeitsprüfung
|
||||||
|
|
||||||
|
- Preflight-Check im `.plg`-Postinstall: Kernel-Version, `cgroup v2` aktiv,
|
||||||
|
vorhandene `iptables`/`nftables`-Binaries, freier Platz auf Zielpfad für
|
||||||
|
`podman.img`. Bei Nichterfüllung: Installation abbrechen mit klarer GUI-Meldung
|
||||||
|
statt eines halb-funktionsfähigen Zustands.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Start-/Stop-Skripte
|
||||||
|
|
||||||
|
### 6.1 `rc.podman` — Design analog zu `rc.docker`
|
||||||
|
|
||||||
|
Klassisches Slackware-BSD-Init-Skript mit Case-Dispatch:
|
||||||
|
|
||||||
|
```
|
||||||
|
rc.podman start # Preflight → Storage mounten → API-Service starten → Autostart-Liste abarbeiten
|
||||||
|
rc.podman stop # Container geordnet stoppen (Timeout) → API-Service stoppen → Storage unmounten
|
||||||
|
rc.podman restart
|
||||||
|
rc.podman status # Health-Check-Ausgabe für GUI/CLI
|
||||||
|
```
|
||||||
|
|
||||||
|
Kernschritte von `start` (konzeptionell, kein Code):
|
||||||
|
|
||||||
|
1. **Preflight** (`podman-preflight.sh`): prüft, ob konfigurierter Storage-Pfad
|
||||||
|
(Cache-Pool/Disk) gemountet ist; falls `podman.img` fehlt → anlegen (Erstinstallation)
|
||||||
|
mit konfigurierter Größe; falls vorhanden → per Loopback mounten.
|
||||||
|
2. **Config-Sync**: Kopiert `/boot/config/plugins/podman/*.conf` nach
|
||||||
|
`/etc/containers/` (Boot-Config ist „Source of Truth“, Laufzeit-Kopie ist Cache).
|
||||||
|
3. **Socket-Service starten**: `podman system service` im Hintergrund (rootful Unix-Socket
|
||||||
|
unter `/var/run/podman/podman.sock`), damit spätere WebUI/API-Konsumenten und CLI
|
||||||
|
dieselbe Instanz sehen (statt pro Aufruf neuer Podman-Fork-Prozesse ohne gemeinsamen State
|
||||||
|
— State liegt zwar in `/var/lib/containers`, ein Service vereinfacht aber Health-Checks,
|
||||||
|
Event-Streaming und spätere WebUI-Anbindung).
|
||||||
|
4. **Netzwerke wiederherstellen**: netavark-Netzwerkdefinitionen aus
|
||||||
|
`/boot/config/plugins/podman/networks/` nach `/etc/containers/networks/` synchronisieren.
|
||||||
|
5. **Autostart** (`podman-autostart.sh`, siehe Abschnitt 10).
|
||||||
|
6. **Statusdatei** schreiben (für GUI-Polling), Event ins Unraid-Log (`logger`).
|
||||||
|
|
||||||
|
`stop` in umgekehrter Reihenfolge, mit konfigurierbarem Stop-Timeout pro Container
|
||||||
|
(analog Dockers „Stop timeout“-Einstellung), danach `podman system service` beenden,
|
||||||
|
zuletzt Loopback-Storage sauber unmounten (verhindert Dateisystemfehler im `podman.img`).
|
||||||
|
|
||||||
|
### 6.2 Boot-Integration
|
||||||
|
|
||||||
|
- **Offizieller Unraid-Plugin-Event-Mechanismus**, kein `/boot/config/go`-Edit:
|
||||||
|
Unraids `emhttpd` führt bei jedem Array-/Boot-Event automatisch jedes
|
||||||
|
ausführbare Skript unter `/usr/local/emhttp/plugins/<name>/event/<eventname>`
|
||||||
|
aus, sofern vorhanden. Dieses Verhalten wurde gegen den echten Quellcode
|
||||||
|
etablierter Plugins verifiziert (`unassigned.devices` nutzt exakt dieselbe
|
||||||
|
Konvention für seine `disks_mounted`/`started`/`stopping_svcs`-Hooks).
|
||||||
|
unraid-podman nutzt zwei Events:
|
||||||
|
- `event/disks_mounted` — feuert, sobald Array-Disks **und** Cache-Pools
|
||||||
|
gemountet sind (Unraids Event-Reihenfolge: `starting` → `array_started` →
|
||||||
|
`disks_mounted` → `svcs_restarted` → `docker_started` → `libvirt_started`
|
||||||
|
→ `started`). Ruft `rc.podman start` im Hintergrund auf (`& disown`),
|
||||||
|
damit der Array-Start nicht blockiert.
|
||||||
|
- `event/stopping` — der **erste** Schritt der Shutdown-Sequenz (vor
|
||||||
|
`stopping_docker`, `stopping_svcs`, `unmounting_disks`, `stopping_array`).
|
||||||
|
Ruft `rc.podman stop` **synchron** auf, damit Container gestoppt und
|
||||||
|
`podman.img` sauber unmountet sind, bevor Unraid die zugrundeliegende
|
||||||
|
Disk/den Cache-Pool unmountet.
|
||||||
|
|
||||||
|
Dadurch kein Start vor Verfügbarkeit von Cache/Array, und beim Deinstallieren
|
||||||
|
ist nichts manuell rückgängig zu machen — die Hook-Skripte verschwinden
|
||||||
|
automatisch mit `removepkg` des `unraid-podman`-Pakets (siehe Abschnitt 3.2).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Persistenz-Strategie (Zusammenfassung der Prinzipien)
|
||||||
|
|
||||||
|
| Datenkategorie | Ablageort | Synchronisationsrichtung |
|
||||||
|
|---|---|---|
|
||||||
|
| Plugin-Settings (Storage-Pfad, Feature-Flags) | `/boot/config/plugins/podman/podman.cfg` | Boot → Laufzeit (read-only Kopie) |
|
||||||
|
| Container-/Netzwerk-/Registry-Config | `/boot/config/plugins/podman/*.conf` | Boot → Laufzeit bei jedem Start |
|
||||||
|
| Autostart-Liste & Reihenfolge | `/boot/config/plugins/podman/autostart` | Boot → Laufzeit; GUI schreibt zurück nach `/boot` |
|
||||||
|
| Images, Container, named Volumes, Layer-Cache | `podman.img` auf Cache-Pool | Laufzeit-only, Backup optional via Snapshot |
|
||||||
|
| Logs | Cache-Pool `logs/` | Laufzeit-only, Rotation |
|
||||||
|
| Netzwerk-Definitionen (netavark JSON) | Boot-Kopie + Laufzeit-Kopie | bidirektional bei Änderung über GUI/CLI (Watcher oder expliziter „Apply“-Schritt) |
|
||||||
|
|
||||||
|
Grundregel: **Jede Änderung, die ein Nutzer über CLI/GUI an persistenzrelevanten
|
||||||
|
Objekten vornimmt (neues Netzwerk, geänderte Autostart-Reihenfolge), muss explizit
|
||||||
|
oder per Watcher nach `/boot/config/plugins/podman/` zurückgeschrieben werden** —
|
||||||
|
sonst geht sie beim nächsten Reboot verloren. Das ist der Kernunterschied zu
|
||||||
|
Standard-Linux-Distributionen und der häufigste Fehlerquell-Kandidat bei Docker-artigen
|
||||||
|
Unraid-Plugins.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Netzwerke
|
||||||
|
|
||||||
|
- **Backend**: `netavark` + `aardvark-dns` (Podman-Default seit 4.x), kein CNI in Phase 1.
|
||||||
|
- **Default-Bridge**: eigene Bridge `podman0` (nicht `docker0`), eigener privater
|
||||||
|
Adressraum (konfigurierbar, Default-Vorschlag außerhalb von Dockers Default-Range,
|
||||||
|
um Kollisionen bei Parallelbetrieb zu vermeiden).
|
||||||
|
- **Custom Networks**: analog Docker-für-Unraid „Custom Networks“-Feature — GUI-Verwaltung
|
||||||
|
in Phase 2, Config-Persistenz-Mechanismus aber bereits in Phase 1 vorsehen (Abschnitt 7).
|
||||||
|
- **macvlan/ipvlan**: gleiche Kernel-Voraussetzungen wie bei Docker; bekannte Unraid-Falle
|
||||||
|
„macvlan call trap“ (Kernel-Panic bei bestimmten NIC-Treibern) muss dokumentiert und im
|
||||||
|
Preflight als Warnung ausgegeben werden, wenn macvlan-Netzwerke angelegt werden.
|
||||||
|
- **iptables/nftables-Koexistenz mit Docker**: netavark legt eigene Chains an
|
||||||
|
(`NETAVARK-*` Präfix); explizit verifizieren, dass diese nicht mit Dockers
|
||||||
|
`DOCKER`-Chains oder Unraids eigenen Firewall-Regeln interferieren. Firewall-Reload
|
||||||
|
durch Docker (bei dessen Start/Stop) darf Podman-Regeln nicht löschen und umgekehrt —
|
||||||
|
ggf. eigenes `iptables-restore`-Hook nach jedem `rc.podman start`.
|
||||||
|
- **DNS**: `aardvark-dns` für Container-zu-Container-Namensauflösung innerhalb
|
||||||
|
benutzerdefinierter Netzwerke, analog Dockers eingebettetem DNS-Server.
|
||||||
|
- **Port-Publishing**: Kollisionsprüfung mit bereits von Docker/anderen Diensten
|
||||||
|
belegten Ports als GUI-seitige Validierung (spätere Phase), technisch bereits
|
||||||
|
durch Kernel/`iptables` ohnehin erzwungen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Volumes
|
||||||
|
|
||||||
|
- **Named Volumes**: liegen innerhalb von `podman.img` unter
|
||||||
|
`.../storage/volumes/`, damit sie vom selben Backup-/Rollback-Mechanismus wie Images
|
||||||
|
erfasst werden.
|
||||||
|
- **Bind-Mounts** auf Unraid-Shares (`/mnt/user/appdata/...`): empfohlener Standardweg
|
||||||
|
für Nutzerdaten (analog Dockers „Path“-Mappings in Community-Applications-Templates),
|
||||||
|
da `/mnt/user/appdata` bereits vom Nutzer im Backup-/Mover-/Cache-Konzept berücksichtigt
|
||||||
|
wird. Bind-Mounts direkt gegen `/mnt/user` (FUSE) sind für Applikationsdaten unkritisch
|
||||||
|
(kein Overlay-Storage-Anspruch), nur der Storage-Graph selbst braucht den echten Mount.
|
||||||
|
- **Permissions/UID-Mapping**: Phase 1 rootful → Container laufen mit denselben
|
||||||
|
UID/GID-Semantiken wie Docker rootful heute; keine zusätzliche User-Namespace-Remapping-
|
||||||
|
Komplexität. Wird in Phase 2 (rootless) relevant.
|
||||||
|
- **Migration-Hilfe (spätere Phase, nicht MVP)**: Werkzeug zum Referenzieren bestehender
|
||||||
|
Docker-Bind-Mount-Pfade beim Anlegen äquivalenter Podman-Container, um Parallelbetrieb
|
||||||
|
mit denselben Appdata-Verzeichnissen zu erleichtern (mit klarer Warnung vor
|
||||||
|
gleichzeitigem Schreibzugriff durch zwei Runtimes).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. Images
|
||||||
|
|
||||||
|
- **Storage-Driver**: `overlay` (native, kein `fuse-overlayfs`) auf dem gemounteten
|
||||||
|
`podman.img` — Performance-Parität mit Dockers Standard-Setup.
|
||||||
|
- **Registries**: Default `registries.conf` mit Docker Hub + ggf. `ghcr.io`, `quay.io`
|
||||||
|
vorkonfiguriert, kurz-Namen-Auflösung (`unqualified-search-registries`) explizit
|
||||||
|
gesetzt statt interaktivem Prompt (der in einer daemonless/Skript-Umgebung nicht
|
||||||
|
funktioniert).
|
||||||
|
- **Pull/Push**: Standard-Podman-CLI-Semantik, keine Besonderheiten; Fortschrittsanzeige
|
||||||
|
später über API-Service-Events für die WebUI nutzbar.
|
||||||
|
- **Image-Größenmanagement**: `podman.img` ist eine feste/dynamisch wachsende
|
||||||
|
Loopback-Datei — GUI muss Füllstand anzeigen und Vergrößern anbieten (analog Dockers
|
||||||
|
„docker.img full“-Problem, das in der Community bekannt und schmerzhaft ist; hier von
|
||||||
|
Anfang an mit klarer Fehlermeldung statt stillem Fehlschlag lösen, siehe Abschnitt 16).
|
||||||
|
- **Community-Applications-Kompatibilität**: kein automatischer Import von
|
||||||
|
Docker-Templates in Phase 1 (eigenes, separates Vorhaben); Podman-Images werden
|
||||||
|
zunächst über CLI/eigene, minimale Template-Definition verwaltet.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. Container (Lifecycle-Management)
|
||||||
|
|
||||||
|
- Lifecycle-Operationen (`create`, `start`, `stop`, `restart`, `remove`, `exec`,
|
||||||
|
`logs`, `inspect`) laufen über den in Abschnitt 6.1 gestarteten Podman-API-Service,
|
||||||
|
nicht über Ad-hoc-CLI-Aufrufe aus der GUI heraus — ein gemeinsamer Service reduziert
|
||||||
|
Race Conditions und ermöglicht Event-Streaming.
|
||||||
|
- **Labels für GUI-Integration**: eigenes Label-Schema (z. B. `net.unraid.podman.icon`,
|
||||||
|
`net.unraid.podman.webui`), angelehnt an, aber getrennt von Dockers
|
||||||
|
`net.unraid.docker.*`-Konventionen, um spätere GUI-Icons/WebUI-Links analog zur
|
||||||
|
Docker-Tab-Darstellung zu ermöglichen, ohne Namensraum-Kollisionen.
|
||||||
|
- **Update-Strategie für Container-Images**: „Check for Updates“-Mechanismus
|
||||||
|
(Digest-Vergleich gegen Registry) als spätere GUI-Funktion, technisch schon in
|
||||||
|
Phase 1 per `podman image inspect`/`skopeo`-Vergleich vorbereitbar.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. Autostart
|
||||||
|
|
||||||
|
Da kein systemd existiert, wird Autostart **explizit vom Plugin verwaltet**, nicht von
|
||||||
|
Podman selbst (Podman hat kein natives „restart on boot“ ohne generierte Unit-Files):
|
||||||
|
|
||||||
|
- Flache Datei `/boot/config/plugins/podman/autostart`, eine Zeile pro Container
|
||||||
|
(Name oder ID), Reihenfolge = Zeilen-Reihenfolge = Startreihenfolge — direktes
|
||||||
|
Äquivalent zu Dockers `/boot/config/plugins/dynamix.docker.manager/docker-autostart`
|
||||||
|
bzw. der DB-gestützten Nachfolgelösung.
|
||||||
|
- Optionale, separate Delay-Datei für Wartezeiten zwischen Starts (Abhängigkeiten
|
||||||
|
zwischen Containern, z. B. DB vor App).
|
||||||
|
- `rc.podman start` liest die Liste sequenziell, startet jeden Container über den
|
||||||
|
API-Service, protokolliert Erfolg/Fehler pro Eintrag, bricht bei Einzelfehlern
|
||||||
|
**nicht** die gesamte Kette ab (ein defekter Container darf nicht alle anderen
|
||||||
|
blockieren).
|
||||||
|
- GUI-Checkbox „Autostart“ pro Container (spätere Phase) schreibt direkt in diese
|
||||||
|
Datei zurück — kein separates DB-Layer in Phase 1, um Komplexität niedrig zu halten
|
||||||
|
(kann bei Bedarf später durch strukturierteres Format, z. B. JSON, ersetzt werden).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 13. Updates
|
||||||
|
|
||||||
|
### 13.1 Plugin-Update (Podman-Version, Init-Skripte)
|
||||||
|
|
||||||
|
- Über den regulären Unraid-Plugin-Update-Mechanismus: `.plg` mit neuer Version +
|
||||||
|
neuen Paket-URLs/MD5s wird erneut ausgeführt.
|
||||||
|
- Vor jedem Update: automatisches Backup (siehe Abschnitt 14) der aktuellen
|
||||||
|
`.txz`-Pakete und Config-Dateien.
|
||||||
|
- **Container laufen während des Plugin-Updates weiter**, sofern nur Binaries
|
||||||
|
ausgetauscht werden (laufende `conmon`/Container-Prozesse sind vom
|
||||||
|
Dateisystem-Austausch unter `/usr/bin` isoliert, solange der Prozess nicht neu
|
||||||
|
exec't wird) — erst ein nachfolgender `rc.podman restart` übernimmt die neue
|
||||||
|
Podman-Version für den API-Service. Klar kommunizieren: Update ≠ sofortiger
|
||||||
|
Container-Neustart.
|
||||||
|
- Config-Migrationen (z. B. neues Feld in `containers.conf`) über ein
|
||||||
|
Versions-gestempeltes Migrationsskript, das beim Postinstall geprüft wird
|
||||||
|
(`podman.cfg` enthält `CONFIG_SCHEMA_VERSION`).
|
||||||
|
|
||||||
|
### 13.2 Container-Image-Updates
|
||||||
|
|
||||||
|
- Getrennt vom Plugin-Update zu betrachten — reine Podman-Funktionalität
|
||||||
|
(`podman pull` + Recreate), GUI-Komfortfunktion für Phase 2.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 14. Rollback
|
||||||
|
|
||||||
|
- **Paket-Rollback**: Vor jedem Update werden die aktuell installierten `.txz` nach
|
||||||
|
`/boot/config/plugins/podman/backup/packages/<alte-version>/` kopiert. Ein
|
||||||
|
„Rollback“-Kommando (Postinstall-Skript, später GUI-Button) installiert diese
|
||||||
|
Pakete erneut per `upgradepkg --reinstall`.
|
||||||
|
- **Config-Rollback**: Vor jeder strukturellen Config-Änderung (Plugin-Update mit
|
||||||
|
Schema-Migration) wird ein Snapshot nach
|
||||||
|
`/boot/config/plugins/podman/backup/config/<timestamp>/` geschrieben; Rollback
|
||||||
|
kopiert diesen Snapshot zurück und setzt `CONFIG_SCHEMA_VERSION` entsprechend zurück.
|
||||||
|
- **Storage-Rollback (Images/Container) ist explizit außerhalb des automatischen
|
||||||
|
Rollbacks**: `podman.img` wird nicht bei jedem Plugin-Update gesichert (zu groß,
|
||||||
|
zu I/O-intensiv). Stattdessen: dokumentierte, manuell auslösbare Snapshot-Funktion
|
||||||
|
(z. B. `cp --reflink` auf BTRFS/ZFS-Cache-Pools, wo Reflinks verfügbar sind) als
|
||||||
|
optionales Feature, kein Zwang.
|
||||||
|
- Rollback-Historie begrenzen (z. B. letzte 3 Versionen), um den knappen
|
||||||
|
Flash-Speicher nicht zu erschöpfen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 15. Logging
|
||||||
|
|
||||||
|
- **Problem**: `/var/log` liegt im RAM und ist bei Reboot weg; `journald` existiert
|
||||||
|
nicht, daher kann Podmans `journald`-Log-Driver nicht Default sein.
|
||||||
|
- **Lösung**: Default-Log-Driver `k8s-file` (JSON-per-Zeile, Podman-nativ) mit
|
||||||
|
Zielverzeichnis unter `/mnt/cache/system/podman/logs/containers/`, per
|
||||||
|
Symlink von `/var/log/podman` dorthin erreichbar (einheitlicher Pfad für Tools,
|
||||||
|
die `/var/log` erwarten, ohne RAM-Verlust).
|
||||||
|
- **Log-Rotation**: eigener Cron-Eintrag (Unraid nutzt `cron` bereits für andere
|
||||||
|
Plugins über `/boot/config/plugins/dynamix/`-Muster bzw. `/etc/cron.d`) mit
|
||||||
|
`logrotate`-Konfiguration oder einfachem Größen-/Alter-basiertem Skript, da
|
||||||
|
Flash/Cache-Platz begrenzt ist.
|
||||||
|
- **Service-Log** (`podman system service`, `rc.podman`-Ausführung) getrennt von
|
||||||
|
Container-Logs, ebenfalls auf Cache-Pool, zusätzlich Kurzfassung wichtiger
|
||||||
|
Ereignisse (Start/Stop/Fehler) über `logger` in Unraids zentrales Syslog, damit
|
||||||
|
sie in der Standard-„System Log“-GUI sichtbar sind.
|
||||||
|
- **GUI-Zugriff (spätere Phase)**: Log-Viewer analog Dockers Container-Log-Fenster,
|
||||||
|
liest direkt aus den `k8s-file`-Logs bzw. streamt über den API-Service.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 16. Fehlerbehandlung
|
||||||
|
|
||||||
|
### 16.1 Startup-Robustheit
|
||||||
|
|
||||||
|
- **Preflight-Checks** vor jedem `rc.podman start` (siehe 6.1): Storage-Mount,
|
||||||
|
Speicherplatz auf `podman.img`, Kernel-Voraussetzungen, Socket-Verfügbarkeit
|
||||||
|
(kein verwaister Socket von vorherigem Absturz).
|
||||||
|
- **Storage-Korruption**: Falls `podman.img` beim Mount-Versuch Dateisystemfehler
|
||||||
|
zeigt (`xfs_repair`/`btrfs check` schlägt fehl oder wird nicht automatisch
|
||||||
|
ausgeführt) → Start abbrechen, klare GUI-Fehlermeldung mit Handlungsempfehlung
|
||||||
|
(Recovery-Tool ausführen, Backup einspielen), **kein** automatisches
|
||||||
|
Neuanlegen/Formatieren ohne Nutzerbestätigung (Datenverlustrisiko).
|
||||||
|
- **Autostart-Fehler pro Container**: einzelner fehlgeschlagener Container wird
|
||||||
|
geloggt und übersprungen, blockiert nicht die restliche Kette (siehe Abschnitt 12);
|
||||||
|
nach N gescheiterten Autostart-Versuchen in Folge wird der Container automatisch
|
||||||
|
aus der Autostart-Kette pausiert („Safe-Mode“ pro Container) mit GUI-Hinweis,
|
||||||
|
um Boot-Loops/Ressourcenverschwendung zu vermeiden.
|
||||||
|
|
||||||
|
### 16.2 Laufzeitfehler
|
||||||
|
|
||||||
|
- **Health-Check des API-Service**: `rc.podman status` prüft Socket-Erreichbarkeit
|
||||||
|
und meldet Diskrepanzen (Prozess läuft, Socket tot o. ä.) verständlich.
|
||||||
|
- **Firewall-/Netzwerk-Konflikte**: bei bekannten Fehlerbildern (z. B. Chain-Konflikt
|
||||||
|
mit Docker, Port bereits belegt) definierte, für Menschen lesbare Fehlermeldungen
|
||||||
|
statt roher `iptables`/Go-Stacktraces — Fehlerkatalog mit Klartext-Ursache +
|
||||||
|
nächstem Schritt.
|
||||||
|
- **Ressourcenerschöpfung** (`podman.img` voll): aktive Prüfung im Preflight und
|
||||||
|
periodisch (Cron), GUI-Warnung bei > 85 % Füllstand, klare Anleitung zum
|
||||||
|
Vergrößern des Loopback-Images (bekannte Docker-für-Unraid-Schwachstelle, hier
|
||||||
|
von Anfang an proaktiv statt reaktiv lösen).
|
||||||
|
- **Unraid-Benachrichtigungssystem**: kritische Fehler (Storage-Mount fehlgeschlagen,
|
||||||
|
Autostart-Kette größtenteils fehlgeschlagen, `podman.img` > Schwellwert) werden
|
||||||
|
über Unraids Standard-Notify-Mechanismus (`/usr/local/emhttp/webGui/scripts/notify`)
|
||||||
|
ausgelöst, damit sie in GUI-Toasts, optional E-Mail/Pushover (falls vom Nutzer
|
||||||
|
konfiguriert) erscheinen — kein Parallel-Notify-System erfinden.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 17. Docker-Parallelbetrieb & spätere Deaktivierung
|
||||||
|
|
||||||
|
### Phase 1 — Parallelbetrieb (Pflicht)
|
||||||
|
|
||||||
|
- Getrennte Storage-Roots (`docker.img` vs. `podman.img`), getrennte Default-Bridges,
|
||||||
|
getrennte iptables-Chain-Präfixe, getrennte Log-Verzeichnisse — keine gemeinsam
|
||||||
|
genutzten mutable Ressourcen außer read-only Kernel-Features (cgroups, netfilter).
|
||||||
|
- Ressourcen-Konkurrenz (CPU/RAM/Disk-IO) ist erwartet und wird nicht technisch
|
||||||
|
verhindert, aber in der GUI/Doku transparent gemacht.
|
||||||
|
- Beide Runtimes dürfen gleichzeitig laufen, ohne dass Start/Stop des einen den
|
||||||
|
anderen beeinflusst (insbesondere Firewall-Reloads, siehe Abschnitt 8).
|
||||||
|
|
||||||
|
### Phase 2+ — Docker optional deaktivierbar
|
||||||
|
|
||||||
|
- Neuer Schalter (z. B. in den bestehenden Docker-Settings oder einem neuen
|
||||||
|
„Container Engine“-Auswahlbereich): „Docker aktiviert / Podman aktiviert / beide“.
|
||||||
|
- Deaktivierung von Docker bedeutet: `rc.docker stop` + Entfernen des
|
||||||
|
Autostart-Hooks aus `/boot/config/go` für Docker, **kein** Deinstallieren des
|
||||||
|
Docker-Plugins selbst (reversibel, Datenerhalt).
|
||||||
|
- Voraussetzung für diese Phase: Podman-Pfad muss funktional äquivalent zu den von
|
||||||
|
Nutzern tatsächlich genutzten Docker-Features sein (mind. Custom Networks, Autostart,
|
||||||
|
Log-Viewer, Update-Check) — technisch als Gate, nicht nur als Empfehlung, im
|
||||||
|
Freigabeprozess verankern.
|
||||||
|
- CA-Kompatibilität (Community Applications erwartet aktuell Docker) ist der
|
||||||
|
wahrscheinlich größte Blocker für „Docker komplett aus“ und sollte als eigenes
|
||||||
|
Arbeitspaket (Template-Übersetzung Docker→Podman) vor Freigabe dieser Option
|
||||||
|
behandelt werden.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 18. Zukünftige WebUI
|
||||||
|
|
||||||
|
### 18.1 Architektur (Zielbild, Umsetzung nach MVP)
|
||||||
|
|
||||||
|
- Eigene GUI-Seite unter `/usr/local/emhttp/plugins/podman/`, PHP + JS im
|
||||||
|
bestehenden Unraid-„Dynamix“-Stil (Konsistenz mit restlicher GUI, wiederverwendbare
|
||||||
|
CSS-/JS-Assets), analog zur Docker-Tab-Struktur (`dynamix.docker.manager`).
|
||||||
|
- Backend-Kommunikation **nicht** über Shell-Exec von `podman`-CLI-Aufrufen aus PHP
|
||||||
|
(fragil, schwer zu parallelisieren), sondern über den in Abschnitt 6.1 laufenden
|
||||||
|
Podman-API-Service (Unix-Socket, REST, Docker-kompatible Teilmenge der API) —
|
||||||
|
ermöglicht später auch Wiederverwendung bestehender Docker-API-kompatibler
|
||||||
|
Frontend-Bibliotheken.
|
||||||
|
- Echtzeit-Updates (Container-Status, Logs) über Server-Sent Events oder Polling
|
||||||
|
gegen den API-Service, kein WebSocket-Zwang in Phase 1 der WebUI (geringerer
|
||||||
|
Implementierungsaufwand, ausreichend für Status-Refresh im Sekundenbereich).
|
||||||
|
|
||||||
|
### 18.2 Funktionsumfang, gestaffelt
|
||||||
|
|
||||||
|
1. **Stufe 1**: Read-only Übersicht (laufende Container, Images, Netzwerke),
|
||||||
|
Start/Stop/Restart einzelner Container.
|
||||||
|
2. **Stufe 2**: Container-Erstellung über einfache Formulare (Image, Ports, Volumes,
|
||||||
|
Env, Labels), Autostart-Verwaltung direkt in GUI (schreibt in Abschnitt-12-Datei).
|
||||||
|
3. **Stufe 3**: Log-Viewer, Netzwerk-Verwaltung (Custom Networks/macvlan-Anlage),
|
||||||
|
Update-Check pro Container.
|
||||||
|
4. **Stufe 4**: Community-Applications-Anbindung/Template-Import (separates Vorhaben).
|
||||||
|
|
||||||
|
### 18.3 Sicherheitsaspekt der WebUI
|
||||||
|
|
||||||
|
- API-Service-Socket nur lokal (`/var/run/podman/podman.sock`), keine TCP-Exposition
|
||||||
|
nach außen ohne explizite, abgesicherte Opt-in-Konfiguration (TLS + Auth), da
|
||||||
|
rootful Podman-API faktisch Root-Äquivalent ist — gleiche Vorsicht wie beim
|
||||||
|
Docker-Socket.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 19. Sicherheitsbetrachtungen (Phase 1, rootful)
|
||||||
|
|
||||||
|
- Rootful Podman-API-Socket ist funktional gleichwertig zu `docker.sock` bzgl.
|
||||||
|
Angriffsfläche (Zugriff = Root auf dem Host) — Berechtigungen auf den Socket
|
||||||
|
(`0660`, Gruppe analog `docker`-Gruppe, z. B. neue Gruppe `podman`) restriktiv setzen.
|
||||||
|
Achtung Docker Sock: root Äquivalenz, daher keine ungeprüfte Gruppenmitgliedschaft
|
||||||
|
für WebUI-Prozesse vergeben, die nicht ohnehin schon rootäquivalent laufen (`emhttpd`
|
||||||
|
läuft unter Unraid ohnehin als root, insofern kein zusätzliches Risiko gegenüber
|
||||||
|
Docker heute — aber explizit dokumentieren, nicht stillschweigend voraussetzen).
|
||||||
|
- Keine automatische, ungefragte Netzwerk-Exposition von Container-Ports über die
|
||||||
|
Standard-Bridge hinaus.
|
||||||
|
- Rollback-/Backup-Mechanismus darf keine Zugangsdaten (Registry-Logins in
|
||||||
|
`auth.json`) ungesichert auf dem FAT32-Flash ablegen, ohne dass sich der Nutzer
|
||||||
|
dessen bewusst ist (Flash ist physisch leicht auslesbar) — ggf. Hinweis, sensible
|
||||||
|
Registry-Credentials bevorzugt nicht dauerhaft dort zu speichern, oder Ablage
|
||||||
|
ausschließlich auf dem Cache-Pool statt `/boot`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 20. Phasenplan
|
||||||
|
|
||||||
|
| Phase | Inhalt |
|
||||||
|
|---|---|
|
||||||
|
| **MVP (Phase 1)** | Plugin-Grundgerüst, Pakete, `rc.podman`, Storage auf Cache-Pool, Autostart-Datei, Basis-Logging, Preflight/Fehlerbehandlung, Rollback für Pakete/Config, **keine WebUI** (CLI-only, `podman` direkt nutzbar) |
|
||||||
|
| **Phase 2** | WebUI Stufe 1–2, Custom-Networks-Verwaltung, Log-Viewer, Storage-Snapshot-Feature |
|
||||||
|
| **Phase 3** | Rootless-Option, Docker-Deaktivierungs-Schalter, CA-Template-Kompatibilitätsschicht |
|
||||||
|
| **Phase 4** | Pods/Quadlet-artige Konzepte (ohne systemd nur eingeschränkt sinnvoll — ggf. eigenes Skript-Äquivalent statt echter Quadlet/systemd-Generatoren) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 21. Offene Fragen / Risiken (zur Klärung vor Implementierungsstart)
|
||||||
|
|
||||||
|
1. Ziel-Podman-Version und Update-Kadenz (Tracking von Upstream-CVEs erfordert
|
||||||
|
Paket-Update-Prozess, nicht nur Erstrelease).
|
||||||
|
2. Cache-Pool-Pflicht vs. Array-Fallback für `podman.img` — Nutzer ohne Cache-Pool
|
||||||
|
sind bei Docker heute ebenfalls im Nachteil; gleiche Community-Erwartungshaltung
|
||||||
|
übernehmen oder verbessern?
|
||||||
|
2a. Verhalten bei mehreren Cache-Pools (Unraid 6.9+ unterstützt mehrere benannte
|
||||||
|
Pools) — welchen Pool per Default vorschlagen, wie in GUI wählbar machen.
|
||||||
|
3. `podman system service` als Dauerprozess vs. Socket-Activation-artiges
|
||||||
|
On-Demand-Start (ohne systemd nur eingeschränkt nachbaubar) — Dauerprozess ist
|
||||||
|
der pragmatischere Start, sollte aber gegen Ressourcenverbrauch im Idle
|
||||||
|
evaluiert werden.
|
||||||
|
4. Genaues Verhalten bei gleichzeitigem Boot von Docker und Podman bzgl.
|
||||||
|
iptables-Reihenfolge/Locking (`iptables`-Aufrufe sind nicht atomar über
|
||||||
|
mehrere Prozesse hinweg) — ggf. Locking-Strategie (`xtables_lock`) explizit prüfen.
|
||||||
|
5. Lizenz-/Signatur-Anforderungen für Community-Verbreitung des Plugins
|
||||||
|
(Unraid-Plugin-Repository-Richtlinien, Signierung der `.plg`/Pakete).
|
||||||
+30
@@ -0,0 +1,30 @@
|
|||||||
|
# FAQ
|
||||||
|
|
||||||
|
**Q: Does this replace Docker on Unraid?**
|
||||||
|
Not initially. Phase 1 runs Podman alongside Docker with fully separate storage,
|
||||||
|
networking, and firewall rules. A later phase adds an option to disable Docker
|
||||||
|
once feature parity is reached — see [ROADMAP.md](ROADMAP.md).
|
||||||
|
|
||||||
|
**Q: Is this rootless or rootful Podman?**
|
||||||
|
Phase 1 is rootful only, matching Docker's current trust model on Unraid.
|
||||||
|
Rootless is planned for a later phase — see
|
||||||
|
[ARCHITECTURE.md](ARCHITECTURE.md#0-ziele--nicht-ziele).
|
||||||
|
|
||||||
|
**Q: Will my existing Docker containers/appdata work with Podman?**
|
||||||
|
Bind-mounted appdata under `/mnt/user/appdata/...` can generally be reused, but
|
||||||
|
there is no automatic Docker→Podman template migration in Phase 1. See
|
||||||
|
[ARCHITECTURE.md, section 9 (Volumes)](ARCHITECTURE.md#9-volumes).
|
||||||
|
|
||||||
|
**Q: Why isn't there a WebUI yet?**
|
||||||
|
The MVP intentionally ships CLI-only to validate the persistence, packaging, and
|
||||||
|
init model first, before investing in GUI work. See
|
||||||
|
[ARCHITECTURE.md, section 18](ARCHITECTURE.md#18-zukünftige-webui).
|
||||||
|
|
||||||
|
**Q: Why does this need a cache pool / dedicated disk instead of just using
|
||||||
|
`/mnt/user`?**
|
||||||
|
Podman's `overlay` storage driver needs real filesystem semantics that the
|
||||||
|
`/mnt/user` FUSE layer (`shfs`) doesn't reliably provide. See
|
||||||
|
[ARCHITECTURE.md, section 4.3](ARCHITECTURE.md#43-persistente-nutzdaten-cache-pool-bevorzugt-array-als-fallback).
|
||||||
|
|
||||||
|
**Q: Is this affiliated with Unraid, Inc. or the Podman project?**
|
||||||
|
No. This is an independent, community-driven plugin project.
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# Installation Guide (draft)
|
||||||
|
|
||||||
|
> This guide is a placeholder. Nothing described here is functional yet — the
|
||||||
|
> project is at the architecture/scaffolding stage. See
|
||||||
|
> [ARCHITECTURE.md](ARCHITECTURE.md) and [ROADMAP.md](ROADMAP.md).
|
||||||
|
|
||||||
|
## Planned installation flow
|
||||||
|
|
||||||
|
Once a release exists, installation will follow standard Unraid conventions:
|
||||||
|
|
||||||
|
1. In the Unraid WebUI, go to **Plugins → Install Plugin**.
|
||||||
|
2. Paste the `.plg` URL (or install via Community Applications, once listed).
|
||||||
|
3. Unraid downloads and runs `podman.plg`, which:
|
||||||
|
- installs the Slackware `.txz` packages under `packages/`,
|
||||||
|
- stages default configuration under `/boot/config/plugins/podman/`,
|
||||||
|
- registers the boot hook in `/boot/config/go`,
|
||||||
|
- starts the Podman service for the first time.
|
||||||
|
|
||||||
|
## Planned requirements
|
||||||
|
|
||||||
|
- Unraid version: TBD minimum (depends on kernel/cgroup v2 requirements — see
|
||||||
|
[ARCHITECTURE.md, section 5.3](ARCHITECTURE.md#53-abhängigkeitsprüfung)).
|
||||||
|
- A cache pool (recommended) or a dedicated array disk path for
|
||||||
|
`podman.img` — see [ARCHITECTURE.md, section 4.3](ARCHITECTURE.md#43-persistente-nutzdaten-cache-pool-bevorzugt-array-als-fallback).
|
||||||
|
- Sufficient free space on that pool/disk for the container storage image.
|
||||||
|
|
||||||
|
## Uninstallation (planned)
|
||||||
|
|
||||||
|
Removing the plugin removes the installed packages and init hooks but
|
||||||
|
**preserves** data under `/mnt/*/system/podman/` and
|
||||||
|
`/boot/config/plugins/podman/` by default. A separate, explicit option will be
|
||||||
|
provided for full data removal.
|
||||||
|
|
||||||
|
## Verifying the installation (planned)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
rc.podman status
|
||||||
|
podman info
|
||||||
|
```
|
||||||
+100
@@ -0,0 +1,100 @@
|
|||||||
|
# Roadmap
|
||||||
|
|
||||||
|
This roadmap mirrors the phase plan in
|
||||||
|
[ARCHITECTURE.md](ARCHITECTURE.md#20-phasenplan). It will be kept in sync as
|
||||||
|
scope shifts; treat ARCHITECTURE.md as the source of truth for *why*, and this
|
||||||
|
file as the tracking view for *what/when*.
|
||||||
|
|
||||||
|
## Phase 1 — MVP (CLI core)
|
||||||
|
|
||||||
|
- [x] Repository scaffolding
|
||||||
|
- [x] Reproducible build system: `versions.env` pins, per-package
|
||||||
|
`SlackBuild` scripts, `scripts/build-packages.sh` orchestrator,
|
||||||
|
`scripts/checksums.sh`, `scripts/release.sh`,
|
||||||
|
`scripts/update-versions.sh`, GitHub Actions
|
||||||
|
(`build-packages.yml`, `release.yml`, `lint.yml`) — builds podman,
|
||||||
|
conmon, crun, netavark, aardvark-dns, passt, fuse-overlayfs, and the
|
||||||
|
plugin's own `unraid-podman` scaffolding package. Not yet run
|
||||||
|
end-to-end in a real Slackware environment (see note below).
|
||||||
|
- [x] `podman.plg` install/update/remove manifest — validated well-formed
|
||||||
|
(including full DTD entity expansion inside INLINE script blocks) and
|
||||||
|
release-round-tripped against `scripts/release.sh` with synthetic
|
||||||
|
build artifacts; not yet installed on a real Unraid system.
|
||||||
|
- [x] `rc.podman` init script (start/stop/restart/status)
|
||||||
|
- [x] Storage on cache pool (`podman.img`, XFS ftype=1, overlay driver) —
|
||||||
|
`podman-storage.sh`
|
||||||
|
- [x] Config sync (`/boot/config/plugins/podman/` ↔ `/etc/containers/`) —
|
||||||
|
`podman-config.sh` (seed/sync logic sandbox-tested)
|
||||||
|
- [x] Autostart flat-file mechanism — `podman-autostart.sh`, including
|
||||||
|
per-container Safe-Mode after repeated failures (parsing logic
|
||||||
|
unit-tested)
|
||||||
|
- [x] Preflight checks & error handling (storage mount, disk space, kernel
|
||||||
|
reqs) — `podman-preflight.sh`
|
||||||
|
- [x] Basic logging — `podman_log`/`podman_log_error` in `podman-common.sh`;
|
||||||
|
log rotation still open (cron-based, see ARCHITECTURE.md section 15)
|
||||||
|
- [x] Package + config rollback (backup previous `.txz` / config snapshots)
|
||||||
|
— `podman-backup.sh`; plus `podman-verify-packages.sh` and
|
||||||
|
`podman-update-packages.sh` for integrity checking and reconciliation
|
||||||
|
- [x] Official Unraid array-event hooks (`event/disks_mounted`,
|
||||||
|
`event/stopping`) instead of editing `/boot/config/go` — verified
|
||||||
|
against real-world plugin source (`unassigned.devices`)
|
||||||
|
- [ ] Real-world install/uninstall/update test on an actual (or virtualized)
|
||||||
|
Unraid system — everything above has been syntax-checked, unit-tested
|
||||||
|
in isolation, and XML-validated, but not run against real Unraid/
|
||||||
|
Podman/Slackware yet
|
||||||
|
|
||||||
|
## Phase 2 — WebUI & network management
|
||||||
|
|
||||||
|
Built ahead of the original phase order (see [webui/README.md](../webui/README.md)
|
||||||
|
and the mockup at `webui/mockups/prototype.html`, which was reviewed before
|
||||||
|
implementation started) — PHP/JS/AJAX, talking to `podman system service`
|
||||||
|
exclusively via `PodmanClient` (two documented, deliberate exceptions: the
|
||||||
|
Terminal panel's one-shot exec model, and the Compose panel's use of the
|
||||||
|
`podman compose` CLI, since libpod has no REST endpoint for either true PTY
|
||||||
|
sessions or Compose — see those ajax/*.php files' header comments).
|
||||||
|
|
||||||
|
- [x] WebUI stage 1: read-only overview (Dashboard), start/stop/restart/remove
|
||||||
|
for Containers/Pods, backend logic unit- and integration-tested against
|
||||||
|
a fake podman.sock server (real Unix socket, real cURL transport)
|
||||||
|
- [x] Images, Volumes, Networks panels (list + create/remove/pull) — not part
|
||||||
|
of the original "stage 2/3" split, built alongside stage 1 since the
|
||||||
|
backend pattern (PodmanClient + one ajax/*.php file) is identical
|
||||||
|
- [x] Autostart management in GUI (Settings panel — reorder/remove, writes
|
||||||
|
the same `/boot/config/plugins/podman/autostart` file
|
||||||
|
`podman-autostart.sh` reads)
|
||||||
|
- [x] Log viewer (Logs panel, polling-based "Follow")
|
||||||
|
- [x] Terminal panel — one-shot exec, not a true interactive PTY (see
|
||||||
|
`ajax/exec.php`); a real PTY would need a WebSocket bridge, tracked
|
||||||
|
as a follow-up, not implemented as a shell-out workaround
|
||||||
|
- [x] Compose panel — project list + YAML view + up/down/pull via the
|
||||||
|
`podman compose` CLI (the one deliberate CLI exception, see
|
||||||
|
`ajax/compose.php`)
|
||||||
|
- [ ] Container **creation** forms — the Containers panel manages the
|
||||||
|
lifecycle of existing containers only; there is no "new container"
|
||||||
|
form yet (the mockup shows one; the real implementation doesn't wire
|
||||||
|
it up)
|
||||||
|
- [ ] Custom network / macvlan creation UI beyond the basic name+subnet form
|
||||||
|
already in the Networks panel
|
||||||
|
- [ ] Real interactive PTY terminal (WebSocket bridge)
|
||||||
|
- [ ] Storage snapshot feature (reflink-based, BTRFS/ZFS cache pools)
|
||||||
|
- [ ] Run against a real Unraid + Podman install — the backend has been
|
||||||
|
tested against a hand-written fake podman.sock server (verifying the
|
||||||
|
transport layer, JSON shaping, and error handling genuinely work),
|
||||||
|
not against real libpod
|
||||||
|
|
||||||
|
## Phase 3 — Rootless & Docker-optional
|
||||||
|
|
||||||
|
- [ ] Rootless Podman option
|
||||||
|
- [ ] Docker enable/disable switch
|
||||||
|
- [ ] Community Applications template compatibility layer
|
||||||
|
|
||||||
|
## Phase 4 — Pods
|
||||||
|
|
||||||
|
- [ ] Pod support (Quadlet-equivalent without systemd — approach TBD, see
|
||||||
|
ARCHITECTURE.md open question)
|
||||||
|
|
||||||
|
## Open questions blocking later phases
|
||||||
|
|
||||||
|
See [ARCHITECTURE.md, section 21](ARCHITECTURE.md#21-offene-fragen--risiken) for
|
||||||
|
the current list (Podman version/update cadence, cache-pool requirement, service
|
||||||
|
vs. on-demand start, iptables locking with Docker, release signing).
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
# Troubleshooting (draft)
|
||||||
|
|
||||||
|
> Placeholder. Will be filled in as real failure modes are observed post-MVP.
|
||||||
|
> Cross-reference [ARCHITECTURE.md, section 16 (Fehlerbehandlung)](ARCHITECTURE.md#16-fehlerbehandlung)
|
||||||
|
> for the error-handling design these entries should map to.
|
||||||
|
|
||||||
|
## How to gather diagnostics
|
||||||
|
|
||||||
|
```sh
|
||||||
|
rc.podman status
|
||||||
|
podman info
|
||||||
|
podman system df
|
||||||
|
```
|
||||||
|
|
||||||
|
Relevant log locations:
|
||||||
|
|
||||||
|
- Container logs: `/mnt/*/system/podman/logs/containers/`
|
||||||
|
- Service log: `/mnt/*/system/podman/logs/podman-service.log`
|
||||||
|
- Unraid System Log: `Tools → System Log` in the WebUI
|
||||||
|
|
||||||
|
Please redact registry credentials/tokens before sharing logs in an issue.
|
||||||
|
|
||||||
|
## Known problem categories (to be populated)
|
||||||
|
|
||||||
|
### Storage
|
||||||
|
|
||||||
|
- `podman.img` full / near-full
|
||||||
|
- Loopback mount failures after unclean shutdown
|
||||||
|
|
||||||
|
### Networking
|
||||||
|
|
||||||
|
- Custom network / macvlan issues
|
||||||
|
- Conflicts with Docker's `iptables` chains
|
||||||
|
|
||||||
|
### Autostart
|
||||||
|
|
||||||
|
- Container stuck in a failed autostart loop
|
||||||
|
|
||||||
|
### Plugin update / rollback
|
||||||
|
|
||||||
|
- Config migration failures after an update
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
# packages/
|
||||||
|
|
||||||
|
One subdirectory per Slackware `.txz` package required by the plugin, built
|
||||||
|
against Unraid's Slackware base. See
|
||||||
|
[docs/ARCHITECTURE.md, section 5](../docs/ARCHITECTURE.md#5-paketmanagement)
|
||||||
|
for the full rationale (why these components, static linking preference,
|
||||||
|
version pinning, build strategy).
|
||||||
|
|
||||||
|
## Packages
|
||||||
|
|
||||||
|
| Directory | Upstream project | Purpose |
|
||||||
|
|---|---|---|
|
||||||
|
| `podman/` | [containers/podman](https://github.com/containers/podman) | Core container engine binary |
|
||||||
|
| `conmon/` | [containers/conmon](https://github.com/containers/conmon) | Container monitor process |
|
||||||
|
| `crun/` | [containers/crun](https://github.com/containers/crun) | OCI runtime (preferred over runc) |
|
||||||
|
| `netavark/` | [containers/netavark](https://github.com/containers/netavark) | Network backend |
|
||||||
|
| `aardvark-dns/` | [containers/aardvark-dns](https://github.com/containers/aardvark-dns) | DNS for container-to-container name resolution |
|
||||||
|
| `containers-common/` | [containers/common](https://github.com/containers/common) | Default config/seccomp/registries templates |
|
||||||
|
| `fuse-overlayfs/` | [containers/fuse-overlayfs](https://github.com/containers/fuse-overlayfs) | Fallback storage driver (rootless, Phase 2) |
|
||||||
|
| `passt/` | [passt.top](https://passt.top/passt/about/) (no GitHub mirror) | Rootless networking (Phase 2), successor to slirp4netns |
|
||||||
|
| `unraid-podman/` | this repository (not an upstream project) | Plugin's own scaffolding — rc.podman, sbin/ scripts, event/ hooks, config templates (see [its README](unraid-podman/README.md)) |
|
||||||
|
|
||||||
|
`podman`, `conmon`, `crun`, `netavark`, `aardvark-dns`, `passt`,
|
||||||
|
`fuse-overlayfs`, and `unraid-podman` are built and released automatically by
|
||||||
|
`.github/workflows/build-packages.yml`. `containers-common` currently only
|
||||||
|
supplies reference config (see [config/](../config)) and is not yet wired
|
||||||
|
into the automated build.
|
||||||
|
|
||||||
|
## Standard layout per package
|
||||||
|
|
||||||
|
Each package directory follows the same skeleton:
|
||||||
|
|
||||||
|
```
|
||||||
|
packages/<name>/
|
||||||
|
├── <name>.SlackBuild # Slackware build script (source download, build, packaging)
|
||||||
|
├── slack-desc # Slackware package description (max 70 cols / 11 lines)
|
||||||
|
├── patches/ # Optional patches applied during the build
|
||||||
|
└── README.md # Upstream version pinned, build notes, patch rationale
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build pipeline
|
||||||
|
|
||||||
|
Packages are built via `scripts/build-packages.sh`, which runs each
|
||||||
|
package's `<name>.SlackBuild` in turn (see `scripts/lib/slackbuild-common.sh`
|
||||||
|
for the shared fetch/verify/package helpers they all use). This must run
|
||||||
|
inside a Slackware-compatible build environment — never on an arbitrary host
|
||||||
|
distro, to avoid glibc/ABI drift against Unraid's base.
|
||||||
|
`.github/workflows/build-packages.yml` is the CI entry point: it runs the
|
||||||
|
same script inside a pinned Slackware container
|
||||||
|
(`scripts/ci/setup-slackware-buildenv.sh` bootstraps any build-time
|
||||||
|
dependency the base image doesn't already ship) and uploads the results as a
|
||||||
|
workflow artifact — nothing built is ever committed to this repository.
|
||||||
|
|
||||||
|
Exact upstream versions and source checksums are pinned centrally in
|
||||||
|
[versions.env](../versions.env) (update via `scripts/update-versions.sh`,
|
||||||
|
never by hand) and, at release time, mirrored into the top-level
|
||||||
|
`plugin/podman.plg` manifest together with MD5 checksums by
|
||||||
|
`scripts/release.sh`, so installs are reproducible and rollback-able — see
|
||||||
|
[docs/ARCHITECTURE.md, section 14 (Rollback)](../docs/ARCHITECTURE.md#14-rollback).
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# packages/aardvark-dns
|
||||||
|
|
||||||
|
Slackware package build recipe for **aardvark-dns**.
|
||||||
|
|
||||||
|
- Upstream: https://github.com/containers/aardvark-dns
|
||||||
|
- Pinned version: TODO
|
||||||
|
- Patches: see `patches/` (currently none)
|
||||||
|
- Build entry point: `aardvark-dns.SlackBuild` (placeholder, not yet implemented)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
TODO: document any Unraid/Slackware-specific build considerations (static
|
||||||
|
linking, cgroup v2 assumptions, kernel feature requirements) once the build
|
||||||
|
script is implemented. See
|
||||||
|
[docs/ARCHITECTURE.md, section 5.2 (Build-Strategie)](../../docs/ARCHITECTURE.md#52-build-strategie).
|
||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# packages/aardvark-dns/aardvark-dns.SlackBuild
|
||||||
|
#
|
||||||
|
# Builds the aardvark-dns .txz package from upstream source (Rust).
|
||||||
|
# aardvark-dns provides container-to-container DNS name resolution within
|
||||||
|
# netavark-managed networks (see docs/ARCHITECTURE.md section 8). Installed
|
||||||
|
# as a libexec helper binary, same rationale as netavark.
|
||||||
|
#
|
||||||
|
# Requires (in the build environment): rustc, cargo.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$REPO_ROOT/scripts/lib/slackbuild-common.sh"
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$REPO_ROOT/versions.env"
|
||||||
|
|
||||||
|
VERSION="$AARDVARK_DNS_VERSION"
|
||||||
|
ARCH="$PKG_ARCH"
|
||||||
|
BUILD="$PKG_BUILD"
|
||||||
|
TAG="$PKG_TAG"
|
||||||
|
|
||||||
|
sb_init "aardvark-dns"
|
||||||
|
|
||||||
|
tarball=$(sb_fetch_and_verify "$AARDVARK_DNS_SRC_URL" "$AARDVARK_DNS_SRC_SHA256" "aardvark-dns-$VERSION.tar.gz")
|
||||||
|
srcdir=$(sb_extract "$tarball")
|
||||||
|
|
||||||
|
for patch in "$CWD"/patches/*.patch; do
|
||||||
|
[ -e "$patch" ] || continue
|
||||||
|
echo "==> [aardvark-dns] applying $(basename "$patch")"
|
||||||
|
patch -d "$srcdir" -p1 < "$patch"
|
||||||
|
done
|
||||||
|
|
||||||
|
cd "$srcdir"
|
||||||
|
|
||||||
|
echo "==> [aardvark-dns] cargo build --release"
|
||||||
|
cargo build --release --locked
|
||||||
|
|
||||||
|
echo "==> [aardvark-dns] install into \$PKG"
|
||||||
|
install -D -m 0755 target/release/aardvark-dns "$PKG/usr/libexec/podman/aardvark-dns"
|
||||||
|
|
||||||
|
sb_install_docs "$srcdir/LICENSE" "$srcdir/README.md"
|
||||||
|
sb_make_package "$VERSION" "$ARCH" "$BUILD" "$TAG"
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# HOW TO EDIT THIS FILE:
|
||||||
|
# The "handy ruler" below makes it easier to edit a package description.
|
||||||
|
# Line up the first '|' above the ':' following the base package name, and
|
||||||
|
# the '|' on the right side marks the last column you can put a character in.
|
||||||
|
# You must make exactly 11 lines for the formatting to be correct. It's also
|
||||||
|
# customary to leave one space after the ':' except on otherwise blank lines.
|
||||||
|
|
||||||
|
|-----handy-ruler------------------------------------------------|
|
||||||
|
aardvark-dns: aardvark-dns (TODO: one-line summary)
|
||||||
|
aardvark-dns:
|
||||||
|
aardvark-dns: TODO: two-to-three sentence description of aardvark-dns, its role in the
|
||||||
|
aardvark-dns: unraid-podman plugin, and a link to the upstream project.
|
||||||
|
aardvark-dns:
|
||||||
|
aardvark-dns: Homepage: https://github.com/containers/aardvark-dns
|
||||||
|
aardvark-dns:
|
||||||
|
aardvark-dns:
|
||||||
|
aardvark-dns:
|
||||||
|
aardvark-dns:
|
||||||
|
aardvark-dns:
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# packages/conmon
|
||||||
|
|
||||||
|
Slackware package build recipe for **conmon**.
|
||||||
|
|
||||||
|
- Upstream: https://github.com/containers/conmon
|
||||||
|
- Pinned version: TODO
|
||||||
|
- Patches: see `patches/` (currently none)
|
||||||
|
- Build entry point: `conmon.SlackBuild` (placeholder, not yet implemented)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
TODO: document any Unraid/Slackware-specific build considerations (static
|
||||||
|
linking, cgroup v2 assumptions, kernel feature requirements) once the build
|
||||||
|
script is implemented. See
|
||||||
|
[docs/ARCHITECTURE.md, section 5.2 (Build-Strategie)](../../docs/ARCHITECTURE.md#52-build-strategie).
|
||||||
Executable
+46
@@ -0,0 +1,46 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# packages/conmon/conmon.SlackBuild
|
||||||
|
#
|
||||||
|
# Builds the conmon .txz package from upstream source (C, glib-based).
|
||||||
|
# conmon is the per-container monitor process podman/crun rely on to keep a
|
||||||
|
# container's stdio and exit status attached even if podman itself restarts.
|
||||||
|
#
|
||||||
|
# Requires (in the build environment): gcc, make, pkg-config, glib2-dev,
|
||||||
|
# libseccomp-dev.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$REPO_ROOT/scripts/lib/slackbuild-common.sh"
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$REPO_ROOT/versions.env"
|
||||||
|
|
||||||
|
VERSION="$CONMON_VERSION"
|
||||||
|
ARCH="$PKG_ARCH"
|
||||||
|
BUILD="$PKG_BUILD"
|
||||||
|
TAG="$PKG_TAG"
|
||||||
|
|
||||||
|
sb_init "conmon"
|
||||||
|
|
||||||
|
tarball=$(sb_fetch_and_verify "$CONMON_SRC_URL" "$CONMON_SRC_SHA256" "conmon-$VERSION.tar.gz")
|
||||||
|
srcdir=$(sb_extract "$tarball")
|
||||||
|
|
||||||
|
for patch in "$CWD"/patches/*.patch; do
|
||||||
|
[ -e "$patch" ] || continue
|
||||||
|
echo "==> [conmon] applying $(basename "$patch")"
|
||||||
|
patch -d "$srcdir" -p1 < "$patch"
|
||||||
|
done
|
||||||
|
|
||||||
|
cd "$srcdir"
|
||||||
|
|
||||||
|
echo "==> [conmon] make"
|
||||||
|
make PREFIX=/usr
|
||||||
|
|
||||||
|
echo "==> [conmon] make install into \$PKG"
|
||||||
|
make install PREFIX=/usr DESTDIR="$PKG"
|
||||||
|
|
||||||
|
sb_install_docs "$srcdir/LICENSE" "$srcdir/README.md"
|
||||||
|
sb_make_package "$VERSION" "$ARCH" "$BUILD" "$TAG"
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# HOW TO EDIT THIS FILE:
|
||||||
|
# The "handy ruler" below makes it easier to edit a package description.
|
||||||
|
# Line up the first '|' above the ':' following the base package name, and
|
||||||
|
# the '|' on the right side marks the last column you can put a character in.
|
||||||
|
# You must make exactly 11 lines for the formatting to be correct. It's also
|
||||||
|
# customary to leave one space after the ':' except on otherwise blank lines.
|
||||||
|
|
||||||
|
|-----handy-ruler------------------------------------------------|
|
||||||
|
conmon: conmon (TODO: one-line summary)
|
||||||
|
conmon:
|
||||||
|
conmon: TODO: two-to-three sentence description of conmon, its role in the
|
||||||
|
conmon: unraid-podman plugin, and a link to the upstream project.
|
||||||
|
conmon:
|
||||||
|
conmon: Homepage: https://github.com/containers/conmon
|
||||||
|
conmon:
|
||||||
|
conmon:
|
||||||
|
conmon:
|
||||||
|
conmon:
|
||||||
|
conmon:
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# packages/containers-common
|
||||||
|
|
||||||
|
Slackware package build recipe for **containers-common**.
|
||||||
|
|
||||||
|
- Upstream: https://github.com/containers/common
|
||||||
|
- Pinned version: TODO
|
||||||
|
- Patches: see `patches/` (currently none)
|
||||||
|
- Build entry point: `containers-common.SlackBuild` (placeholder, not yet implemented)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
TODO: document any Unraid/Slackware-specific build considerations (static
|
||||||
|
linking, cgroup v2 assumptions, kernel feature requirements) once the build
|
||||||
|
script is implemented. See
|
||||||
|
[docs/ARCHITECTURE.md, section 5.2 (Build-Strategie)](../../docs/ARCHITECTURE.md#52-build-strategie).
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# SlackBuild for containers-common — unraid-podman
|
||||||
|
#
|
||||||
|
# Builds a Slackware-compatible .txz package for containers-common, pinned to a specific
|
||||||
|
# upstream release from https://github.com/containers/common.
|
||||||
|
# See docs/ARCHITECTURE.md section 5 (Paketmanagement) and packages/README.md.
|
||||||
|
#
|
||||||
|
# STATUS: placeholder — not yet implemented.
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
PRGNAM=containers-common
|
||||||
|
VERSION=${VERSION:-0.0.0} # TODO: pin exact upstream version
|
||||||
|
ARCH=${ARCH:-x86_64}
|
||||||
|
BUILD=${BUILD:-1}
|
||||||
|
TAG=${TAG:-_unraidpodman}
|
||||||
|
|
||||||
|
CWD=$(pwd)
|
||||||
|
TMP=${TMP:-/tmp/SBo}
|
||||||
|
PKG=$TMP/package-$PRGNAM
|
||||||
|
OUTPUT=${OUTPUT:-/tmp}
|
||||||
|
|
||||||
|
# TODO:
|
||||||
|
# 1. Fetch source tarball for $PRGNAM $VERSION from
|
||||||
|
# https://github.com/containers/common
|
||||||
|
# 2. Apply patches from packages/containers-common/patches/, if any
|
||||||
|
# 3. Build (Go build for Go-based components, make for C-based components)
|
||||||
|
# 4. Install into $PKG following Slackware package filesystem layout
|
||||||
|
# 5. Copy packages/containers-common/slack-desc into $PKG/install/slack-desc
|
||||||
|
# 6. makepkg to produce $OUTPUT/$PRGNAM-$VERSION-$ARCH-$BUILD$TAG.txz
|
||||||
|
|
||||||
|
echo "TODO: implement containers-common.SlackBuild"
|
||||||
|
exit 1
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# HOW TO EDIT THIS FILE:
|
||||||
|
# The "handy ruler" below makes it easier to edit a package description.
|
||||||
|
# Line up the first '|' above the ':' following the base package name, and
|
||||||
|
# the '|' on the right side marks the last column you can put a character in.
|
||||||
|
# You must make exactly 11 lines for the formatting to be correct. It's also
|
||||||
|
# customary to leave one space after the ':' except on otherwise blank lines.
|
||||||
|
|
||||||
|
|-----handy-ruler------------------------------------------------|
|
||||||
|
containers-common: containers-common (TODO: one-line summary)
|
||||||
|
containers-common:
|
||||||
|
containers-common: TODO: two-to-three sentence description of containers-common, its role in the
|
||||||
|
containers-common: unraid-podman plugin, and a link to the upstream project.
|
||||||
|
containers-common:
|
||||||
|
containers-common: Homepage: https://github.com/containers/common
|
||||||
|
containers-common:
|
||||||
|
containers-common:
|
||||||
|
containers-common:
|
||||||
|
containers-common:
|
||||||
|
containers-common:
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# packages/crun
|
||||||
|
|
||||||
|
Slackware package build recipe for **crun**.
|
||||||
|
|
||||||
|
- Upstream: https://github.com/containers/crun
|
||||||
|
- Pinned version: TODO
|
||||||
|
- Patches: see `patches/` (currently none)
|
||||||
|
- Build entry point: `crun.SlackBuild` (placeholder, not yet implemented)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
TODO: document any Unraid/Slackware-specific build considerations (static
|
||||||
|
linking, cgroup v2 assumptions, kernel feature requirements) once the build
|
||||||
|
script is implemented. See
|
||||||
|
[docs/ARCHITECTURE.md, section 5.2 (Build-Strategie)](../../docs/ARCHITECTURE.md#52-build-strategie).
|
||||||
Executable
+59
@@ -0,0 +1,59 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# packages/crun/crun.SlackBuild
|
||||||
|
#
|
||||||
|
# Builds the crun .txz package from upstream source (C, autotools). crun is
|
||||||
|
# the OCI runtime this project uses (preferred over runc — lighter, cgroup
|
||||||
|
# v2-friendly, see docs/ARCHITECTURE.md section 5.1).
|
||||||
|
#
|
||||||
|
# Requires (in the build environment): gcc, make, autoconf, automake,
|
||||||
|
# libtool, pkg-config, libcap-dev, libseccomp-dev, libyajl-dev, python3
|
||||||
|
# (used by crun's build-time code generator).
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$REPO_ROOT/scripts/lib/slackbuild-common.sh"
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$REPO_ROOT/versions.env"
|
||||||
|
|
||||||
|
VERSION="$CRUN_VERSION"
|
||||||
|
ARCH="$PKG_ARCH"
|
||||||
|
BUILD="$PKG_BUILD"
|
||||||
|
TAG="$PKG_TAG"
|
||||||
|
|
||||||
|
sb_init "crun"
|
||||||
|
|
||||||
|
tarball=$(sb_fetch_and_verify "$CRUN_SRC_URL" "$CRUN_SRC_SHA256" "crun-$VERSION.tar.gz")
|
||||||
|
srcdir=$(sb_extract "$tarball")
|
||||||
|
|
||||||
|
for patch in "$CWD"/patches/*.patch; do
|
||||||
|
[ -e "$patch" ] || continue
|
||||||
|
echo "==> [crun] applying $(basename "$patch")"
|
||||||
|
patch -d "$srcdir" -p1 < "$patch"
|
||||||
|
done
|
||||||
|
|
||||||
|
cd "$srcdir"
|
||||||
|
|
||||||
|
# GitHub's source archive does not include vendored git submodules
|
||||||
|
# (libocispec) that crun's release tarballs normally bundle; init/update
|
||||||
|
# them explicitly.
|
||||||
|
if [ -f .gitmodules ]; then
|
||||||
|
echo "==> [crun] fetching submodules"
|
||||||
|
git -c protocol.file.allow=always submodule update --init --recursive || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> [crun] autogen + configure"
|
||||||
|
./autogen.sh
|
||||||
|
./configure --prefix=/usr --disable-systemd
|
||||||
|
|
||||||
|
echo "==> [crun] make"
|
||||||
|
make
|
||||||
|
|
||||||
|
echo "==> [crun] make install into \$PKG"
|
||||||
|
make install DESTDIR="$PKG"
|
||||||
|
|
||||||
|
sb_install_docs "$srcdir/COPYING" "$srcdir/README.md"
|
||||||
|
sb_make_package "$VERSION" "$ARCH" "$BUILD" "$TAG"
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# HOW TO EDIT THIS FILE:
|
||||||
|
# The "handy ruler" below makes it easier to edit a package description.
|
||||||
|
# Line up the first '|' above the ':' following the base package name, and
|
||||||
|
# the '|' on the right side marks the last column you can put a character in.
|
||||||
|
# You must make exactly 11 lines for the formatting to be correct. It's also
|
||||||
|
# customary to leave one space after the ':' except on otherwise blank lines.
|
||||||
|
|
||||||
|
|-----handy-ruler------------------------------------------------|
|
||||||
|
crun: crun (TODO: one-line summary)
|
||||||
|
crun:
|
||||||
|
crun: TODO: two-to-three sentence description of crun, its role in the
|
||||||
|
crun: unraid-podman plugin, and a link to the upstream project.
|
||||||
|
crun:
|
||||||
|
crun: Homepage: https://github.com/containers/crun
|
||||||
|
crun:
|
||||||
|
crun:
|
||||||
|
crun:
|
||||||
|
crun:
|
||||||
|
crun:
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# packages/fuse-overlayfs
|
||||||
|
|
||||||
|
Slackware package build recipe for **fuse-overlayfs**.
|
||||||
|
|
||||||
|
- Upstream: https://github.com/containers/fuse-overlayfs
|
||||||
|
- Pinned version: TODO
|
||||||
|
- Patches: see `patches/` (currently none)
|
||||||
|
- Build entry point: `fuse-overlayfs.SlackBuild` (placeholder, not yet implemented)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
TODO: document any Unraid/Slackware-specific build considerations (static
|
||||||
|
linking, cgroup v2 assumptions, kernel feature requirements) once the build
|
||||||
|
script is implemented. See
|
||||||
|
[docs/ARCHITECTURE.md, section 5.2 (Build-Strategie)](../../docs/ARCHITECTURE.md#52-build-strategie).
|
||||||
+54
@@ -0,0 +1,54 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# packages/fuse-overlayfs/fuse-overlayfs.SlackBuild
|
||||||
|
#
|
||||||
|
# Builds the fuse-overlayfs .txz package from upstream source (C, autotools).
|
||||||
|
# Not used by the rootful storage path in Phase 1 (which uses the native
|
||||||
|
# overlay driver on the podman.img loopback filesystem — see
|
||||||
|
# docs/ARCHITECTURE.md section 10), but required ahead of time for the
|
||||||
|
# rootless Podman phase, where unprivileged overlay mounts generally aren't
|
||||||
|
# available (see docs/ARCHITECTURE.md section 0, "Nicht-Ziele" / ROADMAP.md
|
||||||
|
# Phase 3).
|
||||||
|
#
|
||||||
|
# Requires (in the build environment): gcc, make, autoconf, automake,
|
||||||
|
# libtool, pkg-config, libfuse3-dev.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$REPO_ROOT/scripts/lib/slackbuild-common.sh"
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$REPO_ROOT/versions.env"
|
||||||
|
|
||||||
|
VERSION="$FUSE_OVERLAYFS_VERSION"
|
||||||
|
ARCH="$PKG_ARCH"
|
||||||
|
BUILD="$PKG_BUILD"
|
||||||
|
TAG="$PKG_TAG"
|
||||||
|
|
||||||
|
sb_init "fuse-overlayfs"
|
||||||
|
|
||||||
|
tarball=$(sb_fetch_and_verify "$FUSE_OVERLAYFS_SRC_URL" "$FUSE_OVERLAYFS_SRC_SHA256" "fuse-overlayfs-$VERSION.tar.gz")
|
||||||
|
srcdir=$(sb_extract "$tarball")
|
||||||
|
|
||||||
|
for patch in "$CWD"/patches/*.patch; do
|
||||||
|
[ -e "$patch" ] || continue
|
||||||
|
echo "==> [fuse-overlayfs] applying $(basename "$patch")"
|
||||||
|
patch -d "$srcdir" -p1 < "$patch"
|
||||||
|
done
|
||||||
|
|
||||||
|
cd "$srcdir"
|
||||||
|
|
||||||
|
echo "==> [fuse-overlayfs] autogen + configure"
|
||||||
|
./autogen.sh
|
||||||
|
./configure --prefix=/usr
|
||||||
|
|
||||||
|
echo "==> [fuse-overlayfs] make"
|
||||||
|
make
|
||||||
|
|
||||||
|
echo "==> [fuse-overlayfs] make install into \$PKG"
|
||||||
|
make install DESTDIR="$PKG"
|
||||||
|
|
||||||
|
sb_install_docs "$srcdir/COPYING" "$srcdir/README.md"
|
||||||
|
sb_make_package "$VERSION" "$ARCH" "$BUILD" "$TAG"
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# HOW TO EDIT THIS FILE:
|
||||||
|
# The "handy ruler" below makes it easier to edit a package description.
|
||||||
|
# Line up the first '|' above the ':' following the base package name, and
|
||||||
|
# the '|' on the right side marks the last column you can put a character in.
|
||||||
|
# You must make exactly 11 lines for the formatting to be correct. It's also
|
||||||
|
# customary to leave one space after the ':' except on otherwise blank lines.
|
||||||
|
|
||||||
|
|-----handy-ruler------------------------------------------------|
|
||||||
|
fuse-overlayfs: fuse-overlayfs (TODO: one-line summary)
|
||||||
|
fuse-overlayfs:
|
||||||
|
fuse-overlayfs: TODO: two-to-three sentence description of fuse-overlayfs, its role in the
|
||||||
|
fuse-overlayfs: unraid-podman plugin, and a link to the upstream project.
|
||||||
|
fuse-overlayfs:
|
||||||
|
fuse-overlayfs: Homepage: https://github.com/containers/fuse-overlayfs
|
||||||
|
fuse-overlayfs:
|
||||||
|
fuse-overlayfs:
|
||||||
|
fuse-overlayfs:
|
||||||
|
fuse-overlayfs:
|
||||||
|
fuse-overlayfs:
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# packages/netavark
|
||||||
|
|
||||||
|
Slackware package build recipe for **netavark**.
|
||||||
|
|
||||||
|
- Upstream: https://github.com/containers/netavark
|
||||||
|
- Pinned version: TODO
|
||||||
|
- Patches: see `patches/` (currently none)
|
||||||
|
- Build entry point: `netavark.SlackBuild` (placeholder, not yet implemented)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
TODO: document any Unraid/Slackware-specific build considerations (static
|
||||||
|
linking, cgroup v2 assumptions, kernel feature requirements) once the build
|
||||||
|
script is implemented. See
|
||||||
|
[docs/ARCHITECTURE.md, section 5.2 (Build-Strategie)](../../docs/ARCHITECTURE.md#52-build-strategie).
|
||||||
Executable
+49
@@ -0,0 +1,49 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# packages/netavark/netavark.SlackBuild
|
||||||
|
#
|
||||||
|
# Builds the netavark .txz package from upstream source (Rust). netavark is
|
||||||
|
# this project's container networking backend (see docs/ARCHITECTURE.md
|
||||||
|
# section 8) — it is installed as a libexec helper binary, not a $PATH tool,
|
||||||
|
# matching upstream's own layout so podman finds it via
|
||||||
|
# `netavark_binary_dir` in containers.conf.
|
||||||
|
#
|
||||||
|
# Requires (in the build environment): rustc, cargo (a recent stable
|
||||||
|
# toolchain — check upstream's Cargo.toml `rust-version` for the current
|
||||||
|
# minimum).
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$REPO_ROOT/scripts/lib/slackbuild-common.sh"
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$REPO_ROOT/versions.env"
|
||||||
|
|
||||||
|
VERSION="$NETAVARK_VERSION"
|
||||||
|
ARCH="$PKG_ARCH"
|
||||||
|
BUILD="$PKG_BUILD"
|
||||||
|
TAG="$PKG_TAG"
|
||||||
|
|
||||||
|
sb_init "netavark"
|
||||||
|
|
||||||
|
tarball=$(sb_fetch_and_verify "$NETAVARK_SRC_URL" "$NETAVARK_SRC_SHA256" "netavark-$VERSION.tar.gz")
|
||||||
|
srcdir=$(sb_extract "$tarball")
|
||||||
|
|
||||||
|
for patch in "$CWD"/patches/*.patch; do
|
||||||
|
[ -e "$patch" ] || continue
|
||||||
|
echo "==> [netavark] applying $(basename "$patch")"
|
||||||
|
patch -d "$srcdir" -p1 < "$patch"
|
||||||
|
done
|
||||||
|
|
||||||
|
cd "$srcdir"
|
||||||
|
|
||||||
|
echo "==> [netavark] cargo build --release"
|
||||||
|
cargo build --release --locked
|
||||||
|
|
||||||
|
echo "==> [netavark] install into \$PKG"
|
||||||
|
install -D -m 0755 target/release/netavark "$PKG/usr/libexec/podman/netavark"
|
||||||
|
|
||||||
|
sb_install_docs "$srcdir/LICENSE" "$srcdir/README.md"
|
||||||
|
sb_make_package "$VERSION" "$ARCH" "$BUILD" "$TAG"
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# HOW TO EDIT THIS FILE:
|
||||||
|
# The "handy ruler" below makes it easier to edit a package description.
|
||||||
|
# Line up the first '|' above the ':' following the base package name, and
|
||||||
|
# the '|' on the right side marks the last column you can put a character in.
|
||||||
|
# You must make exactly 11 lines for the formatting to be correct. It's also
|
||||||
|
# customary to leave one space after the ':' except on otherwise blank lines.
|
||||||
|
|
||||||
|
|-----handy-ruler------------------------------------------------|
|
||||||
|
netavark: netavark (TODO: one-line summary)
|
||||||
|
netavark:
|
||||||
|
netavark: TODO: two-to-three sentence description of netavark, its role in the
|
||||||
|
netavark: unraid-podman plugin, and a link to the upstream project.
|
||||||
|
netavark:
|
||||||
|
netavark: Homepage: https://github.com/containers/netavark
|
||||||
|
netavark:
|
||||||
|
netavark:
|
||||||
|
netavark:
|
||||||
|
netavark:
|
||||||
|
netavark:
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# packages/passt
|
||||||
|
|
||||||
|
Slackware package build recipe for **passt** (and its `pasta` counterpart).
|
||||||
|
|
||||||
|
- Upstream: https://passt.top/passt/about/ (cgit, no GitHub mirror)
|
||||||
|
- Pinned commit: see `PASST_COMMIT` in [versions.env](../../versions.env)
|
||||||
|
- Patches: see `patches/` (currently none)
|
||||||
|
- Build entry point: `passt.SlackBuild`
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
Unlike the other six packages, passt has no tagged releases or semver
|
||||||
|
versioning upstream — distributions package it straight from a pinned git
|
||||||
|
commit (this is also how Fedora/Debian build it). `scripts/update-versions.sh`
|
||||||
|
handles re-pinning to a newer commit and recomputing the snapshot checksum;
|
||||||
|
see [versions.env](../../versions.env) for the exact snapshot URL pattern.
|
||||||
|
|
||||||
|
Not required for the Phase 1 (rootful) MVP — passt/pasta become relevant once
|
||||||
|
rootless Podman support is added (see
|
||||||
|
[docs/ROADMAP.md](../../docs/ROADMAP.md), Phase 3). It is built now so the
|
||||||
|
package pipeline covers the full dependency set from day one.
|
||||||
Executable
+49
@@ -0,0 +1,49 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# packages/passt/passt.SlackBuild
|
||||||
|
#
|
||||||
|
# Builds the passt/pasta .txz package from upstream source (C, plain
|
||||||
|
# Makefile — no autotools). passt is Podman's modern unprivileged
|
||||||
|
# user-mode network transport for rootless containers, superseding
|
||||||
|
# slirp4netns (see versions.env for why this project pins it via a git
|
||||||
|
# commit snapshot rather than a tagged release: upstream has no GitHub
|
||||||
|
# mirror and does not use semver tags).
|
||||||
|
#
|
||||||
|
# Requires (in the build environment): gcc, make. No external library
|
||||||
|
# dependencies beyond libc — passt deliberately avoids them.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$REPO_ROOT/scripts/lib/slackbuild-common.sh"
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$REPO_ROOT/versions.env"
|
||||||
|
|
||||||
|
VERSION="$PASST_VERSION"
|
||||||
|
ARCH="$PKG_ARCH"
|
||||||
|
BUILD="$PKG_BUILD"
|
||||||
|
TAG="$PKG_TAG"
|
||||||
|
|
||||||
|
sb_init "passt"
|
||||||
|
|
||||||
|
tarball=$(sb_fetch_and_verify "$PASST_SRC_URL" "$PASST_SRC_SHA256" "passt-$PASST_COMMIT.tar.gz")
|
||||||
|
srcdir=$(sb_extract "$tarball")
|
||||||
|
|
||||||
|
for patch in "$CWD"/patches/*.patch; do
|
||||||
|
[ -e "$patch" ] || continue
|
||||||
|
echo "==> [passt] applying $(basename "$patch")"
|
||||||
|
patch -d "$srcdir" -p1 < "$patch"
|
||||||
|
done
|
||||||
|
|
||||||
|
cd "$srcdir"
|
||||||
|
|
||||||
|
echo "==> [passt] make"
|
||||||
|
make
|
||||||
|
|
||||||
|
echo "==> [passt] make install into \$PKG"
|
||||||
|
make install prefix=/usr DESTDIR="$PKG"
|
||||||
|
|
||||||
|
sb_install_docs "$srcdir/LICENSES/GPL-2.0-or-later.txt" "$srcdir/README.md"
|
||||||
|
sb_make_package "$VERSION" "$ARCH" "$BUILD" "$TAG"
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# HOW TO EDIT THIS FILE:
|
||||||
|
# The "handy ruler" below makes it easier to edit a package description.
|
||||||
|
# Line up the first '|' above the ':' following the base package name, and
|
||||||
|
# the '|' on the right side marks the last column you can put a character in.
|
||||||
|
# You must make exactly 11 lines for the formatting to be correct. It's also
|
||||||
|
# customary to leave one space after the ':' except on otherwise blank lines.
|
||||||
|
|
||||||
|
|-----handy-ruler------------------------------------------------|
|
||||||
|
passt: passt (unprivileged user-mode networking for VMs/containers)
|
||||||
|
passt:
|
||||||
|
passt: passt and pasta provide unprivileged (rootless) user-mode network
|
||||||
|
passt: connectivity for containers and VMs, without requiring a tap device
|
||||||
|
passt: or elevated capabilities. Podman uses it as the rootless network
|
||||||
|
passt: transport, superseding slirp4netns.
|
||||||
|
passt:
|
||||||
|
passt: Homepage: https://passt.top/passt/about/
|
||||||
|
passt:
|
||||||
|
passt:
|
||||||
|
passt:
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
# packages/podman
|
||||||
|
|
||||||
|
Slackware package build recipe for **podman**.
|
||||||
|
|
||||||
|
- Upstream: https://github.com/containers/podman
|
||||||
|
- Pinned version: TODO
|
||||||
|
- Patches: see `patches/` (currently none)
|
||||||
|
- Build entry point: `podman.SlackBuild` (placeholder, not yet implemented)
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
TODO: document any Unraid/Slackware-specific build considerations (static
|
||||||
|
linking, cgroup v2 assumptions, kernel feature requirements) once the build
|
||||||
|
script is implemented. See
|
||||||
|
[docs/ARCHITECTURE.md, section 5.2 (Build-Strategie)](../../docs/ARCHITECTURE.md#52-build-strategie).
|
||||||
Executable
+74
@@ -0,0 +1,74 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# packages/podman/podman.SlackBuild
|
||||||
|
#
|
||||||
|
# Builds the podman .txz package from upstream source (Go). Version and
|
||||||
|
# checksum are pinned centrally in versions.env — see
|
||||||
|
# scripts/lib/slackbuild-common.sh for the shared fetch/verify/package
|
||||||
|
# helpers used below, and docs/ARCHITECTURE.md section 5 for why the
|
||||||
|
# graphdriver build tags below are what they are (this project only ever
|
||||||
|
# uses the overlay storage driver, see docs/ARCHITECTURE.md section 10).
|
||||||
|
#
|
||||||
|
# Requires (in the build environment): go >= 1.22, make, gcc, pkg-config,
|
||||||
|
# libseccomp-dev. GPG signature verification support is intentionally left
|
||||||
|
# out to keep the dependency footprint minimal for a from-scratch Slackware
|
||||||
|
# build.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$REPO_ROOT/scripts/lib/slackbuild-common.sh"
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$REPO_ROOT/versions.env"
|
||||||
|
|
||||||
|
VERSION="$PODMAN_VERSION"
|
||||||
|
ARCH="$PKG_ARCH"
|
||||||
|
BUILD="$PKG_BUILD"
|
||||||
|
TAG="$PKG_TAG"
|
||||||
|
|
||||||
|
sb_init "podman"
|
||||||
|
|
||||||
|
tarball=$(sb_fetch_and_verify "$PODMAN_SRC_URL" "$PODMAN_SRC_SHA256" "podman-$VERSION.tar.gz")
|
||||||
|
srcdir=$(sb_extract "$tarball")
|
||||||
|
|
||||||
|
# Apply any local patches (none currently — see packages/podman/patches/).
|
||||||
|
for patch in "$CWD"/patches/*.patch; do
|
||||||
|
[ -e "$patch" ] || continue
|
||||||
|
echo "==> [podman] applying $(basename "$patch")"
|
||||||
|
patch -d "$srcdir" -p1 < "$patch"
|
||||||
|
done
|
||||||
|
|
||||||
|
cd "$srcdir"
|
||||||
|
|
||||||
|
# Build tags: exclude storage backends this project doesn't use (btrfs,
|
||||||
|
# devicemapper — see docs/ARCHITECTURE.md section 10, overlay-only), exclude
|
||||||
|
# systemd integration (Unraid has no systemd — see docs/ARCHITECTURE.md
|
||||||
|
# "Rahmenbedingungen"), keep seccomp support.
|
||||||
|
export BUILDTAGS="seccomp exclude_graphdriver_btrfs exclude_graphdriver_devicemapper containers_image_openpgp"
|
||||||
|
export CGO_ENABLED=1
|
||||||
|
export GOFLAGS="${GOFLAGS:--mod=mod}"
|
||||||
|
|
||||||
|
echo "==> [podman] make (BUILDTAGS=$BUILDTAGS)"
|
||||||
|
make BUILDTAGS="$BUILDTAGS" GO_BUILD_FLAGS="-ldflags -s"
|
||||||
|
|
||||||
|
echo "==> [podman] make install into \$PKG"
|
||||||
|
make install \
|
||||||
|
DESTDIR="$PKG" \
|
||||||
|
PREFIX=/usr \
|
||||||
|
ETCDIR="$PKG/etc" \
|
||||||
|
BUILDTAGS="$BUILDTAGS"
|
||||||
|
|
||||||
|
# We ship our own containers.conf/storage.conf/registries.conf/policy.json
|
||||||
|
# templates (see config/) staged via plugin/podman.plg instead of upstream's
|
||||||
|
# defaults, so drop the ones `make install` places under $PKG/etc to avoid
|
||||||
|
# ambiguity about which file is authoritative on a running system.
|
||||||
|
rm -rf "$PKG/etc/containers"
|
||||||
|
|
||||||
|
# No systemd units — Unraid has no systemd (see docs/ARCHITECTURE.md,
|
||||||
|
# "Rahmenbedingungen"); process lifecycle is entirely owned by rc.podman.
|
||||||
|
rm -rf "$PKG/usr/lib/systemd" "$PKG/lib/systemd"
|
||||||
|
|
||||||
|
sb_install_docs "$srcdir/LICENSE" "$srcdir/README.md"
|
||||||
|
sb_make_package "$VERSION" "$ARCH" "$BUILD" "$TAG"
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# HOW TO EDIT THIS FILE:
|
||||||
|
# The "handy ruler" below makes it easier to edit a package description.
|
||||||
|
# Line up the first '|' above the ':' following the base package name, and
|
||||||
|
# the '|' on the right side marks the last column you can put a character in.
|
||||||
|
# You must make exactly 11 lines for the formatting to be correct. It's also
|
||||||
|
# customary to leave one space after the ':' except on otherwise blank lines.
|
||||||
|
|
||||||
|
|-----handy-ruler------------------------------------------------|
|
||||||
|
podman: podman (TODO: one-line summary)
|
||||||
|
podman:
|
||||||
|
podman: TODO: two-to-three sentence description of podman, its role in the
|
||||||
|
podman: unraid-podman plugin, and a link to the upstream project.
|
||||||
|
podman:
|
||||||
|
podman: Homepage: https://github.com/containers/podman
|
||||||
|
podman:
|
||||||
|
podman:
|
||||||
|
podman:
|
||||||
|
podman:
|
||||||
|
podman:
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
# packages/unraid-podman
|
||||||
|
|
||||||
|
Packaging recipe for the plugin's own scaffolding — **not** an upstream
|
||||||
|
component like the other seven package directories. See
|
||||||
|
`unraid-podman.SlackBuild`'s header comment for the full rationale.
|
||||||
|
|
||||||
|
Bundles:
|
||||||
|
- `plugin/rc.d/rc.podman` → `/etc/rc.d/rc.podman`
|
||||||
|
- `plugin/sbin/*.sh` → `/usr/local/sbin/`
|
||||||
|
- `plugin/event/*` → `/usr/local/emhttp/plugins/podman/event/` (official
|
||||||
|
Unraid array-event hooks — see those files' own header comments)
|
||||||
|
- `config/*.conf`, `config/podman.cfg.example` → `/usr/local/share/unraid-podman/templates/`
|
||||||
|
- `webui/plugins/podman/` → `/usr/local/emhttp/plugins/podman/` (once populated, see [docs/ROADMAP.md](../../docs/ROADMAP.md))
|
||||||
|
|
||||||
|
Version is read directly from `plugin/podman.plg`'s `<!ENTITY version>` —
|
||||||
|
there's no separate version to keep in sync.
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# HOW TO EDIT THIS FILE:
|
||||||
|
# The "handy ruler" below makes it easier to edit a package description.
|
||||||
|
# Line up the first '|' above the ':' following the base package name, and
|
||||||
|
# the '|' on the right side marks the last column you can put a character in.
|
||||||
|
# You must make exactly 11 lines for the formatting to be correct. It's also
|
||||||
|
# customary to leave one space after the ':' except on otherwise blank lines.
|
||||||
|
|
||||||
|
|-----handy-ruler------------------------------------------------|
|
||||||
|
unraid-podman: unraid-podman (plugin scaffolding: rc.podman, hooks, config)
|
||||||
|
unraid-podman:
|
||||||
|
unraid-podman: This package contains the unraid-podman plugin's own files —
|
||||||
|
unraid-podman: the rc.podman init script, helper scripts under
|
||||||
|
unraid-podman: /usr/local/sbin, the official Unraid array event hooks, and
|
||||||
|
unraid-podman: default configuration templates. It does not contain Podman
|
||||||
|
unraid-podman: itself (see the separate podman/conmon/crun/... packages).
|
||||||
|
unraid-podman:
|
||||||
|
unraid-podman: Homepage: https://github.com/OWNER/unraid-podman
|
||||||
|
unraid-podman:
|
||||||
|
unraid-podman:
|
||||||
+84
@@ -0,0 +1,84 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# packages/unraid-podman/unraid-podman.SlackBuild
|
||||||
|
#
|
||||||
|
# Unlike the other seven packages, this one does not compile anything from
|
||||||
|
# an external upstream source — it packages THIS repository's own plugin
|
||||||
|
# scaffolding (rc.podman, the sbin/ helper scripts, the official Unraid
|
||||||
|
# event/ hooks, and the default config templates) into a single .txz,
|
||||||
|
# exactly matching how real-world Unraid plugins bundle their own files
|
||||||
|
# (verified against the actual unassigned.devices.plg / package layout —
|
||||||
|
# see docs/ARCHITECTURE.md section 3.2 for the reference check that led to
|
||||||
|
# this design). plugin/podman.plg installs this alongside the seven
|
||||||
|
# compiled component packages, all via the same
|
||||||
|
# `upgradepkg --install-new --reinstall` mechanism.
|
||||||
|
#
|
||||||
|
# Version: taken directly from plugin/podman.plg's own <!ENTITY version>,
|
||||||
|
# so there is exactly one place a release's plugin version is defined —
|
||||||
|
# scripts/release.sh already updates that entity, and this script just
|
||||||
|
# reads it back rather than duplicating it in versions.env (which is
|
||||||
|
# reserved for pinning EXTERNAL upstream sources, see that file's header).
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$REPO_ROOT/scripts/lib/slackbuild-common.sh"
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$REPO_ROOT/versions.env"
|
||||||
|
|
||||||
|
VERSION=$(grep -oP '(?<=<!ENTITY version[[:space:]]{1,10}")[^"]+' "$REPO_ROOT/plugin/podman.plg" | head -n1)
|
||||||
|
if [ -z "$VERSION" ]; then
|
||||||
|
echo "!! Could not read <!ENTITY version> from $REPO_ROOT/plugin/podman.plg" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
ARCH="$PKG_ARCH"
|
||||||
|
BUILD="$PKG_BUILD"
|
||||||
|
TAG="$PKG_TAG"
|
||||||
|
|
||||||
|
sb_init "unraid-podman"
|
||||||
|
|
||||||
|
# --- Stage rc.podman -----------------------------------------------------
|
||||||
|
install -D -m 0755 "$REPO_ROOT/plugin/rc.d/rc.podman" "$PKG/etc/rc.d/rc.podman"
|
||||||
|
|
||||||
|
# --- Stage sbin helper scripts ---------------------------------------------
|
||||||
|
for f in "$REPO_ROOT"/plugin/sbin/*.sh; do
|
||||||
|
install -D -m 0755 "$f" "$PKG/usr/local/sbin/$(basename "$f")"
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- Stage official Unraid plugin event hooks -------------------------------
|
||||||
|
# See plugin/event/*/'s own header comments: this is the officially
|
||||||
|
# documented mechanism (verified against unassigned.devices), NOT a
|
||||||
|
# /boot/config/go edit.
|
||||||
|
for f in "$REPO_ROOT"/plugin/event/*; do
|
||||||
|
install -D -m 0755 "$f" "$PKG/usr/local/emhttp/plugins/podman/event/$(basename "$f")"
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- Stage default config templates -----------------------------------------
|
||||||
|
# Consumed by podman-config.sh's `seed` command at install/first-boot time —
|
||||||
|
# see that script's header comment for the seed-only-if-missing contract.
|
||||||
|
for f in containers.conf storage.conf registries.conf policy.json; do
|
||||||
|
install -D -m 0644 "$REPO_ROOT/config/$f" "$PKG/usr/local/share/unraid-podman/templates/$f"
|
||||||
|
done
|
||||||
|
install -D -m 0644 "$REPO_ROOT/config/podman.cfg.example" \
|
||||||
|
"$PKG/usr/local/share/unraid-podman/templates/podman.cfg.example"
|
||||||
|
|
||||||
|
# --- Stage WebUI files, if present ------------------------------------------
|
||||||
|
# webui/plugins/podman/ is populated starting in a later development phase
|
||||||
|
# (see docs/ROADMAP.md, Phase 2) — copy it in wholesale when it exists so
|
||||||
|
# this SlackBuild doesn't need to change again once it does.
|
||||||
|
if [ -d "$REPO_ROOT/webui/plugins/podman" ]; then
|
||||||
|
mkdir -p "$PKG/usr/local/emhttp/plugins/podman"
|
||||||
|
cp -a "$REPO_ROOT/webui/plugins/podman/." "$PKG/usr/local/emhttp/plugins/podman/"
|
||||||
|
# event/ was already staged above with correct permissions — avoid
|
||||||
|
# clobbering it with a possibly-non-executable copy from webui/ (which
|
||||||
|
# has no reason to contain its own event/ directory, but be defensive).
|
||||||
|
for f in "$REPO_ROOT"/plugin/event/*; do
|
||||||
|
install -D -m 0755 "$f" "$PKG/usr/local/emhttp/plugins/podman/event/$(basename "$f")"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
sb_install_docs "$REPO_ROOT/LICENSE" "$REPO_ROOT/README.md"
|
||||||
|
sb_make_package "$VERSION" "$ARCH" "$BUILD" "$TAG"
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# plugin/boot-config/plugins/podman/
|
||||||
|
|
||||||
|
This directory is a staged mirror of what `/boot/config/plugins/podman/` should
|
||||||
|
look like immediately after a first install — see
|
||||||
|
[docs/ARCHITECTURE.md, section 4.1](../../../../docs/ARCHITECTURE.md#41-auf-dem-flash-device-boot-persistent-klein).
|
||||||
|
|
||||||
|
The `.plg` postinstall step (see `plugin/podman.plg`) copies these files to
|
||||||
|
`/boot/config/plugins/podman/` **only if they don't already exist**, so
|
||||||
|
re-running an install/update never clobbers a user's existing configuration.
|
||||||
|
|
||||||
|
Config file *content* templates (`containers.conf`, `storage.conf`,
|
||||||
|
`registries.conf`, `policy.json`) are sourced from the top-level [config/](../../../../config)
|
||||||
|
directory rather than duplicated here — this directory only defines the
|
||||||
|
directory skeleton and the plugin-specific settings files (`podman.cfg`,
|
||||||
|
`autostart`, ...) that have no equivalent elsewhere.
|
||||||
|
|
||||||
|
```
|
||||||
|
plugins/podman/
|
||||||
|
├── podman.cfg # copied from config/podman.cfg.example
|
||||||
|
├── autostart # empty by default, see docs/ARCHITECTURE.md section 12
|
||||||
|
├── autostart-delay # empty by default
|
||||||
|
├── networks/ # empty, populated as custom networks are created
|
||||||
|
├── backup/
|
||||||
|
│ ├── packages/ # empty, populated on first update
|
||||||
|
│ └── config/ # empty, populated on first update
|
||||||
|
└── plugin.log # empty, install/update log
|
||||||
|
```
|
||||||
Executable
+25
@@ -0,0 +1,25 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# plugin/event/disks_mounted
|
||||||
|
#
|
||||||
|
# Official Unraid plugin event hook — NOT a custom mechanism. Unraid's
|
||||||
|
# emhttpd fires every executable found at
|
||||||
|
# /usr/local/emhttp/plugins/<name>/event/<eventname> as each boot/array
|
||||||
|
# event happens (see e.g. the real-world unassigned.devices plugin, which
|
||||||
|
# uses this same convention for its own disks_mounted/started/stopping_svcs
|
||||||
|
# hooks). This project relies exclusively on this official mechanism —
|
||||||
|
# specifically NOT on hand-editing /boot/config/go, which is unnecessary
|
||||||
|
# and easy to get wrong across plugin updates/uninstalls.
|
||||||
|
#
|
||||||
|
# "disks_mounted" fires once array disks AND cache pools are mounted —
|
||||||
|
# exactly the precondition unraid-podman needs before it can create/mount
|
||||||
|
# podman.img on the configured cache pool/disk (see
|
||||||
|
# docs/ARCHITECTURE.md section 4.3 and section 6.2).
|
||||||
|
#
|
||||||
|
# Backgrounded (& disown) so array startup / the Main GUI page isn't
|
||||||
|
# blocked on podman's full startup sequence (preflight, storage mount,
|
||||||
|
# service start, autostart chain) — mirrors how unassigned.devices
|
||||||
|
# backgrounds its own longer-running "started" hook.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
/etc/rc.d/rc.podman start > /dev/null 2>&1 & disown
|
||||||
Executable
+19
@@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# plugin/event/stopping
|
||||||
|
#
|
||||||
|
# Official Unraid plugin event hook (see plugin/event/disks_mounted for the
|
||||||
|
# full explanation of this mechanism). "stopping" is the FIRST event fired
|
||||||
|
# in Unraid's shutdown sequence — before stopping_docker, stopping_svcs,
|
||||||
|
# unmounting_disks, stopping_array — which gives unraid-podman the largest
|
||||||
|
# possible window to stop containers and unmount podman.img BEFORE Unraid
|
||||||
|
# unmounts the underlying cache pool/disk out from under it.
|
||||||
|
#
|
||||||
|
# Run synchronously (no backgrounding, unlike disks_mounted) — shutdown
|
||||||
|
# must actually wait for containers to stop and storage to unmount
|
||||||
|
# cleanly; racing the array-stop sequence here risks filesystem corruption
|
||||||
|
# on podman.img (see docs/ARCHITECTURE.md section 16.1 on why this project
|
||||||
|
# treats storage integrity conservatively).
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
/etc/rc.d/rc.podman stop
|
||||||
@@ -0,0 +1,365 @@
|
|||||||
|
<?xml version="1.0" standalone="yes"?>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
podman.plg — Unraid plugin manifest for unraid-podman.
|
||||||
|
|
||||||
|
This file is the single entry point Unraid's Plugin Manager uses to
|
||||||
|
install, update, and remove the plugin. Everything it does is built on
|
||||||
|
OFFICIAL Unraid plugin mechanisms only — verified directly against the
|
||||||
|
real source of two long-established, widely used Unraid plugins
|
||||||
|
(unraid/community.applications and unraid/unassigned.devices) rather than
|
||||||
|
guessed:
|
||||||
|
|
||||||
|
- A FILE block with a Name attribute, an upgradepkg Run command (using
|
||||||
|
its install new and reinstall flags together), and a URL/MD5 child
|
||||||
|
pair is the standard way to download and install a Slackware .txz
|
||||||
|
package, and the same command works for both a fresh install and an
|
||||||
|
update in one step (confirmed: both reference plugins use exactly
|
||||||
|
this pattern rather than separate installpkg/upgradepkg branches).
|
||||||
|
- Plugin uninstall logic lives in a <FILE Run="/bin/bash" Method="remove">
|
||||||
|
block — this exact `Method="remove"` attribute is what Unraid's
|
||||||
|
plugin manager looks for when the user clicks "Remove" (confirmed
|
||||||
|
against community.applications.plg).
|
||||||
|
- Boot/shutdown integration uses the official Unraid plugin EVENT
|
||||||
|
mechanism: any executable at
|
||||||
|
/usr/local/emhttp/plugins/<name>/event/<eventname> is run automatically
|
||||||
|
by emhttpd as the corresponding event fires (confirmed against
|
||||||
|
unassigned.devices, which uses this exact convention for its own
|
||||||
|
disks_mounted/started/stopping_svcs hooks). This project deliberately
|
||||||
|
does NOT hand-edit /boot/config/go — see plugin/event/disks_mounted
|
||||||
|
and plugin/event/stopping for the two hooks used here, and
|
||||||
|
docs/ARCHITECTURE.md section 6.2 for the full event-ordering rationale.
|
||||||
|
|
||||||
|
Structure of this file:
|
||||||
|
1. DOCTYPE entity block — plugin metadata + one version/file/md5 triple
|
||||||
|
per package (the seven upstream components plus this project's own
|
||||||
|
"unraid-podman" scaffolding package, see packages/unraid-podman/).
|
||||||
|
Entities are rewritten automatically by scripts/release.sh; never
|
||||||
|
hand-edit a *_txz_version/_file/_md5 entity — see that script.
|
||||||
|
2. <PLUGIN> body:
|
||||||
|
a. <CHANGES> — kept in sync with CHANGELOG.md by hand for now.
|
||||||
|
b. Pre-install architecture sanity check.
|
||||||
|
c. Eight <FILE> package install/update blocks.
|
||||||
|
d. Postinstall <FILE Run="/bin/bash"> — directory/config seeding,
|
||||||
|
install-manifest generation, first start.
|
||||||
|
e. <FILE Run="/bin/bash" Method="remove"> — uninstall.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!DOCTYPE PLUGIN [
|
||||||
|
<!ENTITY name "podman">
|
||||||
|
<!ENTITY author "unraid-podman contributors">
|
||||||
|
<!ENTITY version "0.0.0">
|
||||||
|
<!-- "Podman" (no parent) — Podman.page declares Menu="Podman", making it
|
||||||
|
its own top-level nav tab next to Docker/VMs, not nested under
|
||||||
|
Settings — see webui/plugins/podman/Podman.page. -->
|
||||||
|
<!ENTITY launch "Podman">
|
||||||
|
<!ENTITY github "OWNER/unraid-podman">
|
||||||
|
<!ENTITY gitURL "https://raw.githubusercontent.com/&github;/main">
|
||||||
|
<!ENTITY pluginURL "&gitURL;/plugin/podman.plg">
|
||||||
|
<!ENTITY supportURL "https://github.com/&github;/discussions">
|
||||||
|
|
||||||
|
<!-- Release asset base — matches scripts/release.sh's RELEASE_BASE_URL
|
||||||
|
exactly; both must agree since release.sh is what publishes the
|
||||||
|
packages this URL is expected to find. -->
|
||||||
|
<!ENTITY baseURL "https://github.com/&github;/releases/download/v&version;">
|
||||||
|
|
||||||
|
<!-- Slackware package naming components — must match versions.env's
|
||||||
|
PKG_ARCH/PKG_BUILD/PKG_TAG (see that file). Kept as entities here so
|
||||||
|
the eight removepkg calls in the Method="remove" block don't have to
|
||||||
|
repeat "x86_64-1_unraidpodman" eight times by hand. -->
|
||||||
|
<!ENTITY pkgArch "x86_64">
|
||||||
|
<!ENTITY pkgBuild "1">
|
||||||
|
<!ENTITY pkgTag "_unraidpodman">
|
||||||
|
|
||||||
|
<!-- One version/file/md5 entity triple per package, filled in by
|
||||||
|
scripts/release.sh from dist/*.txz.md5 at release time (see that
|
||||||
|
script's "Update per-package entities" step). Until a release has been
|
||||||
|
cut, these are placeholders and the FILE blocks below will correctly
|
||||||
|
fail to download anything — that is intentional; there is nothing to
|
||||||
|
install before the first tagged release. -->
|
||||||
|
|
||||||
|
<!ENTITY podman_txz_version "0.0.0">
|
||||||
|
<!ENTITY podman_txz_file "podman-&podman_txz_version;-&pkgArch;-&pkgBuild;&pkgTag;.txz">
|
||||||
|
<!ENTITY podman_txz_md5 "00000000000000000000000000000000">
|
||||||
|
|
||||||
|
<!ENTITY conmon_txz_version "0.0.0">
|
||||||
|
<!ENTITY conmon_txz_file "conmon-&conmon_txz_version;-&pkgArch;-&pkgBuild;&pkgTag;.txz">
|
||||||
|
<!ENTITY conmon_txz_md5 "00000000000000000000000000000000">
|
||||||
|
|
||||||
|
<!ENTITY crun_txz_version "0.0.0">
|
||||||
|
<!ENTITY crun_txz_file "crun-&crun_txz_version;-&pkgArch;-&pkgBuild;&pkgTag;.txz">
|
||||||
|
<!ENTITY crun_txz_md5 "00000000000000000000000000000000">
|
||||||
|
|
||||||
|
<!ENTITY netavark_txz_version "0.0.0">
|
||||||
|
<!ENTITY netavark_txz_file "netavark-&netavark_txz_version;-&pkgArch;-&pkgBuild;&pkgTag;.txz">
|
||||||
|
<!ENTITY netavark_txz_md5 "00000000000000000000000000000000">
|
||||||
|
|
||||||
|
<!ENTITY aardvark_dns_txz_version "0.0.0">
|
||||||
|
<!ENTITY aardvark_dns_txz_file "aardvark-dns-&aardvark_dns_txz_version;-&pkgArch;-&pkgBuild;&pkgTag;.txz">
|
||||||
|
<!ENTITY aardvark_dns_txz_md5 "00000000000000000000000000000000">
|
||||||
|
|
||||||
|
<!ENTITY passt_txz_version "0.0.0">
|
||||||
|
<!ENTITY passt_txz_file "passt-&passt_txz_version;-&pkgArch;-&pkgBuild;&pkgTag;.txz">
|
||||||
|
<!ENTITY passt_txz_md5 "00000000000000000000000000000000">
|
||||||
|
|
||||||
|
<!ENTITY fuse_overlayfs_txz_version "0.0.0">
|
||||||
|
<!ENTITY fuse_overlayfs_txz_file "fuse-overlayfs-&fuse_overlayfs_txz_version;-&pkgArch;-&pkgBuild;&pkgTag;.txz">
|
||||||
|
<!ENTITY fuse_overlayfs_txz_md5 "00000000000000000000000000000000">
|
||||||
|
|
||||||
|
<!-- unraid-podman is this project's OWN scaffolding package (rc.podman,
|
||||||
|
sbin/ scripts, event/ hooks, config templates — see
|
||||||
|
packages/unraid-podman/README.md), not an upstream component. Its
|
||||||
|
version always equals the plugin's own &version; — see
|
||||||
|
packages/unraid-podman/unraid-podman.SlackBuild, which reads it
|
||||||
|
straight out of this very file rather than tracking it twice. -->
|
||||||
|
<!ENTITY unraid_podman_txz_version "&version;">
|
||||||
|
<!ENTITY unraid_podman_txz_file "unraid-podman-&unraid_podman_txz_version;-&pkgArch;-&pkgBuild;&pkgTag;.txz">
|
||||||
|
<!ENTITY unraid_podman_txz_md5 "00000000000000000000000000000000">
|
||||||
|
]>
|
||||||
|
|
||||||
|
<PLUGIN name="&name;"
|
||||||
|
author="&author;"
|
||||||
|
version="&version;"
|
||||||
|
launch="&launch;"
|
||||||
|
pluginURL="&pluginURL;"
|
||||||
|
support="&supportURL;"
|
||||||
|
icon="cubes"
|
||||||
|
min="6.12.0">
|
||||||
|
<!--
|
||||||
|
min="6.12.0": Unraid 6.12 introduced folder-based (non-loopback) Docker
|
||||||
|
storage and is a reasonably conservative baseline for the cgroup v2 /
|
||||||
|
kernel networking features netavark, aardvark-dns, and passt assume. See
|
||||||
|
docs/ARCHITECTURE.md section 21 (open question — revisit if real-world
|
||||||
|
testing shows an earlier or later minimum is actually required).
|
||||||
|
-->
|
||||||
|
|
||||||
|
<CHANGES>
|
||||||
|
##podman
|
||||||
|
|
||||||
|
###0.0.0
|
||||||
|
- Initial scaffolding. Not a functional release — see CHANGELOG.md for the
|
||||||
|
authoritative, up-to-date history; this block is kept in sync with it by
|
||||||
|
hand at release time (scripts/release.sh does not touch this section).
|
||||||
|
</CHANGES>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Pre-install sanity check: this project only builds/ships x86_64 packages
|
||||||
|
(see versions.env's PKG_ARCH) — fail with a clear message on any other
|
||||||
|
architecture rather than letting eight package downloads 404 one by one.
|
||||||
|
-->
|
||||||
|
<FILE Run="/bin/bash">
|
||||||
|
<INLINE>
|
||||||
|
if [ "$(uname -m)" != "x86_64" ]; then
|
||||||
|
echo "unraid-podman only supports x86_64 (detected: $(uname -m)) - aborting install."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
</INLINE>
|
||||||
|
</FILE>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
The seven upstream component packages. Each is downloaded straight into
|
||||||
|
its backup slot under /boot/config/plugins/&name;/backup/packages/&version;/
|
||||||
|
(grouped by PLUGIN version, not each component's own version — a rollback
|
||||||
|
targets "this plugin release" as one unit, see
|
||||||
|
plugin/sbin/podman-backup.sh's header comment) and installed from there
|
||||||
|
in place via upgradepkg's install new / reinstall flags, which handle
|
||||||
|
both a fresh install and an update to a newer version identically (see
|
||||||
|
file header comment). This download-destination-doubles-as-backup approach is
|
||||||
|
what makes plugin/sbin/podman-backup.sh's restore-packages command work
|
||||||
|
with no separate copy step.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<FILE Name="/boot/config/plugins/&name;/backup/packages/&version;/&podman_txz_file;" Run="upgradepkg --install-new --reinstall">
|
||||||
|
<URL>
|
||||||
|
&baseURL;/&podman_txz_file;
|
||||||
|
</URL>
|
||||||
|
<MD5>
|
||||||
|
&podman_txz_md5;
|
||||||
|
</MD5>
|
||||||
|
</FILE>
|
||||||
|
|
||||||
|
<FILE Name="/boot/config/plugins/&name;/backup/packages/&version;/&conmon_txz_file;" Run="upgradepkg --install-new --reinstall">
|
||||||
|
<URL>
|
||||||
|
&baseURL;/&conmon_txz_file;
|
||||||
|
</URL>
|
||||||
|
<MD5>
|
||||||
|
&conmon_txz_md5;
|
||||||
|
</MD5>
|
||||||
|
</FILE>
|
||||||
|
|
||||||
|
<FILE Name="/boot/config/plugins/&name;/backup/packages/&version;/&crun_txz_file;" Run="upgradepkg --install-new --reinstall">
|
||||||
|
<URL>
|
||||||
|
&baseURL;/&crun_txz_file;
|
||||||
|
</URL>
|
||||||
|
<MD5>
|
||||||
|
&crun_txz_md5;
|
||||||
|
</MD5>
|
||||||
|
</FILE>
|
||||||
|
|
||||||
|
<FILE Name="/boot/config/plugins/&name;/backup/packages/&version;/&netavark_txz_file;" Run="upgradepkg --install-new --reinstall">
|
||||||
|
<URL>
|
||||||
|
&baseURL;/&netavark_txz_file;
|
||||||
|
</URL>
|
||||||
|
<MD5>
|
||||||
|
&netavark_txz_md5;
|
||||||
|
</MD5>
|
||||||
|
</FILE>
|
||||||
|
|
||||||
|
<FILE Name="/boot/config/plugins/&name;/backup/packages/&version;/&aardvark_dns_txz_file;" Run="upgradepkg --install-new --reinstall">
|
||||||
|
<URL>
|
||||||
|
&baseURL;/&aardvark_dns_txz_file;
|
||||||
|
</URL>
|
||||||
|
<MD5>
|
||||||
|
&aardvark_dns_txz_md5;
|
||||||
|
</MD5>
|
||||||
|
</FILE>
|
||||||
|
|
||||||
|
<FILE Name="/boot/config/plugins/&name;/backup/packages/&version;/&passt_txz_file;" Run="upgradepkg --install-new --reinstall">
|
||||||
|
<URL>
|
||||||
|
&baseURL;/&passt_txz_file;
|
||||||
|
</URL>
|
||||||
|
<MD5>
|
||||||
|
&passt_txz_md5;
|
||||||
|
</MD5>
|
||||||
|
</FILE>
|
||||||
|
|
||||||
|
<FILE Name="/boot/config/plugins/&name;/backup/packages/&version;/&fuse_overlayfs_txz_file;" Run="upgradepkg --install-new --reinstall">
|
||||||
|
<URL>
|
||||||
|
&baseURL;/&fuse_overlayfs_txz_file;
|
||||||
|
</URL>
|
||||||
|
<MD5>
|
||||||
|
&fuse_overlayfs_txz_md5;
|
||||||
|
</MD5>
|
||||||
|
</FILE>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
The plugin's own scaffolding package — rc.podman, sbin/ helper scripts,
|
||||||
|
the event/ hooks below, and default config templates. See
|
||||||
|
packages/unraid-podman/README.md.
|
||||||
|
-->
|
||||||
|
<FILE Name="/boot/config/plugins/&name;/backup/packages/&version;/&unraid_podman_txz_file;" Run="upgradepkg --install-new --reinstall">
|
||||||
|
<URL>
|
||||||
|
&baseURL;/&unraid_podman_txz_file;
|
||||||
|
</URL>
|
||||||
|
<MD5>
|
||||||
|
&unraid_podman_txz_md5;
|
||||||
|
</MD5>
|
||||||
|
</FILE>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Postinstall: everything that has to happen AFTER the eight packages above
|
||||||
|
are on disk, but isn't itself package content — directory/config
|
||||||
|
seeding, the install-manifest that plugin/sbin/podman-verify-packages.sh
|
||||||
|
and podman-update-packages.sh read, and the first start. Runs on both
|
||||||
|
fresh install and every update (Unraid re-runs the whole .plg either way)
|
||||||
|
— every step here is written to be idempotent, per
|
||||||
|
docs/ARCHITECTURE.md section 7.
|
||||||
|
-->
|
||||||
|
<!--
|
||||||
|
IMPORTANT for anyone editing this block: it is deliberately NOT wrapped
|
||||||
|
in <![CDATA[ ]]>. CDATA sections suppress XML entity expansion entirely,
|
||||||
|
which would leave literal, unexpanded text like "&version;" in the
|
||||||
|
generated installed-versions.env instead of the real version number —
|
||||||
|
confirmed by direct testing against this exact file. This is also why
|
||||||
|
real Unraid plugins (community.applications, unassigned.devices) never
|
||||||
|
wrap their entity-referencing INLINE scripts in CDATA either. The
|
||||||
|
practical consequence: avoid literal '<' in this script (heredocs
|
||||||
|
included) — a sequence of individual "echo >>" lines is used below
|
||||||
|
instead for exactly that reason. A lone '>' is fine unescaped in XML
|
||||||
|
content (only '<' and '&' are not) — verified empirically, not assumed.
|
||||||
|
-->
|
||||||
|
<FILE Run="/bin/bash">
|
||||||
|
<INLINE>
|
||||||
|
set -u
|
||||||
|
|
||||||
|
echo "Setting permissions..."
|
||||||
|
chmod 0755 /etc/rc.d/rc.podman
|
||||||
|
chmod 0755 /usr/local/sbin/podman-*.sh
|
||||||
|
chmod 0755 /usr/local/emhttp/plugins/podman/event/disks_mounted
|
||||||
|
chmod 0755 /usr/local/emhttp/plugins/podman/event/stopping
|
||||||
|
|
||||||
|
echo "Writing installed-package manifest..."
|
||||||
|
mkdir -p /usr/local/share/unraid-podman
|
||||||
|
MANIFEST=/usr/local/share/unraid-podman/installed-versions.env
|
||||||
|
echo "# Generated by podman.plg postinstall - do not edit by hand." > "$MANIFEST"
|
||||||
|
echo "# Read by podman-verify-packages.sh and podman-update-packages.sh." >> "$MANIFEST"
|
||||||
|
echo "PLUGIN_VERSION=\"&version;\"" >> "$MANIFEST"
|
||||||
|
echo "PODMAN_INSTALLED_VERSION=\"&podman_txz_version;\"" >> "$MANIFEST"
|
||||||
|
echo "CONMON_INSTALLED_VERSION=\"&conmon_txz_version;\"" >> "$MANIFEST"
|
||||||
|
echo "CRUN_INSTALLED_VERSION=\"&crun_txz_version;\"" >> "$MANIFEST"
|
||||||
|
echo "NETAVARK_INSTALLED_VERSION=\"&netavark_txz_version;\"" >> "$MANIFEST"
|
||||||
|
echo "AARDVARK_DNS_INSTALLED_VERSION=\"&aardvark_dns_txz_version;\"" >> "$MANIFEST"
|
||||||
|
echo "PASST_INSTALLED_VERSION=\"&passt_txz_version;\"" >> "$MANIFEST"
|
||||||
|
echo "FUSE_OVERLAYFS_INSTALLED_VERSION=\"&fuse_overlayfs_txz_version;\"" >> "$MANIFEST"
|
||||||
|
echo "UNRAID_PODMAN_INSTALLED_VERSION=\"&unraid_podman_txz_version;\"" >> "$MANIFEST"
|
||||||
|
|
||||||
|
echo "Seeding /boot/config/plugins/podman/ configuration (existing files left untouched)..."
|
||||||
|
/usr/local/sbin/podman-config.sh seed
|
||||||
|
|
||||||
|
echo "Verifying package installation..."
|
||||||
|
if /usr/local/sbin/podman-verify-packages.sh --quiet; then
|
||||||
|
echo "Package verification OK."
|
||||||
|
else
|
||||||
|
echo "WARNING: package verification reported problems - see /boot/config/plugins/podman/plugin.log"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Starting podman..."
|
||||||
|
if /etc/rc.d/rc.podman start; then
|
||||||
|
echo ""
|
||||||
|
echo "----------------------------------------------------"
|
||||||
|
echo " unraid-podman &version; has been installed and started."
|
||||||
|
echo " CLI: podman --url unix:///var/run/podman/podman.sock ..."
|
||||||
|
echo " Docs: https://github.com/&github;"
|
||||||
|
echo "----------------------------------------------------"
|
||||||
|
echo ""
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo "----------------------------------------------------"
|
||||||
|
echo " unraid-podman &version; was installed but did not start cleanly."
|
||||||
|
echo " It will be retried automatically the next time the array starts"
|
||||||
|
echo " (see /usr/local/emhttp/plugins/podman/event/disks_mounted)."
|
||||||
|
echo " Check /boot/config/plugins/podman/plugin.log for details."
|
||||||
|
echo "----------------------------------------------------"
|
||||||
|
echo ""
|
||||||
|
fi
|
||||||
|
</INLINE>
|
||||||
|
</FILE>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Uninstall. Triggered by Unraid's Plugin Manager when the user clicks
|
||||||
|
"Remove" — the Method="remove" attribute is what makes this block
|
||||||
|
(rather than any of the install blocks above) run in that case.
|
||||||
|
|
||||||
|
Order matters: podman-uninstall-cleanup.sh runs FIRST, while the
|
||||||
|
unraid-podman package (which contains it, at /usr/local/sbin/) is still
|
||||||
|
on disk — removepkg-ing it out from under itself mid-script would be a
|
||||||
|
mistake. See that script's own header comment for exactly what is and
|
||||||
|
is not removed by default (user data is preserved unless the purge-data
|
||||||
|
flag is passed, which this normal removal flow deliberately never does).
|
||||||
|
-->
|
||||||
|
<!-- Not CDATA-wrapped — see the comment above the postinstall block for why: this content needs its &..._txz_file; entity references expanded, and CDATA would suppress that. Nothing here needs an unescaped '<'. -->
|
||||||
|
<FILE Run="/bin/bash" Method="remove">
|
||||||
|
<INLINE>
|
||||||
|
echo "Running unraid-podman cleanup..."
|
||||||
|
/usr/local/sbin/podman-uninstall-cleanup.sh
|
||||||
|
|
||||||
|
echo "Removing packages..."
|
||||||
|
removepkg &podman_txz_file;
|
||||||
|
removepkg &conmon_txz_file;
|
||||||
|
removepkg &crun_txz_file;
|
||||||
|
removepkg &netavark_txz_file;
|
||||||
|
removepkg &aardvark_dns_txz_file;
|
||||||
|
removepkg &passt_txz_file;
|
||||||
|
removepkg &fuse_overlayfs_txz_file;
|
||||||
|
removepkg &unraid_podman_txz_file;
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "unraid-podman removed. Your configuration, backups, and container"
|
||||||
|
echo "storage under /boot/config/plugins/podman/ and the configured"
|
||||||
|
echo "STORAGE_PATH were left in place - see docs/INSTALL.md if you want"
|
||||||
|
echo "to remove them too."
|
||||||
|
echo ""
|
||||||
|
</INLINE>
|
||||||
|
</FILE>
|
||||||
|
|
||||||
|
</PLUGIN>
|
||||||
Executable
+244
@@ -0,0 +1,244 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# plugin/rc.d/rc.podman
|
||||||
|
#
|
||||||
|
# Init script for unraid-podman, in the classic Slackware BSD
|
||||||
|
# start|stop|restart|status style — Unraid has no systemd, so this script
|
||||||
|
# alone owns the entire process lifecycle (see docs/ARCHITECTURE.md
|
||||||
|
# section 6, "Start-/Stop-Skripte", and the top-level "Rahmenbedingungen"
|
||||||
|
# table on why that's the case).
|
||||||
|
#
|
||||||
|
# Staged by plugin/podman.plg to /etc/rc.d/rc.podman. Its boot-time
|
||||||
|
# invocation is registered (also by podman.plg) in /boot/config/go, gated on
|
||||||
|
# Unraid's "array started" event rather than running unconditionally at
|
||||||
|
# boot — the configured storage path (cache pool/disk) is not guaranteed to
|
||||||
|
# be available before that point. See ARCHITECTURE.md section 6.2.
|
||||||
|
#
|
||||||
|
# The individual responsibilities below are deliberately split into small,
|
||||||
|
# single-purpose scripts under /usr/local/sbin/ (staged from plugin/sbin/)
|
||||||
|
# rather than inlined here — see each script's own header comment for why
|
||||||
|
# it's separate:
|
||||||
|
# podman-preflight.sh startup validation
|
||||||
|
# podman-config.sh config seeding + sync
|
||||||
|
# podman-storage.sh podman.img create/mount/unmount
|
||||||
|
# podman-autostart.sh autostart chain
|
||||||
|
# podman-backup.sh config/package snapshots for rollback
|
||||||
|
# podman-verify-packages.sh package integrity checks
|
||||||
|
# podman-update-packages.sh package reconciliation/update
|
||||||
|
# podman-uninstall-cleanup.sh called from podman.plg's removepkg block
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
SBIN_DIR="/usr/local/sbin"
|
||||||
|
# shellcheck source=../sbin/podman-common.sh
|
||||||
|
. "$SBIN_DIR/podman-common.sh"
|
||||||
|
|
||||||
|
podman_load_cfg
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# podman_start
|
||||||
|
#
|
||||||
|
# See file header for the full sequence. Every step is expected to be
|
||||||
|
# idempotent (safe to re-run `rc.podman start` against an already-running
|
||||||
|
# instance without breaking anything) — preflight and podman-storage.sh
|
||||||
|
# both already implement this on their own, but we also short-circuit here
|
||||||
|
# if the service is clearly already up, to avoid doing redundant work.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
podman_start() {
|
||||||
|
if [ "${PODMAN_ENABLED:-yes}" != "yes" ]; then
|
||||||
|
podman_log "start: PODMAN_ENABLED is not 'yes' in podman.cfg, not starting"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -S "$PODMAN_SOCKET" ] && [ -f "$PODMAN_SERVICE_PID_FILE" ] \
|
||||||
|
&& kill -0 "$(cat "$PODMAN_SERVICE_PID_FILE" 2> /dev/null)" 2> /dev/null; then
|
||||||
|
podman_log "start: already running (pid $(cat "$PODMAN_SERVICE_PID_FILE"))"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
podman_log "start: beginning startup sequence"
|
||||||
|
|
||||||
|
# 1. Preflight — aborts loudly (and already notified) on failure.
|
||||||
|
if ! "$SBIN_DIR/podman-preflight.sh"; then
|
||||||
|
podman_log_error "start: preflight checks failed, aborting"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 2. Ensure config exists (no-op if already seeded) and sync it to the
|
||||||
|
# RAM-root /etc/containers/ — see podman-config.sh.
|
||||||
|
"$SBIN_DIR/podman-config.sh" seed
|
||||||
|
if ! "$SBIN_DIR/podman-config.sh" sync; then
|
||||||
|
podman_log_error "start: config sync failed, aborting"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 3. Storage: create the image if this is a first start, then mount it.
|
||||||
|
if ! "$SBIN_DIR/podman-storage.sh" create; then
|
||||||
|
podman_log_error "start: storage creation failed, aborting"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
if ! "$SBIN_DIR/podman-storage.sh" mount; then
|
||||||
|
podman_log_error "start: storage mount failed, aborting"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. Restore persisted netavark network definitions (see
|
||||||
|
# docs/ARCHITECTURE.md section 8, Netzwerke) from the boot-persistent
|
||||||
|
# copy into the RAM-root config directory netavark reads from.
|
||||||
|
mkdir -p "$PODMAN_ETC_DIR/networks"
|
||||||
|
if [ -d "$PODMAN_NETWORKS_BOOT_DIR" ] && [ -n "$(ls -A "$PODMAN_NETWORKS_BOOT_DIR" 2> /dev/null)" ]; then
|
||||||
|
cp -a "$PODMAN_NETWORKS_BOOT_DIR"/. "$PODMAN_ETC_DIR/networks/"
|
||||||
|
podman_log "start: restored custom network definitions"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 5. Start the Podman API service, rootful, on a unix socket — see
|
||||||
|
# docs/ARCHITECTURE.md section 6.1 for why this runs as a persistent
|
||||||
|
# service rather than being started fresh per CLI/WebUI call:
|
||||||
|
# a shared process gives consistent state and event streaming.
|
||||||
|
# --time=0 disables the idle-shutdown timeout (this is a long-running
|
||||||
|
# daemon under our process management, not an on-demand activation).
|
||||||
|
mkdir -p "$PODMAN_RUN_DIR"
|
||||||
|
podman_log "start: starting podman system service on unix://$PODMAN_SOCKET"
|
||||||
|
nohup podman system service --time=0 "unix://$PODMAN_SOCKET" \
|
||||||
|
> "$PODMAN_LOG_DIR/podman-service.log" 2>&1 &
|
||||||
|
local service_pid=$!
|
||||||
|
echo "$service_pid" > "$PODMAN_SERVICE_PID_FILE"
|
||||||
|
|
||||||
|
# Wait for the socket to actually appear rather than assuming the fork
|
||||||
|
# succeeded instantly — up to 15s, polled every 200ms.
|
||||||
|
local waited=0
|
||||||
|
while [ ! -S "$PODMAN_SOCKET" ] && [ "$waited" -lt 15000 ]; do
|
||||||
|
sleep 0.2
|
||||||
|
waited=$((waited + 200))
|
||||||
|
if ! kill -0 "$service_pid" 2> /dev/null; then
|
||||||
|
podman_log_error "start: podman system service exited immediately — see $PODMAN_LOG_DIR/podman-service.log"
|
||||||
|
"$SBIN_DIR/podman-storage.sh" unmount || true
|
||||||
|
podman_notify "Podman failed to start" \
|
||||||
|
"podman system service exited immediately. See $PODMAN_LOG_DIR/podman-service.log." \
|
||||||
|
"alert"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ ! -S "$PODMAN_SOCKET" ]; then
|
||||||
|
podman_log_error "start: timed out waiting for $PODMAN_SOCKET to appear"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
podman_log "start: podman system service is up (pid $service_pid)"
|
||||||
|
|
||||||
|
# 6. Autostart. A failure here is logged/notified by the script itself
|
||||||
|
# and does not abort rc.podman start — the service is already usable.
|
||||||
|
"$SBIN_DIR/podman-autostart.sh" || podman_log_error "start: autostart chain reported errors (see above)"
|
||||||
|
|
||||||
|
echo "running" > "$PODMAN_STATUS_FILE"
|
||||||
|
podman_log "start: startup sequence complete"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# podman_stop
|
||||||
|
#
|
||||||
|
# Stops containers first (each with its own STOP_TIMEOUT grace period),
|
||||||
|
# then the API service, then unmounts storage — the reverse of start, so
|
||||||
|
# nothing is torn down out from under something still using it.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
podman_stop() {
|
||||||
|
if [ ! -S "$PODMAN_SOCKET" ]; then
|
||||||
|
podman_log "stop: not running (no socket at $PODMAN_SOCKET)"
|
||||||
|
# Still attempt an unmount in case a prior stop was interrupted after
|
||||||
|
# the service went down but before the unmount completed.
|
||||||
|
"$SBIN_DIR/podman-storage.sh" unmount || true
|
||||||
|
rm -f "$PODMAN_STATUS_FILE"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
podman_log "stop: stopping running containers (timeout ${STOP_TIMEOUT:-10}s each)"
|
||||||
|
local running_ids
|
||||||
|
running_ids=$(podman --url "unix://$PODMAN_SOCKET" ps -q 2> /dev/null || true)
|
||||||
|
if [ -n "$running_ids" ]; then
|
||||||
|
local id
|
||||||
|
for id in $running_ids; do
|
||||||
|
podman --url "unix://$PODMAN_SOCKET" stop -t "${STOP_TIMEOUT:-10}" "$id" \
|
||||||
|
> /dev/null 2>&1 \
|
||||||
|
|| podman_log_error "stop: failed to stop container $id within timeout"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
podman_log "stop: stopping podman system service"
|
||||||
|
if [ -f "$PODMAN_SERVICE_PID_FILE" ]; then
|
||||||
|
local pid
|
||||||
|
pid=$(cat "$PODMAN_SERVICE_PID_FILE")
|
||||||
|
if kill -0 "$pid" 2> /dev/null; then
|
||||||
|
kill "$pid" 2> /dev/null || true
|
||||||
|
local waited=0
|
||||||
|
while kill -0 "$pid" 2> /dev/null && [ "$waited" -lt 10 ]; do
|
||||||
|
sleep 1
|
||||||
|
waited=$((waited + 1))
|
||||||
|
done
|
||||||
|
kill -0 "$pid" 2> /dev/null && kill -9 "$pid" 2> /dev/null || true
|
||||||
|
fi
|
||||||
|
rm -f "$PODMAN_SERVICE_PID_FILE"
|
||||||
|
fi
|
||||||
|
rm -f "$PODMAN_SOCKET"
|
||||||
|
|
||||||
|
"$SBIN_DIR/podman-storage.sh" unmount || podman_log_error "stop: storage unmount failed"
|
||||||
|
|
||||||
|
rm -f "$PODMAN_STATUS_FILE"
|
||||||
|
podman_log "stop: stopped"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# podman_status
|
||||||
|
#
|
||||||
|
# Human-readable health summary — see docs/ARCHITECTURE.md section 16.2.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
podman_status() {
|
||||||
|
echo "PODMAN_ENABLED: ${PODMAN_ENABLED:-yes}"
|
||||||
|
|
||||||
|
if [ -S "$PODMAN_SOCKET" ] && [ -f "$PODMAN_SERVICE_PID_FILE" ] \
|
||||||
|
&& kill -0 "$(cat "$PODMAN_SERVICE_PID_FILE" 2> /dev/null)" 2> /dev/null; then
|
||||||
|
echo "service: running (pid $(cat "$PODMAN_SERVICE_PID_FILE"), socket $PODMAN_SOCKET)"
|
||||||
|
else
|
||||||
|
echo "service: stopped"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
"$SBIN_DIR/podman-storage.sh" status
|
||||||
|
|
||||||
|
echo
|
||||||
|
if [ -f "$PODMAN_AUTOSTART_FILE" ]; then
|
||||||
|
local count
|
||||||
|
count=$(grep -vcE '^\s*(#|$)' "$PODMAN_AUTOSTART_FILE" 2> /dev/null || echo 0)
|
||||||
|
echo "autostart entries: $count (see $PODMAN_AUTOSTART_FILE)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -S "$PODMAN_SOCKET" ]; then
|
||||||
|
echo
|
||||||
|
echo "podman info:"
|
||||||
|
podman --url "unix://$PODMAN_SOCKET" info --format \
|
||||||
|
' containers: {{.Store.ContainerStore.Number}} images: {{.Store.ImageStore.Number}}' \
|
||||||
|
2> /dev/null || echo " (failed to query — service may still be initializing)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
start)
|
||||||
|
podman_start
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
podman_stop
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
podman_stop
|
||||||
|
podman_start
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
podman_status
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "usage: $0 {start|stop|restart|status}"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
Executable
+172
@@ -0,0 +1,172 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# plugin/sbin/podman-autostart.sh
|
||||||
|
#
|
||||||
|
# "Autostart einrichten" — starts containers listed in
|
||||||
|
# /boot/config/plugins/podman/autostart, in file order, via the running
|
||||||
|
# Podman API service. Run by `rc.podman start` after the service is up.
|
||||||
|
# See docs/ARCHITECTURE.md section 12 (Autostart).
|
||||||
|
#
|
||||||
|
# Design constraints from the architecture doc, both implemented below:
|
||||||
|
# - A single container failing to start must NOT abort the rest of the
|
||||||
|
# chain (one broken container shouldn't take down everything else).
|
||||||
|
# - After repeated consecutive failures, a container is automatically
|
||||||
|
# paused out of the autostart chain ("Safe-Mode pro Container") so a
|
||||||
|
# persistently crash-looping container doesn't waste boot time or
|
||||||
|
# resources forever — with a GUI notification explaining why.
|
||||||
|
#
|
||||||
|
# File formats:
|
||||||
|
# autostart one container name per line; '#' starts a comment;
|
||||||
|
# blank lines ignored. Order = start order.
|
||||||
|
# autostart-delay optional "<container-name>=<seconds>" lines — sleep
|
||||||
|
# that many seconds AFTER starting that container
|
||||||
|
# before moving to the next (for startup dependencies,
|
||||||
|
# e.g. a database before the app that needs it).
|
||||||
|
#
|
||||||
|
# Failure tracking: a small per-container counter file under
|
||||||
|
# $PODMAN_BOOT_DIR/autostart-failures/<name> holds the consecutive-failure
|
||||||
|
# count. Reset to 0 on any successful start.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=./podman-common.sh
|
||||||
|
. "$SCRIPT_DIR/podman-common.sh"
|
||||||
|
|
||||||
|
podman_load_cfg
|
||||||
|
|
||||||
|
# After this many consecutive failed autostart attempts, a container is
|
||||||
|
# skipped and flagged rather than retried again on the next boot, until a
|
||||||
|
# human clears it (see clear_failure_flag below).
|
||||||
|
MAX_CONSECUTIVE_FAILURES=3
|
||||||
|
|
||||||
|
FAILURES_DIR="$PODMAN_BOOT_DIR/autostart-failures"
|
||||||
|
mkdir -p "$FAILURES_DIR"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# get_delay <container-name>
|
||||||
|
#
|
||||||
|
# Looks up an optional post-start delay for a container from
|
||||||
|
# autostart-delay ("name=seconds" lines). Returns 0 if not configured.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
get_delay() {
|
||||||
|
local name="$1"
|
||||||
|
if [ -f "$PODMAN_AUTOSTART_DELAY_FILE" ]; then
|
||||||
|
awk -F= -v n="$name" '$1 == n { print $2; found=1 } END { if (!found) print 0 }' \
|
||||||
|
"$PODMAN_AUTOSTART_DELAY_FILE"
|
||||||
|
else
|
||||||
|
echo 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
failure_count() {
|
||||||
|
local name="$1"
|
||||||
|
local f="$FAILURES_DIR/$name"
|
||||||
|
[ -f "$f" ] && cat "$f" || echo 0
|
||||||
|
}
|
||||||
|
|
||||||
|
record_failure() {
|
||||||
|
local name="$1"
|
||||||
|
local count
|
||||||
|
count=$(($(failure_count "$name") + 1))
|
||||||
|
echo "$count" > "$FAILURES_DIR/$name"
|
||||||
|
echo "$count"
|
||||||
|
}
|
||||||
|
|
||||||
|
clear_failure_flag() {
|
||||||
|
local name="$1"
|
||||||
|
rm -f "$FAILURES_DIR/$name"
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# start_one <container-name>
|
||||||
|
#
|
||||||
|
# Starts a single container via `podman start`, which talks to the already
|
||||||
|
# running API service over $PODMAN_SOCKET rather than spawning an unrelated
|
||||||
|
# podman process tree — see docs/ARCHITECTURE.md section 11 (Container).
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
start_one() {
|
||||||
|
local name="$1"
|
||||||
|
|
||||||
|
local prior_failures
|
||||||
|
prior_failures=$(failure_count "$name")
|
||||||
|
if [ "$prior_failures" -ge "$MAX_CONSECUTIVE_FAILURES" ]; then
|
||||||
|
podman_log_error "autostart: skipping '$name' — $prior_failures consecutive prior failures (Safe-Mode). Remove $FAILURES_DIR/$name to re-enable."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
podman_log "autostart: starting '$name'"
|
||||||
|
if podman --url "unix://$PODMAN_SOCKET" start "$name" > /tmp/podman-autostart-"$name".log 2>&1; then
|
||||||
|
clear_failure_flag "$name"
|
||||||
|
podman_log "autostart: '$name' started successfully"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local new_count
|
||||||
|
new_count=$(record_failure "$name")
|
||||||
|
podman_log_error "autostart: '$name' failed to start (attempt $new_count/$MAX_CONSECUTIVE_FAILURES) — see /tmp/podman-autostart-$name.log"
|
||||||
|
|
||||||
|
if [ "$new_count" -ge "$MAX_CONSECUTIVE_FAILURES" ]; then
|
||||||
|
podman_notify "Podman container disabled from autostart" \
|
||||||
|
"'$name' failed to start $new_count times in a row and has been paused from autostart. Fix the underlying issue, then remove $FAILURES_DIR/$name to re-enable." \
|
||||||
|
"warning"
|
||||||
|
fi
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# main
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
if [ ! -f "$PODMAN_AUTOSTART_FILE" ]; then
|
||||||
|
podman_log "autostart: no autostart file at $PODMAN_AUTOSTART_FILE, nothing to do"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -S "$PODMAN_SOCKET" ]; then
|
||||||
|
podman_log_error "autostart: podman API socket ($PODMAN_SOCKET) not present — is the service running?"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
started=0
|
||||||
|
skipped=0
|
||||||
|
failed=0
|
||||||
|
|
||||||
|
while IFS= read -r raw_line || [ -n "$raw_line" ]; do
|
||||||
|
# Strip comments and surrounding whitespace; skip blank lines.
|
||||||
|
line="${raw_line%%#*}"
|
||||||
|
line="$(echo "$line" | xargs || true)"
|
||||||
|
[ -z "$line" ] && continue
|
||||||
|
|
||||||
|
if start_one "$line"; then
|
||||||
|
started=$((started + 1))
|
||||||
|
else
|
||||||
|
prior=$(failure_count "$line")
|
||||||
|
if [ "$prior" -ge "$MAX_CONSECUTIVE_FAILURES" ]; then
|
||||||
|
skipped=$((skipped + 1))
|
||||||
|
else
|
||||||
|
failed=$((failed + 1))
|
||||||
|
fi
|
||||||
|
# A single container's failure does not abort the loop — see file
|
||||||
|
# header. Continue to the next entry.
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
delay=$(get_delay "$line")
|
||||||
|
if [ "$delay" -gt 0 ] 2> /dev/null; then
|
||||||
|
podman_log "autostart: waiting ${delay}s after '$line' (configured dependency delay)"
|
||||||
|
sleep "$delay"
|
||||||
|
fi
|
||||||
|
done < "$PODMAN_AUTOSTART_FILE"
|
||||||
|
|
||||||
|
podman_log "autostart: complete (started=$started failed=$failed skipped-safe-mode=$skipped)"
|
||||||
|
|
||||||
|
# Exit non-zero only if EVERY entry failed outright (as opposed to a mix,
|
||||||
|
# or entries already in Safe-Mode) — rc.podman treats that as worth
|
||||||
|
# flagging loudly, whereas a partial failure is already individually
|
||||||
|
# notified above.
|
||||||
|
if [ "$started" -eq 0 ] && [ "$failed" -gt 0 ]; then
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
Executable
+168
@@ -0,0 +1,168 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# plugin/sbin/podman-backup.sh
|
||||||
|
#
|
||||||
|
# Rollback support — see docs/ARCHITECTURE.md section 14 (Rollback). Two
|
||||||
|
# independent kinds of backup, matching the two kinds of thing an update can
|
||||||
|
# break:
|
||||||
|
#
|
||||||
|
# PACKAGES plugin/podman.plg downloads each release's .txz files
|
||||||
|
# straight into $PODMAN_BOOT_DIR/backup/packages/<plugin-version>/
|
||||||
|
# — all 7 component packages of one plugin release share a
|
||||||
|
# single directory named after the PLUGIN version (not each
|
||||||
|
# component's own version), since a rollback means "go back to
|
||||||
|
# plugin release X" as one unit — rather than into a scratch
|
||||||
|
# dir that gets discarded, so the previously installed
|
||||||
|
# version's packages are automatically still on disk after an
|
||||||
|
# update, with no separate copy step. `restore-packages
|
||||||
|
# <plugin-version>` reinstalls every package from one of those
|
||||||
|
# directories.
|
||||||
|
#
|
||||||
|
# CONFIG Before a config schema migration (see podman.cfg's
|
||||||
|
# CONFIG_SCHEMA_VERSION), the plg's postinstall calls
|
||||||
|
# `snapshot-config` to copy the current *.conf + podman.cfg
|
||||||
|
# into $PODMAN_BOOT_DIR/backup/config/<timestamp>/.
|
||||||
|
# `restore-config <timestamp>` copies them back.
|
||||||
|
#
|
||||||
|
# `prune` bounds how many old snapshots of each kind are kept, since flash
|
||||||
|
# space is limited (see ARCHITECTURE.md section 14, "Rollback-Historie
|
||||||
|
# begrenzen").
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# podman-backup.sh snapshot-config
|
||||||
|
# podman-backup.sh restore-config <timestamp>
|
||||||
|
# podman-backup.sh restore-packages <version>
|
||||||
|
# podman-backup.sh list
|
||||||
|
# podman-backup.sh prune [--keep N]
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=./podman-common.sh
|
||||||
|
. "$SCRIPT_DIR/podman-common.sh"
|
||||||
|
|
||||||
|
DEFAULT_KEEP=3
|
||||||
|
|
||||||
|
cmd_snapshot_config() {
|
||||||
|
local ts
|
||||||
|
ts=$(date -u +%Y%m%dT%H%M%SZ)
|
||||||
|
local dest="$PODMAN_BACKUP_DIR/config/$ts"
|
||||||
|
mkdir -p "$dest"
|
||||||
|
|
||||||
|
local f
|
||||||
|
for f in podman.cfg containers.conf storage.conf registries.conf policy.json; do
|
||||||
|
[ -f "$PODMAN_BOOT_DIR/$f" ] && cp "$PODMAN_BOOT_DIR/$f" "$dest/"
|
||||||
|
done
|
||||||
|
|
||||||
|
podman_log "backup: config snapshot saved to $dest"
|
||||||
|
echo "$ts"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_restore_config() {
|
||||||
|
local ts="${1:?usage: podman-backup.sh restore-config <timestamp>}"
|
||||||
|
local src="$PODMAN_BACKUP_DIR/config/$ts"
|
||||||
|
|
||||||
|
if [ ! -d "$src" ]; then
|
||||||
|
podman_log_error "backup: no config snapshot found at $src"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local f
|
||||||
|
for f in "$src"/*; do
|
||||||
|
[ -f "$f" ] || continue
|
||||||
|
cp "$f" "$PODMAN_BOOT_DIR/$(basename "$f")"
|
||||||
|
done
|
||||||
|
|
||||||
|
podman_log "backup: config restored from snapshot $ts — restart podman (rc.podman restart) to apply"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_restore_packages() {
|
||||||
|
local version="${1:?usage: podman-backup.sh restore-packages <version>}"
|
||||||
|
local src="$PODMAN_BACKUP_DIR/packages/$version"
|
||||||
|
|
||||||
|
if [ ! -d "$src" ]; then
|
||||||
|
podman_log_error "backup: no package backup found for version $version at $src"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
podman_require_command upgradepkg
|
||||||
|
|
||||||
|
local txz
|
||||||
|
local restored=0
|
||||||
|
for txz in "$src"/*.txz; do
|
||||||
|
[ -f "$txz" ] || continue
|
||||||
|
podman_log "backup: reinstalling $(basename "$txz") from backup (version $version)"
|
||||||
|
upgradepkg --reinstall --install-new "$txz"
|
||||||
|
restored=$((restored + 1))
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$restored" -eq 0 ]; then
|
||||||
|
podman_log_error "backup: no .txz files found in $src"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
podman_notify "Podman packages rolled back" \
|
||||||
|
"Reinstalled $restored package(s) from the version $version backup. Run 'rc.podman restart' to apply." \
|
||||||
|
"warning"
|
||||||
|
podman_log "backup: package rollback to version $version complete ($restored package(s))"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_list() {
|
||||||
|
echo "Config snapshots ($PODMAN_BACKUP_DIR/config/):"
|
||||||
|
if [ -d "$PODMAN_BACKUP_DIR/config" ]; then
|
||||||
|
find "$PODMAN_BACKUP_DIR/config" -mindepth 1 -maxdepth 1 -type d -printf ' %f\n' 2> /dev/null | sort -r
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
echo "Package backups ($PODMAN_BACKUP_DIR/packages/):"
|
||||||
|
if [ -d "$PODMAN_BACKUP_DIR/packages" ]; then
|
||||||
|
find "$PODMAN_BACKUP_DIR/packages" -mindepth 1 -maxdepth 1 -type d -printf ' %f\n' 2> /dev/null | sort -r
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_prune() {
|
||||||
|
local keep="$DEFAULT_KEEP"
|
||||||
|
if [ "${1:-}" = "--keep" ] && [ -n "${2:-}" ]; then
|
||||||
|
keep="$2"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local kind
|
||||||
|
for kind in config packages; do
|
||||||
|
local dir="$PODMAN_BACKUP_DIR/$kind"
|
||||||
|
[ -d "$dir" ] || continue
|
||||||
|
|
||||||
|
# Sort newest-first by directory name (timestamps and semver both sort
|
||||||
|
# correctly lexicographically here — timestamps are zero-padded ISO8601,
|
||||||
|
# versions are compared via `sort -V`), then remove everything beyond
|
||||||
|
# $keep.
|
||||||
|
local sorted
|
||||||
|
if [ "$kind" = "config" ]; then
|
||||||
|
sorted=$(find "$dir" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | sort -r)
|
||||||
|
else
|
||||||
|
sorted=$(find "$dir" -mindepth 1 -maxdepth 1 -type d -printf '%f\n' | sort -rV)
|
||||||
|
fi
|
||||||
|
|
||||||
|
local i=0
|
||||||
|
local name
|
||||||
|
while IFS= read -r name; do
|
||||||
|
[ -z "$name" ] && continue
|
||||||
|
i=$((i + 1))
|
||||||
|
if [ "$i" -gt "$keep" ]; then
|
||||||
|
podman_log "backup: pruning old $kind backup: $name"
|
||||||
|
rm -rf "${dir:?}/$name"
|
||||||
|
fi
|
||||||
|
done <<< "$sorted"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
snapshot-config) cmd_snapshot_config ;;
|
||||||
|
restore-config) shift; cmd_restore_config "$@" ;;
|
||||||
|
restore-packages) shift; cmd_restore_packages "$@" ;;
|
||||||
|
list) cmd_list ;;
|
||||||
|
prune) shift; cmd_prune "$@" ;;
|
||||||
|
*)
|
||||||
|
echo "usage: $0 {snapshot-config|restore-config <ts>|restore-packages <version>|list|prune [--keep N]}" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
Executable
+173
@@ -0,0 +1,173 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# plugin/sbin/podman-common.sh
|
||||||
|
#
|
||||||
|
# Shared constants and helper functions sourced by every other script under
|
||||||
|
# plugin/sbin/ and by plugin/rc.d/rc.podman. Centralizing these here means:
|
||||||
|
# - every script agrees on the same paths (no risk of one script writing
|
||||||
|
# config to a path another script reads from a slightly different one),
|
||||||
|
# - logging/notification behavior is consistent everywhere,
|
||||||
|
# - each individual script stays focused on its one job instead of
|
||||||
|
# re-implementing "how do I log this" or "where is podman.cfg".
|
||||||
|
#
|
||||||
|
# This file is NOT meant to be executed directly — it only defines
|
||||||
|
# functions/variables for other scripts to source:
|
||||||
|
# . "$(dirname "${BASH_SOURCE[0]}")/podman-common.sh"
|
||||||
|
#
|
||||||
|
# See docs/ARCHITECTURE.md sections 4 (Verzeichnislayout), 7 (Persistenz),
|
||||||
|
# 15 (Logging), 16 (Fehlerbehandlung) for the design this implements.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# We deliberately do NOT `set -e` in this file: it is sourced by scripts
|
||||||
|
# that set their own error-handling mode, and a library changing its
|
||||||
|
# caller's shell options would be a surprising action at a distance.
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Path constants.
|
||||||
|
#
|
||||||
|
# PODMAN_BOOT_DIR is the persistent, source-of-truth configuration directory
|
||||||
|
# on the Unraid flash device (survives reboots — see ARCHITECTURE.md 4.1).
|
||||||
|
# Everything under /etc, /var, /usr is RAM-root and rebuilt from here (or
|
||||||
|
# from the array/cache-backed storage dir) on every boot.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
PODMAN_BOOT_DIR="/boot/config/plugins/podman"
|
||||||
|
PODMAN_CFG_FILE="$PODMAN_BOOT_DIR/podman.cfg"
|
||||||
|
PODMAN_AUTOSTART_FILE="$PODMAN_BOOT_DIR/autostart"
|
||||||
|
PODMAN_AUTOSTART_DELAY_FILE="$PODMAN_BOOT_DIR/autostart-delay"
|
||||||
|
PODMAN_NETWORKS_BOOT_DIR="$PODMAN_BOOT_DIR/networks"
|
||||||
|
PODMAN_BACKUP_DIR="$PODMAN_BOOT_DIR/backup"
|
||||||
|
PODMAN_PLUGIN_LOG="$PODMAN_BOOT_DIR/plugin.log"
|
||||||
|
|
||||||
|
# Runtime (RAM-root) locations rebuilt/synced on every rc.podman start.
|
||||||
|
PODMAN_ETC_DIR="/etc/containers"
|
||||||
|
PODMAN_RUN_DIR="/var/run/podman"
|
||||||
|
PODMAN_SOCKET="$PODMAN_RUN_DIR/podman.sock"
|
||||||
|
PODMAN_STATUS_FILE="$PODMAN_RUN_DIR/rc.podman.status"
|
||||||
|
PODMAN_SERVICE_PID_FILE="$PODMAN_RUN_DIR/podman-service.pid"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# podman_load_cfg
|
||||||
|
#
|
||||||
|
# Sources /boot/config/plugins/podman/podman.cfg (the user-editable settings
|
||||||
|
# file, see config/podman.cfg.example) and applies safe defaults for any
|
||||||
|
# setting that file doesn't define — so every other script can simply
|
||||||
|
# reference $STORAGE_PATH, $PODMAN_ENABLED, etc. after calling this, without
|
||||||
|
# each script having its own copy of the defaults.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
podman_load_cfg() {
|
||||||
|
# Defaults, applied BEFORE sourcing podman.cfg so the file only needs to
|
||||||
|
# override what the user actually wants to change.
|
||||||
|
STORAGE_PATH="/mnt/cache/system/podman"
|
||||||
|
STORAGE_IMAGE_SIZE_GB="20"
|
||||||
|
PODMAN_ENABLED="yes"
|
||||||
|
STOP_TIMEOUT="10"
|
||||||
|
CONFIG_SCHEMA_VERSION="1"
|
||||||
|
|
||||||
|
if [ -f "$PODMAN_CFG_FILE" ]; then
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$PODMAN_CFG_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Values derived from STORAGE_PATH, computed after sourcing so a custom
|
||||||
|
# STORAGE_PATH is honored.
|
||||||
|
PODMAN_STORAGE_IMAGE="$STORAGE_PATH/podman.img"
|
||||||
|
PODMAN_LOG_DIR="$STORAGE_PATH/logs"
|
||||||
|
PODMAN_GRAPHROOT="/var/lib/containers/storage"
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# podman_log <message>
|
||||||
|
#
|
||||||
|
# Writes a timestamped line to both stdout (so it shows up in `rc.podman`
|
||||||
|
# invocations and CI/manual runs) and to the persistent plugin log on flash
|
||||||
|
# (so it survives a reboot — see ARCHITECTURE.md section 15, Logging). Kept
|
||||||
|
# deliberately terse (no log levels/rotation logic here) — this is for
|
||||||
|
# install/lifecycle events, not container output, which lives under
|
||||||
|
# $PODMAN_LOG_DIR instead.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
podman_log() {
|
||||||
|
local msg="$1"
|
||||||
|
local line
|
||||||
|
line="$(date -u +'%Y-%m-%dT%H:%M:%SZ') [podman] $msg"
|
||||||
|
echo "$line"
|
||||||
|
# Best-effort: the boot directory should always exist post-install, but
|
||||||
|
# never let logging itself fail a caller that has `set -e`.
|
||||||
|
mkdir -p "$PODMAN_BOOT_DIR" 2> /dev/null || true
|
||||||
|
echo "$line" >> "$PODMAN_PLUGIN_LOG" 2> /dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# podman_log_error <message>
|
||||||
|
#
|
||||||
|
# Like podman_log, but also mirrors the message to the system log via
|
||||||
|
# `logger`, so it is visible in Unraid's Tools -> System Log GUI without the
|
||||||
|
# user having to know where the plugin's own log lives. Reserved for
|
||||||
|
# conditions the user should actually notice (see ARCHITECTURE.md 16.2).
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
podman_log_error() {
|
||||||
|
local msg="$1"
|
||||||
|
podman_log "ERROR: $msg"
|
||||||
|
if command -v logger > /dev/null 2>&1; then
|
||||||
|
logger -t podman-plugin "$msg"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# podman_notify <subject> <description> [importance]
|
||||||
|
#
|
||||||
|
# Surfaces a message via Unraid's own GUI notification system
|
||||||
|
# (/usr/local/emhttp/webGui/scripts/notify) instead of inventing a parallel
|
||||||
|
# notification mechanism — see ARCHITECTURE.md section 16.2. Silently
|
||||||
|
# no-ops if that script isn't present (e.g. when running outside a real
|
||||||
|
# Unraid system, such as in CI/lint contexts).
|
||||||
|
#
|
||||||
|
# importance: "normal" (default), "warning", or "alert".
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
podman_notify() {
|
||||||
|
local subject="$1"
|
||||||
|
local description="$2"
|
||||||
|
local importance="${3:-normal}"
|
||||||
|
local notify_bin="/usr/local/emhttp/webGui/scripts/notify"
|
||||||
|
|
||||||
|
podman_log "notify [$importance] $subject: $description"
|
||||||
|
|
||||||
|
if [ -x "$notify_bin" ]; then
|
||||||
|
"$notify_bin" -e "unraid-podman" -s "$subject" -d "$description" -i "$importance" \
|
||||||
|
> /dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# podman_storage_path_is_safe
|
||||||
|
#
|
||||||
|
# Refuses storage paths under /mnt/user (FUSE/shfs) — see
|
||||||
|
# ARCHITECTURE.md section 4.3 for why the overlay storage driver must live
|
||||||
|
# on a real mounted filesystem (cache pool or a specific disk), not shfs.
|
||||||
|
# Returns 0 (safe) or 1 (unsafe) and prints a reason on failure.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
podman_storage_path_is_safe() {
|
||||||
|
local path="$1"
|
||||||
|
case "$path" in
|
||||||
|
/mnt/user/*|/mnt/user)
|
||||||
|
echo "STORAGE_PATH ($path) is under /mnt/user (FUSE/shfs)." >&2
|
||||||
|
echo "The overlay storage driver needs a real mounted filesystem —" >&2
|
||||||
|
echo "use a cache pool or a specific disk path instead, e.g. /mnt/cache/system/podman." >&2
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# podman_require_command <binary>
|
||||||
|
#
|
||||||
|
# Fails loudly (rather than letting a script continue and fail confusingly
|
||||||
|
# three steps later) if a required binary isn't on PATH.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
podman_require_command() {
|
||||||
|
local bin="$1"
|
||||||
|
if ! command -v "$bin" > /dev/null 2>&1; then
|
||||||
|
podman_log_error "required command not found: $bin"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
Executable
+141
@@ -0,0 +1,141 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# plugin/sbin/podman-config.sh
|
||||||
|
#
|
||||||
|
# "Konfiguration anlegen" — creates and synchronizes unraid-podman's
|
||||||
|
# configuration. Has two responsibilities, run as two subcommands:
|
||||||
|
#
|
||||||
|
# podman-config.sh seed
|
||||||
|
# First-install (or "file went missing") step: ensures
|
||||||
|
# /boot/config/plugins/podman/ exists with its full directory skeleton,
|
||||||
|
# and copies default config templates into it — but ONLY for files that
|
||||||
|
# don't already exist. This is what makes plugin updates safe: an
|
||||||
|
# existing user configuration is never overwritten (see
|
||||||
|
# docs/ARCHITECTURE.md section 3.2 and section 7).
|
||||||
|
#
|
||||||
|
# podman-config.sh sync
|
||||||
|
# Boot-time step: copies the current, authoritative config from
|
||||||
|
# /boot/config/plugins/podman/*.conf into /etc/containers/ (which lives
|
||||||
|
# on Unraid's RAM-root and is empty again after every reboot). Run by
|
||||||
|
# rc.podman on every `start`.
|
||||||
|
#
|
||||||
|
# Template source: the plugin ships its default config templates (from this
|
||||||
|
# repo's config/) to /usr/local/share/unraid-podman/templates/ at install
|
||||||
|
# time (see plugin/podman.plg) — that is what `seed` copies FROM, and
|
||||||
|
# /boot/config/plugins/podman/ is what it copies TO.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# podman-config.sh seed
|
||||||
|
# podman-config.sh sync
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=./podman-common.sh
|
||||||
|
. "$SCRIPT_DIR/podman-common.sh"
|
||||||
|
|
||||||
|
TEMPLATES_DIR="/usr/local/share/unraid-podman/templates"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# seed_file <template-filename> <dest-path>
|
||||||
|
#
|
||||||
|
# Copies a template into place only if the destination doesn't already
|
||||||
|
# exist. This single rule is what protects user customizations across
|
||||||
|
# plugin updates — see docs/ARCHITECTURE.md section 7, "Persistenz-Strategie".
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
seed_file() {
|
||||||
|
local template_name="$1"
|
||||||
|
local dest_path="$2"
|
||||||
|
local src_path="$TEMPLATES_DIR/$template_name"
|
||||||
|
|
||||||
|
if [ -f "$dest_path" ]; then
|
||||||
|
podman_log "config: $dest_path already exists, leaving untouched"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$src_path" ]; then
|
||||||
|
podman_log_error "config: template missing: $src_path (plugin install incomplete?)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$dest_path")"
|
||||||
|
cp "$src_path" "$dest_path"
|
||||||
|
podman_log "config: seeded $dest_path from template $template_name"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_seed() {
|
||||||
|
podman_log "config: seeding /boot/config/plugins/podman/ (first install or repair)"
|
||||||
|
|
||||||
|
# Full directory skeleton — see docs/ARCHITECTURE.md section 4.1 and
|
||||||
|
# plugin/boot-config/plugins/podman/README.md for the reference layout.
|
||||||
|
mkdir -p \
|
||||||
|
"$PODMAN_BOOT_DIR" \
|
||||||
|
"$PODMAN_NETWORKS_BOOT_DIR" \
|
||||||
|
"$PODMAN_BACKUP_DIR/packages" \
|
||||||
|
"$PODMAN_BACKUP_DIR/config"
|
||||||
|
|
||||||
|
seed_file "podman.cfg.example" "$PODMAN_CFG_FILE"
|
||||||
|
seed_file "containers.conf" "$PODMAN_BOOT_DIR/containers.conf"
|
||||||
|
seed_file "storage.conf" "$PODMAN_BOOT_DIR/storage.conf"
|
||||||
|
seed_file "registries.conf" "$PODMAN_BOOT_DIR/registries.conf"
|
||||||
|
seed_file "policy.json" "$PODMAN_BOOT_DIR/policy.json"
|
||||||
|
|
||||||
|
# Autostart files: empty by default, `touch` is the "seed" here (no
|
||||||
|
# template content to copy — see docs/ARCHITECTURE.md section 12).
|
||||||
|
[ -f "$PODMAN_AUTOSTART_FILE" ] || { touch "$PODMAN_AUTOSTART_FILE"; podman_log "config: created empty $PODMAN_AUTOSTART_FILE"; }
|
||||||
|
[ -f "$PODMAN_AUTOSTART_DELAY_FILE" ] || { touch "$PODMAN_AUTOSTART_DELAY_FILE"; podman_log "config: created empty $PODMAN_AUTOSTART_DELAY_FILE"; }
|
||||||
|
[ -f "$PODMAN_PLUGIN_LOG" ] || touch "$PODMAN_PLUGIN_LOG"
|
||||||
|
|
||||||
|
podman_log "config: seeding complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_sync() {
|
||||||
|
podman_load_cfg
|
||||||
|
|
||||||
|
if [ ! -d "$PODMAN_BOOT_DIR" ]; then
|
||||||
|
podman_log_error "config: $PODMAN_BOOT_DIR does not exist — run 'podman-config.sh seed' first"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
podman_log "config: syncing $PODMAN_BOOT_DIR/*.conf -> $PODMAN_ETC_DIR/"
|
||||||
|
mkdir -p "$PODMAN_ETC_DIR"
|
||||||
|
|
||||||
|
local any_missing=0
|
||||||
|
for f in containers.conf storage.conf registries.conf policy.json; do
|
||||||
|
if [ ! -f "$PODMAN_BOOT_DIR/$f" ]; then
|
||||||
|
podman_log_error "config: missing $PODMAN_BOOT_DIR/$f (run 'podman-config.sh seed')"
|
||||||
|
any_missing=1
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
cp "$PODMAN_BOOT_DIR/$f" "$PODMAN_ETC_DIR/$f"
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$any_missing" -ne 0 ]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# storage.conf's graphroot/runroot are environment-specific (depend on
|
||||||
|
# STORAGE_PATH from podman.cfg), so they are appended here at sync time
|
||||||
|
# rather than hardcoded in the template — see config/storage.conf's own
|
||||||
|
# comment on this split.
|
||||||
|
{
|
||||||
|
echo ""
|
||||||
|
echo "# --- appended at boot by podman-config.sh sync, from podman.cfg ---"
|
||||||
|
echo "[storage]"
|
||||||
|
echo "driver = \"overlay\""
|
||||||
|
echo "graphroot = \"$PODMAN_GRAPHROOT\""
|
||||||
|
echo "runroot = \"/var/run/containers/storage\""
|
||||||
|
} >> "$PODMAN_ETC_DIR/storage.conf"
|
||||||
|
|
||||||
|
podman_log "config: sync complete"
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
seed) cmd_seed ;;
|
||||||
|
sync) cmd_sync ;;
|
||||||
|
*)
|
||||||
|
echo "usage: $0 {seed|sync}" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
Executable
+150
@@ -0,0 +1,150 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# plugin/sbin/podman-preflight.sh
|
||||||
|
#
|
||||||
|
# Startup validation, run by `rc.podman start` BEFORE anything is mounted or
|
||||||
|
# started. The goal is to fail fast with one clear message, instead of
|
||||||
|
# letting `podman system service` fail three steps later with a cryptic
|
||||||
|
# error. See docs/ARCHITECTURE.md section 16.1 (Startup-Robustheit).
|
||||||
|
#
|
||||||
|
# Every check below is independent and all of them run even if an earlier
|
||||||
|
# one fails, so a single invocation reports every problem at once rather
|
||||||
|
# than forcing the user through a fix-one-rerun-find-the-next loop.
|
||||||
|
#
|
||||||
|
# Exit code: 0 if every check passed, 1 if any failed (with all failures
|
||||||
|
# already logged/notified by that point).
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -u # deliberately not -e: see the "run every check" note above
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=./podman-common.sh
|
||||||
|
. "$SCRIPT_DIR/podman-common.sh"
|
||||||
|
|
||||||
|
podman_load_cfg
|
||||||
|
|
||||||
|
FAILURES=0
|
||||||
|
|
||||||
|
fail() {
|
||||||
|
podman_log_error "preflight: $1"
|
||||||
|
FAILURES=$((FAILURES + 1))
|
||||||
|
}
|
||||||
|
|
||||||
|
ok() {
|
||||||
|
podman_log "preflight: OK - $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. Required binaries are actually installed.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
for bin in podman conmon crun mount umount xfs_repair mkfs.xfs; do
|
||||||
|
if command -v "$bin" > /dev/null 2>&1; then
|
||||||
|
ok "$bin found"
|
||||||
|
else
|
||||||
|
fail "required binary '$bin' not found on PATH — is the plugin fully installed?"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
for helper in /usr/libexec/podman/netavark /usr/libexec/podman/aardvark-dns; do
|
||||||
|
if [ -x "$helper" ]; then
|
||||||
|
ok "$helper found"
|
||||||
|
else
|
||||||
|
fail "required helper '$helper' not found — is the plugin fully installed?"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. Storage path configured, safe, and mounted (cache pool / disk, not
|
||||||
|
# /mnt/user — see docs/ARCHITECTURE.md section 4.3).
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
if podman_storage_path_is_safe "$STORAGE_PATH" 2> /tmp/podman-preflight-storage-safety.log; then
|
||||||
|
ok "STORAGE_PATH ($STORAGE_PATH) is not under /mnt/user"
|
||||||
|
else
|
||||||
|
fail "$(cat /tmp/podman-preflight-storage-safety.log)"
|
||||||
|
fi
|
||||||
|
rm -f /tmp/podman-preflight-storage-safety.log
|
||||||
|
|
||||||
|
if [ -d "$STORAGE_PATH" ] && mountpoint -q "$STORAGE_PATH" 2> /dev/null; then
|
||||||
|
ok "STORAGE_PATH ($STORAGE_PATH) is a mounted filesystem"
|
||||||
|
elif [ -d "$STORAGE_PATH" ]; then
|
||||||
|
# Not every valid target is a mountpoint itself (e.g. a subdirectory of a
|
||||||
|
# cache pool root) — warn rather than fail, but only if the parent chain
|
||||||
|
# is mounted somewhere.
|
||||||
|
if findmnt -T "$STORAGE_PATH" > /dev/null 2>&1; then
|
||||||
|
ok "STORAGE_PATH ($STORAGE_PATH) resolves onto a mounted filesystem"
|
||||||
|
else
|
||||||
|
fail "STORAGE_PATH ($STORAGE_PATH) does not appear to be on a mounted filesystem"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
fail "STORAGE_PATH ($STORAGE_PATH) does not exist — is the configured cache pool/disk present and started?"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Free space check (only meaningful once STORAGE_PATH exists).
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
if [ -d "$STORAGE_PATH" ]; then
|
||||||
|
available_kb=$(df --output=avail -k "$STORAGE_PATH" 2> /dev/null | tail -n1 | tr -d '[:space:]')
|
||||||
|
if [ -n "${available_kb:-}" ]; then
|
||||||
|
available_gb=$((available_kb / 1024 / 1024))
|
||||||
|
if [ -f "$PODMAN_STORAGE_IMAGE" ]; then
|
||||||
|
# Image already exists — just warn if the pool itself is nearly full,
|
||||||
|
# since podman.img growth or new image pulls need headroom too.
|
||||||
|
if [ "$available_gb" -lt 2 ]; then
|
||||||
|
fail "less than 2G free on $STORAGE_PATH (${available_gb}G) — image pulls will likely fail"
|
||||||
|
else
|
||||||
|
ok "${available_gb}G free on $STORAGE_PATH"
|
||||||
|
fi
|
||||||
|
elif [ "$available_gb" -lt "$STORAGE_IMAGE_SIZE_GB" ]; then
|
||||||
|
fail "not enough free space on $STORAGE_PATH to create a ${STORAGE_IMAGE_SIZE_GB}G podman.img (only ${available_gb}G free)"
|
||||||
|
else
|
||||||
|
ok "${available_gb}G free on $STORAGE_PATH (enough for a fresh ${STORAGE_IMAGE_SIZE_GB}G podman.img)"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. Kernel supports cgroup v2 (required by crun/netavark's expectations).
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
if [ -f /sys/fs/cgroup/cgroup.controllers ]; then
|
||||||
|
ok "cgroup v2 unified hierarchy is active"
|
||||||
|
else
|
||||||
|
fail "cgroup v2 unified hierarchy not detected (/sys/fs/cgroup/cgroup.controllers missing) — check Unraid's syslinux cgroup boot parameters"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. No orphaned socket/pidfile from a previous unclean shutdown.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
if [ -S "$PODMAN_SOCKET" ]; then
|
||||||
|
if [ -f "$PODMAN_SERVICE_PID_FILE" ] && kill -0 "$(cat "$PODMAN_SERVICE_PID_FILE")" 2> /dev/null; then
|
||||||
|
fail "podman system service already appears to be running (pid $(cat "$PODMAN_SERVICE_PID_FILE")) — is rc.podman already started?"
|
||||||
|
else
|
||||||
|
podman_log "preflight: removing orphaned socket $PODMAN_SOCKET from a previous unclean shutdown"
|
||||||
|
rm -f "$PODMAN_SOCKET"
|
||||||
|
ok "cleared orphaned socket"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
ok "no orphaned podman.sock"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. Boot-config directory exists (i.e. 'seed' has run at least once).
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
if [ -f "$PODMAN_CFG_FILE" ]; then
|
||||||
|
ok "$PODMAN_CFG_FILE present"
|
||||||
|
else
|
||||||
|
fail "$PODMAN_CFG_FILE missing — run 'podman-config.sh seed' (should happen automatically on install)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Summary
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
if [ "$FAILURES" -gt 0 ]; then
|
||||||
|
podman_notify "Podman preflight checks failed" \
|
||||||
|
"$FAILURES check(s) failed — podman was not started. See $PODMAN_PLUGIN_LOG for details." \
|
||||||
|
"alert"
|
||||||
|
podman_log_error "preflight: $FAILURES check(s) failed, aborting start"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
podman_log "preflight: all checks passed"
|
||||||
|
exit 0
|
||||||
Executable
+180
@@ -0,0 +1,180 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# plugin/sbin/podman-storage.sh
|
||||||
|
#
|
||||||
|
# "Container Storage erstellen" — creates, mounts, and unmounts the
|
||||||
|
# podman.img loopback filesystem that backs Podman's overlay storage graph
|
||||||
|
# (images, containers, named volumes). See docs/ARCHITECTURE.md section 4.3
|
||||||
|
# for why this needs to be a real mounted filesystem (XFS with ftype=1, on a
|
||||||
|
# cache pool or dedicated disk) rather than a directory under /mnt/user —
|
||||||
|
# the FUSE (shfs) layer behind /mnt/user does not reliably support the
|
||||||
|
# overlay storage driver's filesystem requirements (d_type, etc).
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# podman-storage.sh create # create podman.img if it doesn't exist yet
|
||||||
|
# podman-storage.sh mount # mount it at $PODMAN_GRAPHROOT (idempotent)
|
||||||
|
# podman-storage.sh unmount # cleanly unmount
|
||||||
|
# podman-storage.sh status # report existence/mount/usage
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=./podman-common.sh
|
||||||
|
. "$SCRIPT_DIR/podman-common.sh"
|
||||||
|
|
||||||
|
podman_load_cfg
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# cmd_create
|
||||||
|
#
|
||||||
|
# Creates podman.img at the configured size if it doesn't already exist,
|
||||||
|
# and formats it XFS with ftype=1 (required for the overlay storage driver
|
||||||
|
# to see correct directory entry types — without it, podman's overlay
|
||||||
|
# backend silently falls back to a slower/less-capable mode or fails
|
||||||
|
# outright, depending on version). Does nothing (and exits 0) if the image
|
||||||
|
# already exists — this script is meant to be safe to call on every boot.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
cmd_create() {
|
||||||
|
if ! podman_storage_path_is_safe "$STORAGE_PATH"; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -f "$PODMAN_STORAGE_IMAGE" ]; then
|
||||||
|
podman_log "storage: $PODMAN_STORAGE_IMAGE already exists, not recreating"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "$STORAGE_PATH" ]; then
|
||||||
|
podman_log_error "storage: $STORAGE_PATH does not exist or is not mounted."
|
||||||
|
podman_log_error "storage: check that the configured cache pool/disk is present before starting podman."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Free space check: refuse to create an image bigger than what's actually
|
||||||
|
# available, with a small safety margin, rather than letting truncate
|
||||||
|
# silently create a sparse file that will fail unpredictably later once
|
||||||
|
# it's actually written to (see docs/ARCHITECTURE.md section 16.2).
|
||||||
|
local available_kb required_kb
|
||||||
|
available_kb=$(df --output=avail -k "$STORAGE_PATH" | tail -n1 | tr -d '[:space:]')
|
||||||
|
required_kb=$((STORAGE_IMAGE_SIZE_GB * 1024 * 1024))
|
||||||
|
if [ "$available_kb" -lt "$required_kb" ]; then
|
||||||
|
podman_log_error "storage: not enough free space on $STORAGE_PATH (need ${STORAGE_IMAGE_SIZE_GB}G, have $((available_kb / 1024 / 1024))G)"
|
||||||
|
podman_notify "Podman storage creation failed" \
|
||||||
|
"Not enough free space on $STORAGE_PATH for a ${STORAGE_IMAGE_SIZE_GB}G podman.img." \
|
||||||
|
"alert"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
podman_log "storage: creating ${STORAGE_IMAGE_SIZE_GB}G image at $PODMAN_STORAGE_IMAGE"
|
||||||
|
mkdir -p "$STORAGE_PATH"
|
||||||
|
|
||||||
|
# Sparse file: only actually consumes disk space as data is written,
|
||||||
|
# matching Docker-for-Unraid's docker.img behavior that users already
|
||||||
|
# understand.
|
||||||
|
truncate -s "${STORAGE_IMAGE_SIZE_GB}G" "$PODMAN_STORAGE_IMAGE"
|
||||||
|
|
||||||
|
podman_require_command mkfs.xfs
|
||||||
|
# -n ftype=1 is the whole reason this has to be a purpose-made image
|
||||||
|
# rather than a plain directory — see file header comment.
|
||||||
|
mkfs.xfs -n ftype=1 -q "$PODMAN_STORAGE_IMAGE"
|
||||||
|
|
||||||
|
podman_log "storage: created and formatted $PODMAN_STORAGE_IMAGE"
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# cmd_mount
|
||||||
|
#
|
||||||
|
# Idempotent: does nothing if $PODMAN_GRAPHROOT is already mounted from
|
||||||
|
# podman.img. Runs a read-only integrity check (xfs_repair -n) before
|
||||||
|
# mounting and refuses to proceed if it reports corruption — per
|
||||||
|
# docs/ARCHITECTURE.md section 16.1, this project does not auto-repair
|
||||||
|
# storage without the user's explicit action, to avoid silent data loss.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
cmd_mount() {
|
||||||
|
if mountpoint -q "$PODMAN_GRAPHROOT" 2> /dev/null; then
|
||||||
|
podman_log "storage: $PODMAN_GRAPHROOT already mounted"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$PODMAN_STORAGE_IMAGE" ]; then
|
||||||
|
podman_log_error "storage: $PODMAN_STORAGE_IMAGE does not exist — run 'podman-storage.sh create' first"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
podman_require_command xfs_repair
|
||||||
|
podman_log "storage: checking filesystem integrity of $PODMAN_STORAGE_IMAGE"
|
||||||
|
if ! xfs_repair -n "$PODMAN_STORAGE_IMAGE" > /tmp/podman-xfs-repair.log 2>&1; then
|
||||||
|
podman_log_error "storage: filesystem check failed for $PODMAN_STORAGE_IMAGE — refusing to mount."
|
||||||
|
podman_log_error "storage: see /tmp/podman-xfs-repair.log. Run 'xfs_repair $PODMAN_STORAGE_IMAGE' manually to attempt repair, or restore from backup."
|
||||||
|
podman_notify "Podman storage corruption detected" \
|
||||||
|
"$PODMAN_STORAGE_IMAGE failed an integrity check and was not mounted. Manual recovery required — see plugin.log." \
|
||||||
|
"alert"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$PODMAN_GRAPHROOT"
|
||||||
|
podman_log "storage: mounting $PODMAN_STORAGE_IMAGE at $PODMAN_GRAPHROOT"
|
||||||
|
mount -o loop "$PODMAN_STORAGE_IMAGE" "$PODMAN_GRAPHROOT"
|
||||||
|
|
||||||
|
# Runtime state (runroot) is tmpfs-backed and fine to live on RAM-root —
|
||||||
|
# only the persistent graphroot needs the loopback filesystem.
|
||||||
|
mkdir -p /var/run/containers/storage
|
||||||
|
|
||||||
|
podman_log "storage: mounted"
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# cmd_unmount
|
||||||
|
#
|
||||||
|
# Unmounts cleanly. Retries briefly if the mount is momentarily busy (a
|
||||||
|
# container process exiting can hold a reference for a few hundred ms),
|
||||||
|
# rather than immediately failing rc.podman's stop sequence.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
cmd_unmount() {
|
||||||
|
if ! mountpoint -q "$PODMAN_GRAPHROOT" 2> /dev/null; then
|
||||||
|
podman_log "storage: $PODMAN_GRAPHROOT not mounted, nothing to do"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
podman_log "storage: unmounting $PODMAN_GRAPHROOT"
|
||||||
|
local attempt
|
||||||
|
for attempt in 1 2 3 4 5; do
|
||||||
|
if umount "$PODMAN_GRAPHROOT" 2> /dev/null; then
|
||||||
|
podman_log "storage: unmounted"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
podman_log_error "storage: failed to unmount $PODMAN_GRAPHROOT after 5 attempts (still busy?)"
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_status() {
|
||||||
|
echo "storage image: $PODMAN_STORAGE_IMAGE"
|
||||||
|
if [ -f "$PODMAN_STORAGE_IMAGE" ]; then
|
||||||
|
echo " exists: yes ($(du -h "$PODMAN_STORAGE_IMAGE" | cut -f1) allocated / ${STORAGE_IMAGE_SIZE_GB}G nominal)"
|
||||||
|
else
|
||||||
|
echo " exists: no"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "graphroot: $PODMAN_GRAPHROOT"
|
||||||
|
if mountpoint -q "$PODMAN_GRAPHROOT" 2> /dev/null; then
|
||||||
|
echo " mounted: yes"
|
||||||
|
df -h "$PODMAN_GRAPHROOT" | tail -n1 | awk '{print " usage: " $3 " used / " $2 " total (" $5 " full)"}'
|
||||||
|
else
|
||||||
|
echo " mounted: no"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
case "${1:-}" in
|
||||||
|
create) cmd_create ;;
|
||||||
|
mount) cmd_mount ;;
|
||||||
|
unmount) cmd_unmount ;;
|
||||||
|
status) cmd_status ;;
|
||||||
|
*)
|
||||||
|
echo "usage: $0 {create|mount|unmount|status}" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
Executable
+94
@@ -0,0 +1,94 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# plugin/sbin/podman-uninstall-cleanup.sh
|
||||||
|
#
|
||||||
|
# "Beim Entfernen sauber aufräumen" — run by plugin/podman.plg's <FILE
|
||||||
|
# Run="removepkg"> block when the plugin is uninstalled via Unraid's Plugins
|
||||||
|
# page. Two-tier cleanup, matching docs/ARCHITECTURE.md section 3.2 /
|
||||||
|
# section 17:
|
||||||
|
#
|
||||||
|
# ALWAYS removed (installed software, safe to regenerate):
|
||||||
|
# - running containers/service stopped cleanly
|
||||||
|
# - runtime config under /etc/containers, /var/run/podman
|
||||||
|
# - staged helper scripts, templates, WebUI files, event hooks
|
||||||
|
#
|
||||||
|
# NOT applicable: this project has no /boot/config/go entry to clean up
|
||||||
|
# in the first place — it hooks the official Unraid plugin event
|
||||||
|
# mechanism (/usr/local/emhttp/plugins/podman/event/*, see
|
||||||
|
# plugin/event/disks_mounted's header comment) instead of editing go,
|
||||||
|
# so removepkg-ing the unraid-podman package (done by podman.plg right
|
||||||
|
# after this script runs) already removes those hooks along with
|
||||||
|
# everything else under /usr/local/emhttp/plugins/podman/.
|
||||||
|
#
|
||||||
|
# PRESERVED BY DEFAULT (user data — see docs/INSTALL.md "Uninstallation"):
|
||||||
|
# - /boot/config/plugins/podman/ (settings, autostart list, backups)
|
||||||
|
# - $STORAGE_PATH (podman.img — every image/container/volume the user
|
||||||
|
# has)
|
||||||
|
#
|
||||||
|
# Full data removal is opt-in only, via:
|
||||||
|
# podman-uninstall-cleanup.sh --purge-data
|
||||||
|
# never the default, and never triggered automatically — deleting a user's
|
||||||
|
# containers/images without an explicit, separate confirmation is exactly
|
||||||
|
# the kind of destructive-by-surprise behavior this project avoids (see
|
||||||
|
# docs/ARCHITECTURE.md "Nicht-Ziele" around data safety).
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=./podman-common.sh
|
||||||
|
. "$SCRIPT_DIR/podman-common.sh"
|
||||||
|
|
||||||
|
PURGE_DATA=0
|
||||||
|
[ "${1:-}" = "--purge-data" ] && PURGE_DATA=1
|
||||||
|
|
||||||
|
podman_log "uninstall: starting cleanup (purge-data=$PURGE_DATA)"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. Stop podman cleanly if it's running, so containers shut down properly
|
||||||
|
# and the storage loopback is unmounted before we remove anything else.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
if [ -x /etc/rc.d/rc.podman ]; then
|
||||||
|
podman_log "uninstall: stopping podman"
|
||||||
|
/etc/rc.d/rc.podman stop || podman_log_error "uninstall: rc.podman stop reported an error (continuing cleanup anyway)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. Remove runtime state (RAM-root anyway, but tidy up now rather than
|
||||||
|
# waiting for the next reboot to implicitly clear it).
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
podman_log "uninstall: removing runtime state"
|
||||||
|
rm -rf "$PODMAN_ETC_DIR" "$PODMAN_RUN_DIR"
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. Remove staged files this plugin installed outside of package-managed
|
||||||
|
# paths. This is technically redundant with podman.plg's removepkg of
|
||||||
|
# the unraid-podman package that immediately follows this script (see
|
||||||
|
# that package's manifest — it owns exactly these paths), but doing it
|
||||||
|
# explicitly here too means this script is also safe to run manually as
|
||||||
|
# a standalone repair tool without relying on removepkg bookkeeping.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
podman_log "uninstall: removing plugin scaffolding"
|
||||||
|
rm -rf /usr/local/share/unraid-podman
|
||||||
|
rm -rf /usr/local/emhttp/plugins/podman
|
||||||
|
rm -f /etc/rc.d/rc.podman
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. User data — preserved unless --purge-data was explicitly passed.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
if [ "$PURGE_DATA" -eq 1 ]; then
|
||||||
|
podman_load_cfg
|
||||||
|
podman_log "uninstall: --purge-data given, removing user data"
|
||||||
|
|
||||||
|
if [ -n "${STORAGE_PATH:-}" ] && [ -d "$STORAGE_PATH" ]; then
|
||||||
|
podman_log "uninstall: removing $STORAGE_PATH (images, containers, volumes, logs)"
|
||||||
|
rm -rf "${STORAGE_PATH:?}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
podman_log "uninstall: removing $PODMAN_BOOT_DIR (settings, autostart, backups)"
|
||||||
|
rm -rf "${PODMAN_BOOT_DIR:?}"
|
||||||
|
else
|
||||||
|
podman_log "uninstall: preserving $PODMAN_BOOT_DIR and podman's storage path (pass --purge-data to remove them too)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
podman_log "uninstall: cleanup complete"
|
||||||
Executable
+134
@@ -0,0 +1,134 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# plugin/sbin/podman-update-packages.sh
|
||||||
|
#
|
||||||
|
# "Pakete aktualisieren" — reconciles installed packages with what the
|
||||||
|
# currently installed plugin version expects, per
|
||||||
|
# /usr/local/share/unraid-podman/installed-versions.env (written by
|
||||||
|
# plugin/podman.plg's postinstall — see podman-verify-packages.sh for the
|
||||||
|
# same manifest used the other direction, to *check* rather than *fix*).
|
||||||
|
#
|
||||||
|
# This is intentionally idempotent and safe to run any time, not just
|
||||||
|
# during a plugin update: if every package already matches, it's a no-op.
|
||||||
|
# It exists as a separate script (rather than being inline in podman.plg)
|
||||||
|
# for two reasons:
|
||||||
|
# 1. plugin/podman.plg's own <FILE Run="upgradepkg"> blocks handle the
|
||||||
|
# *normal* update path (new .txz already downloaded by the plg,
|
||||||
|
# installed as part of the same transaction) — this script is the
|
||||||
|
# *repair* path for when that didn't fully complete (e.g. Unraid was
|
||||||
|
# rebooted mid-update), and can be re-run safely from the command line
|
||||||
|
# or from a future WebUI "check for package issues" action.
|
||||||
|
# 2. It calls podman-backup.sh snapshot-config first, since any package
|
||||||
|
# swap is exactly the moment a config schema migration might be
|
||||||
|
# needed — see docs/ARCHITECTURE.md section 13.1.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# podman-update-packages.sh # reconcile all 7 packages
|
||||||
|
# podman-update-packages.sh podman # reconcile a single package
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=./podman-common.sh
|
||||||
|
. "$SCRIPT_DIR/podman-common.sh"
|
||||||
|
|
||||||
|
INSTALLED_VERSIONS_FILE="/usr/local/share/unraid-podman/installed-versions.env"
|
||||||
|
ALL_PACKAGES="podman conmon crun netavark aardvark-dns passt fuse-overlayfs unraid-podman"
|
||||||
|
|
||||||
|
if [ ! -f "$INSTALLED_VERSIONS_FILE" ]; then
|
||||||
|
podman_log_error "update-packages: $INSTALLED_VERSIONS_FILE missing — plugin install metadata not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$INSTALLED_VERSIONS_FILE"
|
||||||
|
|
||||||
|
if [ -z "${PLUGIN_VERSION:-}" ]; then
|
||||||
|
podman_log_error "update-packages: PLUGIN_VERSION not recorded in $INSTALLED_VERSIONS_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
targets="${*:-$ALL_PACKAGES}"
|
||||||
|
|
||||||
|
podman_require_command upgradepkg
|
||||||
|
podman_require_command installpkg
|
||||||
|
|
||||||
|
updated=0
|
||||||
|
already_current=0
|
||||||
|
errors=0
|
||||||
|
|
||||||
|
for name in $targets; do
|
||||||
|
entity_prefix=$(echo "$name" | tr '[:lower:]-' '[:upper:]_')
|
||||||
|
expected_version_var="${entity_prefix}_INSTALLED_VERSION"
|
||||||
|
expected_version="${!expected_version_var:-}"
|
||||||
|
|
||||||
|
if [ -z "$expected_version" ]; then
|
||||||
|
podman_log_error "update-packages: no expected version recorded for '$name', skipping"
|
||||||
|
errors=$((errors + 1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
installed_record=$(find /var/log/packages -maxdepth 1 -name "${name}-*" -print 2> /dev/null | head -n1)
|
||||||
|
installed_basename=$(basename "${installed_record:-__none__}")
|
||||||
|
|
||||||
|
case "$installed_basename" in
|
||||||
|
"${name}-${expected_version}-"*)
|
||||||
|
podman_log "update-packages: $name already at expected version $expected_version"
|
||||||
|
already_current=$((already_current + 1))
|
||||||
|
continue
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# The package for the currently expected version was downloaded straight
|
||||||
|
# into its backup slot by podman.plg (grouped by PLUGIN_VERSION, since a
|
||||||
|
# rollback targets "this plugin release" — see podman-backup.sh's header
|
||||||
|
# comment for why that's also where we install FROM.
|
||||||
|
txz=$(find "$PODMAN_BACKUP_DIR/packages/$PLUGIN_VERSION" -maxdepth 1 -name "${name}-*.txz" 2> /dev/null | head -n1)
|
||||||
|
if [ -z "$txz" ]; then
|
||||||
|
podman_log_error "update-packages: no package file found for $name (expected version $expected_version) in $PODMAN_BACKUP_DIR/packages/$PLUGIN_VERSION/"
|
||||||
|
errors=$((errors + 1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
podman_log "update-packages: installing $name -> $expected_version ($txz)"
|
||||||
|
|
||||||
|
# Snapshot config once, before the first actual package change — a
|
||||||
|
# version bump is exactly when a schema migration might be needed (see
|
||||||
|
# file header). Only do this once per run, not once per package.
|
||||||
|
if [ "$updated" -eq 0 ]; then
|
||||||
|
"$SCRIPT_DIR/podman-backup.sh" snapshot-config > /dev/null
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$installed_record" ]; then
|
||||||
|
if upgradepkg --install-new "$txz"; then
|
||||||
|
updated=$((updated + 1))
|
||||||
|
else
|
||||||
|
podman_log_error "update-packages: upgradepkg failed for $name ($txz)"
|
||||||
|
errors=$((errors + 1))
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if installpkg "$txz"; then
|
||||||
|
updated=$((updated + 1))
|
||||||
|
else
|
||||||
|
podman_log_error "update-packages: installpkg failed for $name ($txz)"
|
||||||
|
errors=$((errors + 1))
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
podman_log "update-packages: done (updated=$updated already-current=$already_current errors=$errors)"
|
||||||
|
|
||||||
|
if [ "$errors" -gt 0 ]; then
|
||||||
|
podman_notify "Podman package update had errors" \
|
||||||
|
"$errors package(s) failed to update — see $PODMAN_PLUGIN_LOG." \
|
||||||
|
"alert"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$updated" -gt 0 ]; then
|
||||||
|
podman_notify "Podman packages updated" \
|
||||||
|
"$updated package(s) updated. Run 'rc.podman restart' to apply." \
|
||||||
|
"normal"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
Executable
+127
@@ -0,0 +1,127 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# plugin/sbin/podman-verify-packages.sh
|
||||||
|
#
|
||||||
|
# "Pakete prüfen" — verifies the seven packages this plugin ships are
|
||||||
|
# actually installed, at the version the plugin expects, and that the
|
||||||
|
# backed-up .txz copies (see podman-backup.sh) haven't bit-rotted on disk.
|
||||||
|
#
|
||||||
|
# Three checks per package:
|
||||||
|
# 1. Installed: Slackware records every installed package under
|
||||||
|
# /var/log/packages/<name>-<version>-<arch>-<build><tag> — its mere
|
||||||
|
# existence IS the install record (standard Slackware pkgtools
|
||||||
|
# convention, nothing custom here).
|
||||||
|
# 2. Expected version: compared against
|
||||||
|
# /usr/local/share/unraid-podman/installed-versions.env, written by
|
||||||
|
# plugin/podman.plg's postinstall step at install/update time.
|
||||||
|
# 3. Backup integrity: if a backed-up .txz exists for the installed
|
||||||
|
# version (see podman-backup.sh), its current SHA256 is re-checked
|
||||||
|
# against the .sha256 sidecar recorded at build time — catches flash
|
||||||
|
# storage corruption on the backup copy before it's needed for a
|
||||||
|
# rollback.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# podman-verify-packages.sh # human-readable report
|
||||||
|
# podman-verify-packages.sh --quiet # exit code only, minimal output
|
||||||
|
#
|
||||||
|
# Exit code: 0 if everything checks out, 1 if any package has a problem.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
# shellcheck source=./podman-common.sh
|
||||||
|
. "$SCRIPT_DIR/podman-common.sh"
|
||||||
|
|
||||||
|
INSTALLED_VERSIONS_FILE="/usr/local/share/unraid-podman/installed-versions.env"
|
||||||
|
PACKAGES="podman conmon crun netavark aardvark-dns passt fuse-overlayfs unraid-podman"
|
||||||
|
|
||||||
|
QUIET=0
|
||||||
|
[ "${1:-}" = "--quiet" ] && QUIET=1
|
||||||
|
|
||||||
|
report() {
|
||||||
|
[ "$QUIET" -eq 0 ] && echo "$1"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ ! -f "$INSTALLED_VERSIONS_FILE" ]; then
|
||||||
|
podman_log_error "verify: $INSTALLED_VERSIONS_FILE missing — plugin install metadata not found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$INSTALLED_VERSIONS_FILE"
|
||||||
|
|
||||||
|
if [ -z "${PLUGIN_VERSION:-}" ]; then
|
||||||
|
podman_log_error "verify: PLUGIN_VERSION not recorded in $INSTALLED_VERSIONS_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
PROBLEMS=0
|
||||||
|
|
||||||
|
for name in $PACKAGES; do
|
||||||
|
entity_prefix=$(echo "$name" | tr '[:lower:]-' '[:upper:]_')
|
||||||
|
expected_version_var="${entity_prefix}_INSTALLED_VERSION"
|
||||||
|
expected_version="${!expected_version_var:-}"
|
||||||
|
|
||||||
|
report "== $name =="
|
||||||
|
|
||||||
|
if [ -z "$expected_version" ]; then
|
||||||
|
report " expected version: UNKNOWN (not recorded in $INSTALLED_VERSIONS_FILE)"
|
||||||
|
PROBLEMS=$((PROBLEMS + 1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
report " expected version: $expected_version"
|
||||||
|
|
||||||
|
# --- Check 1: installed -----------------------------------------------
|
||||||
|
installed_record=$(find /var/log/packages -maxdepth 1 -name "${name}-*" -print 2> /dev/null | head -n1)
|
||||||
|
if [ -z "$installed_record" ]; then
|
||||||
|
report " installed: NO"
|
||||||
|
podman_log_error "verify: $name is not installed (no /var/log/packages/$name-* record)"
|
||||||
|
PROBLEMS=$((PROBLEMS + 1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
installed_basename=$(basename "$installed_record")
|
||||||
|
report " installed package: $installed_basename"
|
||||||
|
|
||||||
|
# --- Check 2: version matches expectation ------------------------------
|
||||||
|
case "$installed_basename" in
|
||||||
|
"${name}-${expected_version}-"*)
|
||||||
|
report " version match: OK"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
report " version match: MISMATCH (installed record does not match expected $expected_version)"
|
||||||
|
podman_log_error "verify: $name installed record '$installed_basename' does not match expected version $expected_version"
|
||||||
|
PROBLEMS=$((PROBLEMS + 1))
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# --- Check 3: backup artifact integrity, if present --------------------
|
||||||
|
# Packages of all 7 components released together as one plugin version
|
||||||
|
# are grouped under a single PLUGIN_VERSION directory (not per-component
|
||||||
|
# version) — a rollback targets "go back to plugin release X", matching
|
||||||
|
# podman-backup.sh's restore-packages <plugin-version>.
|
||||||
|
backup_dir="$PODMAN_BACKUP_DIR/packages/$PLUGIN_VERSION"
|
||||||
|
backup_txz=$(find "$backup_dir" -maxdepth 1 -name "${name}-*.txz" 2> /dev/null | head -n1)
|
||||||
|
if [ -n "$backup_txz" ] && [ -f "$backup_txz.sha256" ]; then
|
||||||
|
if ( cd "$(dirname "$backup_txz")" && sha256sum -c "$(basename "$backup_txz").sha256" > /dev/null 2>&1 ); then
|
||||||
|
report " backup integrity: OK ($backup_txz)"
|
||||||
|
else
|
||||||
|
report " backup integrity: CORRUPT ($backup_txz)"
|
||||||
|
podman_log_error "verify: backup artifact $backup_txz failed checksum verification"
|
||||||
|
PROBLEMS=$((PROBLEMS + 1))
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
report " backup integrity: no backup artifact on file (nothing to verify)"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
report ""
|
||||||
|
if [ "$PROBLEMS" -gt 0 ]; then
|
||||||
|
report "$PROBLEMS problem(s) found."
|
||||||
|
podman_notify "Podman package verification found problems" \
|
||||||
|
"$PROBLEMS issue(s) found — see $PODMAN_PLUGIN_LOG for details." \
|
||||||
|
"warning"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
report "All packages verified OK."
|
||||||
|
exit 0
|
||||||
Executable
+103
@@ -0,0 +1,103 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# scripts/build-packages.sh
|
||||||
|
#
|
||||||
|
# Orchestrates building all seven Slackware .txz packages this plugin ships
|
||||||
|
# (podman, conmon, crun, netavark, aardvark-dns, passt, fuse-overlayfs), by
|
||||||
|
# running each package's <name>.SlackBuild in turn. See
|
||||||
|
# docs/ARCHITECTURE.md section 5.2 (Build-Strategie).
|
||||||
|
#
|
||||||
|
# This script itself does not containerize anything — it assumes it is
|
||||||
|
# already running inside a Slackware-compatible build environment (see
|
||||||
|
# .github/workflows/build-packages.yml, which runs it inside a pinned
|
||||||
|
# Slackware Docker image). Running it on a non-Slackware host will likely
|
||||||
|
# still compile successfully for most components but the resulting .txz
|
||||||
|
# should not be trusted for an actual Unraid install — see the container
|
||||||
|
# image note in the CI workflow.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# scripts/build-packages.sh # build all packages
|
||||||
|
# scripts/build-packages.sh podman conmon # build only the named ones
|
||||||
|
#
|
||||||
|
# Output: $REPO_ROOT/dist/<name>-<version>-<arch>-<build><tag>.txz
|
||||||
|
# plus a .sha256 and .md5 sidecar file per package (see
|
||||||
|
# scripts/lib/slackbuild-common.sh, sb_make_package).
|
||||||
|
# Nothing under dist/ is committed to git — see .gitignore.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
PACKAGES_DIR="$REPO_ROOT/packages"
|
||||||
|
DIST_DIR="$REPO_ROOT/dist"
|
||||||
|
|
||||||
|
# The full, ordered set of packages this project builds automatically.
|
||||||
|
# containers-common is intentionally excluded for now — see packages/README.md.
|
||||||
|
# unraid-podman (the plugin's own scaffolding, not an upstream component —
|
||||||
|
# see packages/unraid-podman/README.md) is built last since it's by far the
|
||||||
|
# fastest and has nothing useful to report on failure that earlier package
|
||||||
|
# failures wouldn't already explain.
|
||||||
|
ALL_PACKAGES=(conmon crun netavark aardvark-dns passt fuse-overlayfs podman unraid-podman)
|
||||||
|
|
||||||
|
# Podman is listed second-to-last on purpose: among the seven upstream
|
||||||
|
# components it is the slowest build and the one most likely to fail on a
|
||||||
|
# dependency/tag mistake, so faster packages surface problems first during
|
||||||
|
# local iteration. unraid-podman is genuinely last since it packages this
|
||||||
|
# repo's own files and has no compile step at all.
|
||||||
|
|
||||||
|
requested=("$@")
|
||||||
|
if [ "${#requested[@]}" -eq 0 ]; then
|
||||||
|
targets=("${ALL_PACKAGES[@]}")
|
||||||
|
else
|
||||||
|
targets=("${requested[@]}")
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$DIST_DIR"
|
||||||
|
|
||||||
|
echo "==> Building packages: ${targets[*]}"
|
||||||
|
echo "==> Output directory: $DIST_DIR"
|
||||||
|
echo
|
||||||
|
|
||||||
|
failed=()
|
||||||
|
built=()
|
||||||
|
|
||||||
|
for name in "${targets[@]}"; do
|
||||||
|
slackbuild="$PACKAGES_DIR/$name/$name.SlackBuild"
|
||||||
|
|
||||||
|
if [ ! -x "$slackbuild" ]; then
|
||||||
|
echo "!! No SlackBuild found/executable for '$name' ($slackbuild)" >&2
|
||||||
|
failed+=("$name")
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "############################################################"
|
||||||
|
echo "## Building: $name"
|
||||||
|
echo "############################################################"
|
||||||
|
|
||||||
|
# Run each SlackBuild with OUTPUT pointed at the shared dist/ directory,
|
||||||
|
# so every package's .txz ends up in one place regardless of the
|
||||||
|
# per-package TMP/PKG scratch dirs it uses internally.
|
||||||
|
if OUTPUT="$DIST_DIR" "$slackbuild"; then
|
||||||
|
built+=("$name")
|
||||||
|
else
|
||||||
|
echo "!! Build failed: $name" >&2
|
||||||
|
failed+=("$name")
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "============================================================"
|
||||||
|
echo "Build summary"
|
||||||
|
echo "============================================================"
|
||||||
|
echo "Succeeded (${#built[@]}): ${built[*]:-none}"
|
||||||
|
echo "Failed (${#failed[@]}): ${failed[*]:-none}"
|
||||||
|
|
||||||
|
if [ "${#failed[@]}" -gt 0 ]; then
|
||||||
|
echo
|
||||||
|
echo "!! One or more packages failed to build — see log above." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "All packages built successfully. Artifacts in $DIST_DIR:"
|
||||||
|
ls -1 "$DIST_DIR"
|
||||||
Executable
+70
@@ -0,0 +1,70 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# scripts/checksums.sh
|
||||||
|
#
|
||||||
|
# Verifies and consolidates checksums for everything in dist/. Each package
|
||||||
|
# already gets its own <file>.sha256 / <file>.md5 sidecar file from
|
||||||
|
# sb_make_package (scripts/lib/slackbuild-common.sh) at build time — this
|
||||||
|
# script:
|
||||||
|
# 1. Re-verifies every .txz against its own sidecar checksum (defense in
|
||||||
|
# depth: catches disk corruption or a tampered artifact between the
|
||||||
|
# build job and the release job in CI).
|
||||||
|
# 2. Writes a single consolidated CHECKSUMS.sha256 manifest covering all
|
||||||
|
# built packages, suitable for attaching to a GitHub Release so users
|
||||||
|
# can verify the whole set with one `sha256sum -c CHECKSUMS.sha256`.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# scripts/checksums.sh [dist-dir] # defaults to <repo>/dist
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
DIST_DIR="${1:-$REPO_ROOT/dist}"
|
||||||
|
|
||||||
|
if [ ! -d "$DIST_DIR" ]; then
|
||||||
|
echo "!! No such directory: $DIST_DIR (nothing built yet?)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
shopt -s nullglob
|
||||||
|
txz_files=("$DIST_DIR"/*.txz)
|
||||||
|
shopt -u nullglob
|
||||||
|
|
||||||
|
if [ "${#txz_files[@]}" -eq 0 ]; then
|
||||||
|
echo "!! No .txz files found in $DIST_DIR" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> Verifying per-package checksums"
|
||||||
|
verify_failed=0
|
||||||
|
for f in "${txz_files[@]}"; do
|
||||||
|
base=$(basename "$f")
|
||||||
|
sidecar="$f.sha256"
|
||||||
|
if [ ! -f "$sidecar" ]; then
|
||||||
|
echo "!! Missing $sidecar for $base" >&2
|
||||||
|
verify_failed=1
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
if ( cd "$DIST_DIR" && sha256sum -c "$(basename "$sidecar")" > /dev/null 2>&1 ); then
|
||||||
|
echo "OK $base"
|
||||||
|
else
|
||||||
|
echo "FAIL $base" >&2
|
||||||
|
verify_failed=1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$verify_failed" -ne 0 ]; then
|
||||||
|
echo "!! Checksum verification failed for one or more packages." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "==> Writing consolidated manifest: $DIST_DIR/CHECKSUMS.sha256"
|
||||||
|
( cd "$DIST_DIR" && sha256sum ./*.txz > CHECKSUMS.sha256 )
|
||||||
|
|
||||||
|
echo "==> Writing consolidated MD5 manifest: $DIST_DIR/CHECKSUMS.md5"
|
||||||
|
( cd "$DIST_DIR" && md5sum ./*.txz > CHECKSUMS.md5 )
|
||||||
|
|
||||||
|
echo "==> Done."
|
||||||
|
cat "$DIST_DIR/CHECKSUMS.sha256"
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# =============================================================================
|
||||||
|
# scripts/ci/buildenv-versions.env
|
||||||
|
#
|
||||||
|
# Version pins for the BUILD ENVIRONMENT itself — the toolchains and C
|
||||||
|
# libraries needed to compile the seven packages in packages/, but which are
|
||||||
|
# not themselves shipped as part of the plugin. Kept separate from the
|
||||||
|
# top-level versions.env, which pins only what actually gets packaged and
|
||||||
|
# installed on an Unraid system (see that file's header comment).
|
||||||
|
#
|
||||||
|
# Consumed by scripts/ci/setup-slackware-buildenv.sh.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# Go toolchain (builds podman). Official upstream tarball, not a distro
|
||||||
|
# package — Slackware ships no Go toolchain in a stock install.
|
||||||
|
GO_VERSION="1.26.5"
|
||||||
|
GO_SRC_URL="https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz"
|
||||||
|
# Checksum as published by go.dev itself (`curl -s https://go.dev/dl/?mode=json`)
|
||||||
|
# — must be re-derived from the same source whenever GO_VERSION changes.
|
||||||
|
GO_SRC_SHA256="88c162b204e6eefcc32499453b492e80209f4a4c78c33092636901c540fb0d05"
|
||||||
|
|
||||||
|
# Rust toolchain (builds netavark, aardvark-dns) — installed via rustup
|
||||||
|
# rather than a pinned tarball, since rustup itself provides reproducible,
|
||||||
|
# checksummed component installation. We pin the *channel*, not an exact
|
||||||
|
# rustc build; per-crate reproducibility comes from each Rust package's
|
||||||
|
# Cargo.lock (built with `cargo build --locked`, see the netavark/
|
||||||
|
# aardvark-dns SlackBuilds) rather than from the compiler version.
|
||||||
|
RUST_CHANNEL="stable"
|
||||||
|
RUSTUP_INIT_URL="https://sh.rustup.rs"
|
||||||
|
|
||||||
|
# --- C library build-time dependencies ---------------------------------------
|
||||||
|
# These are expected to already be present in the Slackware base image (part
|
||||||
|
# of a stock "full" Slackware 15.0 install): glib2 (conmon), libcap (crun),
|
||||||
|
# fuse3 (fuse-overlayfs). setup-slackware-buildenv.sh fails fast with a clear
|
||||||
|
# message if any of these are missing, rather than silently vendoring them.
|
||||||
|
#
|
||||||
|
# libseccomp and yajl are NOT part of a stock Slackware install and are
|
||||||
|
# built from source by setup-slackware-buildenv.sh if pkg-config doesn't
|
||||||
|
# find them.
|
||||||
|
|
||||||
|
LIBSECCOMP_VERSION="2.6.1"
|
||||||
|
LIBSECCOMP_SRC_URL="https://github.com/seccomp/libseccomp/archive/refs/tags/v${LIBSECCOMP_VERSION}.tar.gz"
|
||||||
|
LIBSECCOMP_SRC_SHA256="f9a13e4c633d319a9240189760ca348caa0837c0ebe2a09b17061da8ceaf60f0"
|
||||||
|
|
||||||
|
YAJL_VERSION="2.1.0"
|
||||||
|
YAJL_SRC_URL="https://github.com/lloyd/yajl/archive/refs/tags/${YAJL_VERSION}.tar.gz"
|
||||||
|
YAJL_SRC_SHA256="3fb73364a5a30efe615046d07e6db9d09fd2b41c763c5f7d3bfb121cd5c5ac5a"
|
||||||
Executable
+152
@@ -0,0 +1,152 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# scripts/ci/setup-slackware-buildenv.sh
|
||||||
|
#
|
||||||
|
# Prepares a Slackware container (see .github/workflows/build-packages.yml)
|
||||||
|
# to build all seven packages under packages/. Idempotent and safe to re-run.
|
||||||
|
#
|
||||||
|
# Strategy: detect what's already present (a stock "full" Slackware 15.0
|
||||||
|
# install already provides gcc/make/autotools/glib2/libcap/fuse3) and only
|
||||||
|
# bootstrap what's genuinely missing (libseccomp, yajl — neither ships in
|
||||||
|
# stock Slackware — plus the Go and Rust toolchains, which no Slackware
|
||||||
|
# install ships). This makes the script tolerant of small differences
|
||||||
|
# between Slackware base image variants instead of assuming one exact image
|
||||||
|
# layout, while still failing loudly if something we cannot self-provision
|
||||||
|
# (a C compiler, basically) is missing.
|
||||||
|
#
|
||||||
|
# Exits non-zero with a clear message if a required tool cannot be found or
|
||||||
|
# provisioned — this script is meant to run early in CI so failures surface
|
||||||
|
# immediately, not halfway through a 20-minute podman build.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$REPO_ROOT/scripts/ci/buildenv-versions.env"
|
||||||
|
|
||||||
|
WORK="/tmp/unraid-podman-buildenv"
|
||||||
|
mkdir -p "$WORK"
|
||||||
|
|
||||||
|
require_binary() {
|
||||||
|
local bin="$1" hint="$2"
|
||||||
|
if ! command -v "$bin" > /dev/null 2>&1; then
|
||||||
|
echo "!! Required tool '$bin' not found in the build image." >&2
|
||||||
|
echo "!! $hint" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "==> found $bin: $(command -v "$bin")"
|
||||||
|
}
|
||||||
|
|
||||||
|
require_pkgconfig() {
|
||||||
|
local module="$1" hint="$2"
|
||||||
|
if ! pkg-config --exists "$module" 2>/dev/null; then
|
||||||
|
echo "!! Required library '$module' not found via pkg-config." >&2
|
||||||
|
echo "!! $hint" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
echo "==> found pkg-config module: $module ($(pkg-config --modversion "$module"))"
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 1. Baseline toolchain expected to already be present in the base image.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
require_binary gcc "Use a Slackware base image with the 'D' (development) series installed."
|
||||||
|
require_binary make "Use a Slackware base image with the 'D' (development) series installed."
|
||||||
|
require_binary autoconf "Needed by crun/fuse-overlayfs; part of Slackware's 'D' series."
|
||||||
|
require_binary automake "Needed by crun/fuse-overlayfs; part of Slackware's 'D' series."
|
||||||
|
require_binary libtool "Needed by crun/fuse-overlayfs; part of Slackware's 'D' series."
|
||||||
|
require_binary pkg-config "Needed to locate C library dependencies."
|
||||||
|
require_binary git "Needed to fetch crun's git submodules."
|
||||||
|
require_binary curl "Needed to fetch pinned source tarballs."
|
||||||
|
require_binary makepkg "Slackware's own packaging tool (pkgtools); should always be present."
|
||||||
|
require_binary strip "Part of binutils; part of Slackware's 'D' series."
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 2. C library dependencies expected to already be present.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
require_pkgconfig glib-2.0 "Install Slackware's glib2 package (needed by conmon)."
|
||||||
|
require_pkgconfig libcap "Install Slackware's libcap package (needed by crun)." || true
|
||||||
|
require_pkgconfig fuse3 "Install Slackware's fuse3 package (needed by fuse-overlayfs)." || true
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 3. libseccomp — not part of stock Slackware, build from source if missing.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
if ! pkg-config --exists libseccomp 2>/dev/null; then
|
||||||
|
echo "==> libseccomp not found, building v$LIBSECCOMP_VERSION from source"
|
||||||
|
d="$WORK/libseccomp"
|
||||||
|
mkdir -p "$d"
|
||||||
|
curl -fL --retry 3 -o "$d/src.tar.gz" "$LIBSECCOMP_SRC_URL"
|
||||||
|
actual=$(sha256sum "$d/src.tar.gz" | awk '{print $1}')
|
||||||
|
[ "$actual" = "$LIBSECCOMP_SRC_SHA256" ] || {
|
||||||
|
echo "!! libseccomp checksum mismatch (expected $LIBSECCOMP_SRC_SHA256, got $actual)" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
mkdir -p "$d/src" && tar -xf "$d/src.tar.gz" -C "$d/src" --strip-components=1
|
||||||
|
( cd "$d/src" && ./autogen.sh && ./configure --prefix=/usr && make -j"$(nproc)" && make install )
|
||||||
|
else
|
||||||
|
echo "==> libseccomp already present, skipping bootstrap build"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 4. yajl — not part of stock Slackware, build from source if missing.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
if ! pkg-config --exists yajl 2>/dev/null; then
|
||||||
|
echo "==> yajl not found, building v$YAJL_VERSION from source"
|
||||||
|
d="$WORK/yajl"
|
||||||
|
mkdir -p "$d"
|
||||||
|
curl -fL --retry 3 -o "$d/src.tar.gz" "$YAJL_SRC_URL"
|
||||||
|
actual=$(sha256sum "$d/src.tar.gz" | awk '{print $1}')
|
||||||
|
[ "$actual" = "$YAJL_SRC_SHA256" ] || {
|
||||||
|
echo "!! yajl checksum mismatch (expected $YAJL_SRC_SHA256, got $actual)" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
mkdir -p "$d/src" && tar -xf "$d/src.tar.gz" -C "$d/src" --strip-components=1
|
||||||
|
# yajl uses its own cmake-free ./configure wrapper script.
|
||||||
|
( cd "$d/src" && ./configure -p /usr && make -C build install )
|
||||||
|
ldconfig 2>/dev/null || true
|
||||||
|
else
|
||||||
|
echo "==> yajl already present, skipping bootstrap build"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 5. Go toolchain (podman) — official upstream tarball.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
if ! command -v go > /dev/null 2>&1; then
|
||||||
|
echo "==> Go not found, installing $GO_VERSION"
|
||||||
|
curl -fL --retry 3 -o "$WORK/go.tar.gz" "$GO_SRC_URL"
|
||||||
|
actual=$(sha256sum "$WORK/go.tar.gz" | awk '{print $1}')
|
||||||
|
[ "$actual" = "$GO_SRC_SHA256" ] || {
|
||||||
|
echo "!! Go toolchain checksum mismatch (expected $GO_SRC_SHA256, got $actual)" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
rm -rf /usr/local/go
|
||||||
|
tar -C /usr/local -xf "$WORK/go.tar.gz"
|
||||||
|
export PATH="/usr/local/go/bin:$PATH"
|
||||||
|
# Persist PATH for subsequent steps in the same GitHub Actions job.
|
||||||
|
if [ -n "${GITHUB_PATH:-}" ]; then
|
||||||
|
echo "/usr/local/go/bin" >> "$GITHUB_PATH"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "==> Go already present: $(go version)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# 6. Rust toolchain (netavark, aardvark-dns) — via rustup.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
if ! command -v cargo > /dev/null 2>&1; then
|
||||||
|
echo "==> Rust/cargo not found, installing via rustup ($RUST_CHANNEL channel)"
|
||||||
|
curl -fL --retry 3 --proto '=https' --tlsv1.2 -sSf "$RUSTUP_INIT_URL" \
|
||||||
|
| sh -s -- -y --default-toolchain "$RUST_CHANNEL" --profile minimal
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
. "$HOME/.cargo/env"
|
||||||
|
if [ -n "${GITHUB_PATH:-}" ]; then
|
||||||
|
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "==> Rust already present: $(cargo --version)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "==> Build environment ready."
|
||||||
Executable
+34
@@ -0,0 +1,34 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# scripts/dev/lint.sh
|
||||||
|
#
|
||||||
|
# Local developer entry point mirroring .github/workflows/lint.yml: runs
|
||||||
|
# ShellCheck over shell scripts/SlackBuilds and xmllint over plugin/podman.plg.
|
||||||
|
# Requires `shellcheck` and `xmllint` (libxml2) to be installed locally.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
cd "$REPO_ROOT"
|
||||||
|
|
||||||
|
status=0
|
||||||
|
|
||||||
|
if command -v shellcheck > /dev/null 2>&1; then
|
||||||
|
echo "==> ShellCheck"
|
||||||
|
find plugin/rc.d plugin/sbin scripts -type f \
|
||||||
|
\( -name '*.sh' -o -name 'rc.*' -o -name '*.SlackBuild' \) \
|
||||||
|
-print0 \
|
||||||
|
| xargs -0 shellcheck --severity=warning --external-sources || status=1
|
||||||
|
else
|
||||||
|
echo "!! shellcheck not installed, skipping (install it to match CI: https://www.shellcheck.net/)" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v xmllint > /dev/null 2>&1; then
|
||||||
|
echo "==> xmllint (plugin/podman.plg)"
|
||||||
|
xmllint --noout plugin/podman.plg || status=1
|
||||||
|
else
|
||||||
|
echo "!! xmllint not installed, skipping (part of libxml2-utils)" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit "$status"
|
||||||
Executable
+176
@@ -0,0 +1,176 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# scripts/lib/slackbuild-common.sh
|
||||||
|
#
|
||||||
|
# Shared helper functions sourced by every packages/*/*.SlackBuild script.
|
||||||
|
# Centralizing this logic means each individual SlackBuild only has to
|
||||||
|
# describe *how to compile* its component — fetching, checksum verification,
|
||||||
|
# and final .txz packaging are implemented once, here, and used the same way
|
||||||
|
# by all seven packages. This is what keeps the seven build scripts
|
||||||
|
# consistent and short instead of each reinventing (and potentially
|
||||||
|
# forgetting) checksum verification or Slackware package metadata.
|
||||||
|
#
|
||||||
|
# Every SlackBuild is expected to:
|
||||||
|
# 1. `. "$(dirname "$0")/../../scripts/lib/slackbuild-common.sh"`
|
||||||
|
# 2. `. "$(dirname "$0")/../../versions.env"`
|
||||||
|
# 3. Call sb_init, sb_fetch_and_verify, do its own compile steps into
|
||||||
|
# $PKG, then call sb_make_package.
|
||||||
|
#
|
||||||
|
# Not meant to be run directly.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# sb_init <pkg-name>
|
||||||
|
#
|
||||||
|
# Sets up the standard SlackBuild working directories and exports the
|
||||||
|
# variables every subsequent helper (and the calling SlackBuild) relies on:
|
||||||
|
# CWD - directory the SlackBuild itself lives in (packages/<name>/)
|
||||||
|
# TMP - scratch/build directory (source is extracted and compiled here)
|
||||||
|
# PKG - staging directory that becomes the .txz payload
|
||||||
|
# OUTPUT - where the finished .txz + checksum files are written
|
||||||
|
# All three of TMP/PKG/OUTPUT are safe to delete/recreate; nothing outside
|
||||||
|
# of them is ever touched, and nothing under them is ever committed to git
|
||||||
|
# (see .gitignore: packages/**/src, packages/**/pkg, packages/**/work).
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
sb_init() {
|
||||||
|
local pkg_name="$1"
|
||||||
|
|
||||||
|
PRGNAM="$pkg_name"
|
||||||
|
CWD="$(cd "$(dirname "${BASH_SOURCE[1]}")" && pwd)"
|
||||||
|
TMP="${TMP:-$CWD/work}"
|
||||||
|
PKG="${PKG:-$TMP/package-$PRGNAM}"
|
||||||
|
OUTPUT="${OUTPUT:-$CWD/../../dist}"
|
||||||
|
|
||||||
|
export PRGNAM CWD TMP PKG OUTPUT
|
||||||
|
|
||||||
|
rm -rf "$TMP"
|
||||||
|
mkdir -p "$TMP" "$PKG" "$OUTPUT"
|
||||||
|
|
||||||
|
echo "==> [$PRGNAM] TMP=$TMP"
|
||||||
|
echo "==> [$PRGNAM] PKG=$PKG"
|
||||||
|
echo "==> [$PRGNAM] OUTPUT=$OUTPUT"
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# sb_fetch_and_verify <url> <expected-sha256> <dest-filename>
|
||||||
|
#
|
||||||
|
# Downloads a source tarball into $TMP and verifies it against the SHA256
|
||||||
|
# pinned in versions.env before anything is extracted or built. Aborts the
|
||||||
|
# build loudly on any mismatch — a checksum mismatch means either
|
||||||
|
# versions.env is stale (a legitimate new upstream release) or the source is
|
||||||
|
# not what it claims to be; either way, an unreviewed build must not proceed.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
sb_fetch_and_verify() {
|
||||||
|
local url="$1"
|
||||||
|
local expected_sha256="$2"
|
||||||
|
local dest_name="$3"
|
||||||
|
local dest_path="$TMP/$dest_name"
|
||||||
|
|
||||||
|
echo "==> [$PRGNAM] Fetching $url"
|
||||||
|
curl -fL --retry 3 --retry-delay 2 -o "$dest_path" "$url"
|
||||||
|
|
||||||
|
local actual_sha256
|
||||||
|
actual_sha256=$(sha256sum "$dest_path" | awk '{print $1}')
|
||||||
|
|
||||||
|
if [ "$actual_sha256" != "$expected_sha256" ]; then
|
||||||
|
echo "!! [$PRGNAM] SHA256 MISMATCH for $dest_name" >&2
|
||||||
|
echo "!! expected: $expected_sha256" >&2
|
||||||
|
echo "!! actual: $actual_sha256" >&2
|
||||||
|
echo "!! Refusing to build against unverified source. If upstream" >&2
|
||||||
|
echo "!! genuinely released a new version, update versions.env via" >&2
|
||||||
|
echo "!! scripts/update-versions.sh instead of editing the hash by hand." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> [$PRGNAM] SHA256 verified ($actual_sha256)"
|
||||||
|
echo "$dest_path"
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# sb_extract <tarball-path> [strip-components]
|
||||||
|
#
|
||||||
|
# Extracts a verified tarball into $TMP/src, normalizing away the
|
||||||
|
# "<repo>-<version>/" wrapper directory GitHub/cgit archives always contain,
|
||||||
|
# so every SlackBuild can `cd "$TMP/src"` regardless of upstream's archive
|
||||||
|
# layout.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
sb_extract() {
|
||||||
|
local tarball="$1"
|
||||||
|
local strip="${2:-1}"
|
||||||
|
|
||||||
|
mkdir -p "$TMP/src"
|
||||||
|
tar -xf "$tarball" -C "$TMP/src" --strip-components="$strip"
|
||||||
|
echo "$TMP/src"
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# sb_install_docs <license-file> [readme-file]
|
||||||
|
#
|
||||||
|
# Installs upstream's license (and optionally README) into the standard
|
||||||
|
# Slackware package documentation location, plus this project's own build
|
||||||
|
# metadata, so `installpkg` output and /usr/doc/<pkg>-<version>/ are
|
||||||
|
# populated per Slackware convention.
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
sb_install_docs() {
|
||||||
|
local license_file="$1"
|
||||||
|
local readme_file="${2:-}"
|
||||||
|
local docdir="$PKG/usr/doc/$PRGNAM-$VERSION"
|
||||||
|
|
||||||
|
mkdir -p "$docdir"
|
||||||
|
[ -f "$license_file" ] && cp "$license_file" "$docdir/"
|
||||||
|
[ -n "$readme_file" ] && [ -f "$readme_file" ] && cp "$readme_file" "$docdir/"
|
||||||
|
|
||||||
|
{
|
||||||
|
echo "Built by unraid-podman from upstream source."
|
||||||
|
echo "Package: $PRGNAM $VERSION"
|
||||||
|
echo "Built: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||||
|
} > "$docdir/unraid-podman.build-info"
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# sb_make_package
|
||||||
|
#
|
||||||
|
# Finalizes the Slackware package: installs slack-desc + doinst.sh (if
|
||||||
|
# present) into $PKG/install/, strips binaries, sets root:root ownership,
|
||||||
|
# runs makepkg to produce the .txz, then writes SHA256 + MD5 sidecar files
|
||||||
|
# next to it.
|
||||||
|
#
|
||||||
|
# - SHA256 is used by scripts/build-packages.sh / CI to verify build
|
||||||
|
# artifacts between jobs.
|
||||||
|
# - MD5 is additionally generated because Unraid's .plg <FILE> mechanism
|
||||||
|
# verifies downloads via MD5 by convention (see
|
||||||
|
# docs/ARCHITECTURE.md section 3.2) — scripts/release.sh reads the .md5
|
||||||
|
# file to populate plugin/podman.plg's entities.
|
||||||
|
#
|
||||||
|
# Produces: $OUTPUT/<name>-<version>-<arch>-<build><tag>.txz (+ .sha256, .md5)
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
sb_make_package() {
|
||||||
|
local version="$1"
|
||||||
|
local arch="$2"
|
||||||
|
local build="$3"
|
||||||
|
local tag="$4"
|
||||||
|
|
||||||
|
mkdir -p "$PKG/install"
|
||||||
|
if [ -f "$CWD/slack-desc" ]; then
|
||||||
|
cp "$CWD/slack-desc" "$PKG/install/slack-desc"
|
||||||
|
else
|
||||||
|
echo "!! [$PRGNAM] missing packages/$PRGNAM/slack-desc" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
[ -f "$CWD/doinst.sh" ] && cp "$CWD/doinst.sh" "$PKG/install/doinst.sh"
|
||||||
|
|
||||||
|
# Strip debug symbols where possible to keep package size down; ignore
|
||||||
|
# failures (e.g. static/stripped-already binaries, non-ELF files).
|
||||||
|
find "$PKG" -type f \( -perm -u+x -o -name '*.so*' \) -exec sh -c \
|
||||||
|
'file "$1" | grep -q ELF && strip --strip-unneeded "$1" 2>/dev/null || true' _ {} \;
|
||||||
|
|
||||||
|
local pkg_file="$PRGNAM-$version-$arch-$build$tag.txz"
|
||||||
|
( cd "$PKG" && makepkg --linkadd y --chown y "$OUTPUT/$pkg_file" )
|
||||||
|
|
||||||
|
( cd "$OUTPUT" && sha256sum "$pkg_file" > "$pkg_file.sha256" )
|
||||||
|
( cd "$OUTPUT" && md5sum "$pkg_file" > "$pkg_file.md5" )
|
||||||
|
|
||||||
|
echo "==> [$PRGNAM] built $OUTPUT/$pkg_file"
|
||||||
|
}
|
||||||
Executable
+146
@@ -0,0 +1,146 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# scripts/release.sh
|
||||||
|
#
|
||||||
|
# Cuts a release of the plugin itself:
|
||||||
|
# 1. Bumps the &version; entity in plugin/podman.plg to <new-version>.
|
||||||
|
# 2. Builds all seven packages (scripts/build-packages.sh) unless
|
||||||
|
# SKIP_BUILD=1 is set (useful when CI already built them in a prior job
|
||||||
|
# and only wants this script to do the .plg/CHANGELOG bookkeeping).
|
||||||
|
# 3. Verifies + consolidates checksums (scripts/checksums.sh).
|
||||||
|
# 4. Rewrites the per-package <!ENTITY ..._txz_version/_txz_file/_txz_md5>
|
||||||
|
# entities in plugin/podman.plg from the freshly built dist/*.txz.md5
|
||||||
|
# sidecar files, and the &baseURL; entity to point at the GitHub
|
||||||
|
# Release that will hold these assets.
|
||||||
|
# 5. Moves CHANGELOG.md's [Unreleased] section under a new dated heading.
|
||||||
|
#
|
||||||
|
# This script deliberately does NOT `git commit`, `git tag`, or `gh release
|
||||||
|
# create` — it only prepares files. Committing/tagging/publishing is left to
|
||||||
|
# the caller (locally) or to .github/workflows/release.yml (in CI), so a
|
||||||
|
# human always reviews the diff before anything becomes public. See
|
||||||
|
# docs/ARCHITECTURE.md section 13 (Updates).
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# scripts/release.sh <new-version> # e.g. scripts/release.sh 0.2.0
|
||||||
|
#
|
||||||
|
# Env vars:
|
||||||
|
# SKIP_BUILD=1 Skip scripts/build-packages.sh (assume dist/ already built)
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
PLG_FILE="$REPO_ROOT/plugin/podman.plg"
|
||||||
|
CHANGELOG_FILE="$REPO_ROOT/CHANGELOG.md"
|
||||||
|
DIST_DIR="$REPO_ROOT/dist"
|
||||||
|
|
||||||
|
NEW_VERSION="${1:-}"
|
||||||
|
if [ -z "$NEW_VERSION" ]; then
|
||||||
|
echo "usage: $0 <new-version>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [[ "$NEW_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||||
|
echo "!! <new-version> must be plain SemVer (X.Y.Z), got: $NEW_VERSION" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# The GitHub Release tag/URL this release's assets will be published under.
|
||||||
|
# Must match whatever .github/workflows/release.yml actually creates the
|
||||||
|
# release as (tag "v<version>") — see that workflow for the release job.
|
||||||
|
RELEASE_TAG="v$NEW_VERSION"
|
||||||
|
REPO_SLUG="${GITHUB_REPOSITORY:-OWNER/unraid-podman}"
|
||||||
|
RELEASE_BASE_URL="https://github.com/$REPO_SLUG/releases/download/$RELEASE_TAG"
|
||||||
|
|
||||||
|
# Component name -> the entity name prefix used in podman.plg. Must match
|
||||||
|
# plugin/podman.plg's <!ENTITY NAME_txz_...> declarations exactly.
|
||||||
|
# unraid-podman is the plugin's own scaffolding package (see
|
||||||
|
# packages/unraid-podman/README.md), not an upstream component, but it's
|
||||||
|
# released and entity-updated exactly like the other seven.
|
||||||
|
COMPONENTS=(podman conmon crun netavark aardvark-dns passt fuse-overlayfs unraid-podman)
|
||||||
|
|
||||||
|
echo "==> Releasing unraid-podman plugin v$NEW_VERSION (packages tag: $RELEASE_TAG)"
|
||||||
|
|
||||||
|
# --- 1. Build ----------------------------------------------------------------
|
||||||
|
if [ "${SKIP_BUILD:-0}" != "1" ]; then
|
||||||
|
echo "==> Building all packages"
|
||||||
|
"$REPO_ROOT/scripts/build-packages.sh"
|
||||||
|
else
|
||||||
|
echo "==> SKIP_BUILD=1, assuming $DIST_DIR is already populated"
|
||||||
|
fi
|
||||||
|
|
||||||
|
"$REPO_ROOT/scripts/checksums.sh" "$DIST_DIR"
|
||||||
|
|
||||||
|
# --- 2. Update plugin version entity ------------------------------------------
|
||||||
|
echo "==> Setting <!ENTITY version> to $NEW_VERSION in $PLG_FILE"
|
||||||
|
sed -i -E "s|(<!ENTITY version[[:space:]]+\")[^\"]*(\">)|\1${NEW_VERSION}\2|" "$PLG_FILE"
|
||||||
|
sed -i -E "s|(<!ENTITY baseURL[[:space:]]+\")[^\"]*(\">)|\1${RELEASE_BASE_URL}\2|" "$PLG_FILE"
|
||||||
|
|
||||||
|
# --- 3. Update per-package entities from dist/*.txz.md5 -----------------------
|
||||||
|
for name in "${COMPONENTS[@]}"; do
|
||||||
|
# dist/ contains files like podman-6.0.1-x86_64-1_unraidpodman.txz — find
|
||||||
|
# the one for this component (there should be exactly one per release).
|
||||||
|
txz_path=$(find "$DIST_DIR" -maxdepth 1 -name "${name}-*-*-*.txz" | head -n1)
|
||||||
|
if [ -z "$txz_path" ]; then
|
||||||
|
echo "!! No built .txz found for component '$name' in $DIST_DIR" >&2
|
||||||
|
echo "!! Did scripts/build-packages.sh run successfully for it?" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
txz_file=$(basename "$txz_path")
|
||||||
|
md5_path="$txz_path.md5"
|
||||||
|
if [ ! -f "$md5_path" ]; then
|
||||||
|
echo "!! Missing $md5_path" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
md5=$(awk '{print $1}' "$md5_path")
|
||||||
|
|
||||||
|
# Component version is everything between "<name>-" and the next "-<arch>-"
|
||||||
|
# e.g. "podman-6.0.1-x86_64-1_unraidpodman.txz" -> "6.0.1"
|
||||||
|
txz_version=$(echo "$txz_file" | sed -E "s/^${name}-(.+)-[^-]+-[0-9]+[^-]*\.txz\$/\1/")
|
||||||
|
|
||||||
|
entity_prefix=$(echo "$name" | tr '-' '_')
|
||||||
|
|
||||||
|
echo "==> [$name] $txz_file (md5=$md5)"
|
||||||
|
sed -i -E "s|(<!ENTITY ${entity_prefix}_txz_version[[:space:]]+\")[^\"]*(\">)|\1${txz_version}\2|" "$PLG_FILE"
|
||||||
|
sed -i -E "s|(<!ENTITY ${entity_prefix}_txz_file[[:space:]]+\")[^\"]*(\">)|\1${txz_file}\2|" "$PLG_FILE"
|
||||||
|
sed -i -E "s|(<!ENTITY ${entity_prefix}_txz_md5[[:space:]]+\")[^\"]*(\">)|\1${md5}\2|" "$PLG_FILE"
|
||||||
|
done
|
||||||
|
|
||||||
|
# --- 4. Update CHANGELOG.md ---------------------------------------------------
|
||||||
|
# Pure awk (no python/perl dependency — this script also has to run inside
|
||||||
|
# the minimal Slackware build container, see .github/workflows/release.yml):
|
||||||
|
# inserts a fresh "## [<version>] - <date>" heading right after the existing
|
||||||
|
# "## [Unreleased]" marker, leaving [Unreleased] itself empty and at the top
|
||||||
|
# for the next round of changes.
|
||||||
|
echo "==> Moving CHANGELOG.md [Unreleased] section under [$NEW_VERSION]"
|
||||||
|
TODAY=$(date -u +%Y-%m-%d)
|
||||||
|
|
||||||
|
if ! grep -q '^## \[Unreleased\]$' "$CHANGELOG_FILE"; then
|
||||||
|
echo "!! No '## [Unreleased]' heading found in $CHANGELOG_FILE" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
awk -v ver="$NEW_VERSION" -v date="$TODAY" '
|
||||||
|
!done && $0 == "## [Unreleased]" {
|
||||||
|
print $0
|
||||||
|
print ""
|
||||||
|
print "## [" ver "] - " date
|
||||||
|
done = 1
|
||||||
|
next
|
||||||
|
}
|
||||||
|
{ print $0 }
|
||||||
|
' "$CHANGELOG_FILE" > "$CHANGELOG_FILE.tmp"
|
||||||
|
mv "$CHANGELOG_FILE.tmp" "$CHANGELOG_FILE"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "==> Release preparation complete for v$NEW_VERSION."
|
||||||
|
echo "==> Review the diff, then:"
|
||||||
|
echo " git add plugin/podman.plg CHANGELOG.md"
|
||||||
|
echo " git commit -m \"release: v$NEW_VERSION\""
|
||||||
|
echo " git tag $RELEASE_TAG"
|
||||||
|
echo " git push && git push origin $RELEASE_TAG"
|
||||||
|
echo "==> Pushing the tag triggers .github/workflows/release.yml, which"
|
||||||
|
echo "==> rebuilds artifacts in CI (for reproducibility/provenance) and"
|
||||||
|
echo "==> publishes the GitHub Release with dist/*.txz + CHECKSUMS.* + the"
|
||||||
|
echo "==> updated podman.plg attached."
|
||||||
Executable
+151
@@ -0,0 +1,151 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# =============================================================================
|
||||||
|
# scripts/update-versions.sh
|
||||||
|
#
|
||||||
|
# Re-pins one (or all) upstream components in versions.env to their current
|
||||||
|
# latest release, recomputing the SHA256 checksum against the freshly
|
||||||
|
# downloaded source tarball. This is the ONLY supported way to change a
|
||||||
|
# version/checksum pair in versions.env — never hand-edit a checksum, since
|
||||||
|
# that defeats the entire point of pinning it (see the header comment in
|
||||||
|
# versions.env).
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# scripts/update-versions.sh # check/update all components
|
||||||
|
# scripts/update-versions.sh podman crun # only these components
|
||||||
|
#
|
||||||
|
# This script only rewrites versions.env. It does not build anything, and it
|
||||||
|
# does not commit — review the diff (`git diff versions.env`) before
|
||||||
|
# committing, ideally by also running a build to confirm the new source
|
||||||
|
# still compiles (scripts/build-packages.sh <name>).
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
VERSIONS_FILE="$REPO_ROOT/versions.env"
|
||||||
|
|
||||||
|
# Maps our internal component name -> GitHub "owner/repo", for every
|
||||||
|
# component that actually has GitHub-tagged releases. passt is handled
|
||||||
|
# separately below (see versions.env for why).
|
||||||
|
declare -A GITHUB_REPO=(
|
||||||
|
[podman]="containers/podman"
|
||||||
|
[conmon]="containers/conmon"
|
||||||
|
[crun]="containers/crun"
|
||||||
|
[netavark]="containers/netavark"
|
||||||
|
[aardvark-dns]="containers/aardvark-dns"
|
||||||
|
[fuse-overlayfs]="containers/fuse-overlayfs"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Maps our internal component name -> the *_VERSION variable prefix used in
|
||||||
|
# versions.env (uppercased, hyphens -> underscores).
|
||||||
|
env_prefix() {
|
||||||
|
echo "$1" | tr '[:lower:]-' '[:upper:]_'
|
||||||
|
}
|
||||||
|
|
||||||
|
update_github_component() {
|
||||||
|
local name="$1"
|
||||||
|
local repo="${GITHUB_REPO[$name]}"
|
||||||
|
local prefix
|
||||||
|
prefix=$(env_prefix "$name")
|
||||||
|
|
||||||
|
echo "==> [$name] checking latest release for $repo"
|
||||||
|
local api_response
|
||||||
|
api_response=$(curl -sL --max-time 15 "https://api.github.com/repos/$repo/releases/latest")
|
||||||
|
|
||||||
|
local tag
|
||||||
|
tag=$(echo "$api_response" | grep -m1 '"tag_name"' | sed -E 's/.*"tag_name":[[:space:]]*"([^"]+)".*/\1/')
|
||||||
|
|
||||||
|
if [ -z "$tag" ]; then
|
||||||
|
echo "!! [$name] could not determine latest tag (rate-limited or repo has no releases?)" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Strip a leading "v" for the version we store, but keep it for the URL
|
||||||
|
# since GitHub tags for these projects are inconsistent about it (crun
|
||||||
|
# tags plain "1.28", others tag "v1.28").
|
||||||
|
local version="${tag#v}"
|
||||||
|
local url="https://github.com/$repo/archive/refs/tags/$tag.tar.gz"
|
||||||
|
|
||||||
|
echo "==> [$name] latest = $version, downloading to verify + checksum"
|
||||||
|
local tmpfile
|
||||||
|
tmpfile=$(mktemp)
|
||||||
|
curl -fL --max-time 120 -o "$tmpfile" "$url"
|
||||||
|
local sha256
|
||||||
|
sha256=$(sha256sum "$tmpfile" | awk '{print $1}')
|
||||||
|
rm -f "$tmpfile"
|
||||||
|
|
||||||
|
echo "==> [$name] sha256=$sha256"
|
||||||
|
apply_update "$prefix" "$version" "$url" "$sha256"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply_update() {
|
||||||
|
local prefix="$1" version="$2" url="$3" sha256="$4"
|
||||||
|
|
||||||
|
# In-place rewrite of the three lines for this component. Using distinct
|
||||||
|
# sed expressions per variable (rather than one blanket substitution)
|
||||||
|
# keeps this safe even if variable order in versions.env changes.
|
||||||
|
sed -i \
|
||||||
|
-e "s|^${prefix}_VERSION=.*|${prefix}_VERSION=\"${version}\"|" \
|
||||||
|
-e "s|^${prefix}_SRC_SHA256=.*|${prefix}_SRC_SHA256=\"${sha256}\"|" \
|
||||||
|
"$VERSIONS_FILE"
|
||||||
|
|
||||||
|
# The *_SRC_URL line is templated against *_VERSION (e.g.
|
||||||
|
# ".../v${PODMAN_VERSION}.tar.gz") in most cases, so it doesn't need
|
||||||
|
# rewriting — only touch it if it isn't already parameterized.
|
||||||
|
if ! grep -q "^${prefix}_SRC_URL=.*\${${prefix}_VERSION}" "$VERSIONS_FILE" \
|
||||||
|
&& ! grep -q "^${prefix}_SRC_URL=.*\$${prefix}_VERSION" "$VERSIONS_FILE"; then
|
||||||
|
sed -i -e "s|^${prefix}_SRC_URL=.*|${prefix}_SRC_URL=\"${url}\"|" "$VERSIONS_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "==> updated ${prefix}_VERSION / ${prefix}_SRC_SHA256 in $VERSIONS_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
update_passt() {
|
||||||
|
echo "==> [passt] checking latest master commit at https://passt.top/passt/"
|
||||||
|
local atom
|
||||||
|
atom=$(curl -sL --max-time 15 "https://passt.top/passt/atom/?h=master")
|
||||||
|
local commit
|
||||||
|
commit=$(echo "$atom" | grep -m1 -oE '<id>[a-f0-9]{40}</id>' | sed -E 's/<\/?id>//g')
|
||||||
|
|
||||||
|
if [ -z "$commit" ]; then
|
||||||
|
echo "!! [passt] could not determine latest commit" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local url="https://passt.top/passt/snapshot/passt-${commit}.tar.gz"
|
||||||
|
echo "==> [passt] latest commit = $commit, downloading to verify + checksum"
|
||||||
|
local tmpfile
|
||||||
|
tmpfile=$(mktemp)
|
||||||
|
curl -fL --max-time 120 -o "$tmpfile" "$url"
|
||||||
|
local sha256
|
||||||
|
sha256=$(sha256sum "$tmpfile" | awk '{print $1}')
|
||||||
|
rm -f "$tmpfile"
|
||||||
|
|
||||||
|
sed -i \
|
||||||
|
-e "s|^PASST_COMMIT=.*|PASST_COMMIT=\"${commit}\"|" \
|
||||||
|
-e "s|^PASST_VERSION=.*|PASST_VERSION=\"git${commit:0:7}\"|" \
|
||||||
|
-e "s|^PASST_SRC_SHA256=.*|PASST_SRC_SHA256=\"${sha256}\"|" \
|
||||||
|
"$VERSIONS_FILE"
|
||||||
|
|
||||||
|
echo "==> updated PASST_COMMIT / PASST_VERSION / PASST_SRC_SHA256 in $VERSIONS_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
requested=("$@")
|
||||||
|
if [ "${#requested[@]}" -eq 0 ]; then
|
||||||
|
requested=("${!GITHUB_REPO[@]}" passt)
|
||||||
|
fi
|
||||||
|
|
||||||
|
for name in "${requested[@]}"; do
|
||||||
|
if [ "$name" = "passt" ]; then
|
||||||
|
update_passt
|
||||||
|
elif [ -n "${GITHUB_REPO[$name]:-}" ]; then
|
||||||
|
update_github_component "$name"
|
||||||
|
else
|
||||||
|
echo "!! Unknown component: $name" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "==> Done. Review the diff before committing:"
|
||||||
|
echo " git -C \"$REPO_ROOT\" diff versions.env"
|
||||||
Executable
+21
@@ -0,0 +1,21 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# version-bump.sh — bumps the plugin version entity in plugin/podman.plg
|
||||||
|
# without performing a full release (useful for pre-release testing builds).
|
||||||
|
#
|
||||||
|
# Usage (planned): scripts/version-bump.sh <new-version>
|
||||||
|
#
|
||||||
|
# STATUS: placeholder skeleton, not yet functional.
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
NEW_VERSION="${1:-}"
|
||||||
|
if [ -z "$NEW_VERSION" ]; then
|
||||||
|
echo "usage: $0 <new-version>" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# TODO: update <!ENTITY version "..."> in plugin/podman.plg
|
||||||
|
|
||||||
|
echo "TODO: implement version-bump.sh for version $NEW_VERSION"
|
||||||
|
exit 1
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# =============================================================================
|
||||||
|
# versions.env — single source of truth for upstream component versions.
|
||||||
|
#
|
||||||
|
# Every SlackBuild under packages/*/ and the orchestrator
|
||||||
|
# (scripts/build-packages.sh) source this file instead of hardcoding a
|
||||||
|
# version or URL. This is what makes the build reproducible: given the same
|
||||||
|
# versions.env, the same source tarballs (verified by SHA256) are fetched and
|
||||||
|
# built, every time.
|
||||||
|
#
|
||||||
|
# To bump a component's version, run scripts/update-versions.sh <component>
|
||||||
|
# (fetches the new upstream release, recomputes the checksum, rewrites the
|
||||||
|
# corresponding block below) rather than editing hashes by hand.
|
||||||
|
#
|
||||||
|
# SHA256 sums below were computed directly against the upstream source
|
||||||
|
# tarball/snapshot at the time of pinning (see the fetch command in each
|
||||||
|
# comment). GitHub's auto-generated "archive/refs/tags" tarballs are stable
|
||||||
|
# in practice but are NOT cryptographically signed by upstream — treat this
|
||||||
|
# checksum as tamper-evidence against a compromised mirror/CDN, not as a
|
||||||
|
# replacement for verifying upstream's own release signing where available.
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# --- podman ------------------------------------------------------------------
|
||||||
|
# https://github.com/containers/podman
|
||||||
|
PODMAN_VERSION="6.0.1"
|
||||||
|
PODMAN_SRC_URL="https://github.com/containers/podman/archive/refs/tags/v${PODMAN_VERSION}.tar.gz"
|
||||||
|
PODMAN_SRC_SHA256="4829d7c1423523a6a4d5537dea7968ae7f6c22ed7f1d5f416638fd81c83caa47"
|
||||||
|
|
||||||
|
# --- conmon --------------------------------------------------------------
|
||||||
|
# https://github.com/containers/conmon
|
||||||
|
CONMON_VERSION="2.2.1"
|
||||||
|
CONMON_SRC_URL="https://github.com/containers/conmon/archive/refs/tags/v${CONMON_VERSION}.tar.gz"
|
||||||
|
CONMON_SRC_SHA256="814fb5979a3a4b8576b1f901e606b482bebb41cb7e57926e6d5765ee786b96d3"
|
||||||
|
|
||||||
|
# --- crun ----------------------------------------------------------------
|
||||||
|
# https://github.com/containers/crun
|
||||||
|
CRUN_VERSION="1.28"
|
||||||
|
CRUN_SRC_URL="https://github.com/containers/crun/archive/refs/tags/${CRUN_VERSION}.tar.gz"
|
||||||
|
CRUN_SRC_SHA256="90284c7f097f8ee72a6447978c263e1b1355727c2f2ca0ac667e6d57788f46f5"
|
||||||
|
|
||||||
|
# --- netavark --------------------------------------------------------------
|
||||||
|
# https://github.com/containers/netavark
|
||||||
|
NETAVARK_VERSION="2.0.0"
|
||||||
|
NETAVARK_SRC_URL="https://github.com/containers/netavark/archive/refs/tags/v${NETAVARK_VERSION}.tar.gz"
|
||||||
|
NETAVARK_SRC_SHA256="031aeeacc930382e8635d40a885798eff1da164dfcf9024b698f822e5995d9c8"
|
||||||
|
|
||||||
|
# --- aardvark-dns ----------------------------------------------------------
|
||||||
|
# https://github.com/containers/aardvark-dns
|
||||||
|
AARDVARK_DNS_VERSION="2.0.0"
|
||||||
|
AARDVARK_DNS_SRC_URL="https://github.com/containers/aardvark-dns/archive/refs/tags/v${AARDVARK_DNS_VERSION}.tar.gz"
|
||||||
|
AARDVARK_DNS_SRC_SHA256="d3f5d6b3be3c2d80e8257fb9467e34ff104f299474427979454034dca6dc88cc"
|
||||||
|
|
||||||
|
# --- fuse-overlayfs --------------------------------------------------------
|
||||||
|
# https://github.com/containers/fuse-overlayfs
|
||||||
|
FUSE_OVERLAYFS_VERSION="1.17"
|
||||||
|
FUSE_OVERLAYFS_SRC_URL="https://github.com/containers/fuse-overlayfs/archive/refs/tags/v${FUSE_OVERLAYFS_VERSION}.tar.gz"
|
||||||
|
FUSE_OVERLAYFS_SRC_SHA256="cefffecfbb001b2784f19af344f27eae07b31a4faa38d345b738af96b2bec59e"
|
||||||
|
|
||||||
|
# --- passt -------------------------------------------------------------------
|
||||||
|
# https://passt.top/passt/about/ — "Plug A Simple Socket Transport". Podman's
|
||||||
|
# modern (post-slirp4netns) rootless network transport. Upstream has NO
|
||||||
|
# GitHub mirror and no semver tags; it is released continuously from the
|
||||||
|
# cgit-hosted git repository at https://passt.top/passt/, identified by full
|
||||||
|
# git commit hash. We pin to a specific commit snapshot for reproducibility,
|
||||||
|
# fetched via cgit's snapshot endpoint:
|
||||||
|
# https://passt.top/passt/snapshot/passt-<commit>.tar.gz
|
||||||
|
PASST_COMMIT="6ef3d1c86ffc690a17a9a4445df4a741446bcd44"
|
||||||
|
PASST_VERSION="git${PASST_COMMIT:0:7}"
|
||||||
|
PASST_SRC_URL="https://passt.top/passt/snapshot/passt-${PASST_COMMIT}.tar.gz"
|
||||||
|
PASST_SRC_SHA256="4c58a77504a77d613464dddf22ae69d749a5ba64cb87e44c3b8c252333e209fc"
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# Slackware package BUILD number (not upstream version). Bump this if a
|
||||||
|
# package must be rebuilt without an upstream version change (e.g. a
|
||||||
|
# packaging-only fix). Reset to 1 whenever *_VERSION above changes.
|
||||||
|
# =============================================================================
|
||||||
|
PKG_BUILD="1"
|
||||||
|
|
||||||
|
# Slackware package architecture. Unraid is x86_64-only today; kept as a
|
||||||
|
# variable rather than hardcoded so the build scripts don't need a second
|
||||||
|
# source of truth if that ever changes.
|
||||||
|
PKG_ARCH="x86_64"
|
||||||
|
|
||||||
|
# Suffix appended to every package's tag field (Slackware convention:
|
||||||
|
# <name>-<version>-<arch>-<build><tag>.txz), identifies packages built by
|
||||||
|
# this project as opposed to a stock Slackware/SBo package of the same name.
|
||||||
|
PKG_TAG="_unraidpodman"
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
# webui/
|
||||||
|
|
||||||
|
Dynamix-style WebUI pages, following Unraid's plugin GUI convention of
|
||||||
|
`/usr/local/emhttp/plugins/<name>/`. `webui/plugins/podman/` is staged to
|
||||||
|
that path by the `unraid-podman` scaffolding package (see
|
||||||
|
`packages/unraid-podman/unraid-podman.SlackBuild`), which podman.plg installs
|
||||||
|
alongside the seven compiled components.
|
||||||
|
|
||||||
|
**Status: implemented**, covering all ten sections from
|
||||||
|
[docs/ARCHITECTURE.md, section 18](../docs/ARCHITECTURE.md#18-zukünftige-webui):
|
||||||
|
Dashboard, Containers, Pods, Images, Volumes, Networks, Logs, Terminal,
|
||||||
|
Compose, Settings. Not yet exercised against a real Unraid/Podman install —
|
||||||
|
see [docs/ROADMAP.md](../docs/ROADMAP.md) for what "implemented" does and
|
||||||
|
doesn't cover yet.
|
||||||
|
|
||||||
|
`webui/mockups/prototype.html` is the static, non-PHP clickable mockup this
|
||||||
|
implementation was built against — kept as the visual reference; it is not
|
||||||
|
staged into the package.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
webui/
|
||||||
|
├── mockups/
|
||||||
|
│ └── prototype.html # static approved mockup, not shipped
|
||||||
|
└── plugins/
|
||||||
|
└── podman/
|
||||||
|
├── Podman.page # page shell: header, sub-nav, one container per panel
|
||||||
|
├── include/
|
||||||
|
│ ├── PodmanClient.php # libpod REST API client (talks to podman.sock only)
|
||||||
|
│ ├── Config.php # reads podman.cfg (mirrors podman-common.sh)
|
||||||
|
│ ├── bootstrap.php # shared include + error handling for ajax/*.php
|
||||||
|
│ └── helpers.php # formatting + JSON-response helpers
|
||||||
|
├── ajax/ # one endpoint per resource, each require()s bootstrap.php
|
||||||
|
│ ├── containers.php # list/start/stop/restart/remove/logs
|
||||||
|
│ ├── pods.php
|
||||||
|
│ ├── images.php
|
||||||
|
│ ├── volumes.php
|
||||||
|
│ ├── networks.php
|
||||||
|
│ ├── exec.php # Terminal — see its header comment for API scope
|
||||||
|
│ ├── compose.php # Compose — the one deliberate CLI exception, see header
|
||||||
|
│ ├── settings.php # plugin's own config, not a libpod resource
|
||||||
|
│ └── system.php # Dashboard aggregation
|
||||||
|
├── javascript/
|
||||||
|
│ ├── app.js # shared AJAX helper + sub-tab router
|
||||||
|
│ └── <panel>.js # one module per panel, registers with app.js
|
||||||
|
├── styles/podman.css # design tokens ported 1:1 from the mockup
|
||||||
|
├── event/ # official Unraid array-event hooks (see plugin/event/)
|
||||||
|
└── images/ # plugin icon assets
|
||||||
|
```
|
||||||
|
|
||||||
|
## Design constraints (see ARCHITECTURE.md for full rationale)
|
||||||
|
|
||||||
|
- Every panel talks to `podman system service` over its Unix socket via
|
||||||
|
`PodmanClient` — no `exec()`/`shell_exec()` of the `podman` binary
|
||||||
|
anywhere in `include/` or in the Containers/Pods/Images/Volumes/Networks/
|
||||||
|
Logs endpoints.
|
||||||
|
- **Two documented, deliberate exceptions**, not oversights:
|
||||||
|
- `ajax/exec.php` (Terminal) uses the real exec REST API, but as
|
||||||
|
one-command-in/output-out rather than a true interactive PTY — libpod's
|
||||||
|
interactive exec needs a persistent hijacked connection that doesn't
|
||||||
|
fit PHP-FPM's request lifecycle. See that file's header comment.
|
||||||
|
- `ajax/compose.php` (Compose) shells out to the `podman compose` CLI via
|
||||||
|
`proc_open()` with an argv array (never a shell string) — because no
|
||||||
|
REST endpoint for Compose exists in libpod at all. See that file's
|
||||||
|
header comment.
|
||||||
|
- The API socket is root-equivalent; no unauthenticated network exposure
|
||||||
|
beyond what Unraid's own WebUI auth already provides — see
|
||||||
|
[.github/SECURITY.md](../.github/SECURITY.md).
|
||||||
|
- Dark/light mode via CSS custom properties (`prefers-color-scheme` +
|
||||||
|
`[data-theme]` override), matching Unraid's own theme mechanism — no
|
||||||
|
separate theme toggle inside the plugin page.
|
||||||
@@ -0,0 +1,949 @@
|
|||||||
|
<title>unraid-podman — WebUI Mockup</title>
|
||||||
|
<style>
|
||||||
|
/* =========================================================================
|
||||||
|
Design tokens
|
||||||
|
Grounded in Unraid's real webGUI chrome: near-black masthead, a warm
|
||||||
|
construction-orange brand accent, dense admin-tool typography via the
|
||||||
|
OS's own system font stack (Unraid itself ships no custom webfont).
|
||||||
|
Semantic status colors (good/warn/bad/neutral) are kept separate from
|
||||||
|
the brand accent, per the container-fleet domain's own vocabulary.
|
||||||
|
========================================================================= */
|
||||||
|
:root {
|
||||||
|
--bg: #eef0f2;
|
||||||
|
--surface: #ffffff;
|
||||||
|
--surface-2: #f4f5f7;
|
||||||
|
--surface-3: #e9ebee;
|
||||||
|
--border: #dde1e6;
|
||||||
|
--text: #1c2024;
|
||||||
|
--text-dim: #5b6572;
|
||||||
|
--text-faint: #8a94a1;
|
||||||
|
--accent: #d8541a;
|
||||||
|
--accent-strong: #b8420f;
|
||||||
|
--accent-contrast: #fff8f3;
|
||||||
|
--good: #1a8f4c;
|
||||||
|
--good-bg: #e4f6ea;
|
||||||
|
--warn: #9a6b00;
|
||||||
|
--warn-bg: #fdf1d6;
|
||||||
|
--bad: #c22b3a;
|
||||||
|
--bad-bg: #fbe6e8;
|
||||||
|
--neutral: #5b6572;
|
||||||
|
--neutral-bg: #e9ebee;
|
||||||
|
--shadow: 0 1px 2px rgba(20, 22, 26, 0.06), 0 4px 12px rgba(20, 22, 26, 0.05);
|
||||||
|
--mast-bg: #16181c;
|
||||||
|
--mast-fg: #d7dae0;
|
||||||
|
--font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||||
|
--font-mono: ui-monospace, "SF Mono", "Cascadia Code", "Roboto Mono", Consolas, "Liberation Mono", monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--bg: #15171b;
|
||||||
|
--surface: #1c1f24;
|
||||||
|
--surface-2: #23262c;
|
||||||
|
--surface-3: #2b2f36;
|
||||||
|
--border: #34383f;
|
||||||
|
--text: #e7e9ec;
|
||||||
|
--text-dim: #9aa1ab;
|
||||||
|
--text-faint: #6b7280;
|
||||||
|
--accent: #ef7f3f;
|
||||||
|
--accent-strong: #f6975f;
|
||||||
|
--accent-contrast: #1a1002;
|
||||||
|
--good: #4cc785;
|
||||||
|
--good-bg: #123322;
|
||||||
|
--warn: #e0b23d;
|
||||||
|
--warn-bg: #3a2e0d;
|
||||||
|
--bad: #ef6470;
|
||||||
|
--bad-bg: #3a1519;
|
||||||
|
--neutral: #9aa1ab;
|
||||||
|
--neutral-bg: #2b2f36;
|
||||||
|
--shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 8px 24px rgba(0, 0, 0, 0.35);
|
||||||
|
--mast-bg: #0e0f11;
|
||||||
|
--mast-fg: #cdd1d7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:root[data-theme="dark"] {
|
||||||
|
--bg: #15171b; --surface: #1c1f24; --surface-2: #23262c; --surface-3: #2b2f36;
|
||||||
|
--border: #34383f; --text: #e7e9ec; --text-dim: #9aa1ab; --text-faint: #6b7280;
|
||||||
|
--accent: #ef7f3f; --accent-strong: #f6975f; --accent-contrast: #1a1002;
|
||||||
|
--good: #4cc785; --good-bg: #123322; --warn: #e0b23d; --warn-bg: #3a2e0d;
|
||||||
|
--bad: #ef6470; --bad-bg: #3a1519; --neutral: #9aa1ab; --neutral-bg: #2b2f36;
|
||||||
|
--shadow: 0 1px 2px rgba(0,0,0,.3), 0 8px 24px rgba(0,0,0,.35);
|
||||||
|
--mast-bg: #0e0f11; --mast-fg: #cdd1d7;
|
||||||
|
}
|
||||||
|
:root[data-theme="light"] {
|
||||||
|
--bg: #eef0f2; --surface: #ffffff; --surface-2: #f4f5f7; --surface-3: #e9ebee;
|
||||||
|
--border: #dde1e6; --text: #1c2024; --text-dim: #5b6572; --text-faint: #8a94a1;
|
||||||
|
--accent: #d8541a; --accent-strong: #b8420f; --accent-contrast: #fff8f3;
|
||||||
|
--good: #1a8f4c; --good-bg: #e4f6ea; --warn: #9a6b00; --warn-bg: #fdf1d6;
|
||||||
|
--bad: #c22b3a; --bad-bg: #fbe6e8; --neutral: #5b6572; --neutral-bg: #e9ebee;
|
||||||
|
--shadow: 0 1px 2px rgba(20,22,26,.06), 0 4px 12px rgba(20,22,26,.05);
|
||||||
|
--mast-bg: #16181c; --mast-fg: #d7dae0;
|
||||||
|
}
|
||||||
|
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
font-family: var(--font-ui);
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.5;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
h1, h2, h3 { text-wrap: balance; margin: 0; font-weight: 600; }
|
||||||
|
::selection { background: var(--accent); color: var(--accent-contrast); }
|
||||||
|
a { color: inherit; }
|
||||||
|
button, input, select { font-family: inherit; font-size: inherit; color: inherit; }
|
||||||
|
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 3px; }
|
||||||
|
.tnum { font-variant-numeric: tabular-nums; }
|
||||||
|
.mono { font-family: var(--font-mono); }
|
||||||
|
|
||||||
|
/* ---- Masthead (real Unraid top bar, non-interactive context) ---- */
|
||||||
|
.mast {
|
||||||
|
background: var(--mast-bg);
|
||||||
|
color: var(--mast-fg);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
padding: 0 16px;
|
||||||
|
height: 46px;
|
||||||
|
}
|
||||||
|
.mast .brand { display: flex; align-items: center; gap: 8px; font-weight: 700; letter-spacing: .02em; }
|
||||||
|
.mast .brand .mark {
|
||||||
|
width: 22px; height: 22px; border-radius: 5px;
|
||||||
|
background: linear-gradient(155deg, var(--accent), var(--accent-strong));
|
||||||
|
display: grid; place-items: center; color: #fff; font-size: 11px; font-weight: 800;
|
||||||
|
}
|
||||||
|
.mast .server { font-size: 12.5px; color: var(--mast-fg); opacity: .75; }
|
||||||
|
.mast .spacer { flex: 1; }
|
||||||
|
.mast .mast-icons { display: flex; gap: 16px; font-size: 13px; opacity: .7; }
|
||||||
|
|
||||||
|
.mainnav {
|
||||||
|
background: color-mix(in srgb, var(--mast-bg) 88%, var(--surface));
|
||||||
|
display: flex;
|
||||||
|
gap: 2px;
|
||||||
|
padding: 0 10px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
.mainnav a {
|
||||||
|
display: block;
|
||||||
|
padding: 10px 14px 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: .04em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--mast-fg);
|
||||||
|
opacity: .55;
|
||||||
|
text-decoration: none;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
white-space: nowrap;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.mainnav a.active {
|
||||||
|
opacity: 1;
|
||||||
|
color: var(--accent-strong);
|
||||||
|
border-bottom-color: var(--accent);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- Page header ---- */
|
||||||
|
.pagehead {
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 22px 24px 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.pagehead .titlewrap { display: flex; align-items: center; gap: 12px; }
|
||||||
|
.pagehead .icon {
|
||||||
|
width: 38px; height: 38px; border-radius: 9px;
|
||||||
|
background: var(--surface-3);
|
||||||
|
display: grid; place-items: center;
|
||||||
|
color: var(--accent);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.pagehead h1 { font-size: 21px; }
|
||||||
|
.pagehead .meta { font-size: 12.5px; color: var(--text-dim); margin-top: 2px; }
|
||||||
|
.pagehead .meta .dot { color: var(--good); }
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
appearance: none;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
background: var(--surface);
|
||||||
|
color: var(--text);
|
||||||
|
padding: 8px 14px;
|
||||||
|
border-radius: 7px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
transition: border-color .12s, background .12s;
|
||||||
|
}
|
||||||
|
.btn:hover { border-color: var(--text-faint); }
|
||||||
|
.btn-primary {
|
||||||
|
background: var(--accent);
|
||||||
|
border-color: var(--accent);
|
||||||
|
color: var(--accent-contrast);
|
||||||
|
}
|
||||||
|
.btn-primary:hover { background: var(--accent-strong); border-color: var(--accent-strong); }
|
||||||
|
.btn-danger { color: var(--bad); }
|
||||||
|
.btn-danger:hover { border-color: var(--bad); }
|
||||||
|
.btn-icon { padding: 6px 8px; }
|
||||||
|
.btn[disabled] { opacity: .4; cursor: not-allowed; }
|
||||||
|
|
||||||
|
/* ---- Sub navigation (the 10 sections) ---- */
|
||||||
|
.subnav {
|
||||||
|
max-width: 1280px;
|
||||||
|
margin: 14px auto 0;
|
||||||
|
padding: 0 24px;
|
||||||
|
display: flex;
|
||||||
|
gap: 4px;
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
.subnav button {
|
||||||
|
appearance: none;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
border-bottom: 2px solid transparent;
|
||||||
|
color: var(--text-dim);
|
||||||
|
padding: 10px 12px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
display: flex; align-items: center; gap: 6px;
|
||||||
|
}
|
||||||
|
.subnav button:hover { color: var(--text); }
|
||||||
|
.subnav button.active { color: var(--accent-strong); border-bottom-color: var(--accent); }
|
||||||
|
.subnav button .n {
|
||||||
|
font-size: 10.5px; padding: 1px 5px; border-radius: 8px;
|
||||||
|
background: var(--surface-3); color: var(--text-dim);
|
||||||
|
}
|
||||||
|
|
||||||
|
.main { max-width: 1280px; margin: 0 auto; padding: 20px 24px 64px; }
|
||||||
|
.panel { display: none; animation: fade .15s ease; }
|
||||||
|
.panel.active { display: block; }
|
||||||
|
@keyframes fade { from { opacity: 0; transform: translateY(2px); } to { opacity: 1; } }
|
||||||
|
@media (prefers-reduced-motion: reduce) { .panel { animation: none; } }
|
||||||
|
|
||||||
|
/* ---- Generic building blocks ---- */
|
||||||
|
.card {
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 10px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
.card-pad { padding: 16px 18px; }
|
||||||
|
.card-head {
|
||||||
|
display: flex; align-items: center; justify-content: space-between;
|
||||||
|
padding: 14px 18px; border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
.card-head h2 { font-size: 14.5px; }
|
||||||
|
.card-head .sub { font-size: 12px; color: var(--text-faint); font-weight: 500; }
|
||||||
|
|
||||||
|
.grid { display: grid; gap: 14px; }
|
||||||
|
.stat-grid { grid-template-columns: repeat(6, 1fr); }
|
||||||
|
@media (max-width: 1080px) { .stat-grid { grid-template-columns: repeat(3, 1fr); } }
|
||||||
|
@media (max-width: 620px) { .stat-grid { grid-template-columns: repeat(2, 1fr); } }
|
||||||
|
|
||||||
|
.stat {
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 14px 16px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
.stat .label { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--text-faint); font-weight: 700; }
|
||||||
|
.stat .value { font-size: 24px; font-weight: 700; margin-top: 6px; }
|
||||||
|
.stat .value small { font-size: 13px; color: var(--text-dim); font-weight: 600; }
|
||||||
|
.stat .bar { height: 5px; border-radius: 3px; background: var(--surface-3); margin-top: 10px; overflow: hidden; }
|
||||||
|
.stat .bar > span { display: block; height: 100%; background: var(--accent); border-radius: 3px; }
|
||||||
|
|
||||||
|
.chip {
|
||||||
|
display: inline-flex; align-items: center; gap: 5px;
|
||||||
|
padding: 3px 9px; border-radius: 100px;
|
||||||
|
font-size: 11.5px; font-weight: 700; letter-spacing: .01em;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.chip .d { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }
|
||||||
|
.chip-good { background: var(--good-bg); color: var(--good); }
|
||||||
|
.chip-warn { background: var(--warn-bg); color: var(--warn); }
|
||||||
|
.chip-bad { background: var(--bad-bg); color: var(--bad); }
|
||||||
|
.chip-neutral { background: var(--neutral-bg); color: var(--neutral); }
|
||||||
|
|
||||||
|
table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||||||
|
thead th {
|
||||||
|
text-align: left; font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
|
||||||
|
color: var(--text-faint); font-weight: 700;
|
||||||
|
padding: 10px 18px; border-bottom: 1px solid var(--border);
|
||||||
|
}
|
||||||
|
tbody td { padding: 11px 18px; border-bottom: 1px solid var(--border); vertical-align: middle; }
|
||||||
|
tbody tr:last-child td { border-bottom: none; }
|
||||||
|
tbody tr:hover { background: var(--surface-2); }
|
||||||
|
.table-wrap { overflow-x: auto; }
|
||||||
|
.row-name { display: flex; align-items: center; gap: 10px; font-weight: 600; }
|
||||||
|
.row-name .ico {
|
||||||
|
width: 26px; height: 26px; border-radius: 6px; flex: none;
|
||||||
|
background: var(--surface-3); display: grid; place-items: center; font-size: 12px;
|
||||||
|
border: 1px solid var(--border); color: var(--text-dim);
|
||||||
|
}
|
||||||
|
.row-sub { font-size: 11.5px; color: var(--text-faint); font-weight: 500; margin-top: 1px; }
|
||||||
|
.actions { display: flex; gap: 4px; justify-content: flex-end; }
|
||||||
|
|
||||||
|
.usage-mini { display: flex; align-items: center; gap: 8px; min-width: 110px; }
|
||||||
|
.usage-mini .track { flex: 1; height: 5px; border-radius: 3px; background: var(--surface-3); overflow: hidden; }
|
||||||
|
.usage-mini .track > span { display: block; height: 100%; background: var(--accent); }
|
||||||
|
.usage-mini .num { font-size: 11.5px; color: var(--text-dim); width: 34px; text-align: right; }
|
||||||
|
|
||||||
|
.toolbar { display: flex; align-items: center; gap: 10px; padding: 14px 18px; border-bottom: 1px solid var(--border); flex-wrap: wrap; }
|
||||||
|
.search {
|
||||||
|
flex: 1; min-width: 180px;
|
||||||
|
background: var(--surface-2); border: 1px solid var(--border); border-radius: 7px;
|
||||||
|
padding: 7px 11px; font-size: 13px; color: var(--text);
|
||||||
|
}
|
||||||
|
.search::placeholder { color: var(--text-faint); }
|
||||||
|
.filterset { display: flex; gap: 4px; background: var(--surface-2); padding: 3px; border-radius: 8px; }
|
||||||
|
.filterset button {
|
||||||
|
appearance: none; border: none; background: none; color: var(--text-dim);
|
||||||
|
font-size: 12px; font-weight: 700; padding: 5px 10px; border-radius: 6px; cursor: pointer;
|
||||||
|
}
|
||||||
|
.filterset button.active { background: var(--surface); color: var(--text); box-shadow: var(--shadow); }
|
||||||
|
|
||||||
|
.two-col { display: grid; grid-template-columns: 1.3fr 1fr; gap: 14px; align-items: start; }
|
||||||
|
@media (max-width: 920px) { .two-col { grid-template-columns: 1fr; } }
|
||||||
|
|
||||||
|
.activity-list { list-style: none; margin: 0; padding: 6px 0; }
|
||||||
|
.activity-list li { display: flex; gap: 10px; padding: 9px 18px; border-bottom: 1px solid var(--border); font-size: 12.5px; }
|
||||||
|
.activity-list li:last-child { border-bottom: none; }
|
||||||
|
.activity-list .dot { width: 7px; height: 7px; border-radius: 50%; margin-top: 5px; flex: none; }
|
||||||
|
.activity-list .txt strong { font-weight: 600; }
|
||||||
|
.activity-list .when { color: var(--text-faint); font-size: 11px; margin-top: 1px; }
|
||||||
|
|
||||||
|
.empty-note { padding: 28px 18px; text-align: center; color: var(--text-faint); font-size: 13px; }
|
||||||
|
|
||||||
|
/* ---- Pods ---- */
|
||||||
|
.pod-card { border: 1px solid var(--border); border-radius: 10px; overflow: hidden; margin-bottom: 14px; background: var(--surface); box-shadow: var(--shadow); }
|
||||||
|
.pod-card:last-child { margin-bottom: 0; }
|
||||||
|
.pod-head { display: flex; align-items: center; gap: 10px; padding: 13px 16px; background: var(--surface-2); border-bottom: 1px solid var(--border); }
|
||||||
|
.pod-head .name { font-weight: 700; font-size: 13.5px; }
|
||||||
|
.pod-head .infra { font-size: 11.5px; color: var(--text-faint); }
|
||||||
|
.pod-body table { font-size: 12.5px; }
|
||||||
|
.pod-body td, .pod-body th { padding: 8px 16px 8px 40px; }
|
||||||
|
|
||||||
|
/* ---- Logs ---- */
|
||||||
|
.logs-layout { display: grid; grid-template-columns: 200px 1fr; min-height: 460px; }
|
||||||
|
@media (max-width: 760px) { .logs-layout { grid-template-columns: 1fr; } }
|
||||||
|
.logs-side { border-right: 1px solid var(--border); }
|
||||||
|
.logs-side .item { padding: 10px 14px; font-size: 12.5px; border-bottom: 1px solid var(--border); cursor: default; display: flex; align-items: center; gap: 8px; }
|
||||||
|
.logs-side .item.active { background: var(--surface-2); font-weight: 700; box-shadow: inset 2px 0 0 var(--accent); }
|
||||||
|
.log-toolbar { display: flex; gap: 8px; align-items: center; padding: 10px 14px; border-bottom: 1px solid var(--border); }
|
||||||
|
.log-pane {
|
||||||
|
background: #0f1114; color: #c7ccd4; font-family: var(--font-mono); font-size: 12.3px;
|
||||||
|
padding: 14px 16px; height: 400px; overflow-y: auto; line-height: 1.65;
|
||||||
|
}
|
||||||
|
:root:not([data-theme="dark"]) .log-pane { background: #14161a; }
|
||||||
|
.log-pane .l { white-space: pre-wrap; word-break: break-word; }
|
||||||
|
.log-pane .ts { color: #6b7280; }
|
||||||
|
.log-pane .lvl-info { color: #6fb2f5; }
|
||||||
|
.log-pane .lvl-warn { color: #e0b23d; }
|
||||||
|
.log-pane .lvl-error { color: #ef6470; }
|
||||||
|
|
||||||
|
/* ---- Terminal ---- */
|
||||||
|
.term-wrap { padding: 16px 18px; }
|
||||||
|
.term-select { display: flex; gap: 8px; margin-bottom: 12px; align-items: center; font-size: 12.5px; color: var(--text-dim); }
|
||||||
|
.term-select select {
|
||||||
|
background: var(--surface-2); border: 1px solid var(--border); border-radius: 6px; padding: 6px 9px; color: var(--text);
|
||||||
|
}
|
||||||
|
.term {
|
||||||
|
background: #0f1114; color: #d7dbe0; font-family: var(--font-mono); font-size: 12.6px;
|
||||||
|
border-radius: 8px; padding: 14px 16px; height: 380px; overflow-y: auto; line-height: 1.7;
|
||||||
|
}
|
||||||
|
.term .prompt { color: #4cc785; }
|
||||||
|
.term .path { color: #6fb2f5; }
|
||||||
|
.term .cur { display: inline-block; width: 7px; height: 14px; background: #d7dbe0; vertical-align: text-bottom; animation: blink 1s step-end infinite; }
|
||||||
|
@media (prefers-reduced-motion: reduce) { .term .cur { animation: none; } }
|
||||||
|
@keyframes blink { 50% { opacity: 0; } }
|
||||||
|
|
||||||
|
/* ---- Compose ---- */
|
||||||
|
.compose-layout { display: grid; grid-template-columns: 230px 1fr; min-height: 480px; }
|
||||||
|
@media (max-width: 800px) { .compose-layout { grid-template-columns: 1fr; } }
|
||||||
|
.compose-side { border-right: 1px solid var(--border); }
|
||||||
|
.compose-proj { padding: 12px 14px; border-bottom: 1px solid var(--border); cursor: default; }
|
||||||
|
.compose-proj.active { background: var(--surface-2); box-shadow: inset 2px 0 0 var(--accent); }
|
||||||
|
.compose-proj .name { font-weight: 700; font-size: 13px; display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
||||||
|
.compose-proj .path { font-size: 11px; color: var(--text-faint); margin-top: 2px; font-family: var(--font-mono); }
|
||||||
|
.compose-body-head { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; border-bottom: 1px solid var(--border); gap: 10px; flex-wrap: wrap; }
|
||||||
|
.yaml { background: #0f1114; color: #c7ccd4; font-family: var(--font-mono); font-size: 12.4px; padding: 16px 18px; height: 420px; overflow: auto; line-height: 1.7; }
|
||||||
|
:root:not([data-theme="dark"]) .yaml { background: #14161a; }
|
||||||
|
.yaml .k { color: #ef9f76; }
|
||||||
|
.yaml .s { color: #9fd88a; }
|
||||||
|
.yaml .c { color: #6b7280; font-style: italic; }
|
||||||
|
.yaml .p { color: #6fb2f5; }
|
||||||
|
|
||||||
|
/* ---- Settings ---- */
|
||||||
|
.settings-grid { display: grid; gap: 14px; }
|
||||||
|
.field-row { display: grid; grid-template-columns: 220px 1fr; gap: 16px; padding: 14px 18px; border-bottom: 1px solid var(--border); align-items: start; }
|
||||||
|
.field-row:last-child { border-bottom: none; }
|
||||||
|
.field-row label { font-weight: 600; font-size: 13px; }
|
||||||
|
.field-row .hint { font-size: 11.5px; color: var(--text-faint); margin-top: 4px; max-width: 46ch; }
|
||||||
|
.field-row input[type="text"], .field-row input[type="number"], .field-row select {
|
||||||
|
background: var(--surface-2); border: 1px solid var(--border); border-radius: 7px;
|
||||||
|
padding: 8px 10px; font-size: 13px; color: var(--text); width: 100%; max-width: 340px;
|
||||||
|
}
|
||||||
|
.field-row .unit { font-size: 12px; color: var(--text-dim); margin-left: 8px; }
|
||||||
|
.switch { position: relative; display: inline-block; width: 38px; height: 22px; }
|
||||||
|
.switch input { display: none; }
|
||||||
|
.switch .track { position: absolute; inset: 0; background: var(--surface-3); border: 1px solid var(--border); border-radius: 20px; transition: .15s; }
|
||||||
|
.switch .track::after { content: ""; position: absolute; width: 16px; height: 16px; border-radius: 50%; background: var(--surface); top: 2px; left: 2px; transition: .15s; box-shadow: 0 1px 2px rgba(0,0,0,.3); }
|
||||||
|
.switch input:checked + .track { background: var(--accent); border-color: var(--accent); }
|
||||||
|
.switch input:checked + .track::after { transform: translateX(16px); background: #fff; }
|
||||||
|
.danger-card { border-color: color-mix(in srgb, var(--bad) 40%, var(--border)); }
|
||||||
|
.danger-card .card-head { border-bottom-color: color-mix(in srgb, var(--bad) 30%, var(--border)); }
|
||||||
|
.danger-card .card-head h2 { color: var(--bad); }
|
||||||
|
|
||||||
|
.badge-update { font-size: 10px; font-weight: 700; color: var(--accent-strong); background: color-mix(in srgb, var(--accent) 16%, transparent); padding: 2px 7px; border-radius: 100px; margin-left: 8px; }
|
||||||
|
|
||||||
|
footer.note { max-width: 1280px; margin: 0 auto; padding: 4px 24px 40px; font-size: 11.5px; color: var(--text-faint); }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<!-- =========================================================================
|
||||||
|
Real Unraid chrome (masthead + main nav) — recreated for context, not
|
||||||
|
interactive. Only "Podman" is the actual subject of this mockup.
|
||||||
|
========================================================================= -->
|
||||||
|
<div class="mast">
|
||||||
|
<div class="brand"><span class="mark">U</span> unraid</div>
|
||||||
|
<div class="server">TOWER · 14d 6h uptime · Array Started</div>
|
||||||
|
<div class="spacer"></div>
|
||||||
|
<div class="mast-icons"><span>ⓘ Updates</span><span>🔔</span><span>👤 root</span></div>
|
||||||
|
</div>
|
||||||
|
<nav class="mainnav">
|
||||||
|
<a>Dashboard</a><a>Main</a><a>Shares</a><a>Users</a><a>Settings</a><a>Plugins</a><a>Docker</a><a>VMs</a>
|
||||||
|
<a class="active">Podman</a><a>Tools</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<header class="pagehead">
|
||||||
|
<div class="titlewrap">
|
||||||
|
<div class="icon" aria-hidden="true">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none"><path d="M12 2l8 4.5v9L12 20l-8-4.5v-9L12 2z" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/><path d="M12 11l8-4.5M12 11v9M12 11L4 6.5" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1>Podman</h1>
|
||||||
|
<div class="meta"><span class="dot">●</span> podman.sock connected · 5 of 8 containers running · v6.0.1</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex; gap:8px;">
|
||||||
|
<button class="btn">↻ Refresh</button>
|
||||||
|
<button class="btn btn-primary">+ New Container</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<nav class="subnav" id="subnav">
|
||||||
|
<button class="active" data-panel="dashboard">Dashboard</button>
|
||||||
|
<button data-panel="containers">Containers <span class="n">8</span></button>
|
||||||
|
<button data-panel="pods">Pods <span class="n">2</span></button>
|
||||||
|
<button data-panel="images">Images <span class="n">14</span></button>
|
||||||
|
<button data-panel="volumes">Volumes <span class="n">9</span></button>
|
||||||
|
<button data-panel="networks">Networks <span class="n">3</span></button>
|
||||||
|
<button data-panel="logs">Logs</button>
|
||||||
|
<button data-panel="terminal">Terminal</button>
|
||||||
|
<button data-panel="compose">Compose</button>
|
||||||
|
<button data-panel="settings">Settings</button>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="main">
|
||||||
|
|
||||||
|
<!-- ============================= DASHBOARD ============================= -->
|
||||||
|
<section class="panel active" id="panel-dashboard">
|
||||||
|
<div class="grid stat-grid">
|
||||||
|
<div class="stat"><div class="label">Running</div><div class="value tnum">5 <small>/ 8</small></div></div>
|
||||||
|
<div class="stat"><div class="label">Pods</div><div class="value tnum">2</div></div>
|
||||||
|
<div class="stat"><div class="label">Images</div><div class="value tnum">14</div></div>
|
||||||
|
<div class="stat"><div class="label">Volumes</div><div class="value tnum">9</div></div>
|
||||||
|
<div class="stat"><div class="label">Networks</div><div class="value tnum">3</div></div>
|
||||||
|
<div class="stat">
|
||||||
|
<div class="label">Storage Used</div>
|
||||||
|
<div class="value tnum">62.4 <small>/ 100 GB</small></div>
|
||||||
|
<div class="bar"><span style="width:62%"></span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="two-col" style="margin-top:14px;">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-head"><h2>Resource Usage</h2><span class="sub">last 60 min</span></div>
|
||||||
|
<div class="card-pad">
|
||||||
|
<svg viewBox="0 0 560 140" width="100%" height="140" preserveAspectRatio="none" role="img" aria-label="CPU and memory usage over the last 60 minutes">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="cpuFill" x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="0" stop-color="var(--accent)" stop-opacity="0.35"/>
|
||||||
|
<stop offset="1" stop-color="var(--accent)" stop-opacity="0"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<g stroke="var(--border)" stroke-width="1">
|
||||||
|
<line x1="0" y1="10" x2="560" y2="10"/><line x1="0" y1="45" x2="560" y2="45"/>
|
||||||
|
<line x1="0" y1="80" x2="560" y2="80"/><line x1="0" y1="115" x2="560" y2="115"/>
|
||||||
|
</g>
|
||||||
|
<path d="M0,100 L40,92 L80,96 L120,70 L160,74 L200,60 L240,66 L280,50 L320,58 L360,42 L400,48 L440,36 L480,40 L520,30 L560,34 L560,140 L0,140 Z" fill="url(#cpuFill)"/>
|
||||||
|
<path d="M0,100 L40,92 L80,96 L120,70 L160,74 L200,60 L240,66 L280,50 L320,58 L360,42 L400,48 L440,36 L480,40 L520,30 L560,34" fill="none" stroke="var(--accent)" stroke-width="2" stroke-linejoin="round" stroke-linecap="round"/>
|
||||||
|
<circle cx="560" cy="34" r="3.5" fill="var(--accent)"/>
|
||||||
|
<path d="M0,120 L40,118 L80,121 L120,112 L160,116 L200,108 L240,110 L280,104 L320,109 L360,100 L400,103 L440,96 L480,99 L520,92 L560,95" fill="none" stroke="var(--text-faint)" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round" stroke-dasharray="1 4"/>
|
||||||
|
<circle cx="560" cy="95" r="3" fill="var(--text-faint)"/>
|
||||||
|
</svg>
|
||||||
|
<div style="display:flex; gap:18px; margin-top:6px; font-size:11.5px; color:var(--text-dim);">
|
||||||
|
<span><span style="color:var(--accent);">●</span> CPU · 24%</span>
|
||||||
|
<span><span style="color:var(--text-faint);">●</span> Memory · 6.1 / 16 GB</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-head"><h2>Recent Activity</h2></div>
|
||||||
|
<ul class="activity-list">
|
||||||
|
<li><span class="dot" style="background:var(--good)"></span><div class="txt"><div><strong>sonarr</strong> started</div><div class="when">2 min ago</div></div></li>
|
||||||
|
<li><span class="dot" style="background:var(--bad)"></span><div class="txt"><div><strong>postgres</strong> health check failed</div><div class="when">11 min ago</div></div></li>
|
||||||
|
<li><span class="dot" style="background:var(--accent)"></span><div class="txt"><div>Pulled <strong>radarr:latest</strong> (image updated)</div><div class="when">38 min ago</div></div></li>
|
||||||
|
<li><span class="dot" style="background:var(--good)"></span><div class="txt"><div>Autostart chain completed (7/8)</div><div class="when">6h ago · array start</div></div></li>
|
||||||
|
<li><span class="dot" style="background:var(--warn)"></span><div class="txt"><div><strong>watchtower</strong> paused (autostart Safe-Mode)</div><div class="when">6h ago</div></div></li>
|
||||||
|
<li><span class="dot" style="background:var(--neutral)"></span><div class="txt"><div>Network <strong>media-net</strong> created</div><div class="when">2d ago</div></div></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" style="margin-top:14px;">
|
||||||
|
<div class="card-head"><h2>Autostart Queue</h2><span class="sub">/boot/config/plugins/podman/autostart</span></div>
|
||||||
|
<div class="table-wrap">
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>#</th><th>Container</th><th>Delay</th><th>Last Result</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td class="tnum">1</td><td>postgres</td><td class="tnum">—</td><td><span class="chip chip-good"><span class="d"></span>started</span></td></tr>
|
||||||
|
<tr><td class="tnum">2</td><td>nextcloud</td><td class="tnum">5s</td><td><span class="chip chip-good"><span class="d"></span>started</span></td></tr>
|
||||||
|
<tr><td class="tnum">3</td><td>sonarr</td><td class="tnum">—</td><td><span class="chip chip-good"><span class="d"></span>started</span></td></tr>
|
||||||
|
<tr><td class="tnum">4</td><td>radarr</td><td class="tnum">—</td><td><span class="chip chip-good"><span class="d"></span>started</span></td></tr>
|
||||||
|
<tr><td class="tnum">5</td><td>watchtower</td><td class="tnum">—</td><td><span class="chip chip-warn"><span class="d"></span>Safe-Mode (3 failures)</span></td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ============================= CONTAINERS ============================= -->
|
||||||
|
<section class="panel" id="panel-containers">
|
||||||
|
<div class="card">
|
||||||
|
<div class="toolbar">
|
||||||
|
<input class="search" type="text" placeholder="Search containers by name or image…">
|
||||||
|
<div class="filterset">
|
||||||
|
<button class="active">All 8</button><button>Running 5</button><button>Stopped 2</button><button>Unhealthy 1</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="table-wrap">
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Status</th><th>Name</th><th>Image</th><th>CPU</th><th>Memory</th><th>Ports</th><th>Uptime</th><th></th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><span class="chip chip-good"><span class="d"></span>running</span></td>
|
||||||
|
<td><div class="row-name"><span class="ico">Px</span>plex</div></td>
|
||||||
|
<td class="mono row-sub">lscr.io/linuxserver/plex:latest</td>
|
||||||
|
<td class="tnum">18%</td>
|
||||||
|
<td><div class="usage-mini"><div class="track"><span style="width:41%"></span></div><span class="num tnum">1.6G</span></div></td>
|
||||||
|
<td class="mono row-sub">32400/tcp</td>
|
||||||
|
<td class="tnum">14d 6h</td>
|
||||||
|
<td class="actions"><button class="btn btn-icon" title="Restart">↻</button><button class="btn btn-icon" title="Logs">📄</button><button class="btn btn-icon" title="Shell">⎋</button><button class="btn btn-icon" title="Stop">■</button></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="chip chip-good"><span class="d"></span>running</span></td>
|
||||||
|
<td><div class="row-name"><span class="ico">So</span>sonarr</div></td>
|
||||||
|
<td class="mono row-sub">lscr.io/linuxserver/sonarr:4.0</td>
|
||||||
|
<td class="tnum">3%</td>
|
||||||
|
<td><div class="usage-mini"><div class="track"><span style="width:22%"></span></div><span class="num tnum">420M</span></div></td>
|
||||||
|
<td class="mono row-sub">8989/tcp</td>
|
||||||
|
<td class="tnum">6h 12m</td>
|
||||||
|
<td class="actions"><button class="btn btn-icon" title="Restart">↻</button><button class="btn btn-icon" title="Logs">📄</button><button class="btn btn-icon" title="Shell">⎋</button><button class="btn btn-icon" title="Stop">■</button></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="chip chip-good"><span class="d"></span>running</span></td>
|
||||||
|
<td><div class="row-name"><span class="ico">Ra</span>radarr <span class="badge-update">update</span></div></td>
|
||||||
|
<td class="mono row-sub">lscr.io/linuxserver/radarr:5.2</td>
|
||||||
|
<td class="tnum">2%</td>
|
||||||
|
<td><div class="usage-mini"><div class="track"><span style="width:19%"></span></div><span class="num tnum">380M</span></div></td>
|
||||||
|
<td class="mono row-sub">7878/tcp</td>
|
||||||
|
<td class="tnum">6h 12m</td>
|
||||||
|
<td class="actions"><button class="btn btn-icon" title="Restart">↻</button><button class="btn btn-icon" title="Logs">📄</button><button class="btn btn-icon" title="Shell">⎋</button><button class="btn btn-icon" title="Stop">■</button></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="chip chip-bad"><span class="d"></span>unhealthy</span></td>
|
||||||
|
<td><div class="row-name"><span class="ico">Pg</span>postgres</div></td>
|
||||||
|
<td class="mono row-sub">postgres:16-alpine</td>
|
||||||
|
<td class="tnum">1%</td>
|
||||||
|
<td><div class="usage-mini"><div class="track"><span style="width:34%"></span></div><span class="num tnum">210M</span></div></td>
|
||||||
|
<td class="mono row-sub">5432/tcp</td>
|
||||||
|
<td class="tnum">2d 1h</td>
|
||||||
|
<td class="actions"><button class="btn btn-icon" title="Restart">↻</button><button class="btn btn-icon" title="Logs">📄</button><button class="btn btn-icon" title="Shell">⎋</button><button class="btn btn-icon" title="Stop">■</button></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="chip chip-good"><span class="d"></span>running</span></td>
|
||||||
|
<td><div class="row-name"><span class="ico">Nc</span>nextcloud</div></td>
|
||||||
|
<td class="mono row-sub">nextcloud:29-apache</td>
|
||||||
|
<td class="tnum">6%</td>
|
||||||
|
<td><div class="usage-mini"><div class="track"><span style="width:58%"></span></div><span class="num tnum">890M</span></div></td>
|
||||||
|
<td class="mono row-sub">443/tcp, 80/tcp</td>
|
||||||
|
<td class="tnum">6h 12m</td>
|
||||||
|
<td class="actions"><button class="btn btn-icon" title="Restart">↻</button><button class="btn btn-icon" title="Logs">📄</button><button class="btn btn-icon" title="Shell">⎋</button><button class="btn btn-icon" title="Stop">■</button></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="chip chip-neutral"><span class="d"></span>exited</span></td>
|
||||||
|
<td><div class="row-name"><span class="ico">Pr</span>prowlarr</div></td>
|
||||||
|
<td class="mono row-sub">lscr.io/linuxserver/prowlarr:1</td>
|
||||||
|
<td class="tnum">—</td>
|
||||||
|
<td class="tnum">—</td>
|
||||||
|
<td class="mono row-sub">9696/tcp</td>
|
||||||
|
<td class="tnum">—</td>
|
||||||
|
<td class="actions"><button class="btn btn-icon" title="Start">▶</button><button class="btn btn-icon" title="Logs">📄</button><button class="btn btn-icon" title="Remove">🗑</button></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="chip chip-warn"><span class="d"></span>Safe-Mode</span></td>
|
||||||
|
<td><div class="row-name"><span class="ico">Wt</span>watchtower</div></td>
|
||||||
|
<td class="mono row-sub">containrrr/watchtower:latest</td>
|
||||||
|
<td class="tnum">—</td>
|
||||||
|
<td class="tnum">—</td>
|
||||||
|
<td class="mono row-sub">—</td>
|
||||||
|
<td class="tnum">—</td>
|
||||||
|
<td class="actions"><button class="btn btn-icon" title="Start">▶</button><button class="btn btn-icon" title="Logs">📄</button><button class="btn btn-icon" title="Remove">🗑</button></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><span class="chip chip-good"><span class="d"></span>running</span></td>
|
||||||
|
<td><div class="row-name"><span class="ico">Ha</span>homeassistant</div></td>
|
||||||
|
<td class="mono row-sub">ghcr.io/home-assistant/home-assistant</td>
|
||||||
|
<td class="tnum">4%</td>
|
||||||
|
<td><div class="usage-mini"><div class="track"><span style="width:30%"></span></div><span class="num tnum">610M</span></div></td>
|
||||||
|
<td class="mono row-sub">8123/tcp</td>
|
||||||
|
<td class="tnum">3d 4h</td>
|
||||||
|
<td class="actions"><button class="btn btn-icon" title="Restart">↻</button><button class="btn btn-icon" title="Logs">📄</button><button class="btn btn-icon" title="Shell">⎋</button><button class="btn btn-icon" title="Stop">■</button></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ============================= PODS ============================= -->
|
||||||
|
<section class="panel" id="panel-pods">
|
||||||
|
<div class="pod-card">
|
||||||
|
<div class="pod-head">
|
||||||
|
<span class="chip chip-good"><span class="d"></span>running</span>
|
||||||
|
<span class="name">media-stack</span>
|
||||||
|
<span class="infra">infra: localhost/podman-pause · shared network namespace</span>
|
||||||
|
</div>
|
||||||
|
<div class="pod-body table-wrap">
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Container</th><th>Image</th><th>Status</th><th>Ports</th><th></th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>sonarr</td><td class="mono row-sub">linuxserver/sonarr:4.0</td><td><span class="chip chip-good"><span class="d"></span>running</span></td><td class="mono row-sub">8989/tcp</td><td class="actions"><button class="btn btn-icon">📄</button></td></tr>
|
||||||
|
<tr><td>radarr</td><td class="mono row-sub">linuxserver/radarr:5.2</td><td><span class="chip chip-good"><span class="d"></span>running</span></td><td class="mono row-sub">7878/tcp</td><td class="actions"><button class="btn btn-icon">📄</button></td></tr>
|
||||||
|
<tr><td>prowlarr</td><td class="mono row-sub">linuxserver/prowlarr:1</td><td><span class="chip chip-neutral"><span class="d"></span>exited</span></td><td class="mono row-sub">9696/tcp</td><td class="actions"><button class="btn btn-icon">📄</button></td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="pod-card">
|
||||||
|
<div class="pod-head">
|
||||||
|
<span class="chip chip-good"><span class="d"></span>running</span>
|
||||||
|
<span class="name">home-stack</span>
|
||||||
|
<span class="infra">infra: localhost/podman-pause · shared network namespace</span>
|
||||||
|
</div>
|
||||||
|
<div class="pod-body table-wrap">
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Container</th><th>Image</th><th>Status</th><th>Ports</th><th></th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>homeassistant</td><td class="mono row-sub">ghcr.io/home-assistant/home-assistant</td><td><span class="chip chip-good"><span class="d"></span>running</span></td><td class="mono row-sub">8123/tcp</td><td class="actions"><button class="btn btn-icon">📄</button></td></tr>
|
||||||
|
<tr><td>mosquitto</td><td class="mono row-sub">eclipse-mosquitto:2</td><td><span class="chip chip-good"><span class="d"></span>running</span></td><td class="mono row-sub">1883/tcp</td><td class="actions"><button class="btn btn-icon">📄</button></td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ============================= IMAGES ============================= -->
|
||||||
|
<section class="panel" id="panel-images">
|
||||||
|
<div class="card">
|
||||||
|
<div class="toolbar">
|
||||||
|
<input class="search" type="text" placeholder="Search images…">
|
||||||
|
<button class="btn">⬇ Pull Image</button>
|
||||||
|
<button class="btn">Prune Unused</button>
|
||||||
|
</div>
|
||||||
|
<div class="table-wrap">
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Repository</th><th>Tag</th><th>Image ID</th><th>Size</th><th>Created</th><th>Used By</th><th></th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>lscr.io/linuxserver/plex</td><td class="mono">latest</td><td class="mono row-sub">a3f9c2e1b7d4</td><td class="tnum">1.42 GB</td><td class="tnum">3d ago</td><td class="tnum">1</td><td class="actions"><button class="btn btn-icon">🗑</button></td></tr>
|
||||||
|
<tr><td>lscr.io/linuxserver/sonarr</td><td class="mono">4.0</td><td class="mono row-sub">7e21ac9f0b12</td><td class="tnum">612 MB</td><td class="tnum">6h ago</td><td class="tnum">1</td><td class="actions"><button class="btn btn-icon">🗑</button></td></tr>
|
||||||
|
<tr><td>lscr.io/linuxserver/radarr <span class="badge-update">update</span></td><td class="mono">5.2</td><td class="mono row-sub">44b6f1d8ce90</td><td class="tnum">598 MB</td><td class="tnum">38m ago</td><td class="tnum">1</td><td class="actions"><button class="btn btn-icon">🗑</button></td></tr>
|
||||||
|
<tr><td>postgres</td><td class="mono">16-alpine</td><td class="mono row-sub">c1a08e4477fd</td><td class="tnum">243 MB</td><td class="tnum">9d ago</td><td class="tnum">1</td><td class="actions"><button class="btn btn-icon">🗑</button></td></tr>
|
||||||
|
<tr><td>nextcloud</td><td class="mono">29-apache</td><td class="mono row-sub">9d3c72b1a5e6</td><td class="tnum">890 MB</td><td class="tnum">12d ago</td><td class="tnum">1</td><td class="actions"><button class="btn btn-icon">🗑</button></td></tr>
|
||||||
|
<tr><td>containrrr/watchtower</td><td class="mono">latest</td><td class="mono row-sub">0f5b9a2d7c31</td><td class="tnum">28 MB</td><td class="tnum">40d ago</td><td class="tnum">1</td><td class="actions"><button class="btn btn-icon">🗑</button></td></tr>
|
||||||
|
<tr><td>ghcr.io/home-assistant/home-assistant</td><td class="mono">2026.7</td><td class="mono row-sub">b7e4f0a19c22</td><td class="tnum">1.1 GB</td><td class="tnum">3d ago</td><td class="tnum">1</td><td class="actions"><button class="btn btn-icon">🗑</button></td></tr>
|
||||||
|
<tr><td>eclipse-mosquitto</td><td class="mono">2</td><td class="mono row-sub">2a6d0e5f8b41</td><td class="tnum">14 MB</td><td class="tnum">40d ago</td><td class="tnum">1</td><td class="actions"><button class="btn btn-icon">🗑</button></td></tr>
|
||||||
|
<tr><td>lscr.io/linuxserver/prowlarr</td><td class="mono">1</td><td class="mono row-sub">d8f231b9a706</td><td class="tnum">340 MB</td><td class="tnum">11d ago</td><td class="tnum">0</td><td class="actions"><button class="btn btn-icon">🗑</button></td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ============================= VOLUMES ============================= -->
|
||||||
|
<section class="panel" id="panel-volumes">
|
||||||
|
<div class="card">
|
||||||
|
<div class="toolbar">
|
||||||
|
<input class="search" type="text" placeholder="Search volumes…">
|
||||||
|
<button class="btn btn-primary">+ New Volume</button>
|
||||||
|
</div>
|
||||||
|
<div class="table-wrap">
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Name</th><th>Driver</th><th>Mountpoint</th><th>Size</th><th>Used By</th><th></th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>postgres-data</td><td class="chip chip-neutral">local</td><td class="mono row-sub">…/storage/volumes/postgres-data/_data</td><td class="tnum">1.8 GB</td><td class="tnum">1</td><td class="actions"><button class="btn btn-icon">📁</button><button class="btn btn-icon">🗑</button></td></tr>
|
||||||
|
<tr><td>nextcloud-data</td><td class="chip chip-neutral">local</td><td class="mono row-sub">…/storage/volumes/nextcloud-data/_data</td><td class="tnum">14.2 GB</td><td class="tnum">1</td><td class="actions"><button class="btn btn-icon">📁</button><button class="btn btn-icon">🗑</button></td></tr>
|
||||||
|
<tr><td>ha-config</td><td class="chip chip-neutral">local</td><td class="mono row-sub">…/storage/volumes/ha-config/_data</td><td class="tnum">340 MB</td><td class="tnum">1</td><td class="actions"><button class="btn btn-icon">📁</button><button class="btn btn-icon">🗑</button></td></tr>
|
||||||
|
<tr><td>mosquitto-data</td><td class="chip chip-neutral">local</td><td class="mono row-sub">…/storage/volumes/mosquitto-data/_data</td><td class="tnum">4 MB</td><td class="tnum">1</td><td class="actions"><button class="btn btn-icon">📁</button><button class="btn btn-icon">🗑</button></td></tr>
|
||||||
|
<tr><td>unused-cache</td><td class="chip chip-neutral">local</td><td class="mono row-sub">…/storage/volumes/unused-cache/_data</td><td class="tnum">610 MB</td><td class="tnum">0</td><td class="actions"><button class="btn btn-icon">📁</button><button class="btn btn-icon">🗑</button></td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="empty-note" style="border-top:1px solid var(--border); text-align:left; padding:12px 18px;">
|
||||||
|
App data mapped from Unraid shares (e.g. <span class="mono">/mnt/user/appdata/…</span>) is managed as bind mounts per-container, not listed here — see each container's edit dialog.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ============================= NETWORKS ============================= -->
|
||||||
|
<section class="panel" id="panel-networks">
|
||||||
|
<div class="card">
|
||||||
|
<div class="toolbar">
|
||||||
|
<input class="search" type="text" placeholder="Search networks…">
|
||||||
|
<button class="btn btn-primary">+ New Network</button>
|
||||||
|
</div>
|
||||||
|
<div class="table-wrap">
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Name</th><th>Driver</th><th>Subnet</th><th>Gateway</th><th>Containers</th><th></th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td><strong>podman0</strong> <span class="row-sub">(default)</span></td><td class="chip chip-neutral">bridge</td><td class="mono">10.89.0.0/24</td><td class="mono">10.89.0.1</td><td class="tnum">3</td><td class="actions"><button class="btn btn-icon">✎</button></td></tr>
|
||||||
|
<tr><td>media-net</td><td class="chip chip-neutral">bridge</td><td class="mono">10.89.1.0/24</td><td class="mono">10.89.1.1</td><td class="tnum">3</td><td class="actions"><button class="btn btn-icon">✎</button><button class="btn btn-icon">🗑</button></td></tr>
|
||||||
|
<tr>
|
||||||
|
<td>iot-macvlan</td><td class="chip chip-neutral">macvlan</td><td class="mono">192.168.10.0/24</td><td class="mono">192.168.10.1</td><td class="tnum">1</td>
|
||||||
|
<td class="actions"><button class="btn btn-icon">✎</button><button class="btn btn-icon">🗑</button></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="empty-note" style="border-top:1px solid var(--border); text-align:left; padding:12px 18px; display:flex; gap:8px; align-items:flex-start;">
|
||||||
|
<span class="chip chip-warn" style="margin-top:1px;"><span class="d"></span>note</span>
|
||||||
|
<span>macvlan networks can trigger a known kernel issue with some NIC drivers on Unraid ("macvlan call trap"). See the docs before attaching containers that need host-network access on the same interface.</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ============================= LOGS ============================= -->
|
||||||
|
<section class="panel" id="panel-logs">
|
||||||
|
<div class="card">
|
||||||
|
<div class="logs-layout">
|
||||||
|
<div class="logs-side">
|
||||||
|
<div class="item active">● postgres</div>
|
||||||
|
<div class="item">● plex</div>
|
||||||
|
<div class="item">● sonarr</div>
|
||||||
|
<div class="item">● radarr</div>
|
||||||
|
<div class="item">● nextcloud</div>
|
||||||
|
<div class="item">● homeassistant</div>
|
||||||
|
<div class="item">● mosquitto</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="log-toolbar">
|
||||||
|
<input class="search" type="text" placeholder="Filter log output…" style="max-width:280px;">
|
||||||
|
<span class="filterset"><button class="active">Follow</button><button>Paused</button></span>
|
||||||
|
<div class="spacer" style="flex:1;"></div>
|
||||||
|
<button class="btn btn-icon" title="Download">⇩</button>
|
||||||
|
</div>
|
||||||
|
<div class="log-pane" role="log">
|
||||||
|
<div class="l"><span class="ts">2026-07-11T09:58:02Z</span> <span class="lvl-info">INFO</span> database system is ready to accept connections</div>
|
||||||
|
<div class="l"><span class="ts">2026-07-11T09:58:14Z</span> <span class="lvl-info">INFO</span> checkpoint starting: time</div>
|
||||||
|
<div class="l"><span class="ts">2026-07-11T10:04:41Z</span> <span class="lvl-warn">WARN</span> could not receive data from client: Connection reset by peer</div>
|
||||||
|
<div class="l"><span class="ts">2026-07-11T10:07:52Z</span> <span class="lvl-error">ERROR</span> connection to server was lost</div>
|
||||||
|
<div class="l"><span class="ts">2026-07-11T10:07:53Z</span> <span class="lvl-info">INFO</span> restartpoint starting: time</div>
|
||||||
|
<div class="l"><span class="ts">2026-07-11T10:07:58Z</span> <span class="lvl-info">INFO</span> database system is ready to accept connections</div>
|
||||||
|
<div class="l"><span class="ts">2026-07-11T10:11:03Z</span> <span class="lvl-warn">WARN</span> autovacuum: found orphan temp table in database "app"</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ============================= TERMINAL ============================= -->
|
||||||
|
<section class="panel" id="panel-terminal">
|
||||||
|
<div class="card">
|
||||||
|
<div class="term-wrap">
|
||||||
|
<div class="term-select">
|
||||||
|
Exec into:
|
||||||
|
<select><option>sonarr</option><option>plex</option><option>postgres</option><option>nextcloud</option></select>
|
||||||
|
Shell: <select><option>/bin/bash</option><option>/bin/sh</option></select>
|
||||||
|
<button class="btn" style="margin-left:auto;">New Session</button>
|
||||||
|
</div>
|
||||||
|
<div class="term">
|
||||||
|
<div><span class="prompt">root@sonarr</span>:<span class="path">/config</span>$ podman exec -it sonarr /bin/bash</div>
|
||||||
|
<div>root@sonarr:/config# ls -la</div>
|
||||||
|
<div>drwxr-xr-x 6 abc abc 4096 Jul 11 09:12 .</div>
|
||||||
|
<div>drwxr-xr-x 1 root root 4096 Jun 02 17:03 ..</div>
|
||||||
|
<div>-rw-r--r-- 1 abc abc 22016 Jul 11 09:58 sonarr.db</div>
|
||||||
|
<div>drwxr-xr-x 2 abc abc 4096 Jul 11 06:00 logs</div>
|
||||||
|
<div> </div>
|
||||||
|
<div>root@sonarr:/config# tail -n 3 logs/sonarr.txt</div>
|
||||||
|
<div>10:11:02|Info|RssSyncService: Starting RSS Sync</div>
|
||||||
|
<div>10:11:04|Info|RssSyncService: RSS Sync Completed. Reported 12 releases</div>
|
||||||
|
<div>10:11:04|Info|DownloadService: Report sent to SABnzbd</div>
|
||||||
|
<div> </div>
|
||||||
|
<div><span class="prompt">root@sonarr</span>:<span class="path">/config</span># <span class="cur"></span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ============================= COMPOSE ============================= -->
|
||||||
|
<section class="panel" id="panel-compose">
|
||||||
|
<div class="card">
|
||||||
|
<div class="compose-layout">
|
||||||
|
<div class="compose-side">
|
||||||
|
<div class="compose-proj active">
|
||||||
|
<div class="name">media-stack <span class="chip chip-good"><span class="d"></span>up</span></div>
|
||||||
|
<div class="path">/boot/config/plugins/podman/compose/media-stack</div>
|
||||||
|
</div>
|
||||||
|
<div class="compose-proj">
|
||||||
|
<div class="name">home-stack <span class="chip chip-good"><span class="d"></span>up</span></div>
|
||||||
|
<div class="path">/boot/config/plugins/podman/compose/home-stack</div>
|
||||||
|
</div>
|
||||||
|
<div class="compose-proj">
|
||||||
|
<div class="name">staging <span class="chip chip-neutral"><span class="d"></span>down</span></div>
|
||||||
|
<div class="path">/boot/config/plugins/podman/compose/staging</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="compose-body-head">
|
||||||
|
<strong>media-stack / compose.yaml</strong>
|
||||||
|
<div style="display:flex; gap:8px;">
|
||||||
|
<button class="btn">⬇ Pull</button>
|
||||||
|
<button class="btn">■ Down</button>
|
||||||
|
<button class="btn btn-primary">▶ Up</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="yaml"><span class="c"># managed via podman kube play — see docs/ARCHITECTURE.md</span>
|
||||||
|
<span class="k">services</span>:
|
||||||
|
<span class="k">sonarr</span>:
|
||||||
|
<span class="k">image</span>: <span class="s">lscr.io/linuxserver/sonarr:4.0</span>
|
||||||
|
<span class="k">ports</span>:
|
||||||
|
- <span class="s">"8989:8989"</span>
|
||||||
|
<span class="k">volumes</span>:
|
||||||
|
- <span class="p">/mnt/user/appdata/sonarr</span>:<span class="p">/config</span>
|
||||||
|
- <span class="p">/mnt/user/media</span>:<span class="p">/media</span>
|
||||||
|
<span class="k">environment</span>:
|
||||||
|
<span class="k">PUID</span>: <span class="s">"99"</span>
|
||||||
|
<span class="k">PGID</span>: <span class="s">"100"</span>
|
||||||
|
<span class="k">restart</span>: <span class="s">unless-stopped</span>
|
||||||
|
|
||||||
|
<span class="k">radarr</span>:
|
||||||
|
<span class="k">image</span>: <span class="s">lscr.io/linuxserver/radarr:5.2</span>
|
||||||
|
<span class="k">ports</span>:
|
||||||
|
- <span class="s">"7878:7878"</span>
|
||||||
|
<span class="k">volumes</span>:
|
||||||
|
- <span class="p">/mnt/user/appdata/radarr</span>:<span class="p">/config</span>
|
||||||
|
- <span class="p">/mnt/user/media</span>:<span class="p">/media</span>
|
||||||
|
<span class="k">restart</span>: <span class="s">unless-stopped</span>
|
||||||
|
|
||||||
|
<span class="k">prowlarr</span>:
|
||||||
|
<span class="k">image</span>: <span class="s">lscr.io/linuxserver/prowlarr:1</span>
|
||||||
|
<span class="k">ports</span>:
|
||||||
|
- <span class="s">"9696:9696"</span>
|
||||||
|
<span class="k">restart</span>: <span class="s">unless-stopped</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ============================= SETTINGS ============================= -->
|
||||||
|
<section class="panel" id="panel-settings">
|
||||||
|
<div class="settings-grid">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-head"><h2>Storage</h2></div>
|
||||||
|
<div class="field-row">
|
||||||
|
<label>Storage path</label>
|
||||||
|
<div>
|
||||||
|
<input type="text" value="/mnt/cache/system/podman" class="mono">
|
||||||
|
<div class="hint">Cache pool or dedicated disk — never a path under /mnt/user (FUSE). See Architecture docs.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="field-row">
|
||||||
|
<label>podman.img size</label>
|
||||||
|
<div><input type="number" value="20" style="max-width:100px;"><span class="unit">GB</span>
|
||||||
|
<div class="bar" style="max-width:340px; margin-top:10px;"><span style="width:62%"></span></div>
|
||||||
|
<div class="hint">62.4 GB used of 20 GB nominal — image will need to grow soon.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-head"><h2>Networking</h2></div>
|
||||||
|
<div class="field-row"><label>Default bridge subnet</label><div><input type="text" value="10.89.0.0/24" class="mono"></div></div>
|
||||||
|
<div class="field-row"><label>DNS backend</label><div><select><option>aardvark-dns (recommended)</option><option>none</option></select></div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-head"><h2>Autostart & Lifecycle</h2></div>
|
||||||
|
<div class="field-row"><label>Start podman on array start</label><div><label class="switch"><input type="checkbox" checked><span class="track"></span></label></div></div>
|
||||||
|
<div class="field-row"><label>Container stop timeout</label><div><input type="number" value="10" style="max-width:100px;"><span class="unit">seconds</span></div></div>
|
||||||
|
<div class="field-row"><label>Autostart Safe-Mode threshold</label><div><input type="number" value="3" style="max-width:100px;"><span class="unit">consecutive failures</span>
|
||||||
|
<div class="hint">watchtower is currently paused — see Dashboard.</div></div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-head"><h2>Updates</h2><span class="sub">unraid-podman 0.1.0</span></div>
|
||||||
|
<div class="field-row"><label>Update channel</label><div><select><option>Stable</option><option>Pre-release</option></select></div></div>
|
||||||
|
<div class="field-row"><label>Package versions</label>
|
||||||
|
<div class="hint mono" style="max-width:none;">
|
||||||
|
podman 6.0.1 · conmon 2.2.1 · crun 1.28 · netavark 2.0.0 · aardvark-dns 2.0.0 · passt git6ef3d1c · fuse-overlayfs 1.17
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card danger-card">
|
||||||
|
<div class="card-head"><h2>Danger Zone</h2></div>
|
||||||
|
<div class="field-row">
|
||||||
|
<label>Roll back plugin</label>
|
||||||
|
<div><button class="btn">Restore previous version (0.0.9)</button></div>
|
||||||
|
</div>
|
||||||
|
<div class="field-row">
|
||||||
|
<label>Remove plugin</label>
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-danger">Uninstall unraid-podman…</button>
|
||||||
|
<div class="hint">Your configuration, backups, and container storage are preserved by default. Full data removal requires a separate confirmation.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer class="note">Mockup only — static sample data, not connected to a live podman.sock. See webui/README.md.</footer>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Tab switching: the only interactive behavior this static mockup needs.
|
||||||
|
const buttons = document.querySelectorAll('#subnav button');
|
||||||
|
buttons.forEach(function (btn) {
|
||||||
|
btn.addEventListener('click', function () {
|
||||||
|
buttons.forEach(function (b) { b.classList.remove('active'); });
|
||||||
|
btn.classList.add('active');
|
||||||
|
document.querySelectorAll('.panel').forEach(function (p) { p.classList.remove('active'); });
|
||||||
|
const target = document.getElementById('panel-' + btn.dataset.panel);
|
||||||
|
if (target) target.classList.add('active');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
Menu="Podman"
|
||||||
|
Title="Podman"
|
||||||
|
Icon="podman"
|
||||||
|
---
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* Podman.page — Unraid Dynamix-style GUI page for unraid-podman.
|
||||||
|
*
|
||||||
|
* This file only renders the page shell: the header, the sub-navigation
|
||||||
|
* for the ten sections, and one (mostly empty) container per panel. Every
|
||||||
|
* panel fills itself in via AJAX after load — see javascript/app.js's
|
||||||
|
* router and each javascript/<panel>.js module — so this file has no
|
||||||
|
* business logic of its own and does not talk to PodmanClient directly.
|
||||||
|
* See docs/ARCHITECTURE.md section 18 for the overall WebUI design and
|
||||||
|
* webui/mockups/prototype.html for the approved visual reference this
|
||||||
|
* page's markup mirrors (same structure, same CSS classes, real data
|
||||||
|
* instead of static samples).
|
||||||
|
*/
|
||||||
|
?>
|
||||||
|
<link rel="stylesheet" type="text/css" href="/plugins/podman/styles/podman.css">
|
||||||
|
|
||||||
|
<div class="podman-plugin">
|
||||||
|
|
||||||
|
<header class="podman-pagehead">
|
||||||
|
<div class="titlewrap">
|
||||||
|
<div class="icon" aria-hidden="true">
|
||||||
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none"><path d="M12 2l8 4.5v9L12 20l-8-4.5v-9L12 2z" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/><path d="M12 11l8-4.5M12 11v9M12 11L4 6.5" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/></svg>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1>Podman</h1>
|
||||||
|
<div class="meta" id="podman-header-meta">Connecting…</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex; gap:8px;">
|
||||||
|
<button class="podman-btn" id="podman-refresh-all">↻ Refresh</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<nav class="podman-subnav">
|
||||||
|
<button class="active" data-panel="dashboard">Dashboard</button>
|
||||||
|
<button data-panel="containers">Containers</button>
|
||||||
|
<button data-panel="pods">Pods</button>
|
||||||
|
<button data-panel="images">Images</button>
|
||||||
|
<button data-panel="volumes">Volumes</button>
|
||||||
|
<button data-panel="networks">Networks</button>
|
||||||
|
<button data-panel="logs">Logs</button>
|
||||||
|
<button data-panel="terminal">Terminal</button>
|
||||||
|
<button data-panel="compose">Compose</button>
|
||||||
|
<button data-panel="settings">Settings</button>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main class="podman-main">
|
||||||
|
|
||||||
|
<!-- ============================= DASHBOARD ============================= -->
|
||||||
|
<section class="podman-panel active" id="podman-panel-dashboard">
|
||||||
|
<div class="podman-grid podman-stat-grid">
|
||||||
|
<div class="podman-stat"><div class="label">Running</div><div class="value tnum"><span id="stat-running">—</span> <small id="stat-running-total"></small></div></div>
|
||||||
|
<div class="podman-stat"><div class="label">Pods</div><div class="value tnum" id="stat-pods">—</div></div>
|
||||||
|
<div class="podman-stat"><div class="label">Images</div><div class="value tnum" id="stat-images">—</div></div>
|
||||||
|
<div class="podman-stat"><div class="label">Volumes</div><div class="value tnum" id="stat-volumes">—</div></div>
|
||||||
|
<div class="podman-stat"><div class="label">Networks</div><div class="value tnum" id="stat-networks">—</div></div>
|
||||||
|
<div class="podman-stat"><div class="label">Images on Disk</div><div class="value tnum" id="stat-images-size">—</div></div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ============================= CONTAINERS ============================= -->
|
||||||
|
<section class="podman-panel" id="podman-panel-containers">
|
||||||
|
<div class="podman-card">
|
||||||
|
<div class="podman-toolbar">
|
||||||
|
<input class="podman-search" id="containers-search" type="text" placeholder="Search containers by name or image…">
|
||||||
|
<div class="filterset" id="containers-filterset" style="display:flex; gap:4px; background:var(--surface-2); padding:3px; border-radius:8px;">
|
||||||
|
<button class="active" data-filter="all" id="containers-count-all">All</button>
|
||||||
|
<button data-filter="running" id="containers-count-running">Running</button>
|
||||||
|
<button data-filter="stopped" id="containers-count-stopped">Stopped</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="podman-table-wrap">
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Status</th><th>Name</th><th>Image</th><th>CPU/Mem</th><th>Ports</th><th>Uptime</th><th></th></tr></thead>
|
||||||
|
<tbody id="containers-tbody"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ============================= PODS ============================= -->
|
||||||
|
<section class="podman-panel" id="podman-panel-pods"></section>
|
||||||
|
|
||||||
|
<!-- ============================= IMAGES ============================= -->
|
||||||
|
<section class="podman-panel" id="podman-panel-images">
|
||||||
|
<div class="podman-card">
|
||||||
|
<div class="podman-toolbar">
|
||||||
|
<input class="podman-search" type="text" placeholder="Search images…" disabled title="Client-side filtering not yet wired up for Images">
|
||||||
|
<button class="podman-btn" id="images-pull-btn">⬇ Pull Image</button>
|
||||||
|
</div>
|
||||||
|
<div class="podman-table-wrap">
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Repository</th><th>Tag</th><th>Image ID</th><th>Size</th><th>Created</th><th>Used By</th><th></th></tr></thead>
|
||||||
|
<tbody id="images-tbody"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ============================= VOLUMES ============================= -->
|
||||||
|
<section class="podman-panel" id="podman-panel-volumes">
|
||||||
|
<div class="podman-card">
|
||||||
|
<div class="podman-toolbar">
|
||||||
|
<div style="flex:1;"></div>
|
||||||
|
<button class="podman-btn podman-btn-primary" id="volumes-create-btn">+ New Volume</button>
|
||||||
|
</div>
|
||||||
|
<div class="podman-table-wrap">
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Name</th><th>Driver</th><th>Mountpoint</th><th>Used By</th><th></th></tr></thead>
|
||||||
|
<tbody id="volumes-tbody"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="podman-empty-note" style="border-top:1px solid var(--border); text-align:left; padding:12px 18px;">
|
||||||
|
App data mapped from Unraid shares (e.g. <span class="mono">/mnt/user/appdata/…</span>) is managed as bind mounts per-container and does not appear here.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ============================= NETWORKS ============================= -->
|
||||||
|
<section class="podman-panel" id="podman-panel-networks">
|
||||||
|
<div class="podman-card">
|
||||||
|
<div class="podman-toolbar">
|
||||||
|
<div style="flex:1;"></div>
|
||||||
|
<button class="podman-btn podman-btn-primary" id="networks-create-btn">+ New Network</button>
|
||||||
|
</div>
|
||||||
|
<div class="podman-table-wrap">
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Name</th><th>Driver</th><th>Subnet</th><th>Gateway</th><th>Containers</th><th></th></tr></thead>
|
||||||
|
<tbody id="networks-tbody"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ============================= LOGS ============================= -->
|
||||||
|
<section class="podman-panel" id="podman-panel-logs">
|
||||||
|
<div class="podman-card">
|
||||||
|
<div class="podman-logs-layout">
|
||||||
|
<div class="podman-logs-side" id="logs-sidebar"></div>
|
||||||
|
<div>
|
||||||
|
<div class="podman-toolbar">
|
||||||
|
<input class="podman-search" id="logs-filter" type="text" placeholder="Filter log output…" style="max-width:280px;">
|
||||||
|
<span id="logs-follow-toggle" style="display:flex; gap:4px; background:var(--surface-2); padding:3px; border-radius:8px;">
|
||||||
|
<button class="active" data-follow="true">Follow</button>
|
||||||
|
<button data-follow="false">Paused</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="podman-log-pane" id="log-pane" role="log"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ============================= TERMINAL ============================= -->
|
||||||
|
<section class="podman-panel" id="podman-panel-terminal">
|
||||||
|
<div class="podman-card">
|
||||||
|
<div class="podman-card-pad">
|
||||||
|
<div style="display:flex; gap:8px; align-items:center; margin-bottom:12px; font-size:12.5px; color:var(--text-dim);">
|
||||||
|
Exec into: <select id="term-container-select"></select>
|
||||||
|
</div>
|
||||||
|
<div class="podman-term" id="term-output"></div>
|
||||||
|
<input class="podman-term-input" id="term-input" type="text" placeholder="Type a command and press Enter… (one-shot exec — see Compose panel note on API scope)" autocomplete="off">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ============================= COMPOSE ============================= -->
|
||||||
|
<section class="podman-panel" id="podman-panel-compose">
|
||||||
|
<div class="podman-card">
|
||||||
|
<div class="podman-compose-layout">
|
||||||
|
<div class="podman-compose-side" id="compose-sidebar"></div>
|
||||||
|
<div>
|
||||||
|
<div class="podman-toolbar">
|
||||||
|
<strong id="compose-title" style="flex:1;">—</strong>
|
||||||
|
<button class="podman-btn" id="compose-action-pull">⬇ Pull</button>
|
||||||
|
<button class="podman-btn" id="compose-action-down">■ Down</button>
|
||||||
|
<button class="podman-btn podman-btn-primary" id="compose-action-up">▶ Up</button>
|
||||||
|
</div>
|
||||||
|
<pre class="podman-yaml" id="compose-yaml"></pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ============================= SETTINGS ============================= -->
|
||||||
|
<section class="podman-panel" id="podman-panel-settings">
|
||||||
|
<div class="podman-grid">
|
||||||
|
<div class="podman-card">
|
||||||
|
<div class="podman-card-head"><h2>Storage</h2></div>
|
||||||
|
<div class="podman-field-row">
|
||||||
|
<label for="settings-storage-path">Storage path</label>
|
||||||
|
<div>
|
||||||
|
<input type="text" id="settings-storage-path" class="mono">
|
||||||
|
<div class="hint">Cache pool or dedicated disk — never a path under /mnt/user (FUSE).</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="podman-field-row">
|
||||||
|
<label for="settings-storage-size">podman.img size</label>
|
||||||
|
<div><input type="number" id="settings-storage-size" style="max-width:100px;"> <span style="font-size:12px;color:var(--text-dim);">GB</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="podman-card">
|
||||||
|
<div class="podman-card-head"><h2>Autostart & Lifecycle</h2></div>
|
||||||
|
<div class="podman-field-row">
|
||||||
|
<label for="settings-enabled">Start podman on array start</label>
|
||||||
|
<div><input type="checkbox" id="settings-enabled"></div>
|
||||||
|
</div>
|
||||||
|
<div class="podman-field-row">
|
||||||
|
<label for="settings-stop-timeout">Container stop timeout</label>
|
||||||
|
<div><input type="number" id="settings-stop-timeout" style="max-width:100px;"> <span style="font-size:12px;color:var(--text-dim);">seconds</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="podman-field-row">
|
||||||
|
<label>Autostart order</label>
|
||||||
|
<div class="podman-table-wrap">
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>#</th><th>Container</th><th></th></tr></thead>
|
||||||
|
<tbody id="autostart-tbody"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="podman-field-row">
|
||||||
|
<label></label>
|
||||||
|
<div><button class="podman-btn podman-btn-primary" id="settings-save-btn">Save Settings</button></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="podman-card">
|
||||||
|
<div class="podman-card-head"><h2>Installed Packages</h2></div>
|
||||||
|
<div class="podman-field-row">
|
||||||
|
<label>Versions</label>
|
||||||
|
<div class="hint mono" id="settings-package-versions" style="max-width:none;">—</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/plugins/podman/javascript/app.js"></script>
|
||||||
|
<script src="/plugins/podman/javascript/dashboard.js"></script>
|
||||||
|
<script src="/plugins/podman/javascript/containers.js"></script>
|
||||||
|
<script src="/plugins/podman/javascript/pods.js"></script>
|
||||||
|
<script src="/plugins/podman/javascript/images.js"></script>
|
||||||
|
<script src="/plugins/podman/javascript/volumes.js"></script>
|
||||||
|
<script src="/plugins/podman/javascript/networks.js"></script>
|
||||||
|
<script src="/plugins/podman/javascript/logs.js"></script>
|
||||||
|
<script src="/plugins/podman/javascript/terminal.js"></script>
|
||||||
|
<script src="/plugins/podman/javascript/compose.js"></script>
|
||||||
|
<script src="/plugins/podman/javascript/settings.js"></script>
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* ajax/compose.php
|
||||||
|
*
|
||||||
|
* Backs the Compose panel — and is the ONE deliberate exception to
|
||||||
|
* "talk to podman.sock, never shell out" in this entire WebUI.
|
||||||
|
*
|
||||||
|
* Why: libpod's REST API has no endpoint that understands
|
||||||
|
* docker-compose.yml/compose.yaml at all. The closest thing,
|
||||||
|
* POST /libpod/kube/play, takes Kubernetes YAML, not Compose YAML, and
|
||||||
|
* correctly translating arbitrary Compose files into Kube manifests is a
|
||||||
|
* substantial project of its own (this is exactly what `podman compose` /
|
||||||
|
* `podman-compose` already do). Reimplementing that translation from
|
||||||
|
* scratch here — instead of using the `podman compose` CLI, which IS the
|
||||||
|
* project's own supported way to run Compose files — would be more
|
||||||
|
* fragile, not more "API-native": there is no API to be native to for
|
||||||
|
* this one feature. So this file, and only this file, runs the
|
||||||
|
* `podman compose` CLI via proc_open(), with strict input validation
|
||||||
|
* (project names are matched against a fixed pattern, never interpolated
|
||||||
|
* into a shell string) rather than passing user input through a shell.
|
||||||
|
*
|
||||||
|
* Compose projects live under $bootDir/compose/<project>/compose.yaml —
|
||||||
|
* see docs/ARCHITECTURE.md; this mirrors how autostart/networks/backups
|
||||||
|
* are all rooted under /boot/config/plugins/podman/ for the same
|
||||||
|
* boot-persistence reasons.
|
||||||
|
*
|
||||||
|
* Actions (?action=...):
|
||||||
|
* list GET -> known projects with up/down status
|
||||||
|
* get GET (&project=...) -> raw compose.yaml content
|
||||||
|
* up POST {"project": "..."}
|
||||||
|
* down POST {"project": "..."}
|
||||||
|
* pull POST {"project": "..."}
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require __DIR__ . '/../include/bootstrap.php';
|
||||||
|
|
||||||
|
$composeDir = $podmanConfig->bootDir . '/compose';
|
||||||
|
$action = $_GET['action'] ?? '';
|
||||||
|
|
||||||
|
switch ($action) {
|
||||||
|
case 'list':
|
||||||
|
podman_json_response(compose_list($composeDir));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'get':
|
||||||
|
$project = (string) ($_GET['project'] ?? '');
|
||||||
|
podman_json_response(['yaml' => compose_read($composeDir, $project)]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'up':
|
||||||
|
podman_json_response(compose_run($composeDir, require_project(podman_read_json_body()), ['up', '-d']));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'down':
|
||||||
|
podman_json_response(compose_run($composeDir, require_project(podman_read_json_body()), ['down']));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'pull':
|
||||||
|
podman_json_response(compose_run($composeDir, require_project(podman_read_json_body()), ['pull']));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
podman_json_error("Unknown action '{$action}'", 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param array<string,mixed> $body */
|
||||||
|
function require_project(array $body): string
|
||||||
|
{
|
||||||
|
$project = (string) ($body['project'] ?? '');
|
||||||
|
if (!is_valid_project_name($project)) {
|
||||||
|
podman_json_error('Missing or invalid project name', 400);
|
||||||
|
}
|
||||||
|
return $project;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project names come from the user (a form field when creating a new
|
||||||
|
* compose project, or a value round-tripped from list()). Restricting
|
||||||
|
* them to a fixed safe character set here — BEFORE they're ever used to
|
||||||
|
* build a filesystem path or a command argument — is what makes it safe
|
||||||
|
* to pass them to proc_open() at all.
|
||||||
|
*/
|
||||||
|
function is_valid_project_name(string $name): bool
|
||||||
|
{
|
||||||
|
return $name !== '' && preg_match('/^[a-zA-Z0-9_-]+$/', $name) === 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array<int,array<string,mixed>> */
|
||||||
|
function compose_list(string $composeDir): array
|
||||||
|
{
|
||||||
|
if (!is_dir($composeDir)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
$projects = [];
|
||||||
|
foreach (scandir($composeDir) ?: [] as $entry) {
|
||||||
|
if ($entry === '.' || $entry === '..' || !is_valid_project_name($entry)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$yamlPath = $composeDir . '/' . $entry . '/compose.yaml';
|
||||||
|
if (!is_file($yamlPath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$projects[] = [
|
||||||
|
'name' => $entry,
|
||||||
|
'path' => $yamlPath,
|
||||||
|
'status' => compose_status($composeDir, $entry),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
usort($projects, static fn($a, $b) => strcmp($a['name'], $b['name']));
|
||||||
|
return $projects;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Best-effort "up"/"down" status via `podman compose ps`; degrades to "unknown" rather than failing the whole list. */
|
||||||
|
function compose_status(string $composeDir, string $project): string
|
||||||
|
{
|
||||||
|
$result = run_compose_command($composeDir, $project, ['ps', '--format', 'json'], 5);
|
||||||
|
if ($result['exitCode'] !== 0) {
|
||||||
|
return 'unknown';
|
||||||
|
}
|
||||||
|
$decoded = json_decode($result['output'], true);
|
||||||
|
return (is_array($decoded) && count($decoded) > 0) ? 'up' : 'down';
|
||||||
|
}
|
||||||
|
|
||||||
|
function compose_read(string $composeDir, string $project): string
|
||||||
|
{
|
||||||
|
if (!is_valid_project_name($project)) {
|
||||||
|
podman_json_error('Invalid project name', 400);
|
||||||
|
}
|
||||||
|
$path = $composeDir . '/' . $project . '/compose.yaml';
|
||||||
|
$content = is_file($path) ? file_get_contents($path) : false;
|
||||||
|
if ($content === false) {
|
||||||
|
podman_json_error("compose.yaml not found for project '{$project}'", 404);
|
||||||
|
}
|
||||||
|
return $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array<string,mixed> */
|
||||||
|
function compose_run(string $composeDir, string $project, array $subcommand): array
|
||||||
|
{
|
||||||
|
$result = run_compose_command($composeDir, $project, $subcommand, 300);
|
||||||
|
if ($result['exitCode'] !== 0) {
|
||||||
|
podman_json_error("podman compose " . implode(' ', $subcommand) . " failed:\n" . $result['output'], 502);
|
||||||
|
}
|
||||||
|
return ['output' => $result['output']];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs `podman compose -f <project>/compose.yaml <subcommand...>` via
|
||||||
|
* proc_open with an argv array (never a shell string — proc_open with an
|
||||||
|
* array argument bypasses the shell entirely, so there is no injection
|
||||||
|
* surface even though $project has already been validated above too).
|
||||||
|
*
|
||||||
|
* @param array<int,string> $subcommand
|
||||||
|
* @return array{exitCode:int,output:string}
|
||||||
|
*/
|
||||||
|
function run_compose_command(string $composeDir, string $project, array $subcommand, int $timeoutSeconds): array
|
||||||
|
{
|
||||||
|
$yamlPath = $composeDir . '/' . $project . '/compose.yaml';
|
||||||
|
$argv = array_merge(['podman', 'compose', '-f', $yamlPath], $subcommand);
|
||||||
|
|
||||||
|
$descriptors = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']];
|
||||||
|
$process = proc_open($argv, $descriptors, $pipes, $composeDir . '/' . $project);
|
||||||
|
if (!is_resource($process)) {
|
||||||
|
return ['exitCode' => 127, 'output' => 'Could not start podman compose process'];
|
||||||
|
}
|
||||||
|
|
||||||
|
stream_set_timeout($pipes[1], $timeoutSeconds);
|
||||||
|
$stdout = stream_get_contents($pipes[1]) ?: '';
|
||||||
|
$stderr = stream_get_contents($pipes[2]) ?: '';
|
||||||
|
fclose($pipes[1]);
|
||||||
|
fclose($pipes[2]);
|
||||||
|
$exitCode = proc_close($process);
|
||||||
|
|
||||||
|
return ['exitCode' => $exitCode, 'output' => trim($stdout . $stderr)];
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* ajax/containers.php
|
||||||
|
*
|
||||||
|
* Backs the Containers panel (and the container rows nested inside the
|
||||||
|
* Pods panel — see ajax/pods.php). All actions go through PodmanClient,
|
||||||
|
* i.e. the real libpod REST API over podman.sock; nothing here shells out.
|
||||||
|
*
|
||||||
|
* Actions (?action=...):
|
||||||
|
* list GET -> normalized array of containers for the table view
|
||||||
|
* inspect GET (&id=...) -> raw inspect JSON, for a detail dialog
|
||||||
|
* start POST {"id": "..."}
|
||||||
|
* stop POST {"id": "...", "timeout": 10}
|
||||||
|
* restart POST {"id": "...", "timeout": 10}
|
||||||
|
* remove POST {"id": "...", "force": false}
|
||||||
|
* logs GET (&id=...&tail=200) -> plain text
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
require __DIR__ . '/../include/bootstrap.php';
|
||||||
|
|
||||||
|
$action = $_GET['action'] ?? '';
|
||||||
|
|
||||||
|
switch ($action) {
|
||||||
|
case 'list':
|
||||||
|
podman_json_response(containers_list($client));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'inspect':
|
||||||
|
$id = (string) ($_GET['id'] ?? '');
|
||||||
|
if ($id === '') {
|
||||||
|
podman_json_error('Missing id', 400);
|
||||||
|
}
|
||||||
|
podman_json_response($client->inspectContainer($id));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'logs':
|
||||||
|
$id = (string) ($_GET['id'] ?? '');
|
||||||
|
if ($id === '') {
|
||||||
|
podman_json_error('Missing id', 400);
|
||||||
|
}
|
||||||
|
$tail = (int) ($_GET['tail'] ?? 200);
|
||||||
|
podman_json_response(['text' => $client->containerLogs($id, $tail)]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'start':
|
||||||
|
$body = podman_read_json_body();
|
||||||
|
$client->startContainer(require_id($body));
|
||||||
|
podman_json_response(['status' => 'started']);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'stop':
|
||||||
|
$body = podman_read_json_body();
|
||||||
|
$client->stopContainer(require_id($body), (int) ($body['timeout'] ?? $podmanConfig->stopTimeoutSeconds));
|
||||||
|
podman_json_response(['status' => 'stopped']);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'restart':
|
||||||
|
$body = podman_read_json_body();
|
||||||
|
$client->restartContainer(require_id($body), (int) ($body['timeout'] ?? $podmanConfig->stopTimeoutSeconds));
|
||||||
|
podman_json_response(['status' => 'restarted']);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'remove':
|
||||||
|
$body = podman_read_json_body();
|
||||||
|
$client->removeContainer(require_id($body), (bool) ($body['force'] ?? false));
|
||||||
|
podman_json_response(['status' => 'removed']);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
podman_json_error("Unknown action '{$action}'", 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param array<string,mixed> $body */
|
||||||
|
function require_id(array $body): string
|
||||||
|
{
|
||||||
|
$id = (string) ($body['id'] ?? '');
|
||||||
|
if ($id === '') {
|
||||||
|
podman_json_error('Missing id in request body', 400);
|
||||||
|
}
|
||||||
|
return $id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Normalizes libpod's /containers/json entries into exactly what the
|
||||||
|
* Containers table (javascript/containers.js) renders — keeping this
|
||||||
|
* shaping logic server-side means the frontend never has to know libpod's
|
||||||
|
* raw field names/quirks (e.g. Names is an array, State vs Status, etc).
|
||||||
|
*
|
||||||
|
* @return array<int,array<string,mixed>>
|
||||||
|
*/
|
||||||
|
function containers_list(PodmanClient $client): array
|
||||||
|
{
|
||||||
|
$raw = $client->listContainers(true);
|
||||||
|
$out = [];
|
||||||
|
|
||||||
|
foreach ($raw as $c) {
|
||||||
|
$names = $c['Names'] ?? [];
|
||||||
|
$name = is_array($names) && count($names) > 0 ? ltrim((string) $names[0], '/') : podman_short_id((string) ($c['Id'] ?? ''));
|
||||||
|
|
||||||
|
$ports = [];
|
||||||
|
foreach (($c['Ports'] ?? []) as $p) {
|
||||||
|
if (isset($p['host_port'], $p['container_port'])) {
|
||||||
|
$ports[] = "{$p['host_port']}:{$p['container_port']}/" . ($p['protocol'] ?? 'tcp');
|
||||||
|
} elseif (isset($p['container_port'])) {
|
||||||
|
$ports[] = "{$p['container_port']}/" . ($p['protocol'] ?? 'tcp');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$startedAt = podman_parse_time($c['StartedAt'] ?? null);
|
||||||
|
$state = strtolower((string) ($c['State'] ?? 'unknown'));
|
||||||
|
|
||||||
|
$out[] = [
|
||||||
|
'id' => (string) ($c['Id'] ?? ''),
|
||||||
|
'shortId' => podman_short_id((string) ($c['Id'] ?? '')),
|
||||||
|
'name' => $name,
|
||||||
|
'image' => (string) ($c['Image'] ?? ''),
|
||||||
|
'state' => $state,
|
||||||
|
'status' => (string) ($c['Status'] ?? ''),
|
||||||
|
'health' => $c['Health']['Status'] ?? null,
|
||||||
|
'ports' => $ports,
|
||||||
|
'pod' => $c['Pod'] ?? null,
|
||||||
|
'podName' => $c['PodName'] ?? null,
|
||||||
|
'uptimeSeconds' => ($state === 'running' && $startedAt !== null) ? (time() - $startedAt) : null,
|
||||||
|
'createdAt' => podman_parse_time($c['Created'] ?? null),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
usort($out, static fn($a, $b) => strcmp($a['name'], $b['name']));
|
||||||
|
return $out;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user