Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,47 @@ range. Versions before 0.3.0 predate the GitHub releases and are reconstructed f
Merge commits, automated `Tagged <version>` commits and version bumps are omitted, as are bullets
that only restate the release note above them.

## Unreleased

### Added

- **`roll shutdown` takes down everything for the end of the day.** It runs `roll env down` in
every environment that has a running container, found through the Compose project labels on the
containers, and then `roll svc down`. Volumes are kept. An environment whose `.env.roll` no longer
carries the name it was started under is left running and reported, because `roll env down` in
that directory would target a different Compose project, and so is one whose project directory or
`.env.roll` has gone (recognised by its `dev.roll.environment.name` network label); the command
then exits non-zero, as it does when any step fails.
- **Superseded images are cleaned up, by hand and automatically.** A pull that finds a newer build
of a tag leaves the previous image behind untagged, and nothing ever removed it, so hosts
pulling the rebuilt images filled up. `roll image-cleanup` removes those superseded images and
every tagged image no container uses; an environment that is only down has no containers, so it
downloads its images again on its next `up`. The superseded half also runs automatically after
`roll svc pull|up` and `roll env pull|up` (`ROLL_IMAGE_AUTO_CLEANUP=0` turns that off); unused
images are never removed automatically, so a `roll shutdown` does not cause re-downloads and
parallel builds cannot delete each other's freshly pulled images. A tag a
container was created from counts as used even when the tag has since moved to a newer image, so
a freshly pulled image is not deleted before its environment's next `up`. Only images roll runs
are touched — `ROLL_IMAGE_REPOSITORY` plus the third-party images named in roll's compose files,
ignoring any name in a user override that is only an interpolation — and removal never forces, so
Docker keeps any image a container still uses. On Docker's containerd image store a superseded
image keeps no name, so roll records its image IDs in `~/.roll/tmp/roll-image-ids` before every
pull and recognises superseded images from that ledger on either store.

### Fixed

- **`roll db import < dump.sql` executes the dump again.** Since 0.8.0 it exited 0 having run
nothing. The MariaDB 11 client probe (`resolveDbBinary` in `commands/db.cmd`) ran
`roll env exec -T db sh -c 'command -v …'` with the caller's stdin, and `exec -T` forwards stdin
into the container, so the probe drained the dump and the real client read an empty stream. The
exit code gave no sign of it; only the missing rows did. The probe now reads from `/dev/null`.
The same drain hit `roll db connect < file.sql` and `echo … | roll db connect`, which are fixed
by the same change.
- **`roll db connect` no longer requests a TTY when stdin is not one.** It passes `-T` to
`roll env exec` unless stdin is a terminal, so piped and scripted use no longer depends on
`docker compose exec` detecting the missing terminal on its own. An interactive session still
gets a TTY.

## [0.8.4](https://github.com/epartment/rolldev/releases/tag/0.8.4) — 2026-09-17

### Added
Expand Down
21 changes: 18 additions & 3 deletions FEATURE-REQUESTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,16 +79,31 @@ None outstanding.
**L4. `roll config schema` silently omits registered keys** — `commands/config.cmd:93-165`

- *What:* The schema display is seven hand-maintained category filters with no catch-all, so any
registered key matching none of them is invisible. `ROLL_ADMIN_AUTOLOGIN` and
`ROLL_MAGENTO_STATIC_CACHING` are two current examples; both are in `initConfigSchema` and neither
appears in the output. Every key added from now on has to be wired into a filter by hand or it
registered key matching none of them is invisible. `ROLL_ADMIN_AUTOLOGIN`,
`ROLL_MAGENTO_STATIC_CACHING`, `ROLL_ADVISORIES` and `ROLL_IMAGE_AUTO_CLEANUP` are current
examples; all are in `initConfigSchema` and none appears in the output. Every key added from now on has to be wired into a filter by hand or it
vanishes from the only listing users have.
- *Why it matters:* `config schema` is the discovery surface for configuration, and an omitted key
reads as "not supported". It also makes the listing quietly wrong rather than visibly incomplete.
- *Suggested fix:* Derive the category from the key once (a `schemaCategoryOf` helper, or a category
column alongside `ROLL_CONFIG_SCHEMA_VALUES`) and render groups from that, so registering a key is
the only step needed. Failing that, add a final "Other" group printing whatever matched nothing.

