From c207a8a038a17c5fb668781a8881e140027d2f18 Mon Sep 17 00:00:00 2001 From: Andrew Hurley Date: Sun, 20 Sep 2026 20:19:34 +0800 Subject: [PATCH] README & other things --- README.md | 107 ++++++++++++++++++++++++++++++++++++++++++++ lxcd.completion | 8 ++++ lxcd.py | 13 ++++-- snap/snapcraft.yaml | 2 +- 4 files changed, 125 insertions(+), 5 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..b2d59b9 --- /dev/null +++ b/README.md @@ -0,0 +1,107 @@ +# lxcd + +A Distrobox-like wrapper for LXD: it creates, manages, and enters LXD dev +containers as drop-in development environments, mapped to your host workspace +and home. + +## Requirements + +- LXD (`lxc`) reachable on your host — either the user is in the `lxd` + group (invoked directly) or `sudo lxc` is used +- Python 3.9+ +- Bash completion (optional): `lxcd.completion` + +## Install + +```sh +make install # copies lxcd.py to /usr/local/bin/lxcd + installs completion +``` + +Or run in place from this directory: + +```sh +./lxcd.py --help +``` + +## Quick start + +```sh +lxcd init # write ~/.lxcdrc (never shown; edit with -e) +lxcd init -e # open the config in $EDITOR, created first if missing + +lxcd create web1 # new container "web1" (defaults from ~/.lxcdrc) +lxcd ls # alias for "list" +lxcd enter web1 # cloud-init workspaces + mappings auto-applied +``` + +## Commands + +| Command | Description | +| --- | --- | +| `init` | Write/edit the `~/.lxcdrc` config | +| `create` | Create a new container (defaults from config) | +| `clone ` | Clone an existing managed container | +| `rename\|mv ` | Rename a container | +| `enter ` | Enter a container (applies workspace + mappings) | +| `list\|ls` | List managed containers | +| `delete\|stop\|start\|restart` | Operate on containers (`-a` for all) | + +Run `lxcd --help` for full options. + +## Config (`~/.lxcdrc`) + +INI format. Keys: + +- `[options]` — `workspace`, `image`, `nested`, `aptcache`, `ssh` + (absolute paths OK for `workspace`/`image` when used as-is) +- `[packages]` — default apt packages installed at create time +- `[mappings]` under `[options]` or top-level — a list of host paths to + mount into every container. A spec ending in `/` mounts a directory; + an absolute host path (starting with `/`) is used **as-is** (not relative to + `~`) and mounts at the same path in the container. + +Example: + +```ini +[options] +workspace=/home/andrew/Code # absolute — mounted as-is +image=ubuntu: +nested=true +aptcache=http://10.0.0.1:3142 +ssh=ssh-ed25519 AAAA...andrew@host + +[packages] +git +vim + +[mappings] +configs/ # directory: ~/configs -> /home//configs/ +notes # file: ~/notes -> /home//notes +/etc/hosts # absolute: used as-is +``` + +## Conventions + +- Containers are tagged with `user.lxcd=true` for listing. +- `security.nesting` is enabled when `nested=true` (cli `-n/--nested`). +- Disk mounts use `shift=true` so container writes map cleanly to the host. +- `enter` mounts the `[options] workspace=` host dir into the container and + applies `[mappings]` before running an interactive shell as the host user. + +## Completion + +`lxcd.completion` (bash). Keep it sourced for container-name + option +completion, including `lxcd list --brief [cols]`. + +## Building a snap + +```sh +snapcraft --destructive-mode -v +``` + +produces `lxcd_0.1.0_amd64.snap` (version pinned in `snap/snapcraft.yaml`). + +## License + +MIT. + diff --git a/lxcd.completion b/lxcd.completion index 98c60a9..b54710f 100644 --- a/lxcd.completion +++ b/lxcd.completion @@ -66,6 +66,14 @@ _lxcd_completion() { fi ;; + list|ls) + if [[ "$cur" == -* ]]; then + COMPREPLY=($(compgen -W "-b --brief" -- "$cur")) + else + COMPREPLY=($(compgen -W "ipv4 status,ipv4 image,ipv4 packages,ipv4 opts image,packages,opts ipv4,image,packages" -- "$cur")) + fi + ;; + *) COMPREPLY=() ;; diff --git a/lxcd.py b/lxcd.py index 6411a7c..e6ae645 100755 --- a/lxcd.py +++ b/lxcd.py @@ -433,12 +433,17 @@ def apply_mappings(name, host_username, host_home): paths = cfg.get("mappings") if not isinstance(paths, list) or not paths: return - for idx, path in enumerate(paths): - host_path = host_home / path - container_path = f"/home/{host_username}/{path}" + for idx, spec in enumerate(paths): + is_dir = spec.endswith("/") + if spec.startswith("/"): + host_path = spec.rstrip("/") + container_path = spec + else: + host_path = str(host_home / spec.rstrip("/")) + container_path = f"/home/{host_username}/{spec}" lxc_subprocess(["config", "device", "remove", name, f"map-{idx}"], capture_output=True, text=True, check=False) - if host_path.exists(): + if os.path.exists(host_path): lxc_run(["config", "device", "add", name, f"map-{idx}", "disk", f"source={host_path}", f"path={container_path}", diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 471ed8f..f6a7da5 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -1,6 +1,6 @@ name: lxcd base: core22 -version: git +version: 0.1.0 summary: A Distrobox-like wrapper for LXD description: | lxcd provides a Distrobox-like experience using LXD containers.