doos 1.1.0: limits, hardening, pasta, signatures, Debian image

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-23 21:59:26 +02:00
co-authored by Claude Sonnet 5.5
parent d446c29372
commit 530582e826
24 changed files with 1182 additions and 127 deletions
+43 -7
View File
@@ -15,6 +15,9 @@ namespace doos {
namespace {
std::uint32_t parseCount(const std::string& text);
std::uint32_t parseCpus(const std::string& text);
// Leading --options, then the first positional, then everything else untouched.
struct Parsed {
Options opts;
@@ -47,6 +50,14 @@ Parsed parse(const std::vector<std::string>& rest, Allowed allow, std::size_t wa
else if (allow.container && is(a, "--image")) p.opts.image = value(i, "--image");
else if (allow.container && is(a, "--size")) p.opts.size = parseSize(value(i, "--size"));
else if (allow.container && is(a, "--ids")) p.opts.ids = IdMap::parse(value(i, "--ids"));
else if (allow.container && is(a, "--memory")) p.opts.memory = parseSize(value(i, "--memory"));
else if (allow.container && is(a, "--pids")) p.opts.pids = parseCount(value(i, "--pids"));
else if (allow.container && is(a, "--cpus")) p.opts.milliCpus = parseCpus(value(i, "--cpus"));
else if (allow.container && is(a, "--hardening")) {
std::string v = value(i, "--hardening");
if (v != "default" && v != "relaxed") throw std::runtime_error("--hardening takes default or relaxed");
p.opts.relaxed = (v == "relaxed");
}
else if (allow.yes && a == "--yes") p.yes = true;
else if (allow.sha256 && is(a, "--sha256")) p.sha256 = value(i, "--sha256");
else if (a.size() > 1 && a[0] == '-') throw std::runtime_error("unknown option " + a);
@@ -59,6 +70,26 @@ Parsed parse(const std::vector<std::string>& rest, Allowed allow, std::size_t wa
return p;
}
std::uint32_t parseCount(const std::string& text) {
if (text.empty() || text.size() > 9 || text.find_first_not_of("0123456789") != std::string::npos) {
throw std::runtime_error("invalid number '" + text + "'");
}
return static_cast<std::uint32_t>(std::stoul(text));
}
// "2", "0.5", "1.25" cpus -> thousandths; "0" means all cores.
std::uint32_t parseCpus(const std::string& text) {
std::size_t dot = text.find('.');
std::string whole = text.substr(0, dot), frac = dot == std::string::npos ? "" : text.substr(dot + 1);
if (whole.empty() || whole.size() > 5 || frac.size() > 3 ||
whole.find_first_not_of("0123456789") != std::string::npos ||
frac.find_first_not_of("0123456789") != std::string::npos) {
throw std::runtime_error("invalid cpus '" + text + "' (examples: 2, 0.5, 1.25, 0 for all cores)");
}
while (frac.size() < 3) frac += '0';
return static_cast<std::uint32_t>(std::stoul(whole)) * 1000 + static_cast<std::uint32_t>(std::stoul(frac));
}
std::string age(long created) {
long d = std::time(nullptr) - created;
if (d < 120) return std::to_string(d) + "s";
@@ -82,13 +113,18 @@ void Cli::usage(std::FILE* out) {
" doos image add [--sha256 HEX] SOURCE [NAME]\n"
" doos image delete [--yes] NAME\n"
"\n"
"options (fixed at creation, except --size):\n"
" --image NAME start from an image instead of the host system (fetched on demand)\n"
" --size SIZE writable-layer cap, 64m..1t, default 4g (e.g. 512m, 8g)\n"
" --net slirp|host|none network mode, default slirp\n"
" --ids single|subuid id mapping, default: subuid when available\n"
"options:\n"
" --image NAME start from an image instead of the host system (fetched on demand) [fixed]\n"
" --net slirp|pasta|host|none network, default slirp (pasta: host-like, private sockets) [fixed]\n"
" --ids single|subuid id mapping, default: subuid when available [fixed]\n"
" --hardening default|relaxed relaxed = no capability drop, no seccomp, full /etc [fixed]\n"
" --size SIZE writable-layer cap, 64m..1t, default 4g\n"
" --memory SIZE memory cap, 64m..1t, default 8g\n"
" --pids N process cap, 16..4194304, default 4096\n"
" --cpus N cpu cap, 0.1..1024, default 0 (all cores)\n"
" size, memory, pids and cpus can be changed on any later call; they apply at the next start.\n"
"\n"
"SOURCE for image add: alpine, ubuntu, ubuntu-24.04, an https URL (needs --sha256), or a tar file.\n"
"SOURCE for image add: alpine, ubuntu, ubuntu-24.04, debian, debian-bookworm, an https URL (needs --sha256), or a tar file.\n"
"attach creates NAME when missing. delete only reports unless --yes is given.\n"
"State lives in $DOOS_HOME or ~/.local/share/doos.\n",
out);
@@ -194,7 +230,7 @@ int Cli::cmdImageAdd(std::vector<std::string> rest) {
src.sha256 = *p.sha256;
} else {
if (!std::filesystem::is_regular_file(source)) {
throw std::runtime_error("'" + source + "' is neither a catalog image (alpine, ubuntu, ubuntu-VERSION), an https URL, nor a file");
throw std::runtime_error("'" + source + "' is neither a catalog image (alpine, ubuntu, ubuntu-VERSION, debian, debian-RELEASE), an https URL, nor a file");
}
src.location = std::filesystem::absolute(source).string();
src.isFile = true;