Skip to content

release: 1.4.0#610

Merged
Jaro-c merged 16 commits into
mainfrom
develop
Jun 25, 2026
Merged

release: 1.4.0#610
Jaro-c merged 16 commits into
mainfrom
develop

Conversation

@Jaro-c

@Jaro-c Jaro-c commented Jun 25, 2026

Copy link
Copy Markdown
Member

Promote develop to main for the 1.4.0 release.

All commits since 1.3.0 — the empirical-sweep fixes (#600-#608, #605) verified against real Podman 5.4.2, plus the editorconfig/docs chores. Merged as a merge commit per the release flow; tag v1.4.0 follows on main.

The README redesign is intentionally NOT included (it lives on its own branch, unmerged).

Jaro-c and others added 16 commits June 23, 2026 23:31
## What

I filled the doc-comment gaps across `internal/` flagged in #579, judged
against our style standard: every public item carries a doc comment
stating its contract (errors, invariants, **units**, side effects), not
a restatement of the signature.

## Changes

- **Real defect fixed** — `engine/health.rs`: the doc block describing
`wait_healthy` was glued above `wait_services_healthy`, so rustdoc
rendered the wrong function's docs and `wait_healthy` had none. Moved it
to the function it describes.
- **libpod wire types** — documented fields with their units/format
where it's a correctness hazard: `HealthConfig` timing = nanoseconds,
`LinuxMemory` = bytes, `shm_size` = bytes, `stop_timeout` = seconds,
throttle rates = bytes/s or iops by field. Verified each unit against
how the value is produced/consumed; converted the serde wire-rename
rationale comments from `//` to `///`.
- **compose types** — every `Service` field (compose key, unit/default,
precedence: `scale` vs `deploy.replicas`, top-level `cpus`/`mem` over
`deploy.resources`, `restart` vs `deploy.restart_policy`, `pull_policy`
values), plus `SecretConfig`/`ConfigConfig`/`ModelConfig`/`ComposeFile`
and the `develop` watch types.
- **engine / quadlet / crate root** —
`build_resource_limits`/`build_ulimits`, `parse_device`, the quadlet
`unit`/`render` helpers, and the public module surface in `lib.rs`.
- **doc-build hygiene** — fixed stale/broken intra-doc links surfaced by
rustdoc (so docs build clean under `-D warnings`) and corrected the
`include.rs` module header that omitted `models` from the merged-fields
list.

## Scope

Documentation only — no behavior change.

## Verification

- `cargo fmt --check` — clean
- `cargo doc --no-deps --document-private-items` under `RUSTDOCFLAGS=-D
warnings` — clean
- `cargo check --all-targets` — clean
- `cargo test --lib` — 836 passing

Closes #579

Signed-off-by: Jaro-c <anacaicedo1224@gmail.com>
Co-authored-by: Jaro-c <anacaicedo1224@gmail.com>
## What

I reviewed every doc under `docs/` (plus `bench/`/`fuzz/` READMEs) one
by one, fixed claims that drifted from the code, and reorganized each by
its document type. `README.md` is intentionally untouched (separate
pass).

## Accuracy fixes (verified against code)

- **`self-update.md`** — failure exit code is **`3`**, not `2`
(`internal/update/mod.rs:109`, test asserts `!= 2`). Added the
package-manager-managed refusal that happens *before* any download
(`mod.rs:79-85`).
- **`docker-migration.md`** — added the `network_mode: bridge`
divergence (isolated netns under rootless Podman; siblings unreachable;
use a shared `networks:` entry) per
`compose/diagnostics/ignored_fields.rs`. Corrected `env_file.format` to
a **warning** (not an error). Fixed `provider`/`models` wording
(modeled-but-not-honored, not "unknown key"). Qualified GPU as
NVIDIA-only/host-dependent. Made the ignored-fields list explicitly
examples, not exhaustive.
- **`commands.md`** — filled missing flags/commands: `up
-t/-V/--timestamps`, `down/stop/restart -t`, `start
--wait/--wait-timeout`, the `build` flag set, `rm -s`, the whole `exec`
option set, `pull --policy/...`, `create` flags, `port --index`,
aliases.

## Restructure (no claim changed)

- **`commands.md`** — grouped by purpose, one options table per command,
exit-status table.
- **`docker-migration.md`** — works-out-of-the-box → divergences →
accepted-no-effect → not-yet-supported.
- **`self-update.md`** — flow ordered to match the code.
- **`security-model.md`** — reorganized for an auditor read (trust
boundaries → connection → secrets → filesystem → memory safety → supply
chain → self-update → reporting).
- **`debian-packaging.md`** — noted the `.deb` is built
`--no-default-features` (self-update compiled out, hence the `podup
update` refusal); native per-arch builds; trimmed an unverifiable aside.

`bench/README.md` and `fuzz/README.md` were already accurate and left as
is.

## Scope

Documentation only — no behavior change. Code fences balanced, every
documented command/flag verified to exist in `internal/cli/`, no
external badges/hotlinks.

Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
## What

Remove the "Refresh the Glyndor apt repository" step from `release.yml`.

## Why

`Glyndor/apt` no longer listens for a `repository_dispatch`
(Glyndor/apt#20) — it rebuilds on its daily schedule and on demand. This
step was already a no-op without an `APT_DISPATCH_TOKEN` (a write
credential on apt, which was never provisioned), so removing it drops
dead code and the need for podup to ever hold any credential on apt. A
new podup release is picked up by apt's next daily build.

Replaces the now-closed #585.

Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
## What

The public security docs carried maintainer-only mechanics that don't
belong on a public surface:

- `docs/self-update.md` — the public-key derivation script
(`RELEASE_SIGN_KEY` → python) and the step-by-step two-release
key-rotation procedure (slot edits, CI-secret switch).
- `docs/debian-packaging.md` — the "Refreshing after a release" runbook
(`gh workflow run publish.yml`, the `APT_DISPATCH_TOKEN` setup).

Those are operator runbooks, not third-party trust-model content.

## Change

I trimmed both docs to keep only what a third party needs to trust and
independently verify a release: the two-key trust anchor, fail-closed
verification, rotation that never strands installs, and
offline/air-gapped verification. The operator procedures move to the
private ops context (no information lost).

Net: +7 −45.

Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
## What

`.editorconfig` set indentation only per language, so a file without a
matching section (e.g. `.py`) inherited no indent rule at all.

## Change

- Moved the tabs-width-4 default onto `[*]` so it applies to every file
type.
- Dropped the now-redundant per-language `rs`/`sh`/`toml` sections
(covered by the default).
- Kept spaces only for the YAML/JSON exception the style standard
allows.
- Markdown keeps its `trim_trailing_whitespace = false` override and
inherits the tab default.

Net: 12 deletions, no behavioural change beyond filling the gap the
standard requires.

Closes #583

Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
Adds `max_line_length = 120` to `[*]`, matching the org scaffold
(`template-repository`) and `apt`. podup's `.editorconfig` was the only
thing the now-closed #584 carried that #588 dropped; this brings it over
without #584's Markdown-on-tabs regression.

Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
Fixes the empty STATUS column and missing host IP in `podup ps` on
Podman 5.

## Problem (#590)
Podman 5's libpod list endpoint leaves `Status` empty and reports the
machine state in `State`. `podup ps` rendered `Status` directly, so the
STATUS column was blank for running containers; the ports column also
dropped the host IP (`:8080` instead of `0.0.0.0:8080`).

## Fix
- `display_status()` falls back to `State` when `Status` is empty (table
+ JSON).
- `format_ports()` defaults an unset host IP to `0.0.0.0`, matching
docker/podman.
- Both helpers are pure and unit-tested against the real wire shape
(empty `Status` + populated `State`).

## Verified
Built and run against real Podman 5.4.2:
```
NAME       IMAGE    STATUS    PORTS
v590-web   alpine   running   0.0.0.0:8080->80/tcp
```
JSON `"Status": "running"`. Was blank + `:8080` before.

Closes #590

Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
…ning (#601)

## Problem (#591)
`podup stats` always failed on Podman 5:
```
HTTP 404: unable to look up container v591-api,v591-web: no such container
```
libpod's `/containers/stats` wants `containers` **repeated** per
container; podup comma-joined them into one value, parsed as a single
container name.

## Fix
Emit `&containers=a&containers=b` instead of `&containers=a,b`. Updated
the unit test that had encoded the comma-joined form (it asserted an
input the daemon never accepts — the fixture enshrined the bug).

## Verified
Probed the real libpod socket: repeated form returns `Stats`,
comma/array forms 404. Built and run against Podman 5.4.2:
```
NAME       CPU %   MEM USAGE / LIMIT   ...
v591-api   0.25%   48.0KiB / 60.7GiB
v591-web   0.26%   48.0KiB / 60.7GiB
```

Closes #591

Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
)

## Problem (#592)
After a runtime `scale`/`up --scale`, by-service commands broke. The
running containers are `svc-1..N` but the targeting commands derived
names from the compose file's **static** replica count (`replica_names`,
which is 1), so they looked for the unsuffixed `project-svc` and 404'd
or warned:
```
$ podup scale api=3 && podup restart api
HTTP 404: no container with name or ID "sweep-api" found
```
Affected
`stop`/`start`/`restart`/`kill`/`pause`/`unpause`/`top`/`stats`/`wait`;
`down` emitted a spurious `could not stop project-svc` warning before
its label sweep.

## Fix
`live_replica_names()` lists the containers Podman actually has for a
service (by the `podup.service` label) and falls back to the static
names only when none exist yet. The targeting commands route through it;
`down` stops the real replicas directly.

## Verified
Built and run against real Podman 5.4.2 — `scale api=3` then:
```
restart api  -> exit 0       (was 404)
top api      -> 3 replicas   (was warning)
stop api     -> 3 stopped    (was 404)
down         -> 3 removed, no warning
```

Closes #592

Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
## Problem (#597)
`podup completions bash | head` aborted with SIGABRT (exit 134):
`clap_complete::generate` panics when its writer errors, and a reader
that closes the pipe early makes the stdout write fail.

## Fix
Render the completion script into a `Vec` (which never fails to write),
then write it to stdout and treat `BrokenPipe` as a clean exit —
standard Unix-tool behaviour.

## Verified (Podman not needed)
```
podup completions bash | head -1   -> exit 0   (was 134)
podup completions zsh  | head -1   -> exit 0
podup completions bash > /dev/null -> exit 0   (output unchanged)
```

Closes #597

Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
## Problem (#594)
`podup logs` printed raw output with no service tag — in a multi-service
stack you can't tell which service a line came from. `docker compose
logs` prefixes each line with the service.

## Fix
`LinePrefixer` buffers stream frames (which may split a line) and tags
each complete line with `<container> | `, applied on both the concurrent
`-f` path and the sequential path, with a tail flush for an unterminated
final line. stdout/stderr keep separate buffers.

## Verified
Built and run against real Podman 5.4.2 on a two-service stack:
```
v594-alpha  | alpha-1
v594-alpha  | alpha-2
v594-beta   | beta-1
v594-beta   | beta-2
```
Plus unit tests for line tagging, partial-frame buffering and tail
flush.

Closes #594

---------

Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
## Problem (part of #593)
A generated `.container` unit set `ContainerName` to the bare service
key (`ContainerName=web`), so the container it creates is named `web` —
colliding with any other project's `web` service and diverging from
`up`, which names it `project-web`.

## Fix
Default `ContainerName` to `{project}-{service}`, matching `up`. An
explicit `container_name:` still wins.

## Verified
`generate quadlet` on project `sweep`:
```
ContainerName=sweep-web    # was: web
```
74 quadlet tests pass, including a new one for the default.

## Out of scope (left on #593)
Prefixing the unit **filenames** (`web.container` →
`sweep-web.container`, and the same for `.network`/`.volume`) needs a
coordinated rename plus an audit of every cross-unit reference
(`After=`/`Requires=`/`Network=`), and Quadlet-resolution verification —
its own PR.

Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
## Problem (#599)
Two CLI output gaps found in the empirical sweep:
1. `podup watch` looked silent — it logs each sync/rebuild/restart at
INFO, but the default WARN log floor hid them.
2. `podup top --format json` errored `service '--format' not found` —
`top` had no `--format` and its `trailing_var_arg` swallowed the flag as
a service name.

## Fix
- Initialize tracing with an **INFO** floor for the interactive `watch`
command (every other command keeps WARN; `RUST_LOG` always overrides).
- Add `--format json` to `top` (like ps/ls/images), emitting
`[{Container, Titles, Processes}]`.

## Verified (Podman 5.4.2)
```
# watch (no RUST_LOG) now prints:
podup: info: synced …/src/f.txt -> /app

# top --format json:
[ { "Container": "v599c-web", "Processes": [[ "root", "1", … ]] } ]
```
Table `top` output unchanged.

Closes #599

---------

Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
#607)

## Problem (#595)
`podup config` printed `field: null` for every unset field plus empty
`volumes: {}`/`networks: {}`/… sections — noisy and unlike `docker
compose config`. It also didn't validate a missing image/build (`up`
does), so `config --quiet` (validate-only) passed an invalid file.

## Fix
- Prune null and empty values from the rendered YAML/JSON, bottom-up (a
section emptied by its own nulls is dropped too).
- Validate every service has an image or build, before the
`--quiet`/`--services` short-circuits, matching `up` and `docker compose
config`.

## Verified (Podman 5.4.2)
```
# before: ~15 `: null` lines + 5 empty sections
# after:
name: v595
services:
  web:
    image: …/alpine:latest
    command: [sleep, '600']
    networks: [default]

$ podup -f no-image.yml config           -> error: service 'bad' has no image or build config (exit 1)
$ podup -f no-image.yml config --quiet    -> same (validate-only now validates)
```
Unit tests for both prune paths.

Closes #595

Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
…608)

## Problem (#598)
`stop`/`start`/`restart`/`kill`/`pause`/`unpause` handled failure
inconsistently: `stop` warned and exited 0 even on a real error (masking
it), while the others propagated every error — including the harmless
"already in that state" (304) and "no such container" (404).

## Fix
A shared `run_lifecycle_op` maps the libpod response consistently:
- **304** (already in the desired state) and **404** (no such container)
→ idempotent no-op, exit 0.
- any other failure → real error, propagated (non-zero exit) instead of
swallowed.

## Verified (Podman 5.4.2, probed the real status codes)
```
start (already running)  -> exit 0   (was 1; 304 is idempotent now)
stop  (already stopped)  -> exit 0   (304)
restart / kill / pause   -> consistent
```
`stop` no longer swallows a genuine podman error into a warning.
(`pause` on an already-paused container still exits non-zero — podman
returns 500 "already paused" there, not 304, so podup faithfully
surfaces it.)

Closes #598

Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
Release 1.4.0 — the empirical-sweep fixes verified against real Podman
5.4.2.

Bumps Cargo.toml/Cargo.lock and prepends the debian/changelog entry.

Highlights since 1.3.0:
- `ps` STATUS/host-IP, `stats` on Podman 5, scale+lifecycle replica
resolution, `logs` prefixes (#600/#601/#602/#604)
- `completions` broken-pipe, `config` prune+validate, lifecycle exit
codes, `top --format json` + `watch` progress (#603/#607/#608/#606)
- quadlet `ContainerName=<project>-<service>` (#605)

After this lands on develop, I'll open the develop → main release PR
(merge commit) and tag v1.4.0.

Signed-off-by: Jaro-c <75870284+Jaro-c@users.noreply.github.com>
@Jaro-c Jaro-c merged commit 6f34558 into main Jun 25, 2026
32 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant