New simplified Chromebook centric branch, without volume mounting on home + new alpine container image (should work on any aarch64)

This commit is contained in:
mrkmntal 2025-11-27 14:04:03 -05:00
commit be80458918
5 changed files with 135 additions and 32 deletions

76
app.py
View file

@ -20,13 +20,17 @@ IMAGES = {
"docker_image": "micro-fedora43-dev:latest",
"label": "Fedora 43 Dev Box (micro-fedora43-dev)"
},
"alpine": {
"docker_image": "micro-alpine-dev:latest",
"label": "Alpine Linux Dev Box (micro-alpine-dev)"
},
}
DEFAULT_IMAGE_KEY = "debian"
DATA_ROOT = "/srv/microcontainers" # per-tenant metadata + disk.img
MOUNT_ROOT = "/mnt/microcontainers" # where disk.img files are mounted
HOSTNAME = "arthur.lan" # or "mentalnet.xyz" if you prefer
HOSTNAME = "localhost" # or "mentalnet.xyz" if you prefer
PORT_RANGE_START = 20000
PORT_RANGE_END = 21000
@ -49,6 +53,33 @@ def find_free_port(start=PORT_RANGE_START, end=PORT_RANGE_END):
raise RuntimeError("No free ports available in range")
def setup_tenant_environment(home_mount: str):
"""
Create default .bashrc, fastfetch config, and other user environment files.
Runs AFTER the home mount is created.
"""
bashrc = os.path.join(home_mount, ".bashrc")
config_dir = os.path.join(home_mount, ".config", "fastfetch")
os.makedirs(config_dir, exist_ok=True)
# Copy fastfetch config
source_cfg = os.path.join(os.path.dirname(__file__), "fastfetch_config.jsonc")
dest_cfg = os.path.join(config_dir, "config.jsonc")
if os.path.exists(source_cfg):
subprocess.run(["cp", source_cfg, dest_cfg], check=True)
# Write a curated .bashrc
with open(bashrc, "a") as f:
f.write("\n# ----- MicroContainers defaults -----\n")
f.write("if command -v fastfetch >/dev/null 2>&1; then fastfetch; fi\n")
f.write('alias fastfetch="fastfetch --config $HOME/.config/fastfetch/config.jsonc"\n')
# Give proper ownership
subprocess.run(["chown", "-R", "1000:1000", home_mount], check=True)
def validate_ssh_key(key: str) -> bool:
"""Very basic SSH public key validation."""
key = key.strip()
@ -76,46 +107,24 @@ def is_mounted(mount_point: str) -> bool:
return False
def ensure_tenant_disk(container_name: str, ssh_key: str, size_gb: int = 3) -> str:
"""
Ensure a per-tenant disk.img exists, is formatted, mounted, and has
.ssh/authorized_keys populated.
def ensure_tenant_home_dir(container_name: str, ssh_key: str) -> str:
home_dir = os.path.join(DATA_ROOT, container_name, "home")
ssh_dir = os.path.join(home_dir, ".ssh")
Returns the host mount point path, which will be bind-mounted to /home/micro.
"""
tenant_root = os.path.join(DATA_ROOT, container_name)
os.makedirs(tenant_root, exist_ok=True)
disk_img = os.path.join(tenant_root, "disk.img")
mount_point = os.path.join(MOUNT_ROOT, container_name)
os.makedirs(mount_point, exist_ok=True)
# 1) Create sparse disk.img if it doesn't exist
if not os.path.exists(disk_img):
subprocess.run(["fallocate", "-l", f"{size_gb}G", disk_img], check=True)
subprocess.run(["mkfs.ext4", "-F", disk_img], check=True)
# 2) Mount it if not already mounted
if not is_mounted(mount_point):
subprocess.run(["mount", "-o", "loop", disk_img, mount_point], check=True)
# 3) Prepare /home/micro layout inside the filesystem
# (we're mounting this whole thing as /home/micro in the container)
ssh_dir = os.path.join(mount_point, ".ssh")
os.makedirs(ssh_dir, exist_ok=True)
# Write authorized_keys
auth_keys_path = os.path.join(ssh_dir, "authorized_keys")
with open(auth_keys_path, "w") as f:
f.write(ssh_key.strip() + "\n")
# Proper perms for SSH
os.chmod(ssh_dir, 0o700)
os.chmod(auth_keys_path, 0o600)
# Make everything in /home/micro look like micro:micro (uid/gid 1000)
subprocess.run(["chown", "-R", "1000:1000", mount_point], check=True)
# Make it owned by micro:micro (uid 1000)
subprocess.run(["chown", "-R", "1000:1000", home_dir], check=True)
return mount_point
return home_dir
def run_docker_container(container_name: str, host_port: int, docker_image: str, home_mount: str):
@ -304,9 +313,12 @@ def index():
base_name = f"mc-{image_key}-{safe_note}"
container_name = f"{base_name}-{suffix}"
# Ensure per-tenant disk (3 GB ext4) and authorized_keys
# Ensure per-tenant disk (1 GB ext4) and authorized_keys
try:
home_mount = ensure_tenant_disk(container_name, ssh_key, size_gb=3)
home_mount = ensure_tenant_home_dir(container_name, ssh_key)
setup_tenant_environment(home_mount)
except subprocess.CalledProcessError as e:
flash(f"Error preparing tenant disk: {e}")
return render_template_string(