Add -v/--volume to create a named volume mapped to a host directory

Creates the host directory if missing (warning if it already exists,
another if it's non-empty) and records name -> directory in the config
file's new "volumes" section. Fails if the name or directory is already
used by an existing volume.

This is a distinct concept from OciImageConfig::volumes (an image's own
declared mount points, still unconsumed) -- a user-defined volume, meant
to be referenced by name once -r/--run starts actually mounting volumes.

config_file.{h,cpp} gains write_config_file(), symmetric to the existing
load_config_file(), built on libyaml's document-building/emitter API.
Rewrites the whole file each time; global.log-level round-trips
untouched alongside the new volumes section.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Gv3s5jckJKzh6JkMoi2Akz
This commit is contained in:
2026-08-21 12:59:37 +00:00
parent be5a0b8202
commit aac656be31
5 changed files with 223 additions and 39 deletions
+30 -17
View File
@@ -16,12 +16,15 @@ status); this file stays the dense, file-by-file reference. Still early-stage.
Source layout (all under `src/`):
- `main.cpp` — CLI entry point, dependency checks, orchestration (`mount_image()`,
`run_container()`, `cleanup_image()`, `unmount_image()`, `list_images_command()`).
`run_container()` unconditionally calls `read_oci_image_config()` and reuses the
result for two independent defaults: the command to run (`Entrypoint ++ Cmd`) when
none is given on the command line, and, when `--user` wasn't given, the sandboxed
process's user/group (`config.User`, split into `OciImageConfig::user`/`group`) —
an explicit `--user`/`--group` on the command line always takes precedence.
`run_container()`, `cleanup_image()`, `unmount_image()`, `list_images_command()`,
`create_volume_command()`). `run_container()` unconditionally calls
`read_oci_image_config()` and reuses the result for two independent defaults: the
command to run (`Entrypoint ++ Cmd`) when none is given on the command line, and,
when `--user` wasn't given, the sandboxed process's user/group (`config.User`,
split into `OciImageConfig::user`/`group`) — an explicit `--user`/`--group` on the
command line always takes precedence. `create_volume_command()` implements
`-v/--volume <name> <directory>`: see `config_file.{h,cpp}` below for what a
"volume" means here (a distinct concept from `OciImageConfig::volumes`).
- `oci_image.{h,cpp}` — validates/parses the OCI Image Layout tar (libarchive +
nlohmann_json) and extracts layer blobs. `list_oci_images()` scans a directory
(non-recursively) for `*.tar`/`*.tar.*` files and, for each valid OCI archive,
@@ -96,17 +99,27 @@ Source layout (all under `src/`):
run would skip `run_container()`'s unmount/cleanup entirely, leaving the layer
imported and/or mounted.
- `config_file.{h,cpp}``load_config_file()` reads and parses (via libyaml's
document API, `<yaml.h>`) the `global` section of the local YAML config file
located by `config_file_path()` (`$XDG_CONFIG_HOME/slocker-lite/config.yaml`,
falling back to `$HOME/.config/slocker-lite/config.yaml`). Only `global.log-level`
is supported today — other long options are one-shot flags, not settings, so they
don't belong in a persistent config file. A missing file returns a
default-constructed (empty) `AppConfig`, not an error; unknown sections/keys are
ignored for forward-compatibility; malformed YAML syntax is a hard error. `main()`
applies `config->log_level` (via the existing `apply_log_level()`) right after
`spdlog::cfg::load_env_levels()` and before parsing CLI options, so an explicit
`--log-level` on the command line always overwrites it afterward — same precedence
pattern already used for `SPDLOG_LEVEL`.
document API, `<yaml.h>`) the `global` and `volumes` sections of the local YAML
config file located by `config_file_path()` (`$XDG_CONFIG_HOME/slocker-lite/config.yaml`,
falling back to `$HOME/.config/slocker-lite/config.yaml`). `global.log-level` is the
only supported `global` key — other long options are one-shot flags, not settings,
so they don't belong in a persistent config file. A missing file returns a
default-constructed (empty) `AppConfig`, not an error; unknown sections/keys (and
malformed individual volume entries) are ignored for forward-compatibility;
malformed YAML syntax is a hard error. `main()` applies `config->log_level` (via
the existing `apply_log_level()`) right after `spdlog::cfg::load_env_levels()` and
before parsing CLI options, so an explicit `--log-level` on the command line always
overwrites it afterward — same precedence pattern already used for `SPDLOG_LEVEL`.
`write_config_file()` writes the whole file back out (via libyaml's
document-building/emitter API, symmetric to the read side) — used by
`-v/--volume` (`create_volume_command()`, `main.cpp`) to persist a new
`VolumeEntry {name, directory}` into the `volumes` section, preserving `global`
untouched. **`VolumeEntry`/the `volumes` section is a distinct concept from
`OciImageConfig::volumes`**: this is a user-defined `name -> host directory`
mapping created via `-v/--volume`, not an image's own declared mount points (still
unconsumed, see `oci_image.{h,cpp}` above) — the two aren't connected yet, though a
future `-r/--run` volume-mounting feature would presumably look volumes up here by
name.
Errors are logged via `spdlog::error`; every external command is also traced at debug
level in `run_process()`/`run_process_foreground()` (`src/process.cpp`) — visible via