Compare commits

..

2 commits

Author SHA1 Message Date
Else Someone
add1c4f6bd uvms: churn 2026-03-10 02:01:54 +02:00
Else Someone
cf95fd33b0 uvms: add --mem
Yeah, yeah, I know, it's stupid, adding add_argument() statements
manually, et c. I'll throw the whole Python thing away whenever I might
have the time
2026-03-07 21:16:53 +02:00
7 changed files with 285 additions and 177 deletions

View file

@ -1,5 +0,0 @@
{
lib,
writers,
}:
writers.writePython3Bin "uvms-guest" { } ./guest.py

View file

@ -13,6 +13,8 @@
strace, strace,
util-linux, util-linux,
virtiofsd, virtiofsd,
python3Packages,
uvmslib,
taps, taps,
baseImage, baseImage,
@ -36,7 +38,11 @@ let
}; };
toolsClosure = writeClosure toolsFarm; toolsClosure = writeClosure toolsFarm;
in in
writers.writePython3Bin "uvms" { } ( writers.writePython3Bin "uvms"
{
libraries = [ uvmslib ];
}
(
replaceVars ./uvms.py { replaceVars ./uvms.py {
BWRAP = "${lib.getExe bubblewrap}"; BWRAP = "${lib.getExe bubblewrap}";
TOOLS = "${toolsFarm}/bin"; TOOLS = "${toolsFarm}/bin";
@ -53,4 +59,4 @@ writers.writePython3Bin "uvms" { } (
baseImage.config.system.build.ch baseImage.config.system.build.ch
]; ];
} }
) )

View file

