diff --git a/lxcd.completion b/lxcd.completion index 2ef34f9..d1a7c29 100644 --- a/lxcd.completion +++ b/lxcd.completion @@ -26,7 +26,7 @@ _lxcd_completion() { case "${subcommand}" in create) 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 COMPREPLY=($(compgen -W "ubuntu: ubuntu/24.04 ubuntu/22.04 debian/12 debian/11" -- "$cur")) fi diff --git a/lxcd.py b/lxcd.py index e98b278..b970254 100755 --- a/lxcd.py +++ b/lxcd.py @@ -115,60 +115,23 @@ def handle_list(args): if addrs: 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 = [] - # Check nesting (works whether running or stopped) - nesting_check = lxc_subprocess( - ["config", "get", name, "security.nesting"], - capture_output=True, text=True, check=False - ) - if nesting_check.stdout.strip() == "true": + # Check nesting via security.nesting (native LXD config) + if get_config("security.nesting") == "true": active_opts.append("nesting") - # Check certs (works whether running or stopped) - dev_check = lxc_subprocess( - ["config", "device", "show", name], - capture_output=True, text=True, check=False - ) - 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(?)") + # Check labels for other features + for feature in ("certs", "git", "sshd", "aptcache", "ssh-client"): + if get_config(f"user.lxcd.{feature}") == "true": + active_opts.append(feature) opts_str = ", ".join(active_opts) if active_opts else "none" print(f"{name:<25} {status:<12} {ipv4:<25} {opts_str:<25}") @@ -283,6 +246,7 @@ runcmd: f"source={gitconfig_path}", f"path={container_git_path}", "shift=true"]) + set_label(args.name, "git") else: 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...") lxc_run([ "config", "set", args.name, "security.nesting", "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 if args.aptcache: print(f"APT cache/proxy will be configured to {args.aptcache} via cloud-init...") + set_label(args.name, "aptcache") # Handle --ssh-client option if args.ssh_client: 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 if args.certs: @@ -309,6 +280,7 @@ runcmd: f"source={host_certs}", f"path={host_certs}", "shift=true"]) + set_label(args.name, "certs") else: print("Warning: Host cert bundle path not found. Skipping certificate mount configuration.") @@ -356,17 +328,17 @@ def handle_enter(args): if 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( - ["exec", args.name, "--", "test", "-d", f"{container_home}/.ssh"], + ["config", "get", args.name, "user.lxcd.ssh-client"], capture_output=True, text=True, check=False - ).returncode == 0 + ).stdout.strip() == "true" # Execute using native LXD flags for user, group, and working directory prefix = lxc_prefix() shell_cmd = "exec /bin/bash --login" if has_ssh_client: - shell_cmd = "eval $(ssh-agent) && ssh-add && " + shell_cmd + shell_cmd = "eval $(ssh-agent) && ssh-add; " + shell_cmd exec_cmd = [ "lxc", "exec", args.name, "--cwd", target_cwd, @@ -405,6 +377,10 @@ def handle_delete(args): lxc_run([ "delete", args.name]) print(f"Container '{args.name}' deleted successfully.") +def set_label(name, feature, value="true"): + """Set a user.lxcd. config key on a container.""" + lxc_run([ "config", "set", name, f"user.lxcd.{feature}", value], check=False) + def container_exists(name): result = lxc_subprocess( ["info", name], @@ -439,10 +415,12 @@ def handle_add(args): print(f"Enabling nesting support for '{args.name}'...") lxc_run([ "config", "set", args.name, "security.nesting", "true"]) lxc_run([ "config", "set", args.name, "security.syscalls.intercept.mknod", "true"]) + set_label(args.name, "nested") if args.sshd: print(f"Activating SSH daemon and socket inside '{args.name}'...") lxc_run([ "exec", args.name, "--", "systemctl", "enable", "--now", "ssh.service", "ssh.socket"]) + set_label(args.name, "sshd") if args.git: was_running = not was_stopped @@ -460,6 +438,7 @@ def handle_add(args): f"source={host_gitconfig}", f"path=/home/{host_username}/.gitconfig", "shift=true"]) + set_label(args.name, "git") if was_running: lxc_run([ "start", args.name]) lxc_run([ "exec", args.name, "--", "cloud-init", "status", "--wait"], check=False) @@ -476,6 +455,7 @@ def handle_add(args): f"source={host_certs}", f"path={host_certs}", "shift=true"]) + set_label(args.name, "certs") else: 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, "--", "sh", "-c", f"echo 'Acquire::http::Proxy \"{args.aptcache}\";' > /etc/apt/apt.conf.d/00lxcd-proxy"]) + set_label(args.name, "aptcache") if args.ssh_client: 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, "--", "chmod", "700", 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: print(f"Returning '{args.name}' to its original stopped state...")