Add -w/--write-config to bootstrap a fully-populated config file
Writes every supported config option explicitly -- the six unshare-* bools defaulted via value_or(true), log-level captured from the actually active spdlog level (so an explicit --log-level passed alongside -w is persisted too) -- creating the file and its parent directory if missing, and prints its full path. Existing values (including volumes) are preserved untouched. Also fixes a stale README row left over from unplugging the namespace probe out of -t/--test.
This commit is contained in:
@@ -115,6 +115,22 @@ Source layout (all under `src/`):
|
|||||||
already gone) — see `config_file.{h,cpp}` below for what a "volume" means here (a
|
already gone) — see `config_file.{h,cpp}` below for what a "volume" means here (a
|
||||||
distinct concept from `OciImageConfig::volumes`). `dispatch_command()`'s
|
distinct concept from `OciImageConfig::volumes`). `dispatch_command()`'s
|
||||||
`Mode::volume`/`Mode::delete_volume`/`Mode::delete_volume_full` cases call these.
|
`Mode::volume`/`Mode::delete_volume`/`Mode::delete_volume_full` cases call these.
|
||||||
|
`write_config_command()` implements `-w/--write-config`: unlike
|
||||||
|
`create_volume_command()`/`delete_volume_command()`'s use of
|
||||||
|
`write_config_file()` (which only ever persists `AppConfig` fields that are
|
||||||
|
already set), this fills in *every* field before writing — the six
|
||||||
|
`unshare-*` bools via `.value_or(true)`, and `log_level` from the actually
|
||||||
|
active `spdlog::get_level()` (not merely a default for when unset — this
|
||||||
|
also captures an explicit `--log-level` passed alongside `-w` on the same
|
||||||
|
command line, overriding whatever an existing config file's own
|
||||||
|
`log-level` already was, since `main()`/`parse_args()` already applied it
|
||||||
|
in that precedence order by the time this runs) — so a bare `-w` bootstraps
|
||||||
|
a complete, fully-populated config file for hand-editing, and `-w` combined
|
||||||
|
with other flags captures their effective values into it. `volumes` is left
|
||||||
|
exactly as loaded — an open-ended list with no "default" entry to
|
||||||
|
materialize. Prints the config file's full path (`write_config_file()`
|
||||||
|
already creates the parent directory and the file itself if missing, so no
|
||||||
|
separate existence check is needed here).
|
||||||
`run_container()` (the `Mode::run` dispatch case) resolves each `-v` spec
|
`run_container()` (the `Mode::run` dispatch case) resolves each `-v` spec
|
||||||
(erroring out, `ok = false`, same as a failed `--user` resolution — `bwrap`
|
(erroring out, `ok = false`, same as a failed `--user` resolution — `bwrap`
|
||||||
is skipped but unmount/cleanup still runs) into a `ResolvedVolumeMount`,
|
is skipped but unmount/cleanup still runs) into a `ResolvedVolumeMount`,
|
||||||
@@ -814,8 +830,8 @@ Build directory is `buildDir/` (already configured).
|
|||||||
full flag list: `-m/--mount`, `-r/--run`, `-u/--umount`, `-c/--cleanup`,
|
full flag list: `-m/--mount`, `-r/--run`, `-u/--umount`, `-c/--cleanup`,
|
||||||
`-l/--list-images`, `-i/--inspect`, `-x/--exec`, `--kill`, `-n/--no-nsenter`, `-D/--daemonize`,
|
`-l/--list-images`, `-i/--inspect`, `-x/--exec`, `--kill`, `-n/--no-nsenter`, `-D/--daemonize`,
|
||||||
`--user`, `--group`, `--hostname`, `--env`, `--env-file`, `-v/--volume`, `--list-volumes`, `--delete-volume`,
|
`--user`, `--group`, `--hostname`, `--env`, `--env-file`, `-v/--volume`, `--list-volumes`, `--delete-volume`,
|
||||||
`--delete-volume-full`, `--list-processes`, `--clean-processes`, `-t/--test`, `--log-level`,
|
`--delete-volume-full`, `--list-processes`, `--clean-processes`, `-w/--write-config`,
|
||||||
`-h/--help`, `-V/--version`)
|
`-t/--test`, `--log-level`, `-h/--help`, `-V/--version`)
|
||||||
- Run tests: `meson test -C buildDir`
|
- Run tests: `meson test -C buildDir`
|
||||||
|
|
||||||
## Code style
|
## Code style
|
||||||
|
|||||||
@@ -94,7 +94,8 @@ slocker-lite -V|--version
|
|||||||
| `--delete-volume-full <name>` | Like `--delete-volume`, but also recursively deletes the volume's host directory. |
|
| `--delete-volume-full <name>` | Like `--delete-volume`, but also recursively deletes the volume's host directory. |
|
||||||
| `--list-processes` | List running `--run` sessions found by their pid files under `$XDG_STATE_HOME/slocker-lite/run/`, with their pid, container name, and status (`running` or `exited`). |
|
| `--list-processes` | List running `--run` sessions found by their pid files under `$XDG_STATE_HOME/slocker-lite/run/`, with their pid, container name, and status (`running` or `exited`). |
|
||||||
| `--clean-processes` | Remove stale pid files (see `--list-processes`) left behind by sessions that are no longer running. |
|
| `--clean-processes` | Remove stale pid files (see `--list-processes`) left behind by sessions that are no longer running. |
|
||||||
| `-t, --test` | Print which `bwrap --unshare-xxx` namespaces the running kernel supports. |
|
| `-w, --write-config` | Write a complete config file (creating it, and its parent directory, if missing), filling in every option's current or default value. Useful to bootstrap one for hand-editing. Prints the config file's full path. |
|
||||||
|
| `-t, --test` | Run the (currently empty) self-test placeholder. |
|
||||||
| `--log-level <level>` | Set log verbosity (`trace`, `debug`, `info`, `warn`, `error`, `critical`, `off`). |
|
| `--log-level <level>` | Set log verbosity (`trace`, `debug`, `info`, `warn`, `error`, `critical`, `off`). |
|
||||||
| `-h, --help` | Print usage and exit. |
|
| `-h, --help` | Print usage and exit. |
|
||||||
| `-V, --version` | Print version information and exit. |
|
| `-V, --version` | Print version information and exit. |
|
||||||
@@ -194,6 +195,14 @@ managed by `-v/--volume` (see above) rather than hand-edited — it's what
|
|||||||
is fine either way (nothing is overridden, and one gets created the first
|
is fine either way (nothing is overridden, and one gets created the first
|
||||||
time `-v/--volume` is used).
|
time `-v/--volume` is used).
|
||||||
|
|
||||||
|
Run `-w/--write-config` to bootstrap a config file: it writes out every
|
||||||
|
supported option explicitly (filling in the current or default value for
|
||||||
|
anything not already set — so the block above is exactly what a fresh `-w`
|
||||||
|
produces), creating the file and its parent directory if they don't exist
|
||||||
|
yet, and prints the file's full path. Combine it with other flags to seed
|
||||||
|
specific values, e.g. `slocker-lite --log-level debug -w` writes
|
||||||
|
`log-level: debug`.
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
Image layers are imported into `containers-storage` (parent-chained) and the
|
Image layers are imported into `containers-storage` (parent-chained) and the
|
||||||
|
|||||||
+13
-2
@@ -50,7 +50,7 @@ constexpr int env_file = 266;
|
|||||||
constexpr int kill = 267;
|
constexpr int kill = 267;
|
||||||
} // namespace options
|
} // namespace options
|
||||||
|
|
||||||
constexpr std::array<struct option, 26> long_options = {{
|
constexpr std::array<struct option, 27> long_options = {{
|
||||||
{"help", no_argument, nullptr, 'h'},
|
{"help", no_argument, nullptr, 'h'},
|
||||||
{"version", no_argument, nullptr, 'V'},
|
{"version", no_argument, nullptr, 'V'},
|
||||||
{"test", no_argument, nullptr, 't'},
|
{"test", no_argument, nullptr, 't'},
|
||||||
@@ -76,6 +76,7 @@ constexpr std::array<struct option, 26> long_options = {{
|
|||||||
{"clean-processes", no_argument, nullptr, options::clean_processes},
|
{"clean-processes", no_argument, nullptr, options::clean_processes},
|
||||||
{"env", required_argument, nullptr, options::env},
|
{"env", required_argument, nullptr, options::env},
|
||||||
{"env-file", required_argument, nullptr, options::env_file},
|
{"env-file", required_argument, nullptr, options::env_file},
|
||||||
|
{"write-config", no_argument, nullptr, 'w'},
|
||||||
{nullptr, 0, nullptr, 0},
|
{nullptr, 0, nullptr, 0},
|
||||||
}};
|
}};
|
||||||
|
|
||||||
@@ -95,6 +96,7 @@ void print_usage(const char* prog) {
|
|||||||
" {0} --delete-volume-full <name>\n"
|
" {0} --delete-volume-full <name>\n"
|
||||||
" {0} --list-processes\n"
|
" {0} --list-processes\n"
|
||||||
" {0} --clean-processes\n"
|
" {0} --clean-processes\n"
|
||||||
|
" {0} -w|--write-config\n"
|
||||||
" {0} -t|--test\n"
|
" {0} -t|--test\n"
|
||||||
" {0} -h|--help\n"
|
" {0} -h|--help\n"
|
||||||
" {0} -V|--version\n"
|
" {0} -V|--version\n"
|
||||||
@@ -194,6 +196,11 @@ void print_usage(const char* prog) {
|
|||||||
" --clean-processes remove stale pid files (see --list-processes)\n"
|
" --clean-processes remove stale pid files (see --list-processes)\n"
|
||||||
" left behind by sessions that are no longer\n"
|
" left behind by sessions that are no longer\n"
|
||||||
" running\n"
|
" running\n"
|
||||||
|
" -w, --write-config write a complete config file (creating it, and\n"
|
||||||
|
" its parent directory, if missing), filling in\n"
|
||||||
|
" every option's current or default value --\n"
|
||||||
|
" useful to bootstrap one for hand-editing.\n"
|
||||||
|
" Prints the config file's full path\n"
|
||||||
" -t, --test run the test suite\n"
|
" -t, --test run the test suite\n"
|
||||||
" --log-level <level> set log verbosity (trace, debug, info, warn,\n"
|
" --log-level <level> set log verbosity (trace, debug, info, warn,\n"
|
||||||
" error, critical, off)\n"
|
" error, critical, off)\n"
|
||||||
@@ -240,7 +247,7 @@ bool apply_log_level(std::string_view name) {
|
|||||||
std::optional<int> parse_args(int argc, char* argv[], ParsedArgs& out) {
|
std::optional<int> parse_args(int argc, char* argv[], ParsedArgs& out) {
|
||||||
opterr = 0;
|
opterr = 0;
|
||||||
int opt;
|
int opt;
|
||||||
while ((opt = getopt_long(argc, argv, ":hVtm:u:r:c:nl:v:i:x:D", long_options.data(), nullptr)) != -1) {
|
while ((opt = getopt_long(argc, argv, ":hVtm:u:r:c:nl:v:i:x:Dw", long_options.data(), nullptr)) != -1) {
|
||||||
switch (opt) {
|
switch (opt) {
|
||||||
case 'h':
|
case 'h':
|
||||||
print_usage(argv[0]);
|
print_usage(argv[0]);
|
||||||
@@ -256,6 +263,7 @@ std::optional<int> parse_args(int argc, char* argv[], ParsedArgs& out) {
|
|||||||
case 'l':
|
case 'l':
|
||||||
case 'i':
|
case 'i':
|
||||||
case 'x':
|
case 'x':
|
||||||
|
case 'w':
|
||||||
case options::list_volumes:
|
case options::list_volumes:
|
||||||
case options::delete_volume:
|
case options::delete_volume:
|
||||||
case options::delete_volume_full:
|
case options::delete_volume_full:
|
||||||
@@ -267,6 +275,9 @@ std::optional<int> parse_args(int argc, char* argv[], ParsedArgs& out) {
|
|||||||
case 't':
|
case 't':
|
||||||
requested = Mode::test;
|
requested = Mode::test;
|
||||||
break;
|
break;
|
||||||
|
case 'w':
|
||||||
|
requested = Mode::write_config;
|
||||||
|
break;
|
||||||
case 'm':
|
case 'm':
|
||||||
requested = Mode::mount;
|
requested = Mode::mount;
|
||||||
break;
|
break;
|
||||||
|
|||||||
+2
-1
@@ -42,7 +42,8 @@ enum class Mode {
|
|||||||
list_processes,
|
list_processes,
|
||||||
clean_processes,
|
clean_processes,
|
||||||
exec,
|
exec,
|
||||||
kill
|
kill,
|
||||||
|
write_config
|
||||||
};
|
};
|
||||||
|
|
||||||
// Everything parse_args() extracts from argv, ready to hand to
|
// Everything parse_args() extracts from argv, ready to hand to
|
||||||
|
|||||||
@@ -366,6 +366,42 @@ int delete_volume_command(const std::string& name, const std::filesystem::path&
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Writes out every supported config option explicitly, defaulting anything
|
||||||
|
// currently unset to its effective value, creating the file (and its parent
|
||||||
|
// directory) if it doesn't exist yet -- unlike create_volume_command()/
|
||||||
|
// delete_volume_command()'s use of write_config_file(), which only ever
|
||||||
|
// persists fields the user actually set. Meant to bootstrap a complete config
|
||||||
|
// file for hand-editing.
|
||||||
|
int write_config_command(const std::filesystem::path& config_path, const AppConfig& config) {
|
||||||
|
AppConfig full = config;
|
||||||
|
// Always the actually active spdlog level -- not merely a default for when
|
||||||
|
// full.log_level is unset -- so that an explicit --log-level passed
|
||||||
|
// alongside -w on this same command line is captured too, not shadowed by
|
||||||
|
// whatever an existing config file's own log-level already was (SPDLOG_LEVEL
|
||||||
|
// env var, an existing config's log-level, and --log-level are applied in
|
||||||
|
// that order, earlier, by main()/parse_args(); spdlog::get_level() reflects
|
||||||
|
// whichever of them won). spdlog::level::to_string_view() returns its own
|
||||||
|
// string_view_t (fmt::basic_string_view), not std::string_view --
|
||||||
|
// constructed explicitly from its data()/size() rather than relying on an
|
||||||
|
// implicit conversion that this fmt/spdlog version doesn't offer.
|
||||||
|
auto level_name = spdlog::level::to_string_view(spdlog::get_level());
|
||||||
|
full.log_level = std::string(level_name.data(), level_name.size());
|
||||||
|
full.unshare_user = full.unshare_user.value_or(true);
|
||||||
|
full.unshare_ipc = full.unshare_ipc.value_or(true);
|
||||||
|
full.unshare_pid = full.unshare_pid.value_or(true);
|
||||||
|
full.unshare_net = full.unshare_net.value_or(true);
|
||||||
|
full.unshare_uts = full.unshare_uts.value_or(true);
|
||||||
|
full.unshare_cgroup = full.unshare_cgroup.value_or(true);
|
||||||
|
// `volumes` is left exactly as loaded -- an open-ended list with no
|
||||||
|
// "default" entry to materialize, unlike the six fixed unshare-* flags.
|
||||||
|
|
||||||
|
if (!write_config_file(config_path, full)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
fmt::print("{}\n", config_path.string());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int run_container(const std::filesystem::path& image_tar,
|
int run_container(const std::filesystem::path& image_tar,
|
||||||
const std::vector<std::string>& requested_command, bool use_nsenter,
|
const std::vector<std::string>& requested_command, bool use_nsenter,
|
||||||
const std::optional<std::string>& user, const std::optional<std::string>& group,
|
const std::optional<std::string>& user, const std::optional<std::string>& group,
|
||||||
@@ -535,6 +571,8 @@ int dispatch_command(const ParsedArgs& args, const std::filesystem::path& config
|
|||||||
return exec_in_session(*args.exec_pid, args.command, args.user_flag, args.group_flag);
|
return exec_in_session(*args.exec_pid, args.command, args.user_flag, args.group_flag);
|
||||||
case Mode::kill:
|
case Mode::kill:
|
||||||
return kill_session(*args.kill_pid);
|
return kill_session(*args.kill_pid);
|
||||||
|
case Mode::write_config:
|
||||||
|
return write_config_command(config_path, config);
|
||||||
case Mode::run: {
|
case Mode::run: {
|
||||||
// As root, containers-storage mount doesn't need to reexec into a private
|
// As root, containers-storage mount doesn't need to reexec into a private
|
||||||
// user namespace to gain privilege, so the mount is already directly
|
// user namespace to gain privilege, so the mount is already directly
|
||||||
|
|||||||
Reference in New Issue
Block a user