@ -10,6 +10,7 @@ import os
import subprocess import subprocess
import socket import socket
import json import json
import re
from argparse import ArgumentParser from argparse import ArgumentParser
from contextlib import contextmanager, closing, ExitStack from contextlib import contextmanager, closing, ExitStack
@ -20,6 +21,7 @@ parser.add_argument("--prefix", default="$HOME/uvms/$VM")
parser.add_argument("--vm-config", default="@BASE_CONFIG@") # noqa: E501 parser.add_argument("--vm-config", default="@BASE_CONFIG@") # noqa: E501
parser.add_argument("--persist-home", action="store_true") parser.add_argument("--persist-home", action="store_true")
parser.add_argument("--run", action="append") parser.add_argument("--run", action="append")
parser.add_argument("--mem", default=None)
parser.add_argument("app", nargs="*", default=()) parser.add_argument("app", nargs="*", default=())
TOOLS_DIR = "@TOOLS@" # noqa: E501 TOOLS_DIR = "@TOOLS@" # noqa: E501
@ -297,6 +299,7 @@ class Processes:
# "-Z", # "-Z",
# "-ff", # "-ff",
CH, CH,
# "-v",
"--api-socket", "--api-socket",
"fd=0", "fd=0",
# f"fd={s.fileno()}" # f"fd={s.fileno()}"
@ -318,6 +321,8 @@ class Processes:
# ro_bind=["/nix/store"], # I give up # ro_bind=["/nix/store"], # I give up
unshare_net=False, unshare_net=False,
shell=False, shell=False,
stdout=None,
stderr=None,
# pass_fds=(s.fileno(),) # pass_fds=(s.fileno(),)
) )
) )
@ -338,29 +343,23 @@ class Processes:
): ):
sock_path = self.prefix + "/gpu.sock" sock_path = self.prefix + "/gpu.sock"
args = [ args = [
SOCKETBINDER,
"-b",
"1",
sock_path,
"s6-ipcserverd",
"-1c1",
# "@STRACE@", # noqa: E501
# "-Z",
# "-ff",
"@CROSVM@", # noqa: E501 "@CROSVM@", # noqa: E501
"--no-syslog", "--no-syslog",
"--log-level",
"debug",
"device", "device",
"gpu", "gpu",
"--fd", "--socket-path",
"0", sock_path,
"--wayland-sock", "--wayland-sock",
f'{PASSTHRU_ENV["XDG_RUNTIME_DIR"]}/{PASSTHRU_ENV["WAYLAND_DISPLAY"]}', # noqa: E501 f'{PASSTHRU_ENV["XDG_RUNTIME_DIR"]}/{PASSTHRU_ENV["WAYLAND_DISPLAY"]}', # noqa: E501
"--params", "--params",
'{ "context-types": "cross-domain:virgl2:venus" }', '{ "context-types": "cross-domain" }',
] ]
with self.popen( with self.popen(
*args, *args,
stderr=None, stderr=None,
stdout=None,
) as proc, removing(sock_path, sock_path + ".lock"): ) as proc, removing(sock_path, sock_path + ".lock"):
yield proc, sock_path yield proc, sock_path
@ -469,6 +468,21 @@ def connect_ch_vsock(
yield s yield s
BYTES_PATTERN = re.compile(r"^([0-9]+)([MmGgKk]?)$")
BYTES_UNITS = {
"k": 1024,
"m": 1048576,
"g": 1024 * 1048576,
}
def parse_bytes(s):
m = BYTES_PATTERN.match(s)
assert m, s
size, unit = m.groups()
return int(size) * BYTES_UNITS.get(unit.lower(), 1)
@contextmanager @contextmanager
def listen_ch_vsock( def listen_ch_vsock(
vsock_sock_path, vsock_sock_path,
@ -564,6 +578,16 @@ def main(args, args_next, cleanup, ps):
) )
virtiofs_socks.append(("home", sock_path)) virtiofs_socks.append(("home", sock_path))
config["payload"]["cmdline"] += " uvms.persist-home=1" config["payload"]["cmdline"] += " uvms.persist-home=1"
if args.mem is not None:
config["memory"]["size"] = parse_bytes(args.mem)
config["memory"]["hotplug_size"] = parse_bytes(args.mem)
if "platform" not in config:
config["platform"] = {}
config["platform"]["oem_strings"] = [
"io.systemd.credential:vmm.notify_socket=vsock-stream:2:8888",
*config["platform"].get("oem_strings", []),
]
gpud, gpud_path = cleanup.enter_context(ps.start_gpu()) gpud, gpud_path = cleanup.enter_context(ps.start_gpu())
@ -590,21 +614,39 @@ def main(args, args_next, cleanup, ps):
ps.exec(*ch_remote, "boot") ps.exec(*ch_remote, "boot")
ps.exec(*ch_remote, "info") ps.exec(*ch_remote, "info")
ready = False
with ready_sock: with ready_sock:
ready_sock.settimeout(20.0) ready_sock.settimeout(8.0)
for _ in range(1048576):
if ready:
break
try: try:
con, _ = ready_sock.accept() con, _ = ready_sock.accept()
except: # noqa: E722 except: # noqa: E722
print( print(
"CH didn't try connecting to the readiness notification socket" "WARNING: CH didn't try connecting to the readiness notification socket" # noqa: E501
) # noqa: E501 )
ready = True
break
else: else:
with con: with con:
msg = con.recv(128) msg = con.recv(1024)
assert msg.startswith(b"READY=1"), msg for ln in msg.split(b"\n"):
ln = ln.strip()
print(ln)
# if ln.startswith(b"X_SYSTEMD_UNIT_ACTIVE=uvms-guest.service"): # noqa: E501
if ln.startswith(b"READY=1"): # noqa: E501
ready = True
break
assert ready
with connect_ch_vsock(ps.prefix + "/vsock.sock", 24601) as guest: with connect_ch_vsock(ps.prefix + "/vsock.sock", 24601) as guest:
for r in args.run: for r in args.run:
res = {}
for _ in range(1):
if "status" in res:
break
try: try:
guest.send( guest.send(
json.dumps( json.dumps(
@ -622,16 +664,21 @@ def main(args, args_next, cleanup, ps):
try: try:
res = json.loads(guest.recv(8192)) res = json.loads(guest.recv(8192))
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
print(f"Couldn't interpret --run {r} response: {e} {res}") print(
continue f"Couldn't interpret --run {r} response: {e} {res}"
) # noqa: E501
res = {}
# res = {"status": "failed"}
except Exception as e:
print(f"Couldn't --run {r}: {repr(e)}")
if "status" not in res:
res["status"] = "fail"
adverb = ( adverb = (
"Successfully" "Successfully"
if res["status"] == "exec succeeded" if res["status"] == "exec succeeded"
else "Failed to" # noqa: E501 else "Failed to" # noqa: E501
) )
print(f"{adverb} --run {r}: {res}") print(f"{adverb} --run {r}: {res}")
except Exception as e:
print(f"Couldn't --run {r}: {repr(e)}")
try: try:
ch.wait() ch.wait()
except KeyboardInterrupt: except KeyboardInterrupt:

21
pkgs/uvmslib/package.nix Normal file
View file

@ -0,0 +1,21 @@
{
lib,
python3Packages,
}:
python3Packages.buildPythonPackage {
pname = "uvmslib";
version = "0.0.0";
pyproject = true;
src =
let
fs = lib.fileset;
in
fs.toSource {
root = ./.;
fileset = fs.unions [
./pyproject.toml
./uvmslib.py
];
};
build-system = [ python3Packages.setuptools ];
}

View file

@ -0,0 +1,10 @@
[build-system]
build-backend = "setuptools.build_meta"
requires = [ "setuptools" ]
[project]
name = "uvms"
version = "0.0.0"
[project.scripts]
"uvms-guest" = "uvmslib:guest_main"

View file

@ -59,7 +59,7 @@ class Processes:
proc = None proc = None
try: try:
proc = self.popen( proc = self.popen(
req["run"]["argv"], run["argv"],
text=text, text=text,
env=env, env=env,
cwd="/home/user", cwd="/home/user",
@ -82,14 +82,14 @@ class Processes:
return res, proc return res, proc
def accept_vsock(self, s): def accept_vsock(self, s):
con, (cid, port) = serv.accept() con, (cid, port) = s.accept()
assert cid == 2, cid assert cid == 2, cid
self.sources.append(con) self.sources.append(con)
self.client_fds.add(con.fileno()) self.client_fds.add(con.fileno())
return con, (cid, port) return con, (cid, port)
if __name__ == "__main__": def guest_main():
ps = Processes() ps = Processes()
serv = socket.fromfd(3, socket.AF_VSOCK, socket.SOCK_STREAM) serv = socket.fromfd(3, socket.AF_VSOCK, socket.SOCK_STREAM)
ps.sources.append(serv) ps.sources.append(serv)
@ -134,3 +134,7 @@ if __name__ == "__main__":
con.send(res) con.send(res)
else: else:
assert False, con.fileno() assert False, con.fileno()
if __name__ == "__main__":
guest_main()

View file

@ -42,7 +42,7 @@ in
_module.args.uvmsPkgs = lib.mkDefault (pkgs.callPackage ../pkgs { }); _module.args.uvmsPkgs = lib.mkDefault (pkgs.callPackage ../pkgs { });
# some.failure-handler.enable = true; # some.failure-handler.enable = true;
hardware.graphics.enable = true; hardware.graphics.enable = true;
boot.kernelPackages = pkgs.linuxPackagesFor uvmsPkgs.linux-uvm; # boot.kernelPackages = pkgs.linuxPackagesFor uvmsPkgs.linux-uvm;
# boot.isContainer = true; # boot.isContainer = true;
boot.initrd.kernelModules = [ boot.initrd.kernelModules = [
"drm" "drm"
@ -251,19 +251,20 @@ in
}; };
systemd.sockets."uvms-guest" = { systemd.sockets."uvms-guest" = {
wantedBy = [ "default.target" ]; requiredBy = [ "multi-user.target" ];
before = [ "multi-user.target" ];
listenStreams = [ listenStreams = [
"vsock::24601" "vsock::24601"
]; ];
partOf = [ "uvms-guest.service" ]; partOf = [ "uvms-guest.service" ];
}; };
systemd.services."uvms-guest" = { systemd.services."uvms-guest" = {
requiredBy = [ "multi-user.target" ]; before = [ "multi-user.target" ];
onFailure = [ "shutdown.service" ]; onFailure = [ "shutdown.service" ];
serviceConfig = { serviceConfig = {
User = "user"; User = "user";
Group = "users"; Group = "users";
ExecStart = "${lib.getExe uvmsPkgs.uvms-guest}"; ExecStart = "${lib.getExe' uvmsPkgs.uvmslib "uvms-guest"}";
ExecStop = [ ExecStop = [
"/run/current-system/sw/bin/echo GUEST DOWN" "/run/current-system/sw/bin/echo GUEST DOWN"
"/run/current-system/sw/bin/systemctl poweroff" "/run/current-system/sw/bin/systemctl poweroff"
@ -288,7 +289,6 @@ in
"console=ttyS0" "console=ttyS0"
"reboot=t" "reboot=t"
"panic=-1" "panic=-1"
"io.systemd.credential:vmm.notify_socket=vsock-stream:2:8888"
# "rootfstype=virtiofs" # "rootfstype=virtiofs"
# "root=rootstore" # "root=rootstore"
]; ];
@ -301,20 +301,25 @@ in
}; };
uvms.ch.settings = mkOption { uvms.ch.settings = mkOption {
default = { }; default = { };
type = types.submodule { type = types.submodule (
let
osConfig = config;
in
{ config, ... }:
{
freeformType = jsonType; freeformType = jsonType;
options = { options = {
payload = { payload = {
cmdline = mkOption { cmdline = mkOption {
type = types.str; type = types.str;
default = concatStringsSep " " ( default = concatStringsSep " " (
config.boot.kernelParams osConfig.boot.kernelParams
++ [ ++ [
# "init=${lib.removePrefix "/nix/store" "${config.system.build.toplevel}"}/init" # "init=${lib.removePrefix "/nix/store" "${osConfig.system.build.toplevel}"}/init"
"init=${config.system.build.toplevel}/init" "init=${osConfig.system.build.toplevel}/init"
] ]
); );
defaultText = ''concatStringsSep " " ${config.boot.kernelParams}''; defaultText = ''concatStringsSep " " ${osConfig.boot.kernelParams}'';
}; };
kernel = mkOption { kernel = mkOption {
type = types.str; type = types.str;
@ -385,7 +390,7 @@ in
options = { options = {
size = mkOption { size = mkOption {
type = types.int; type = types.int;
default = 3 * 1024 * 1048576; default = 4 * 1024 * 1048576;
}; };
shared = mkOption { shared = mkOption {
type = types.bool; type = types.bool;
@ -395,6 +400,25 @@ in
type = types.bool; type = types.bool;
default = true; default = true;
}; };
hotplug_method = mkOption {
default = "VirtioMem";
type = types.enum [
"Acpi"
"VirtioMem"
];
};
hotplugged_size = mkOption {
type = types.int;
default = 512 * 1048576;
};
hotplug_size = mkOption {
type = types.int;
default = config.memory.size;
};
# hugepages = mkOption {
# type = types.bool;
# default = true;
# };
}; };
}; };
}; };
@ -415,7 +439,8 @@ in
}; };
}; };
}; };
}; }
);
}; };
}; };
} }