**L5. `roll env up` exits 1 on an environment without `php-fpm` whenever an SSH agent is running** — `commands/env.cmd:245-248`

- *What:* After a successful `up`/`start`, `env.cmd` runs `roll root chown` and `roll root chmod` on
`/run/host-services/ssh-auth.sock` whenever `SSH_AUTH_SOCK` is set. Both exec into the shell
container (`php-fpm` by default), so on an environment that has none — the `local` type with its
own `.roll/roll-env.yml`, or any project with `ROLL_ENV_SHELL_CONTAINER` pointing at a missing
service — the command ends with `service "php-fpm" is not running` and exit status 1 although
every container started. **Verified** on macOS with a `local` environment running only redis;
the same code is in 0.8.4.
- *Why it matters:* A script, CI job or the build server cannot tell a
failed `up` from a successful one, and a developer sees an error for a step that does not apply.
- *Suggested fix:* Run the two socket commands only when the shell container is part of this
environment (for example `roll env ps -q "${ROLL_ENV_SHELL_CONTAINER}"` returns an id), and do not
let their failure decide the exit status of `up`.

## Checked, and already covered

Recorded so the next reader does not re-propose them:
Expand Down
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ responsibility is still describable.
| `bin/roll` | 98 lines | Entry point, dispatch, argument parsing | **Coherent** |
| `utils/core.sh` | 184 lines | Messaging helpers, array/version utilities, network peering | **Coherent** — the three box-drawing functions are one-line wrappers around a shared `box` helper |
| `utils/config.sh` | 946 lines | Config schema, loading, validation, post-processing, `.env.roll` writes | **Coherent**; sole owner of configuration defaults |
| `utils/images.sh` | 375 lines | Service catalog (version key, toggle and image per service) and image-tag discovery from the registry | **Coherent** |
| `utils/images.sh` | 655 lines | Service catalog (version key, toggle and image per service), image-tag discovery from the registry, and cleanup of the local images roll runs | **Coherent** |
| `utils/registry.sh` | 461 lines | Command discovery and priority resolution | **Oversized for what it delivers** — ~200 lines serve `roll registry`'s reporting subcommands; the metadata layer they report on is a stub (**M6**) |
| `utils/env.sh` | 108 lines | Env path location, partial precedence, env-type validation | **Coherent** |
| `utils/install.sh` | 63 lines | Host install assertion, SSH config | **Coherent** |
Expand Down Expand Up @@ -425,6 +425,20 @@ roll env down
roll restart
```

Take down every running environment and then the shared services (end of the working day):

```bash
roll shutdown
```

Remove superseded roll images (the untagged previous builds a pull leaves behind) and tagged roll
images no container uses. The superseded half also runs automatically after `svc pull|up` and
`env pull|up` unless `ROLL_IMAGE_AUTO_CLEANUP=0`:

```bash
roll image-cleanup
```

Open a shell in the `php-fpm` container as `www-data`:

```bash
Expand Down Expand Up @@ -529,6 +543,7 @@ exists and where it lives.
| `roll config check-pins` / `fix-pins` | `commands/config.cmd` |
| `roll config versions [service]` — pinned versions, or a service's published versions one per line on stdout | `commands/config.cmd`, `utils/images.sh` |
| `roll config version <service> <version>` — set a pin without prompting | `commands/config.cmd` |
| `roll image-cleanup [--dry-run]` — remove (or list) superseded and unused images, never prompts | `commands/image-cleanup.cmd`, `utils/images.sh` |
| Flag-first prompts | `utils/interact.sh` |

