This commit is contained in:
Andrew Hurley 2026-06-27 22:28:08 +08:00
parent 35f3197142
commit f987c72c5f
2 changed files with 37 additions and 55 deletions

View File

@ -26,7 +26,7 @@ _lxcd_completion() {
case "${subcommand}" in case "${subcommand}" in
create) create)
if [[ "$cur" == -* ]]; then if [[ "$cur" == -* ]]; then
COMPREPLY=($(compgen -W "--nested --sshd --git --certs --aptcache --ssh-client" -- "$cur")) COMPREPLY=($(compgen -W "--nested --sshd --git --certs --aptcache --ssh-client -i --image" -- "$cur"))
else else
COMPREPLY=($(compgen -W "ubuntu: ubuntu/24.04 ubuntu/22.04 debian/12 debian/11" -- "$cur")) COMPREPLY=($(compgen -W "ubuntu: ubuntu/24.04 ubuntu/22.04 debian/12 debian/11" -- "$cur"))
fi fi

90
lxcd.py
View File

@ -115,60 +115,23 @@ def handle_list(args):
if addrs: if addrs:
ipv4 = addrs[0] ipv4 = addrs[0]
# Read lxcd feature labels via individual config get
def get_config(key):
return lxc_subprocess(
["config", "get", name, key],
capture_output=True, text=True, check=False
).stdout.strip()
active_opts = [] active_opts = []
# Check nesting (works whether running or stopped) # Check nesting via security.nesting (native LXD config)
nesting_check = lxc_subprocess( if get_config("security.nesting") == "true":
["config", "get", name, "security.nesting"],
capture_output=True, text=True, check=False
)
if nesting_check.stdout.strip() == "true":
active_opts.append("nesting") active_opts.append("nesting")
# Check certs (works whether running or stopped) # Check labels for other features
dev_check = lxc_subprocess( for feature in ("certs", "git", "sshd", "aptcache", "ssh-client"):
["config", "device", "show", name], if get_config(f"user.lxcd.{feature}") == "true":
capture_output=True, text=True, check=False active_opts.append(feature)
)
if "host-certs" in dev_check.stdout:
active_opts.append("certs")
# Check git (works whether running or stopped)
if "gitconfig" in dev_check.stdout:
active_opts.append("git")
# Running-only checks
if status.upper() == "RUNNING":
ssh_check = lxc_subprocess(
["exec", name, "--", "systemctl", "is-active", "ssh.socket"],
capture_output=True, text=True, check=False
)
if ssh_check.stdout.strip() == "active":
active_opts.append("sshd")
apt_cache_check = lxc_subprocess(
["exec", name, "--", "test", "-f", "/etc/apt/apt.conf.d/00lxcd-proxy"],
capture_output=True, text=True, check=False
)
if apt_cache_check.returncode == 0:
active_opts.append("aptcache")
ssh_client_check = lxc_subprocess(
["exec", name, "--", "which", "ssh"],
capture_output=True, text=True, check=False
)
if ssh_client_check.returncode == 0:
active_opts.append("ssh-client")
else:
# Offline guess: check cloud-init config for apt proxy
ci_check = lxc_subprocess(
["config", "get", name, "cloud-init.user-data"],
capture_output=True, text=True, check=False
)
if "Acquire::http::Proxy" in ci_check.stdout:
active_opts.append("aptcache(?)")
if "openssh-client" in ci_check.stdout:
active_opts.append("ssh-client(?)")
opts_str = ", ".join(active_opts) if active_opts else "none" opts_str = ", ".join(active_opts) if active_opts else "none"
print(f"{name:<25} {status:<12} {ipv4:<25} {opts_str:<25}") print(f"{name:<25} {status:<12} {ipv4:<25} {opts_str:<25}")
@ -283,6 +246,7 @@ runcmd:
f"source={gitconfig_path}", f"source={gitconfig_path}",
f"path={container_git_path}", f"path={container_git_path}",
"shift=true"]) "shift=true"])
set_label(args.name, "git")
else: else:
print(f"Warning: --git requested, but {gitconfig_path} was not found on host.", file=sys.stderr) print(f"Warning: --git requested, but {gitconfig_path} was not found on host.", file=sys.stderr)
@ -291,14 +255,21 @@ runcmd:
print("Enabling nesting capabilities...") print("Enabling nesting capabilities...")
lxc_run([ "config", "set", args.name, "security.nesting", "true"]) lxc_run([ "config", "set", args.name, "security.nesting", "true"])
lxc_run([ "config", "set", args.name, "security.syscalls.intercept.mknod", "true"]) lxc_run([ "config", "set", args.name, "security.syscalls.intercept.mknod", "true"])
set_label(args.name, "nested")
# Handle --sshd label (already handled in cloud-init config above)
if args.sshd:
set_label(args.name, "sshd")
# Handle --aptcache option # Handle --aptcache option
if args.aptcache: if args.aptcache:
print(f"APT cache/proxy will be configured to {args.aptcache} via cloud-init...") print(f"APT cache/proxy will be configured to {args.aptcache} via cloud-init...")
set_label(args.name, "aptcache")
# Handle --ssh-client option # Handle --ssh-client option
if args.ssh_client: if args.ssh_client:
print(f"SSH client and ~/.ssh directory will be set up for '{host_username}' via cloud-init...") print(f"SSH client and ~/.ssh directory will be set up for '{host_username}' via cloud-init...")
set_label(args.name, "ssh-client")
# Handle --certs option # Handle --certs option
if args.certs: if args.certs:
@ -309,6 +280,7 @@ runcmd:
f"source={host_certs}", f"source={host_certs}",
f"path={host_certs}", f"path={host_certs}",
"shift=true"]) "shift=true"])
set_label(args.name, "certs")
else: else:
print("Warning: Host cert bundle path not found. Skipping certificate mount configuration.") print("Warning: Host cert bundle path not found. Skipping certificate mount configuration.")
@ -356,17 +328,17 @@ def handle_enter(args):
if display: if display:
env_prefix += f"DISPLAY={display} " env_prefix += f"DISPLAY={display} "
# Check if container has ~/.ssh (set up by --ssh-client) and start ssh-agent if so # Check if container has the ssh-client feature (set by --ssh-client)
has_ssh_client = lxc_subprocess( has_ssh_client = lxc_subprocess(
["exec", args.name, "--", "test", "-d", f"{container_home}/.ssh"], ["config", "get", args.name, "user.lxcd.ssh-client"],
capture_output=True, text=True, check=False capture_output=True, text=True, check=False
).returncode == 0 ).stdout.strip() == "true"
# Execute using native LXD flags for user, group, and working directory # Execute using native LXD flags for user, group, and working directory
prefix = lxc_prefix() prefix = lxc_prefix()
shell_cmd = "exec /bin/bash --login" shell_cmd = "exec /bin/bash --login"
if has_ssh_client: if has_ssh_client:
shell_cmd = "eval $(ssh-agent) && ssh-add && " + shell_cmd shell_cmd = "eval $(ssh-agent) && ssh-add; " + shell_cmd
exec_cmd = [ exec_cmd = [
"lxc", "exec", args.name, "lxc", "exec", args.name,
"--cwd", target_cwd, "--cwd", target_cwd,
@ -405,6 +377,10 @@ def handle_delete(args):
lxc_run([ "delete", args.name]) lxc_run([ "delete", args.name])
print(f"Container '{args.name}' deleted successfully.") print(f"Container '{args.name}' deleted successfully.")
def set_label(name, feature, value="true"):
"""Set a user.lxcd.<feature> config key on a container."""
lxc_run([ "config", "set", name, f"user.lxcd.{feature}", value], check=False)
def container_exists(name): def container_exists(name):
result = lxc_subprocess( result = lxc_subprocess(
["info", name], ["info", name],
@ -439,10 +415,12 @@ def handle_add(args):
print(f"Enabling nesting support for '{args.name}'...") print(f"Enabling nesting support for '{args.name}'...")
lxc_run([ "config", "set", args.name, "security.nesting", "true"]) lxc_run([ "config", "set", args.name, "security.nesting", "true"])
lxc_run([ "config", "set", args.name, "security.syscalls.intercept.mknod", "true"]) lxc_run([ "config", "set", args.name, "security.syscalls.intercept.mknod", "true"])
set_label(args.name, "nested")
if args.sshd: if args.sshd:
print(f"Activating SSH daemon and socket inside '{args.name}'...") print(f"Activating SSH daemon and socket inside '{args.name}'...")
lxc_run([ "exec", args.name, "--", "systemctl", "enable", "--now", "ssh.service", "ssh.socket"]) lxc_run([ "exec", args.name, "--", "systemctl", "enable", "--now", "ssh.service", "ssh.socket"])
set_label(args.name, "sshd")
if args.git: if args.git:
was_running = not was_stopped was_running = not was_stopped
@ -460,6 +438,7 @@ def handle_add(args):
f"source={host_gitconfig}", f"source={host_gitconfig}",
f"path=/home/{host_username}/.gitconfig", f"path=/home/{host_username}/.gitconfig",
"shift=true"]) "shift=true"])
set_label(args.name, "git")
if was_running: if was_running:
lxc_run([ "start", args.name]) lxc_run([ "start", args.name])
lxc_run([ "exec", args.name, "--", "cloud-init", "status", "--wait"], check=False) lxc_run([ "exec", args.name, "--", "cloud-init", "status", "--wait"], check=False)
@ -476,6 +455,7 @@ def handle_add(args):
f"source={host_certs}", f"source={host_certs}",
f"path={host_certs}", f"path={host_certs}",
"shift=true"]) "shift=true"])
set_label(args.name, "certs")
else: else:
print("Warning: Host cert bundle path not found. Skipping certificate configuration.") print("Warning: Host cert bundle path not found. Skipping certificate configuration.")
@ -484,6 +464,7 @@ def handle_add(args):
lxc_run([ "exec", args.name, "--", "mkdir", "-p", "/etc/apt/apt.conf.d"]) lxc_run([ "exec", args.name, "--", "mkdir", "-p", "/etc/apt/apt.conf.d"])
lxc_run([ "exec", args.name, "--", "sh", "-c", lxc_run([ "exec", args.name, "--", "sh", "-c",
f"echo 'Acquire::http::Proxy \"{args.aptcache}\";' > /etc/apt/apt.conf.d/00lxcd-proxy"]) f"echo 'Acquire::http::Proxy \"{args.aptcache}\";' > /etc/apt/apt.conf.d/00lxcd-proxy"])
set_label(args.name, "aptcache")
if args.ssh_client: if args.ssh_client:
print(f"Installing SSH client and setting up ~/.ssh inside '{args.name}'...") print(f"Installing SSH client and setting up ~/.ssh inside '{args.name}'...")
@ -492,6 +473,7 @@ def handle_add(args):
lxc_run([ "exec", args.name, "--", "mkdir", "-p", f"/home/{host_username}/.ssh"]) lxc_run([ "exec", args.name, "--", "mkdir", "-p", f"/home/{host_username}/.ssh"])
lxc_run([ "exec", args.name, "--", "chmod", "700", f"/home/{host_username}/.ssh"]) lxc_run([ "exec", args.name, "--", "chmod", "700", f"/home/{host_username}/.ssh"])
lxc_run([ "exec", args.name, "--", "chown", f"{host_username}:{host_username}", f"/home/{host_username}/.ssh"]) lxc_run([ "exec", args.name, "--", "chown", f"{host_username}:{host_username}", f"/home/{host_username}/.ssh"])
set_label(args.name, "ssh-client")
if was_stopped: if was_stopped:
print(f"Returning '{args.name}' to its original stopped state...") print(f"Returning '{args.name}' to its original stopped state...")