Files
maggesandClaude Sonnet 5 7e2ed451e3
Lint / ShellCheck (push) Successful in 14s
Lint / Validate .plg XML (push) Successful in 11s
Lint / EditorConfig (push) Successful in 6s
Add Apps/Store tab, template WebUI/URL import, container RO volumes/device passthrough/run-as-user, and in-app confirm dialogs
Replaces every native confirm() with a shared P.confirm() modal (a hung
native dialog was found live to block the whole tab, including
auto-refresh, and once even double-confirmed an unrelated deletion).
Also fixes Edit Container silently resetting to Bridge/blanking the
Static IP for any container on a custom network, and a context menu
losing its anchor to a mid-read auto-refresh.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 19:24:32 +00:00

281 lines
17 KiB
Markdown

# 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]
### Fixed
- Edit Container always reset the Network dropdown to "Bridge" and blanked
the Static IP field, even for a container actually on a custom/macvlan
network with a real IP — `HostConfig.NetworkMode` turns out to just say
"bridge" regardless of what a container is actually attached to via a
custom network (verified live: a running container on "Lan" reported
NetworkMode "bridge" while `NetworkSettings.Networks` only had a "Lan"
entry, no "bridge" one at all). The real network name now comes from
that one `NetworkSettings.Networks` key instead, except when it's
podman's own literal default bridge network (named "podman", not
"bridge") — found while investigating why Sonarr's real static IP never
showed up in its own Edit form.
- A row's context menu (opened from a container's name or its "⋮") kept
the ~2s auto-refresh running underneath it — found live: leaving the
menu open longer than that (reading it, or opening "Move to Folder"
after a pause) let a refresh replace the whole table's rows in the
background, so the menu's anchor button was no longer the one actually
on screen, and a submenu opened from it then positioned itself
wherever that stale anchor now was instead of anywhere sensible.
Auto-refresh now also pauses while any context menu is open, the same
way it already paused for an open modal.
### Added
- Create/Edit Container: "Run as user (optional)" overrides the image's
own default user (e.g. `99:100`) — found live migrating a real
container (Seerr) that Docker had run as `--user 99:100` to match its
bind-mounted appdata's ownership; without this field, podman fell back
to the image's own `USER node` (UID 1000), which couldn't write to
files/directories owned by `nobody:users`. Pre-fills from an existing
container's own `Config.User` when editing.
- Clicking a container's name in the Containers table now opens its row
menu (Details/Pause/Kill/Rename/Edit/Remove), matching how a folder's
member chips already worked — Details becomes just the first menu item
again, consistent everywhere a container is represented, rather than
only inside a folder.
- Create/Edit Container: volumes can now be marked read-only (a "RO"
checkbox per row), and a new "Device passthrough" field passes an
arbitrary host device (e.g. a USB serial adapter like `/dev/ttyACM0`)
through at the same path inside the container — the existing GPU
passthrough field is unchanged and stays the right choice for
`/dev/dri/*`. Both were verified live against podman's own API before
wiring them up (`RW:false` on the resulting mount, and the device
showing up as `PathOnHost`/`PathInContainer`). Device paths are
restricted to `/dev/...` (no `..`) — this goes straight into a podman
create request, not anywhere it could reach untrusted input otherwise.
Read-only also round-trips through templates now (dockerMan's own
`Mode="ro"` convention on a `Path` Config — found in the wild on a real
template that mounts `/mnt/user` read-only for a storage-stats
sidecar). Generic device passthrough is a container-only field for now,
not yet part of the template schema.
- Templates now carry a container's WebUI URL through save/export/import
too (`<WebUI>`, the same tag Unraid's own Docker templates already use
for this) — "Use template" now pre-fills the WebUI URL field, and this
applies to existing Community Applications/dockerMan templates on
import too, not just ones authored by this plugin.
- Templates: "Import from a URL" (e.g. a raw GitHub link to a Community
Applications template), fetched server-side rather than requiring
copy-paste. The fetch only allows plain http(s) to a hostname that
resolves exclusively to public addresses (checked before the request,
then pinned via curl's `CURLOPT_RESOLVE` so a DNS answer can't change
between that check and the actual connection), doesn't follow
redirects, and caps the response size — see
`template_fetch_url()` in `ajax/templates.php`.
- The Templates tab is now "Apps", with a Store/My Templates toggle.
Store browses/searches Community Applications' own public app feed
directly (the same catalog CA's own plugin is built on — see
`ca_feed_search()` in `ajax/templates.php`), paginated (24/page, with
Prev/Next) rather than a single capped-length list. Browsing (no search
term) defaults to Newest-first (by the feed's own FirstSeen timestamp —
when CA's feed first picked the template up), with a toggle to
alphabetical; an actual search is always alphabetical regardless of that
toggle — the only other candidate signal, the feed's own "downloads"
figure, turns out to just be the underlying Docker image's Docker Hub
pull count (found live: dozens of unrelated templates that all happen to
wrap the official nginx/postgres/redis images share the exact same,
enormous number), which would make search results look ranked by
relevance while really just favoring whichever match wraps the
most-pulled base image. "Install" fetches + imports an app the same way
pasting its template URL always did, then opens it straight in the
Create Container form, pre-filled — the same experience "Use" already
gives a saved template, since installing one also saves it as one. My
Templates is this plugin's own saved-template grid, unchanged, with
"Import Template" (paste XML / a URL / one of Unraid's own existing
local Docker templates) still a modal off of it.
- A shared in-app confirm dialog (`P.confirm()` in `app.js`) replaces
every browser-native `confirm()` across the whole plugin (containers,
templates, compose, images, volumes, networks, pods, settings). A
native `confirm()` blocks the entire tab until dismissed — including
this plugin's own auto-refresh — and found live to be an actual
liability: a hung dialog blocked further interaction outright, and in
one case a stray keypress meant to dismiss it ended up confirming a
second, unrelated deletion too.
### Fixed
- `podman-verify-packages.sh`/`podman-update-packages.sh` reported every
single package as "not installed" right after a genuinely successful
install (confirmed live on a real v0.1.5 install: `/var/log/packages/`
had the correct records the whole time). Root cause:
`/var/log/packages` is itself a symlink on Unraid
(`-> ../lib/pkgtools/packages`), and GNU `find`'s default `-P` mode
doesn't descend into a symlinked starting path at all without `-L`
it just returns the symlink itself and nothing below it. Fixed via a
new shared `podman_find_installed_package_record()` helper in
`podman-common.sh`, which also fixes a second bug found while testing
the first fix: "podman"'s own glob also matched podman-compose's file
(a literal prefix collision), and `find`'s unsorted output let
podman-compose's record silently win podman's own check on the same
real host.
## [0.1.5] - 2026-07-13
### Added
- Containers panel: folders to group containers (name + optional icon
URL), purely cosmetic organizational metadata stored in the plugin's
own `folders.json` — podman itself has no such concept, same as
Unraid's own Docker page's folders. "+ New Folder", a "Move to Folder"
submenu on each container's row menu, and per-folder rename/delete
(deleting a folder only ungroups its containers, never touches them).
A folder with nothing assigned yet still shows up (so there's
somewhere to move a container into); one that's merely hidden by the
current search/filter does not. A folder's header always shows an
icon+name+status chip per member — collapsed or expanded, matching how
Unraid's own Docker page folders behave — rather than hiding everything
behind a bare count; clicking a chip opens the same row menu the "⋮"
button does (Details is now a menu item there too, alongside
Pause/Kill/Rename/Edit/Remove) rather than jumping straight to the
detail modal. "Move to Folder" only shows for a container that isn't
grouped yet — once it's in one, that item becomes a direct "Remove
from Folder" instead, since "move to a folder" reads as "add" and is
ambiguous/redundant once it's already in one. Containers can also
carry an icon URL (settable in the create/edit form, auto-filled when
creating from a template) shown in the table instead of the 2-letter
initials avatar.
- Containers can now carry a WebUI URL too (create/edit form, preserved
through Update/Update All the same way the icon URL is), shown as a
small open-in-new-tab button next to the name in the Containers table
when set. Both this and the icon are stored as the plugin's own
container labels (`podman-webui.weburl`/`.icon`), read straight off
the already-fetched container list — no extra per-container calls.
- Container detail modal: 5 new tabs (Resources, Logs, Console, Events,
Healthcheck), rounding it out from 6 to 11 of prompt.md's 12 requested
tabs (Volumes was left merged into the existing Mounts tab — same
underlying source/destination data, a separate tab would just repeat
it). Resources shows live CPU/memory alongside configured limits
(memory/swap/CPU/PIDs/block-I/O — a `0` in podman's own HostConfig
means "unlimited", not zero). Logs reuses the existing per-container
logs endpoint. Console opens a real ttyd/podman-exec session scoped to
the modal, cleaned up (killing the ttyd process server-side) on every
way of leaving — switching tabs, Close, backdrop click, or Escape —
not just on an explicit Disconnect, so peeking at a console doesn't
leak an orphaned ttyd process. Events and Healthcheck are both one-shot
historical queries (libpod's `/events?stream=false` for the last 7
days, and inspect's own `State.Health.Log`) rather than a live stream,
which stays out of scope (see dashboard.js's own note on why).
- A real toast notification system (success/warning/error/info, auto-
dismissing, stacked bottom-right, dismissible early) replacing every
`alert()` used for one-way feedback across Containers, Images, Volumes,
Networks, Pods, Templates, Compose, and Settings — confirmations stay
native `confirm()` (a decision, not a notice), and long command output
(`podman compose up/down`, previously also an `alert()`) now goes to
the existing scrolling log-modal instead, which fits it better than a
toast ever could.
- Dashboard reworked: a clean 6-tile row of plain counts (Running,
Stopped, Pods, Images, Volumes, Networks — colored green/red on
Running/Stopped so fleet health reads at a glance), a single "Resource
Usage" card with aligned CPU/Memory/Swap/Storage meter rows (bars shift
to warn/bad colors above 75%/90% instead of staying accent-colored at
any value), and an "Autostart Queue" table (reusing
`podman-autostart.sh`'s own per-container failure counters, so it shows
exactly what that script would decide, not a second tracked history).
Bar-and-percentage metrics were kept out of the plain-count tile grid
entirely after an earlier attempt at combining them (a tile spanning
multiple grid columns) left dead, empty cells in the layout.
- Dashboard and Containers now auto-refresh every ~2s (paused while the
tab is hidden or a modal is open) instead of requiring a manual
Refresh click to see current state.
- Settings: an "Add container" dropdown + button above the Autostart
order table — previously that table could only reorder/remove
containers already in the chain, with no way to add one in the first
place.
- Containers: "Update All" now also removes the old, now-unused image
version each updated container leaves behind (only when at least one
container actually updated), instead of leaving stale images to pile up
on disk after every update run.
### Fixed
- Dashboard memory usage was calculated from libpod's raw `memFree` (which
excludes reclaimable buffers/cache), showing usage far higher than
reality — e.g. 67% "used" where `free -h` reported 19%. Now reads
`/proc/meminfo`'s `MemAvailable` directly, matching what `free -h` and
most monitoring tools show.
- Dashboard CPU usage never changed — libpod's own `/info` computes
`cpuUtilization` once and never resamples it (confirmed live: three
calls seconds apart returned byte-identical numbers). Now computed from
`/proc/stat` deltas between successive requests, the same technique
`top`/`htop` use, which auto-refresh's ~2s cadence fits naturally.
- Context menus (a container's "⋮", a folder's "⋮") always dropped down
from the anchor at a fixed position — opened from a row near the
bottom of the viewport, the menu ran off-screen with its last items
unreachable. Now measures its own actual rendered size (which varies
with item count) after appending and flips above the anchor instead
when there isn't enough room below, clamped horizontally too.
- Expanding/collapsing a folder shifted every column (including the
header text) in the Containers table — its default `table-layout:
auto` sizes each column from the widest content among only the
currently-visible rows, so adding/removing a folder's rows changed
what counted as "widest" on every toggle. Columns now use explicit
fixed widths that don't depend on row content at all.
- The Name column's content (icon + name + WebUI button) was
misaligned with its own header, drifting further off with every row —
putting `display:flex` directly on the `<td>` (to keep the WebUI
button on the same line as the name) pulls that cell out of
table-cell layout entirely, so with `table-layout:fixed` it stopped
respecting the column width its `<th>` assigns. The flex layout now
lives on a `<div>` inside the `<td>` instead, so the `<td>` itself
stays a normal, fixed-width table cell.
## [0.1.3] - 2026-07-12
### Added
- Settings: a "Podman Service" card with live status plus Start/Stop/Restart,
each shown in a progress log modal — diagnosing and recovering a podman
that failed to start no longer needs SSH/terminal access at all.
- Settings: "Format a Disk for Podman Storage", for a single-disk system
with no cache pool. Only ever lists disks with no existing partition,
filesystem, or RAID/ZFS membership signature, and never the Unraid boot
flash — both excluded by multiple independent checks, not just one.
Formatted disks are remounted by UUID on every boot.
### Fixed
- `STORAGE_PATH` was never auto-created, even when its parent (a real,
already-mounted cache pool) existed — only failed if genuinely nothing
was mounted at all. Root cause of a real "cannot reach the Podman API
socket" report on a fresh install with a perfectly normal cache pool.
- Cache-busting for the WebUI's own JS/CSS never actually worked, at all,
the entire time — Unraid runs `.page` PHP through `eval()`, and
`__DIR__` inside `eval()`'d code resolves to the *eval() call site's*
directory, not the plugin's. Every "hard refresh" this project's own
docs/commits ever recommended only worked because Ctrl+Shift+R bypasses
the browser cache directly, independent of this (broken) mechanism.
- Settings' service status chip showed "Not running" immediately after a
successful Start, because the "is it running" check only recognized
`rc.podman status`'s own wording, not `start`/`stop`/`restart`'s.
- The Podman Service log showed `rc.podman`'s multi-line output as one
run-together paragraph (missing `white-space: pre-wrap` on the pane
itself, not just its per-line children).
## [0.1.1] - 2026-07-12
### Fixed
- `plugin/podman.plg`'s `<URL>`/`<MD5>` entity values were split across their
own lines (`<URL>\n&baseURL;/...\n</URL>`) — Unraid's plugin manager passes
that text straight into a `wget ... -O <name> <url>` shell command without
trimming it, so the leading newline broke the command in two: `wget` saw no
URL argument at all, and the URL text ran on the next line as its own
(failing) shell command. Every real Unraid plugin (verified against
`unassigned.devices.plg` on a live host) keeps `<URL>...</URL>` on one
line — found by running the plugin installer's own CLI (`scripts/plugin
install`) directly on a real Unraid host and reading its raw output,
rather than trusting the webGUI's summarized install log.
## [0.1.0] - 2026-07-12
### 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