Two rules bind anything added here:
Expand Down Expand Up @@ -678,6 +693,7 @@ line-audited, and neither backup nor restore was executed against a live environ
| A command run right after `roll env up` fails to connect to the DB or search engine | No fragment declares a `healthcheck:` and `up` does not pass `--wait`, so it returns when containers *start* | Retry with a wait loop against the service. See `FEATURE-REQUESTS.md` H1 |
| `Mutagen sync sessions are not used on "linux" host environments` | `roll sync` is macOS-only | Expected; Linux and WSL bind-mount directly |
| `Mutagen configuration does not exist for environment type "…"` | Only `magento1`, `magento2` and `shopware` ship a `.mutagen.yml` | Expected; that type uses a bind mount on macOS too |
| `roll db import < dump.sql` exits 0 but no rows change (RollDev 0.8.0–0.8.4) | The client-binary probe in `commands/db.cmd` inherited stdin and drained the dump before the real client ran | Upgrade. On an affected version pipe straight into `roll env exec -T db sh -c 'mariadb -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE"'` (`mysql` on MySQL images) and check row counts, not the exit code |
| In-container `composer install` reports a missing `.git` in a `vendor/` package | Mutagen's `ignore.vcs: true` strips `.git` at every depth, so source installs have no checkout | Remove the affected `vendor/` directories and re-run `composer install` to force dist installs |
| Files written to `var/` or `pub/static/` on the host are invisible in the container | Both paths are excluded from the Mutagen session | Write through the container (`roll cli`), or `docker cp` into the volume |
| A `roll` subcommand exits 1 printing only its opening `INFO:` lines, on Linux but not macOS | `set -e` plus a statement returning non-zero — classically a bare `((x++))` from 0 — kills the sourced command, and the `ERR` trap is not inherited by functions | Use `x=$((x + 1))`; see [Dispatch](#dispatch) |
Expand Down
2 changes: 1 addition & 1 deletion bin/roll
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ declare ROLL_PARAMS=()
declare ROLL_CMD_VERB=
declare ROLL_CMD_EXEC=
declare ROLL_CMD_HELP=
declare ROLL_CMD_ANYARGS=(svc env db redis sync shell debug rootnotty rootshell clinotty root node npm cli copyfromcontainer copytocontainer composer grunt magento magerun mydumper myloader backup restore restore-full duplicate magento2-init epartment-init pull tapsync status describe registry doctor)
declare ROLL_CMD_ANYARGS=(svc env db redis sync shell debug rootnotty rootshell clinotty root node npm cli copyfromcontainer copytocontainer composer grunt magento magerun mydumper myloader backup restore restore-full duplicate magento2-init epartment-init pull tapsync status describe registry doctor image-cleanup)

## parse first argument as command and determine validity
if (( "$#" )); then
Expand Down
10 changes: 8 additions & 2 deletions commands/db.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@ eval "$(
## MariaDB 11 images dropped the mysql*/mysqldump compatibility symlinks and only ship
## mariadb/mariadb-dump; probe once per invocation and use whichever pair is present, so both
## MariaDB 11 and MySQL/MariaDB 10.x images keep working. Sets RESOLVED_DB_BIN on success.
## The probe must not inherit stdin: `exec -T` forwards it into the container, so without the
## </dev/null it would drain the dump that `import` (or a piped `connect`) is about to read.
resolveDbBinary() {
local preferred="$1" fallback="$2" bin

bin="$("${ROLL_DIR}/bin/roll" env exec -T db sh -c "command -v ${preferred} || command -v ${fallback}" 2>/dev/null)" || true
bin="$("${ROLL_DIR}/bin/roll" env exec -T db sh -c "command -v ${preferred} || command -v ${fallback}" </dev/null 2>/dev/null)" || true
if [[ ! ${bin} ]]; then
local image
image="$(docker container inspect "${DB_CONTAINER}" --format '{{.Config.Image}}' 2>/dev/null)" || true
Expand All @@ -50,7 +52,11 @@ resolveDbBinary() {
case "${ROLL_PARAMS[0]}" in
connect)
resolveDbBinary mariadb mysql
"${ROLL_DIR}/bin/roll" env exec db \
## Only ask for a TTY when stdin is one, so `roll db connect < file.sql` and scripted
## `-e` calls do not depend on docker compose noticing the missing terminal itself.
CONNECT_EXEC_FLAGS=()
[[ -t 0 ]] || CONNECT_EXEC_FLAGS=(-T)
"${ROLL_DIR}/bin/roll" env exec "${CONNECT_EXEC_FLAGS[@]}" db \
"${RESOLVED_DB_BIN}" -u"${MYSQL_USER}" -p"${MYSQL_PASSWORD}" --database="${MYSQL_DATABASE}" "${ROLL_PARAMS[@]:1}" "$@"
;;
import)
Expand Down
11 changes: 11 additions & 0 deletions commands/env.cmd
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,12 @@ if [[ "${ROLL_PARAMS[0]}" == "sh" ]]; then
ROLL_PARAMS=("exec" "-T" "${ROLL_PARAMS[1]}" "sh" "-c" "${ROLL_PARAMS[2]}")
fi

## put the images this pull may supersede on record while they still carry their name; the
## containerd image store keeps no name on a superseded image (see utils/images.sh)
if [[ "${ROLL_PARAMS[0]}" == "pull" || "${ROLL_PARAMS[0]}" == "up" ]]; then
recordRollImageIds
fi

## disconnect peered service containers from environment network
if [[ "${ROLL_PARAMS[0]}" == "down" ]]; then
disconnectPeeredServices "$(renderEnvNetworkName)"
Expand Down Expand Up @@ -204,6 +210,11 @@ docker compose \
--env-file "${ROLL_ENV_PATH}/.env.roll" --project-directory "${ROLL_ENV_PATH}" -p "${ROLL_ENV_NAME}" \
"${DOCKER_COMPOSE_ARGS[@]}" "${ROLL_PARAMS[@]}" "$@"

## remove the images this pull (or the up after it) left superseded; see utils/images.sh
if [[ "${ROLL_PARAMS[0]}" == "pull" || "${ROLL_PARAMS[0]}" == "up" ]]; then
autoCleanupRollImages
fi

if [[ ("${ROLL_PARAMS[0]}" == "up" || "${ROLL_PARAMS[0]}" == "start") && -n "${ROLL_EXTRA_PHP_EXT}" ]]; then
info "Adding additional PHP extension, This may take a while... (output hidden)"
roll add-php-ext "${ROLL_EXTRA_PHP_EXT}" > /dev/null 2>&1
Expand Down
90 changes: 90 additions & 0 deletions commands/image-cleanup.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/usr/bin/env bash
[[ ! ${ROLL_DIR} ]] && >&2 echo -e "\033[31mThis script is not intended to be run directly!\033[0m" && exit 1

## `image-cleanup` is on roll's ROLL_CMD_ANYARGS list so its flags reach this script, which means
## roll's own parser stops at the first dash-prefixed argument and leaves ROLL_PARAMS empty.
## Parse flags from "$@", and render help by sourcing usage.cmd - re-invoking
## `roll image-cleanup --help` would land right back here and fork until killed.
CLEANUP_DRY_RUN=0

if (( ${#ROLL_PARAMS[@]} > 0 )); then
fatal "Unsupported argument ${ROLL_PARAMS[0]}"
fi

while (( "$#" )); do
case "$1" in
-h|--help)
source "${ROLL_DIR}/commands/usage.cmd"
;;
--dry-run)
CLEANUP_DRY_RUN=1
;;
*)
fatal "Unsupported argument $1"
;;
esac
shift
done

assertDockerRunning

## ROLL_IMAGE_REPOSITORY decides which images are roll's; this command is host-wide, so only the
## global configuration applies, loaded in the same order loadRollConfig uses
initConfigSchema
for globalConfig in "${ROLL_HOME_DIR}/.env.roll" "${ROLL_HOME_DIR}/.env"; do
if [[ -f "${globalConfig}" ]]; then
loadConfigFromFile "${globalConfig}" "false" "global"
fi
done
ROLL_IMAGE_REPOSITORY="$(getConfig ROLL_IMAGE_REPOSITORY "ghcr.io/epartment/roll")"

recordRollImageIds

cleanupReferences=()
cleanupListing=()
supersededCount=0
unusedCount=0

while IFS=$'\t' read -r reference name size created; do
cleanupReferences+=("${reference}")
printf -v listingLine ' %-10s %-60s %-10s %s' "superseded" "${name}" "${size}" "${created}"
cleanupListing+=("${listingLine}")
supersededCount=$((supersededCount + 1))
done < <(listSupersededRollImages)

while IFS=$'\t' read -r reference name size created; do
cleanupReferences+=("${reference}")
printf -v listingLine ' %-10s %-60s %-10s %s' "unused" "${name}" "${size}" "${created}"
cleanupListing+=("${listingLine}")
unusedCount=$((unusedCount + 1))
done < <(listUnusedRollImages)

if (( ${#cleanupReferences[@]} == 0 )); then
info "No superseded or unused roll images found."
exit 0
fi

printf ' %-10s %-60s %-10s %s\n' "KIND" "IMAGE" "SIZE" "CREATED"
printf '%s\n' "${cleanupListing[@]}"
echo ""
info "Sizes include layers images share with each other, so the space freed can be less than their sum."

if [[ ${CLEANUP_DRY_RUN} -eq 1 ]]; then
info "Dry run: ${supersededCount} superseded and ${unusedCount} unused image(s) would be removed."
exit 0
fi

removeImages "${cleanupReferences[@]}"

success "Removed ${ROLL_IMAGES_REMOVED} of ${#cleanupReferences[@]} image(s). An environment that needs one of them again downloads it on its next 'roll env up'."

if (( ROLL_IMAGES_GONE > 0 )); then
info "${ROLL_IMAGES_GONE} image(s) were already gone, removed by another roll run in the meantime."
fi

if (( ${#ROLL_IMAGES_KEPT[@]} > 0 )); then
warning "Docker kept ${#ROLL_IMAGES_KEPT[@]} image(s), usually because a container still uses them:"
for kept in "${ROLL_IMAGES_KEPT[@]}"; do
>&2 printf ' %s\n' "${kept}"
done
fi
40 changes: 40 additions & 0 deletions commands/image-cleanup.help
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
[[ ! ${ROLL_DIR} ]] && >&2 echo -e "\033[31mThis script is not intended to be run directly!\033[0m" && exit 1
## @description: Remove Docker images roll downloaded and no longer needs.
## @category: utility

ROLL_USAGE=$(cat <<'USAGE'
\033[33mUsage:\033[0m
image-cleanup Remove superseded and unused roll images
image-cleanup --dry-run List what would be removed and change nothing

\033[33mOptions:\033[0m
--dry-run Only list the images
-h, --help Display this help menu

\033[33mDescription:\033[0m
Removes two kinds of image, without asking:

superseded the previous build of a tag, left behind without a tag when a pull found a
newer one. Nothing can use it again.
unused a tagged image no container uses, running or stopped, such as an old PHP
version a project no longer pins. An environment taken down with roll env down
has no containers left, so its images are removed as well and downloaded again
on its next roll env up.

A tag a container was created from counts as used even after a pull moved it to a newer
build, so an environment that is up or stopped keeps its images.

Superseded images are also removed automatically after roll svc pull, roll svc up,
roll env pull and roll env up; unused ones only by this command. Set
ROLL_IMAGE_AUTO_CLEANUP=0 in ~/.roll/.env to turn the automatic run off.

Only images roll itself runs are touched: everything under ROLL_IMAGE_REPOSITORY plus the
third-party images named in the roll compose files (traefik, mailpit, selenium and others).
Docker refuses to remove an image a container still uses, and this command never forces it.

The containerd image store of newer Docker Desktop installs keeps no name on a superseded
image, so roll records the IDs of its images in ~/.roll/tmp/roll-image-ids before every pull
and recognises them from there. An image superseded before roll recorded it is left alone.
USAGE
)
Loading
Loading