Compare commits
1 commit
master
...
chromebook
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be80458918 |
5 changed files with 135 additions and 32 deletions
76
app.py
76
app.py
|
|
@ -20,13 +20,17 @@ IMAGES = {
|
||||||
"docker_image": "micro-fedora43-dev:latest",
|
"docker_image": "micro-fedora43-dev:latest",
|
||||||
"label": "Fedora 43 Dev Box (micro-fedora43-dev)"
|
"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"
|
DEFAULT_IMAGE_KEY = "debian"
|
||||||
|
|
||||||
DATA_ROOT = "/srv/microcontainers" # per-tenant metadata + disk.img
|
DATA_ROOT = "/srv/microcontainers" # per-tenant metadata + disk.img
|
||||||
MOUNT_ROOT = "/mnt/microcontainers" # where disk.img files are mounted
|
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_START = 20000
|
||||||
PORT_RANGE_END = 21000
|
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")
|
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:
|
def validate_ssh_key(key: str) -> bool:
|
||||||
"""Very basic SSH public key validation."""
|
"""Very basic SSH public key validation."""
|
||||||
key = key.strip()
|
key = key.strip()
|
||||||
|
|
@ -76,46 +107,24 @@ def is_mounted(mount_point: str) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def ensure_tenant_disk(container_name: str, ssh_key: str, size_gb: int = 3) -> str:
|
def ensure_tenant_home_dir(container_name: str, ssh_key: str) -> str:
|
||||||
"""
|
home_dir = os.path.join(DATA_ROOT, container_name, "home")
|
||||||
Ensure a per-tenant disk.img exists, is formatted, mounted, and has
|
ssh_dir = os.path.join(home_dir, ".ssh")
|
||||||
.ssh/authorized_keys populated.
|
|
||||||
|
|
||||||
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)
|
os.makedirs(ssh_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Write authorized_keys
|
||||||
auth_keys_path = os.path.join(ssh_dir, "authorized_keys")
|
auth_keys_path = os.path.join(ssh_dir, "authorized_keys")
|
||||||
with open(auth_keys_path, "w") as f:
|
with open(auth_keys_path, "w") as f:
|
||||||
f.write(ssh_key.strip() + "\n")
|
f.write(ssh_key.strip() + "\n")
|
||||||
|
|
||||||
# Proper perms for SSH
|
|
||||||
os.chmod(ssh_dir, 0o700)
|
os.chmod(ssh_dir, 0o700)
|
||||||
os.chmod(auth_keys_path, 0o600)
|
os.chmod(auth_keys_path, 0o600)
|
||||||
|
|
||||||
# Make everything in /home/micro look like micro:micro (uid/gid 1000)
|
# Make it owned by micro:micro (uid 1000)
|
||||||
subprocess.run(["chown", "-R", "1000:1000", mount_point], check=True)
|
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):
|
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}"
|
base_name = f"mc-{image_key}-{safe_note}"
|
||||||
container_name = f"{base_name}-{suffix}"
|
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:
|
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:
|
except subprocess.CalledProcessError as e:
|
||||||
flash(f"Error preparing tenant disk: {e}")
|
flash(f"Error preparing tenant disk: {e}")
|
||||||
return render_template_string(
|
return render_template_string(
|
||||||
|
|
|
||||||
15
fastfetch_config.jsonc
Normal file
15
fastfetch_config.jsonc
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"display": {
|
||||||
|
"separator": " == "
|
||||||
|
},
|
||||||
|
"modules": [
|
||||||
|
"title",
|
||||||
|
"os",
|
||||||
|
"kernel",
|
||||||
|
"cpu",
|
||||||
|
"memory",
|
||||||
|
"disk",
|
||||||
|
"shell"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
53
micro-alpine-dev/Dockerfile
Normal file
53
micro-alpine-dev/Dockerfile
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
FROM alpine:latest
|
||||||
|
|
||||||
|
# Core packages (Alpine equivalents)
|
||||||
|
RUN apk update && apk add --no-cache \
|
||||||
|
openssh \
|
||||||
|
sudo \
|
||||||
|
ca-certificates \
|
||||||
|
git \
|
||||||
|
curl wget \
|
||||||
|
vim nano \
|
||||||
|
htop \
|
||||||
|
build-base \
|
||||||
|
fastfetch
|
||||||
|
|
||||||
|
# Create 'micro' user with UID 1000 and primary group 'micro'
|
||||||
|
RUN addgroup -g 1000 micro && \
|
||||||
|
adduser -D -u 1000 -G micro -s /bin/sh micro && \
|
||||||
|
echo "micro:ChangeMe123" | chpasswd
|
||||||
|
|
||||||
|
# Create sudo group and add micro to it
|
||||||
|
RUN addgroup -S sudo && \
|
||||||
|
adduser micro sudo && \
|
||||||
|
# Enable sudo for %sudo group in /etc/sudoers
|
||||||
|
sed -i 's/# %sudo/%sudo/' /etc/sudoers
|
||||||
|
|
||||||
|
# Prepare .ssh directory
|
||||||
|
RUN mkdir -p /home/micro/.ssh && \
|
||||||
|
chown -R micro:micro /home/micro && \
|
||||||
|
chmod 700 /home/micro/.ssh
|
||||||
|
|
||||||
|
# SSH server config: key-only login, use ~/.ssh/authorized_keys
|
||||||
|
RUN sed -i 's/#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config || true && \
|
||||||
|
sed -i 's/PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config || true && \
|
||||||
|
sed -i 's/#KbdInteractiveAuthentication yes/KbdInteractiveAuthentication no/' /etc/ssh/sshd_config || true && \
|
||||||
|
sed -i 's/#PubkeyAuthentication yes/PubkeyAuthentication yes/' /etc/ssh/sshd_config || true && \
|
||||||
|
sed -i 's|#AuthorizedKeysFile.*|AuthorizedKeysFile .ssh/authorized_keys|' /etc/ssh/sshd_config || true && \
|
||||||
|
echo 'UsePAM no' >> /etc/ssh/sshd_config
|
||||||
|
|
||||||
|
# sshd runtime dirs + host keys
|
||||||
|
RUN mkdir -p /var/run/sshd && \
|
||||||
|
ssh-keygen -A
|
||||||
|
|
||||||
|
# Fastfetch config for micro
|
||||||
|
RUN mkdir -p /home/micro/.config/fastfetch
|
||||||
|
COPY fastfetch_config.json /home/micro/.config/fastfetch/config.jsonc
|
||||||
|
RUN chown -R micro:micro /home/micro/.config && \
|
||||||
|
echo 'if command -v fastfetch >/dev/null 2>&1; then fastfetch; fi' >> /home/micro/.profile && \
|
||||||
|
chown micro:micro /home/micro/.profile
|
||||||
|
|
||||||
|
EXPOSE 22
|
||||||
|
|
||||||
|
CMD ["/usr/sbin/sshd", "-D"]
|
||||||
|
|
||||||
8
micro-alpine-dev/build-dockerfile.sh
Executable file
8
micro-alpine-dev/build-dockerfile.sh
Executable file
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
IMAGE_NAME="$(basename "$(pwd)")"
|
||||||
|
|
||||||
|
echo "Building Docker image: ${IMAGE_NAME}:latest"
|
||||||
|
docker build -t "${IMAGE_NAME}:latest" .
|
||||||
|
|
||||||
15
micro-alpine-dev/fastfetch_config.json
Normal file
15
micro-alpine-dev/fastfetch_config.json
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"display": {
|
||||||
|
"separator": " == "
|
||||||
|
},
|
||||||
|
"modules": [
|
||||||
|
"title",
|
||||||
|
"os",
|
||||||
|
"kernel",
|
||||||
|
"cpu",
|
||||||
|
"memory",
|
||||||
|
"disk",
|
||||||
|
"shell"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue