README & other things

This commit is contained in:
Andrew Hurley 2026-09-20 20:19:34 +08:00
parent eddf850c4f
commit c207a8a038
4 changed files with 125 additions and 5 deletions

107
README.md Normal file
View File

@ -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 <src> <name>` | Clone an existing managed container |
| `rename\|mv <old> <new>` | Rename a container |
| `enter <name>` | Enter a container (applies workspace + mappings) |
| `list\|ls` | List managed containers |
| `delete\|stop\|start\|restart` | Operate on containers (`-a` for all) |
Run `lxcd <command> --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/<user>/configs/
notes # file: ~/notes -> /home/<user>/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.
</content>

View File

@ -66,6 +66,14 @@ _lxcd_completion() {
fi 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=() COMPREPLY=()
;; ;;

13
lxcd.py
View File

@ -433,12 +433,17 @@ def apply_mappings(name, host_username, host_home):
paths = cfg.get("mappings") paths = cfg.get("mappings")
if not isinstance(paths, list) or not paths: if not isinstance(paths, list) or not paths:
return return
for idx, path in enumerate(paths): for idx, spec in enumerate(paths):
host_path = host_home / path is_dir = spec.endswith("/")
container_path = f"/home/{host_username}/{path}" 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}"], lxc_subprocess(["config", "device", "remove", name, f"map-{idx}"],
capture_output=True, text=True, check=False) 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", lxc_run(["config", "device", "add", name, f"map-{idx}", "disk",
f"source={host_path}", f"source={host_path}",
f"path={container_path}", f"path={container_path}",

View File

@ -1,6 +1,6 @@
name: lxcd name: lxcd
base: core22 base: core22
version: git version: 0.1.0
summary: A Distrobox-like wrapper for LXD summary: A Distrobox-like wrapper for LXD
description: | description: |
lxcd provides a Distrobox-like experience using LXD containers. lxcd provides a Distrobox-like experience using LXD containers.