From de82cb4494f3b5218438815aaff582c1210f8d99 Mon Sep 17 00:00:00 2001 From: Robert Gingras Date: Fri, 15 May 2026 17:40:27 -0400 Subject: [PATCH 01/16] fix: add the auth cache directory to the docker image for NGINX --- images/agent/Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/images/agent/Dockerfile b/images/agent/Dockerfile index 45bef8da..1982b49a 100644 --- a/images/agent/Dockerfile +++ b/images/agent/Dockerfile @@ -19,6 +19,9 @@ RUN sed -i \ && sed -i -e 's/IncludeOptional/Include/' /usr/share/modsecurity-crs/owasp-crs.load \ && sed -i -e 's/^SecRuleEngine .*$/SecRuleEngine DetectionOnly/' /etc/nginx/modsecurity.conf +# Create the auth cache directory for NGINX forward auth +RUN mkdir -p /var/cache/nginx/auth_cache + # Logrotate overrides for NGINX and ModSecurity to work around a logrotate # repoen bug at https://github.com/owasp-modsecurity/ModSecurity-nginx/issues/351 COPY ./images/agent/nginx.logrotate /etc/logrotate.d/nginx From dcc22d7090cf21cc85b39112c903442eefa5cfad Mon Sep 17 00:00:00 2001 From: Robert Gingras Date: Fri, 15 May 2026 18:04:09 -0400 Subject: [PATCH 02/16] fix: properly set secure and domain values --- create-a-container/server.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/create-a-container/server.js b/create-a-container/server.js index 4a7f4b95..5772000c 100644 --- a/create-a-container/server.js +++ b/create-a-container/server.js @@ -11,6 +11,7 @@ const path = require('path'); const RateLimit = require('express-rate-limit'); const nodemailer = require('nodemailer'); const crypto = require('crypto'); +const net = require('net'); const swaggerUi = require('swagger-ui-express'); const YAML = require('yamljs'); const { sequelize, SessionSecret } = require('./models'); @@ -63,8 +64,6 @@ async function main() { db: sequelize, }); - const isProduction = process.env.NODE_ENV === 'production'; - app.use(session({ secret: await getSessionSecrets(), store: sessionStore, @@ -73,15 +72,22 @@ async function main() { // Dynamic cookie: drop the host part and set domain to the parent domain // (e.g., manager.example.com → .example.com) so the session cookie is // shared across sibling subdomains for nginx auth_request. + // For IP addresses (IPv4/IPv6) and single-label hosts like "localhost", + // omit the domain attribute so the browser scopes the cookie to the + // exact host (RFC 6265 forbids domain attributes on IP literals). + // `secure` is derived from the request protocol (honoring `trust proxy` + // and X-Forwarded-Proto from nginx) rather than NODE_ENV, so the flag + // tracks the actual transport — set on HTTPS, omitted on plain HTTP + // bootstrap/dev access. cookie: function(req) { const hostname = req.hostname || ''; const parts = hostname.split('.'); - const domain = parts.length >= 2 ? '.' + parts.slice(1).join('.') : undefined; + const shouldDropHost = !net.isIP(hostname) && parts.length > 2; return { - secure: isProduction, + secure: req.secure, maxAge: 24 * 60 * 60 * 1000, // 24 hours sameSite: 'lax', - domain + domain: shouldDropHost ? `.${parts.slice(1).join('.')}` : hostname }; } })); From 4b53aada3251cddd09988f21f5f48a57fc69fe9e Mon Sep 17 00:00:00 2001 From: Robert Gingras Date: Fri, 15 May 2026 18:08:48 -0400 Subject: [PATCH 03/16] feat: full local docker env --- compose.yml | 111 ++++++++++++++++++ .../proxmox-ve/99-container-creator-dev.conf | 4 + images/proxmox-ve/Dockerfile | 49 ++++++++ images/proxmox-ve/create-manager.service | 28 +++++ images/proxmox-ve/enable-lxcfs.conf | 4 + images/proxmox-ve/interfaces | 28 +++++ images/proxmox-ve/pve-install-repo.sources | 5 + 7 files changed, 229 insertions(+) create mode 100644 compose.yml create mode 100644 images/proxmox-ve/99-container-creator-dev.conf create mode 100644 images/proxmox-ve/Dockerfile create mode 100644 images/proxmox-ve/create-manager.service create mode 100644 images/proxmox-ve/enable-lxcfs.conf create mode 100644 images/proxmox-ve/interfaces create mode 100644 images/proxmox-ve/pve-install-repo.sources diff --git a/compose.yml b/compose.yml new file mode 100644 index 00000000..9794dcf4 --- /dev/null +++ b/compose.yml @@ -0,0 +1,111 @@ +--- +x-proxmox-service: &proxmox-service + image: proxmox-ve:9 + build: images/proxmox-ve + volumes: + - local:/var/lib/vz + - ./:/opt/opensource-server + environment: + MANAGER_TAG: ${MANAGER_TAG:-latest} + +services: + # This service runs once when bringing the compose stack up to ensure the + # latest manager image (overridable by .env) is installed into the Proxmox + # template cache. This can run in parallel with other build tasks, slightly + # speeding up the overall time to a complete setup. + pull-image: + <<: *proxmox-service + entrypoint: ["/bin/bash", "-c"] + command: + - | + MANAGER_TAG=$${MANAGER_TAG:-latest} + FILENAME=/var/lib/vz/template/cache/manager_$${MANAGER_TAG}.tar + if [ ! -f "$$FILENAME" ]; then + skopeo copy docker://ghcr.io/mieweb/opensource-server/manager:$${MANAGER_TAG} oci-archive:$${FILENAME} + fi + + # This service runs once when bringing the compose stack up to ensure the + # node_modules are populated in the user's create-a-container directory. This + # is usually handled by the manager's Dockerfile, but we're mounting over that + # directory so we need to be sure the user has them. + node: + image: node:24-trixie-slim + volumes: + - ./:/opt/opensource-server + working_dir: /opt/opensource-server/create-a-container + command: npm ci --no-audit --no-fund + + # Similar to the npm-install service, this service builds the documentation in + # mie-opensource-landing to ensure it's populated in the dev environment. This + # is also usually handled by the container build, but again, we're overriding + # it with the bind mount of the working directory. + build-docs: + image: astral/uv:python3.12-bookworm-slim + volumes: + - ./:/opt/opensource-server + working_dir: /opt/opensource-server/mie-opensource-landing + command: uv run zensical build + # This is the only long-running service in the stack. On startup it creates + # the manager container as CTID 100 (if it needs to) with the current + # directory mounted over the /opt/opensource-server in the container so local + # changes easily propogate inwards. `npm install` and the documentation build + # should be managed by their respective services above so there's no questions + # about environment or permissions. TODO: make those services rebuild when + # there's changes to their directories. TODO: add a second Proxmox to test + # multiple nodes. TODO: persist the manager in a volume so docker compose down + # + docker compose up doesn't have to re-bootstrap every time. + proxmox: + <<: *proxmox-service + volumes: + - local:/var/lib/vz + - ./:/opt/opensource-server:ro + privileged: true + devices: + - /dev/loop-control:/dev/loop-control + ports: + - 127.0.0.1:80:80 + - 127.0.0.1:443:443 + - 127.0.0.1:8006:8006 + hostname: proxmox + domainname: cluster.internal + dns: + - 10.254.0.2 # Manager IP, enables ldap container lookup + dns_search: cluster.internal + security_opt: + - label=disable + - seccomp=unconfined + depends_on: + pull-image: + condition: service_completed_successfully + node: + condition: service_completed_successfully + build-docs: + condition: service_completed_successfully + + # After the Proxmox (which includes the manager LXC) is healthy we have to + # bootstrap the Manager setup. The commands below were derived from doing a + # manual bootstrap and copying the network requests. + # + # - Will try to recreate everything each up/start even if they already exist + # - Doesn't work after the last step because the "proxmox" hostname no + # longer routes to the container-creator instance + # - TODO: This step should be the one to configure the ldap container(s) and + # set the Proxmox up to allow login through them + # - Can we move create-manager.service from the Proxmox image to here? + # + bootstrap-manager: + image: curlimages/curl:latest + entrypoint: ["sh", "-c"] + command: + - | + curl -c cookies.txt -k -X POST --data 'role=admin' https://proxmox/login/dev + curl -b cookies.txt -k -X POST --data 'name=Development&internalDomain=cluster.internal&dhcpRange=10.254.1.1%2C10.254.254.254&subnetMask=255.255.0.0&gateway=10.254.0.1&dnsForwarders=8.8.8.8%2C1.1.1.1&externalIp=127.0.0.1' https://proxmox/sites + curl -b cookies.txt -k -X POST --data 'apiUrl=https%3A%2F%2F10.254.0.1%3A8006&username=root%40pam&password=root&tlsVerify=false' https://proxmox/sites/1/nodes/import + curl -b cookies.txt -k -X POST --data 'name=localhost&siteId=1&authServer=https://manager.localhost' https://proxmox/external-domains + curl -b cookies.txt -k -X PUT --data 'services%5B0%5D%5Bdeleted%5D=false&services%5B0%5D%5Btype%5D=http&services%5B0%5D%5BinternalPort%5D=3000&services%5B0%5D%5BexternalHostname%5D=manager&services%5B0%5D%5BexternalDomainId%5D=1' https://proxmox/sites/1/containers/1 + depends_on: + proxmox: + condition: service_healthy + +volumes: + local: diff --git a/images/proxmox-ve/99-container-creator-dev.conf b/images/proxmox-ve/99-container-creator-dev.conf new file mode 100644 index 00000000..a2c8fa37 --- /dev/null +++ b/images/proxmox-ve/99-container-creator-dev.conf @@ -0,0 +1,4 @@ +[Service] +Environment=NODE_ENV=development +ExecStart= +ExecStart=/opt/opensource-server/create-a-container/node_modules/.bin/nodemon server.js diff --git a/images/proxmox-ve/Dockerfile b/images/proxmox-ve/Dockerfile new file mode 100644 index 00000000..3bb67a96 --- /dev/null +++ b/images/proxmox-ve/Dockerfile @@ -0,0 +1,49 @@ +# syntax=docker/dockerfile:1 +# We intentionally revert to the Docker-provided Debian 13 for this image rather +# than our "base" image since the Proxmox is expected to be administrator- +# managed in a production environment. Thus the only changes we make are to +# convert it from Debian to Proxmox, a few modifications so it'll run in Docker, +# and then the bootstrap service that would normally require the admin to run. +FROM debian:13 + +# Add the Proxmox repo key +ADD --chmod=0644 \ + https://enterprise.proxmox.com/debian/proxmox-archive-keyring-trixie.gpg \ + /usr/share/keyrings/proxmox-archive-keyring.gpg + +# Add the PVE repo +COPY pve-install-repo.sources /etc/apt/sources.list.d/ + +# Install the Proxmox packages +RUN apt-get update \ + && apt-get install -y proxmox-ve postfix open-iscsi chrony e2fsprogs \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* \ + && rm /etc/apt/sources.list.d/pve-enterprise.sources /etc/network/interfaces.new + +# Add the init service +COPY create-manager.service /etc/systemd/system/create-manager.service +RUN systemctl enable create-manager.service + +# Override standard settings that fail under Docker containerization +COPY enable-lxcfs.conf /etc/systemd/system/lxcfs.service.d/enable-lxcfs.conf + +# Set default password +RUN echo 'root' | passwd --stdin + +# Configure network interfaces +COPY interfaces /etc/network/interfaces + +# Set the entrypoint so all the Proxmox services will run +ENTRYPOINT ["/sbin/init"] + +# The healthcheck checks for the Manager container to be up and running not just +# the Proxmox. This is used to delay running the bootstrap script in the compose +# file until the manager is ready for API calls. +HEALTHCHECK \ + --interval=30s \ + --timeout=30s \ + --start-period=60s \ + --start-interval=5s \ + --retries=5 \ + CMD curl -Ik https://10.254.0.2 diff --git a/images/proxmox-ve/create-manager.service b/images/proxmox-ve/create-manager.service new file mode 100644 index 00000000..51ed3b83 --- /dev/null +++ b/images/proxmox-ve/create-manager.service @@ -0,0 +1,28 @@ +[Unit] +Description=Initialize Manager LXC +After=pve-cluster.service pvedaemon.service pvestatd.service network-online.target +Wants=network-online.target + +[Service] +Type=oneshot +PassEnvironment=MANAGER_TAG +ExecStart=/bin/bash -ec '\ + until [ -d /etc/pve/local ]; do sleep 0.5; done; \ + CTID=100; \ + BRIDGE=vmbr0; \ + MANAGER_TAG=$${MANAGER_TAG:-latest}; \ + if [ -f /etc/pve/lxc/$$CTID.conf ]; then exit 0; fi; \ + if [ ! -d /sys/class/net/$$BRIDGE ]; then exit 1; fi; \ + if [ ! -f /var/lib/vz/template/cache/manager_$${MANAGER_TAG}.tar ]; then exit 1; fi; \ + pct create 100 local:vztmpl/manager_$${MANAGER_TAG}.tar --cores=4 --features=nesting=1 --hostname=manager --memory=8192 --net0=name=eth0,bridge=$$BRIDGE,gw=10.254.0.1,ip=10.254.0.2/24 --onboot=1 --ostype=debian --rootfs=local:50 --entrypoint="/sbin/init systemd.unit=emergency.target"; \ + pct start 100; \ + pct exec 100 -- openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -keyout /etc/ssl/private/localhost.key -out /etc/ssl/certs/localhost.crt -days 3650 -noenc -subj /CN=localhost; \ + pct exec 100 -- mkdir -p /etc/systemd/system/container-creator.service.d; \ + pct push 100 /opt/opensource-server/images/proxmox-ve/99-container-creator-dev.conf /etc/systemd/system/container-creator.service.d/99-container-creator-dev.conf; \ + pct shutdown 100; \ + pct set 100 --mp0=/opt/opensource-server,mp=/opt/opensource-server --entrypoint=/sbin/init; \ + pct start 100; \ +' + +[Install] +WantedBy=multi-user.target diff --git a/images/proxmox-ve/enable-lxcfs.conf b/images/proxmox-ve/enable-lxcfs.conf new file mode 100644 index 00000000..115c1fa5 --- /dev/null +++ b/images/proxmox-ve/enable-lxcfs.conf @@ -0,0 +1,4 @@ +# Disable virtualization detection on LXCFS since we won't otherwise have it to +# hook into for the nested LXC containers +[Unit] +ConditionVirtualization= diff --git a/images/proxmox-ve/interfaces b/images/proxmox-ve/interfaces new file mode 100644 index 00000000..bc7f6296 --- /dev/null +++ b/images/proxmox-ve/interfaces @@ -0,0 +1,28 @@ +# network interface settings; autogenerated +# Please do NOT modify this file directly, unless you know what +# you're doing. +# +# If you want to manage parts of the network configuration manually, +# please utilize the 'source' or 'source-directory' directives to do +# so. +# PVE will preserve these directives, but will NOT read its network +# configuration from sourced files, so do not attempt to move any of +# the PVE managed interfaces into external files! + +auto lo +iface lo inet loopback + +auto vmbr0 +iface vmbr0 inet static + address 10.254.0.1/16 + bridge-ports none + bridge-stp off + bridge-fd 0 + + post-up echo 1 > /proc/sys/net/ipv4/ip_forward + post-up iptables -t nat -A POSTROUTING -s 10.254.0.0/16 -o eth0 -j MASQUERADE + post-up iptables -t nat -A PREROUTING -i eth0 -p tcp -m multiport --dports 80,443,2000:2999 -j DNAT --to-destination 10.254.0.2 + post-up iptables -t nat -A PREROUTING -i eth0 -p udp -m multiport --dports 80,443,2000:2999 -j DNAT --to-destination 10.254.0.2 + post-down iptables -t nat -D POSTROUTING -s 10.254.0.0/16 -o eth0 -j MASQUERADE + post-down iptables -t nat -D PREROUTING -i eth0 -p tcp -m multiport --dports 80,443,2000:2999 -j DNAT --to-destination 10.254.0.2 + post-down iptables -t nat -D PREROUTING -i eth0 -p udp -m multiport --dports 80,443,2000:2999 -j DNAT --to-destination 10.254.0.2 diff --git a/images/proxmox-ve/pve-install-repo.sources b/images/proxmox-ve/pve-install-repo.sources new file mode 100644 index 00000000..fcf253e8 --- /dev/null +++ b/images/proxmox-ve/pve-install-repo.sources @@ -0,0 +1,5 @@ +Types: deb +URIs: http://download.proxmox.com/debian/pve +Suites: trixie +Components: pve-no-subscription +Signed-By: /usr/share/keyrings/proxmox-archive-keyring.gpg From 91210247f0e8afed7a9bda2dfa43c5bc41c7e842 Mon Sep 17 00:00:00 2001 From: Robert Gingras Date: Fri, 15 May 2026 23:37:32 -0400 Subject: [PATCH 04/16] fix: use macros for correct localhost url handling in docs site --- compose.yml | 4 + .../users/creating-containers/api-keys.md | 2 +- .../users/creating-containers/command-line.md | 4 +- .../docs/users/creating-containers/web-gui.md | 2 +- .../docs/users/getting-started.md | 2 +- .../docs/users/mcp-server.md | 2 +- .../docs/users/monitoring-container.md | 2 +- .../proxmox-launchpad/getting-started.md | 2 +- mie-opensource-landing/main.py | 21 +++ mie-opensource-landing/uv.lock | 132 +++++++++++++++--- mie-opensource-landing/zensical.toml | 1 + 11 files changed, 149 insertions(+), 25 deletions(-) create mode 100644 mie-opensource-landing/main.py diff --git a/compose.yml b/compose.yml index 9794dcf4..c447c2a2 100644 --- a/compose.yml +++ b/compose.yml @@ -45,6 +45,10 @@ services: - ./:/opt/opensource-server working_dir: /opt/opensource-server/mie-opensource-landing command: uv run zensical build + environment: + PROXMOX_URL: https://localhost:8006 + MANAGER_URL: https://manager.localhost + # This is the only long-running service in the stack. On startup it creates # the manager container as CTID 100 (if it needs to) with the current # directory mounted over the /opt/opensource-server in the container so local diff --git a/mie-opensource-landing/docs/users/creating-containers/api-keys.md b/mie-opensource-landing/docs/users/creating-containers/api-keys.md index ec1f6ae0..459ef132 100644 --- a/mie-opensource-landing/docs/users/creating-containers/api-keys.md +++ b/mie-opensource-landing/docs/users/creating-containers/api-keys.md @@ -40,4 +40,4 @@ curl -H "Authorization: Bearer YOUR_API_KEY" \ https://your-server/apikeys ``` -For the full list of available endpoints, request and response schemas, and a "Try it out" console, visit the [interactive API documentation](https://manager.os.mieweb.org/api) at `/api` on your server. +For the full list of available endpoints, request and response schemas, and a "Try it out" console, visit the [interactive API documentation]({{ manager_url }}/api) at `/api` on your server. diff --git a/mie-opensource-landing/docs/users/creating-containers/command-line.md b/mie-opensource-landing/docs/users/creating-containers/command-line.md index 9443a6da..06abb421 100644 --- a/mie-opensource-landing/docs/users/creating-containers/command-line.md +++ b/mie-opensource-landing/docs/users/creating-containers/command-line.md @@ -8,7 +8,7 @@ This page predates the last major rewrite and may not be accurate. Check back weekly for updates. -API endpoint: https://manager.os.mieweb.org +API endpoint: {{ manager_url }} **Prerequisites:** @@ -92,7 +92,7 @@ curl -X GET 'https://create-a-container.opensource.mieweb.org/api/containers' \ **HTTP:** `https://.` -**Proxmox:** Navigate to [https://os.mieweb.org:8006](https://os.mieweb.org:8006) — your container is listed with your username in the tags field. +**Proxmox:** Navigate to [{{ proxmox_url }}]({{ proxmox_url }}) — your container is listed with your username in the tags field. ![Hostname](img/proxmox-lxc.jpg) diff --git a/mie-opensource-landing/docs/users/creating-containers/web-gui.md b/mie-opensource-landing/docs/users/creating-containers/web-gui.md index 45fa6b77..9aaef8c1 100644 --- a/mie-opensource-landing/docs/users/creating-containers/web-gui.md +++ b/mie-opensource-landing/docs/users/creating-containers/web-gui.md @@ -85,7 +85,7 @@ Click **Create Container**. You'll be redirected to a page to watch the creation **HTTP:** `https://.` -**Proxmox Console:** [https://os.mieweb.org:8006](https://os.mieweb.org:8006) +**Proxmox Console:** [{{ proxmox_url }}]({{ proxmox_url }}) ## Managing Containers diff --git a/mie-opensource-landing/docs/users/getting-started.md b/mie-opensource-landing/docs/users/getting-started.md index f7ac9656..9946f39d 100644 --- a/mie-opensource-landing/docs/users/getting-started.md +++ b/mie-opensource-landing/docs/users/getting-started.md @@ -1,6 +1,6 @@ # Getting Started -To use the cluster, you need an account on the web interface at [https://manager.os.mieweb.org](https://manager.os.mieweb.org). +To use the cluster, you need an account on the web interface at [{{ manager_url }}]({{ manager_url }}). ## Registering with an Invite diff --git a/mie-opensource-landing/docs/users/mcp-server.md b/mie-opensource-landing/docs/users/mcp-server.md index 9313c46d..5cbbaaa7 100644 --- a/mie-opensource-landing/docs/users/mcp-server.md +++ b/mie-opensource-landing/docs/users/mcp-server.md @@ -5,7 +5,7 @@ Use the MCP (Model Context Protocol) server to manage your containers through AI **Prerequisites:** - VS Code with an MCP-capable AI extension (e.g., [GitHub Copilot](https://marketplace.visualstudio.com/items?itemName=GitHub.copilot), [Claude for VS Code](https://marketplace.visualstudio.com/items?itemName=AnthropicPublicBeta.claude-for-vscode)) - [uv](https://docs.astral.sh/uv/getting-started/installation/) installed -- An API key from [your server](https://manager.os.mieweb.org/apikeys) (see [API Keys](./creating-containers/api-keys.md)) +- An API key from [your server]({{ manager_url }}/apikeys) (see [API Keys](./creating-containers/api-keys.md)) ## 1. Configure VS Code diff --git a/mie-opensource-landing/docs/users/monitoring-container.md b/mie-opensource-landing/docs/users/monitoring-container.md index b3e42dcd..a10de83f 100644 --- a/mie-opensource-landing/docs/users/monitoring-container.md +++ b/mie-opensource-landing/docs/users/monitoring-container.md @@ -1,6 +1,6 @@ # Monitoring Containers in Proxmox -Monitor container performance in the Proxmox Web GUI at [https://os.mieweb.org:8006](https://os.mieweb.org:8006). +Monitor container performance in the Proxmox Web GUI at [{{ proxmox_url }}]({{ proxmox_url }}). ## Dashboard diff --git a/mie-opensource-landing/docs/users/proxmox-launchpad/getting-started.md b/mie-opensource-landing/docs/users/proxmox-launchpad/getting-started.md index 8c921c6b..391b656b 100644 --- a/mie-opensource-landing/docs/users/proxmox-launchpad/getting-started.md +++ b/mie-opensource-landing/docs/users/proxmox-launchpad/getting-started.md @@ -157,7 +157,7 @@ jobs: ## Container Access -After deployment, you'll receive access details located in your containers page on manager.os.mieweb.org including: +After deployment, you'll receive access details located in your containers page on {{ manager_url }} including: ``` ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ diff --git a/mie-opensource-landing/main.py b/mie-opensource-landing/main.py new file mode 100644 index 00000000..e6ea2251 --- /dev/null +++ b/mie-opensource-landing/main.py @@ -0,0 +1,21 @@ +"""Macro definitions for the Zensical macros extension. + +Exposes URL variables sourced from environment variables so that documentation +can reference deployment-specific endpoints without hard-coding hostnames. + +Variables: + proxmox_url — Proxmox web UI base URL (env: PROXMOX_URL) + manager_url — Manager web UI base URL (env: MANAGER_URL) +""" + +from __future__ import annotations + +import os + +DEFAULT_PROXMOX_URL = "https://os.mieweb.org:8006" +DEFAULT_MANAGER_URL = "https://manager.os.mieweb.org" + + +def define_env(env) -> None: + env.variables["proxmox_url"] = os.environ.get("PROXMOX_URL", DEFAULT_PROXMOX_URL) + env.variables["manager_url"] = os.environ.get("MANAGER_URL", DEFAULT_MANAGER_URL) diff --git a/mie-opensource-landing/uv.lock b/mie-opensource-landing/uv.lock index 6941dbdb..cc07feb3 100644 --- a/mie-opensource-landing/uv.lock +++ b/mie-opensource-landing/uv.lock @@ -32,6 +32,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2d/82/e5d2c1c67d19841e9edc74954c827444ae826978499bde3dfc1d007c8c11/deepmerge-2.0-py3-none-any.whl", hash = "sha256:6de9ce507115cff0bed95ff0ce9ecc31088ef50cbdf09bc90a09349a318b3d00", size = 13475, upload-time = "2024-08-30T05:31:48.659Z" }, ] +[[package]] +name = "jinja2" +version = "3.1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markupsafe" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, +] + [[package]] name = "markdown" version = "3.10.2" @@ -41,6 +53,91 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/de/1f/77fa3081e4f66ca3576c896ae5d31c3002ac6607f9747d2e3aa49227e464/markdown-3.10.2-py3-none-any.whl", hash = "sha256:e91464b71ae3ee7afd3017d9f358ef0baf158fd9a298db92f1d4761133824c36", size = 108180, upload-time = "2026-02-09T14:57:25.787Z" }, ] +[[package]] +name = "markupsafe" +version = "3.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/4b/3541d44f3937ba468b75da9eebcae497dcf67adb65caa16760b0a6807ebb/markupsafe-3.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f981d352f04553a7171b8e44369f2af4055f888dfb147d55e42d29e29e74559", size = 11631, upload-time = "2025-09-27T18:36:05.558Z" }, + { url = "https://files.pythonhosted.org/packages/98/1b/fbd8eed11021cabd9226c37342fa6ca4e8a98d8188a8d9b66740494960e4/markupsafe-3.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e1c1493fb6e50ab01d20a22826e57520f1284df32f2d8601fdd90b6304601419", size = 12057, upload-time = "2025-09-27T18:36:07.165Z" }, + { url = "https://files.pythonhosted.org/packages/40/01/e560d658dc0bb8ab762670ece35281dec7b6c1b33f5fbc09ebb57a185519/markupsafe-3.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ba88449deb3de88bd40044603fafffb7bc2b055d626a330323a9ed736661695", size = 22050, upload-time = "2025-09-27T18:36:08.005Z" }, + { url = "https://files.pythonhosted.org/packages/af/cd/ce6e848bbf2c32314c9b237839119c5a564a59725b53157c856e90937b7a/markupsafe-3.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f42d0984e947b8adf7dd6dde396e720934d12c506ce84eea8476409563607591", size = 20681, upload-time = "2025-09-27T18:36:08.881Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2a/b5c12c809f1c3045c4d580b035a743d12fcde53cf685dbc44660826308da/markupsafe-3.0.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0c0b3ade1c0b13b936d7970b1d37a57acde9199dc2aecc4c336773e1d86049c", size = 20705, upload-time = "2025-09-27T18:36:10.131Z" }, + { url = "https://files.pythonhosted.org/packages/cf/e3/9427a68c82728d0a88c50f890d0fc072a1484de2f3ac1ad0bfc1a7214fd5/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:0303439a41979d9e74d18ff5e2dd8c43ed6c6001fd40e5bf2e43f7bd9bbc523f", size = 21524, upload-time = "2025-09-27T18:36:11.324Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/23578f29e9e582a4d0278e009b38081dbe363c5e7165113fad546918a232/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:d2ee202e79d8ed691ceebae8e0486bd9a2cd4794cec4824e1c99b6f5009502f6", size = 20282, upload-time = "2025-09-27T18:36:12.573Z" }, + { url = "https://files.pythonhosted.org/packages/56/21/dca11354e756ebd03e036bd8ad58d6d7168c80ce1fe5e75218e4945cbab7/markupsafe-3.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:177b5253b2834fe3678cb4a5f0059808258584c559193998be2601324fdeafb1", size = 20745, upload-time = "2025-09-27T18:36:13.504Z" }, + { url = "https://files.pythonhosted.org/packages/87/99/faba9369a7ad6e4d10b6a5fbf71fa2a188fe4a593b15f0963b73859a1bbd/markupsafe-3.0.3-cp310-cp310-win32.whl", hash = "sha256:2a15a08b17dd94c53a1da0438822d70ebcd13f8c3a95abe3a9ef9f11a94830aa", size = 14571, upload-time = "2025-09-27T18:36:14.779Z" }, + { url = "https://files.pythonhosted.org/packages/d6/25/55dc3ab959917602c96985cb1253efaa4ff42f71194bddeb61eb7278b8be/markupsafe-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:c4ffb7ebf07cfe8931028e3e4c85f0357459a3f9f9490886198848f4fa002ec8", size = 15056, upload-time = "2025-09-27T18:36:16.125Z" }, + { url = "https://files.pythonhosted.org/packages/d0/9e/0a02226640c255d1da0b8d12e24ac2aa6734da68bff14c05dd53b94a0fc3/markupsafe-3.0.3-cp310-cp310-win_arm64.whl", hash = "sha256:e2103a929dfa2fcaf9bb4e7c091983a49c9ac3b19c9061b6d5427dd7d14d81a1", size = 13932, upload-time = "2025-09-27T18:36:17.311Z" }, + { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, + { url = "https://files.pythonhosted.org/packages/e1/2e/5898933336b61975ce9dc04decbc0a7f2fee78c30353c5efba7f2d6ff27a/markupsafe-3.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4bd4cd07944443f5a265608cc6aab442e4f74dff8088b0dfc8238647b8f6ae9a", size = 12058, upload-time = "2025-09-27T18:36:19.444Z" }, + { url = "https://files.pythonhosted.org/packages/1d/09/adf2df3699d87d1d8184038df46a9c80d78c0148492323f4693df54e17bb/markupsafe-3.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b5420a1d9450023228968e7e6a9ce57f65d148ab56d2313fcd589eee96a7a50", size = 24287, upload-time = "2025-09-27T18:36:20.768Z" }, + { url = "https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0bf2a864d67e76e5c9a34dc26ec616a66b9888e25e7b9460e1c76d3293bd9dbf", size = 22940, upload-time = "2025-09-27T18:36:22.249Z" }, + { url = "https://files.pythonhosted.org/packages/19/ae/31c1be199ef767124c042c6c3e904da327a2f7f0cd63a0337e1eca2967a8/markupsafe-3.0.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc51efed119bc9cfdf792cdeaa4d67e8f6fcccab66ed4bfdd6bde3e59bfcbb2f", size = 21887, upload-time = "2025-09-27T18:36:23.535Z" }, + { url = "https://files.pythonhosted.org/packages/b2/76/7edcab99d5349a4532a459e1fe64f0b0467a3365056ae550d3bcf3f79e1e/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:068f375c472b3e7acbe2d5318dea141359e6900156b5b2ba06a30b169086b91a", size = 23692, upload-time = "2025-09-27T18:36:24.823Z" }, + { url = "https://files.pythonhosted.org/packages/a4/28/6e74cdd26d7514849143d69f0bf2399f929c37dc2b31e6829fd2045b2765/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7be7b61bb172e1ed687f1754f8e7484f1c8019780f6f6b0786e76bb01c2ae115", size = 21471, upload-time = "2025-09-27T18:36:25.95Z" }, + { url = "https://files.pythonhosted.org/packages/62/7e/a145f36a5c2945673e590850a6f8014318d5577ed7e5920a4b3448e0865d/markupsafe-3.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9e130248f4462aaa8e2552d547f36ddadbeaa573879158d721bbd33dfe4743a", size = 22923, upload-time = "2025-09-27T18:36:27.109Z" }, + { url = "https://files.pythonhosted.org/packages/0f/62/d9c46a7f5c9adbeeeda52f5b8d802e1094e9717705a645efc71b0913a0a8/markupsafe-3.0.3-cp311-cp311-win32.whl", hash = "sha256:0db14f5dafddbb6d9208827849fad01f1a2609380add406671a26386cdf15a19", size = 14572, upload-time = "2025-09-27T18:36:28.045Z" }, + { url = "https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:de8a88e63464af587c950061a5e6a67d3632e36df62b986892331d4620a35c01", size = 15077, upload-time = "2025-09-27T18:36:29.025Z" }, + { url = "https://files.pythonhosted.org/packages/35/73/893072b42e6862f319b5207adc9ae06070f095b358655f077f69a35601f0/markupsafe-3.0.3-cp311-cp311-win_arm64.whl", hash = "sha256:3b562dd9e9ea93f13d53989d23a7e775fdfd1066c33494ff43f5418bc8c58a5c", size = 13876, upload-time = "2025-09-27T18:36:29.954Z" }, + { url = "https://files.pythonhosted.org/packages/5a/72/147da192e38635ada20e0a2e1a51cf8823d2119ce8883f7053879c2199b5/markupsafe-3.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d53197da72cc091b024dd97249dfc7794d6a56530370992a5e1a08983ad9230e", size = 11615, upload-time = "2025-09-27T18:36:30.854Z" }, + { url = "https://files.pythonhosted.org/packages/9a/81/7e4e08678a1f98521201c3079f77db69fb552acd56067661f8c2f534a718/markupsafe-3.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1872df69a4de6aead3491198eaf13810b565bdbeec3ae2dc8780f14458ec73ce", size = 12020, upload-time = "2025-09-27T18:36:31.971Z" }, + { url = "https://files.pythonhosted.org/packages/1e/2c/799f4742efc39633a1b54a92eec4082e4f815314869865d876824c257c1e/markupsafe-3.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a7e8ae81ae39e62a41ec302f972ba6ae23a5c5396c8e60113e9066ef893da0d", size = 24332, upload-time = "2025-09-27T18:36:32.813Z" }, + { url = "https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6dd0be5b5b189d31db7cda48b91d7e0a9795f31430b7f271219ab30f1d3ac9d", size = 22947, upload-time = "2025-09-27T18:36:33.86Z" }, + { url = "https://files.pythonhosted.org/packages/2c/54/887f3092a85238093a0b2154bd629c89444f395618842e8b0c41783898ea/markupsafe-3.0.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:94c6f0bb423f739146aec64595853541634bde58b2135f27f61c1ffd1cd4d16a", size = 21962, upload-time = "2025-09-27T18:36:35.099Z" }, + { url = "https://files.pythonhosted.org/packages/c9/2f/336b8c7b6f4a4d95e91119dc8521402461b74a485558d8f238a68312f11c/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:be8813b57049a7dc738189df53d69395eba14fb99345e0a5994914a3864c8a4b", size = 23760, upload-time = "2025-09-27T18:36:36.001Z" }, + { url = "https://files.pythonhosted.org/packages/32/43/67935f2b7e4982ffb50a4d169b724d74b62a3964bc1a9a527f5ac4f1ee2b/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:83891d0e9fb81a825d9a6d61e3f07550ca70a076484292a70fde82c4b807286f", size = 21529, upload-time = "2025-09-27T18:36:36.906Z" }, + { url = "https://files.pythonhosted.org/packages/89/e0/4486f11e51bbba8b0c041098859e869e304d1c261e59244baa3d295d47b7/markupsafe-3.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:77f0643abe7495da77fb436f50f8dab76dbc6e5fd25d39589a0f1fe6548bfa2b", size = 23015, upload-time = "2025-09-27T18:36:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/2f/e1/78ee7a023dac597a5825441ebd17170785a9dab23de95d2c7508ade94e0e/markupsafe-3.0.3-cp312-cp312-win32.whl", hash = "sha256:d88b440e37a16e651bda4c7c2b930eb586fd15ca7406cb39e211fcff3bf3017d", size = 14540, upload-time = "2025-09-27T18:36:38.761Z" }, + { url = "https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:26a5784ded40c9e318cfc2bdb30fe164bdb8665ded9cd64d500a34fb42067b1c", size = 15105, upload-time = "2025-09-27T18:36:39.701Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f1/216fc1bbfd74011693a4fd837e7026152e89c4bcf3e77b6692fba9923123/markupsafe-3.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:35add3b638a5d900e807944a078b51922212fb3dedb01633a8defc4b01a3c85f", size = 13906, upload-time = "2025-09-27T18:36:40.689Z" }, + { url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" }, + { url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" }, + { url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" }, + { url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" }, + { url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" }, + { url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" }, + { url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" }, + { url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" }, + { url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" }, + { url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" }, + { url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" }, + { url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" }, + { url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" }, + { url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" }, + { url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" }, + { url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" }, + { url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" }, + { url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" }, + { url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" }, + { url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" }, + { url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" }, + { url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" }, + { url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" }, + { url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" }, + { url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" }, + { url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" }, + { url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" }, + { url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" }, + { url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" }, + { url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" }, + { url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" }, + { url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" }, + { url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" }, + { url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" }, + { url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" }, + { url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" }, + { url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" }, + { url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, +] + [[package]] name = "mie-opensource-landing" version = "0.1.0" @@ -63,15 +160,15 @@ wheels = [ [[package]] name = "pymdown-extensions" -version = "10.21.2" +version = "10.21.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "markdown" }, { name = "pyyaml" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/df/08/f1c908c581fd11913da4711ea7ba32c0eee40b0190000996bb863b0c9349/pymdown_extensions-10.21.2.tar.gz", hash = "sha256:c3f55a5b8a1d0edf6699e35dcbea71d978d34ff3fa79f3d807b8a5b3fa90fbdc", size = 853922, upload-time = "2026-03-29T15:01:55.233Z" } +sdist = { url = "https://files.pythonhosted.org/packages/9e/26/d1015444da4d952a1ca487a236b522eb979766f0295a0bd0c5fc089989a9/pymdown_extensions-10.21.3.tar.gz", hash = "sha256:72cfcf55f07aea0d4af2c4f11dd4e52466ddfb1bb819673146398e0bd3a77354", size = 854140, upload-time = "2026-05-13T12:57:32.267Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl", hash = "sha256:5c0fd2a2bea14eb39af8ff284f1066d898ab2187d81b889b75d46d4348c01638", size = 268901, upload-time = "2026-03-29T15:01:53.244Z" }, + { url = "https://files.pythonhosted.org/packages/7e/85/545a951eecc270fcd688288c600017e2050a1aacb56c711d208586d3e470/pymdown_extensions-10.21.3-py3-none-any.whl", hash = "sha256:d7a5d08014fc571e80ca21dd6f854e31f94c489800350564d55d15b3c41e76b6", size = 269002, upload-time = "2026-05-13T12:57:30.296Z" }, ] [[package]] @@ -194,29 +291,30 @@ wheels = [ [[package]] name = "zensical" -version = "0.0.36" +version = "0.0.42" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "click" }, { name = "deepmerge" }, + { name = "jinja2" }, { name = "markdown" }, { name = "pygments" }, { name = "pymdown-extensions" }, { name = "pyyaml" }, { name = "tomli" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/52/e9/8d0e66ad113e702d7f5eed2cc5ad0f035cb212c49b0415553473f2da900b/zensical-0.0.36.tar.gz", hash = "sha256:32126c57fd241267e55c863f2bdd31bfe4422c376280e74e4a1036a89c0d513c", size = 3897092, upload-time = "2026-04-23T15:37:46.892Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7a/dd/04e89ab92aed1ef9e36c76ef095fb587ffcbe4162aa7f3fe6d63aafade4a/zensical-0.0.42.tar.gz", hash = "sha256:cc346b833868a59412fe8d8498a152be90be9f3d8fb87e1f1a1c2e1146cbae1b", size = 3931093, upload-time = "2026-05-15T10:22:45.354Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f2/ff/2846737502a9ae783570b32aac4f20f5232512fbf245bbf1c0398728c7ed/zensical-0.0.36-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:3d42312267c4124ed67ddfd2809167bdd3ea4f71892c8a20897be98b66da8b73", size = 12515534, upload-time = "2026-04-23T15:37:07.815Z" }, - { url = "https://files.pythonhosted.org/packages/84/e9/443b561793ed6626cb46c328fd8fd916a7b18e5af5349934c5346438548c/zensical-0.0.36-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:8462c133c8da5234cd301ad3c722d52d66a0092a51b7b93e2ce12f217976b29b", size = 12384874, upload-time = "2026-04-23T15:37:11.617Z" }, - { url = "https://files.pythonhosted.org/packages/7a/f0/faecf0a5dff381ff331b7b87d385c8335ca0b7297a33d85abc3313cfa598/zensical-0.0.36-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0a6dc86dc0d8488b18c6501d62b63989a538350a33173347da8b9f1f54bed2c", size = 12764889, upload-time = "2026-04-23T15:37:14.512Z" }, - { url = "https://files.pythonhosted.org/packages/b0/56/1ddee63d323d779733e5bf00e99c878f03e50b77f294711a850c1e1ceddb/zensical-0.0.36-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d31c726d7f13601a568a2a9e80592472da24657ff5428ef15c2c95bc458cb65b", size = 12705679, upload-time = "2026-04-23T15:37:18.038Z" }, - { url = "https://files.pythonhosted.org/packages/9b/61/4b264b1466251450856ed4768fa9a793f7c24172039f47f562cd899e0744/zensical-0.0.36-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a7e8b32e41784d19122cb16a0bd6fcb53852177ce689ceba1ba7a8bb20fe3a0", size = 13057470, upload-time = "2026-04-23T15:37:21.594Z" }, - { url = "https://files.pythonhosted.org/packages/17/9b/c44a1ebc2fe8daadecbd9ea41c498e545c494204e239314347fbcec51159/zensical-0.0.36-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abe5d24716107edb033c2326c816b891952b98b9637c5308f5320712a2e70aac", size = 12792788, upload-time = "2026-04-23T15:37:24.784Z" }, - { url = "https://files.pythonhosted.org/packages/97/94/4d0e345f75f892fce029b513a26f4491b6dd39ff73c5bee3f8fbb9305e8c/zensical-0.0.36-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9ed7a54465b497d1548aeb6b38a99ac6f45c8f191a5cf2a180902af28c0cd58a", size = 12940940, upload-time = "2026-04-23T15:37:27.975Z" }, - { url = "https://files.pythonhosted.org/packages/de/2e/4612b97d8d493a6ac591ebb28a6b3a592eb4d969bbb8a92311125fe0b874/zensical-0.0.36-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:282eb4eaf7cd3bd389a4b826c1c13a30136e5c6fcfcafce26fc27cd05acc660f", size = 12980355, upload-time = "2026-04-23T15:37:30.998Z" }, - { url = "https://files.pythonhosted.org/packages/c1/90/c1a91b503aec105cdb7ccf4d466e8612c113186f090c61d795272cecce27/zensical-0.0.36-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:36d5719df268697dbcf7aa5bbea9eea353501c80b1c6c17d6c7f2c69405be9af", size = 13124220, upload-time = "2026-04-23T15:37:34.506Z" }, - { url = "https://files.pythonhosted.org/packages/ac/e0/b9ffadaff0b80498699aaf0f2bcc0b659db074fd94071520d22f035e5125/zensical-0.0.36-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7771aaf33f7d06f779e041930812fe65f5f97a6f4fbd1c7e51924ce1a27c0c66", size = 13070894, upload-time = "2026-04-23T15:37:38.092Z" }, - { url = "https://files.pythonhosted.org/packages/c5/c3/aea29875f7b89d7c79b84a30259356404bf778d42c27c36632ef19aa826c/zensical-0.0.36-cp310-abi3-win32.whl", hash = "sha256:61f1dff7c38a8d0acb054c11426c25f0a57b973703eb3d0bf1e8cc04ca54d047", size = 12084318, upload-time = "2026-04-23T15:37:41.093Z" }, - { url = "https://files.pythonhosted.org/packages/6d/fd/6d7b2088180624e3c6dd9471788ac277b9ae3091a4da1b23a191c8ed6419/zensical-0.0.36-cp310-abi3-win_amd64.whl", hash = "sha256:be08cdf13599cfa92d71563ec12058ab20f234ed5489293b83b0f29563cc588a", size = 12301398, upload-time = "2026-04-23T15:37:44.07Z" }, + { url = "https://files.pythonhosted.org/packages/fb/19/2ca4e52769307959f7485d4c5da7b24787339787c1cbc371885cef448e50/zensical-0.0.42-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:bffd7a34b570fa3ccadf1d23babb0f7c4851c6b626e4fc8ed9f21c2eaae85968", size = 12705326, upload-time = "2026-05-15T10:22:07.905Z" }, + { url = "https://files.pythonhosted.org/packages/2c/82/0832b0d2c0c2800174141d5519a017105d3dace9194e2c29730e7a676adf/zensical-0.0.42-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:ee1a79789f9462ef44a4b6ebbfc8b5bf4b2447607da8bc5b35bc9c4ce4ea2370", size = 12568663, upload-time = "2026-05-15T10:22:11.072Z" }, + { url = "https://files.pythonhosted.org/packages/ac/87/272b3998322958ca38f09323d2347cb121dfc851477c36962b71319242a5/zensical-0.0.42-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e9a5d508ce8d1b07d8417f0623be476f6b37d445ab4356481a71e613a7979d6", size = 12948460, upload-time = "2026-05-15T10:22:13.792Z" }, + { url = "https://files.pythonhosted.org/packages/ae/1b/e5f153401f162f48cae2d58e96b95fd39ba5bd1728fb5881a60e502f4e1d/zensical-0.0.42-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:9fbc0951a676e48afe7df3a9b2a30958dcf9c426ed2480972d3c04d6de485ba3", size = 12913460, upload-time = "2026-05-15T10:22:16.791Z" }, + { url = "https://files.pythonhosted.org/packages/9b/4f/5186b4204bdfdf132851b7515a37b9602bfc153fb601db5fb244339bae52/zensical-0.0.42-cp310-abi3-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f0e96e53f39b9e4b929a25d9df70bd7fa8217166a854e2c8f3185983dd01500", size = 13276704, upload-time = "2026-05-15T10:22:19.819Z" }, + { url = "https://files.pythonhosted.org/packages/f2/df/b57b5fcc631ac7a4b4c6834d8cf0b88d3fca37c9db42fc6bbf9f097200ed/zensical-0.0.42-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b7d586e57436d603e88acd856864f99f0771aef24bf6560b2de238417bd3817c", size = 12987069, upload-time = "2026-05-15T10:22:22.537Z" }, + { url = "https://files.pythonhosted.org/packages/a3/3a/b326a44a065d98e89b472645ad33037201e3385340c2e6e35627b18ab3fa/zensical-0.0.42-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3c026f023330d67f986a94b68ffd36dc5066882e697e1125c37308d8d684135c", size = 13124195, upload-time = "2026-05-15T10:22:25.543Z" }, + { url = "https://files.pythonhosted.org/packages/1b/1e/823740a662e357a8826dc8eeb87e06705e64219b2774430bc555f7c53d57/zensical-0.0.42-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:e5908bc09cf5c1c50c9504241e37f89955daf3e89ba1b9d71c17972578b24804", size = 13182981, upload-time = "2026-05-15T10:22:28.89Z" }, + { url = "https://files.pythonhosted.org/packages/80/6d/9fe261267ac36a7d57051d790022408e9043bc925c9ad21971a1e5b6c3e8/zensical-0.0.42-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:c0bf96b55f0a44e8716bcb334a16380ed56772b555145da775a7d8ac8678cb6f", size = 13332666, upload-time = "2026-05-15T10:22:32.249Z" }, + { url = "https://files.pythonhosted.org/packages/9b/57/9b0e4f131a7ad15cf1aca081748ea7336c084fb8e16be202a6bed32f595c/zensical-0.0.42-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:47cd99583738a8ab03fac4080741275c56e741a06dc8edfb541f4c1649a5ae69", size = 13270817, upload-time = "2026-05-15T10:22:35.388Z" }, + { url = "https://files.pythonhosted.org/packages/bb/fd/bdb85cc444e4146e8970a22e48a903bfed5bf83276ad7d755caa415dda64/zensical-0.0.42-cp310-abi3-win32.whl", hash = "sha256:83090e53fba061967ecb3dff81500b1900f288bae108bf54084a2aeb6648ebd0", size = 12256227, upload-time = "2026-05-15T10:22:38.869Z" }, + { url = "https://files.pythonhosted.org/packages/e0/b9/09d1f735c8e6d3eb61d176ed5ebcf658b65b126d7d4bbc03a7d366a1e17d/zensical-0.0.42-cp310-abi3-win_amd64.whl", hash = "sha256:2e4304e103f9cd5c637045bbae1ff29de3009ab01b16e99c2fd6d4bbceb7a3ee", size = 12486598, upload-time = "2026-05-15T10:22:42.158Z" }, ] diff --git a/mie-opensource-landing/zensical.toml b/mie-opensource-landing/zensical.toml index f8a01adc..2b98d857 100644 --- a/mie-opensource-landing/zensical.toml +++ b/mie-opensource-landing/zensical.toml @@ -94,6 +94,7 @@ accent = "green" icon = "material/brightness-4" name = "Switch to light mode" +[project.markdown_extensions.zensical.extensions.macros] [project.markdown_extensions.admonition] [project.markdown_extensions.pymdownx.details] [project.markdown_extensions.pymdownx.superfences] From 256bcf912f45d215df2c258abc41f93615acf82c Mon Sep 17 00:00:00 2001 From: Robert Gingras Date: Sat, 16 May 2026 00:26:06 -0400 Subject: [PATCH 05/16] feat: rebuild docs on any change using zensical serve --- compose.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/compose.yml b/compose.yml index c447c2a2..36269720 100644 --- a/compose.yml +++ b/compose.yml @@ -35,17 +35,21 @@ services: working_dir: /opt/opensource-server/create-a-container command: npm ci --no-audit --no-fund - # Similar to the npm-install service, this service builds the documentation in - # mie-opensource-landing to ensure it's populated in the dev environment. This - # is also usually handled by the container build, but again, we're overriding - # it with the bind mount of the working directory. - build-docs: + # This service will watch the documentation for any changes and rebuild when + # needed. Technically it's listening on port 8000, but we only care about the + # HTML being placed in ./mie-opensource-landing/site which is shared with the + # Manager LXC running in the Proxmox. The Proxmox service doesn't have a + # direct dependency on this because it's meant to be a development convenience + # not a hard dependency. The Proxmox service works just fine if the docs are + # not getting rebuilt or even if they were never built in the first place. + zensical: image: astral/uv:python3.12-bookworm-slim volumes: - ./:/opt/opensource-server working_dir: /opt/opensource-server/mie-opensource-landing - command: uv run zensical build + command: uv run --active zensical serve environment: + VIRTUAL_ENV: /opt/zensical PROXMOX_URL: https://localhost:8006 MANAGER_URL: https://manager.localhost @@ -83,8 +87,6 @@ services: condition: service_completed_successfully node: condition: service_completed_successfully - build-docs: - condition: service_completed_successfully # After the Proxmox (which includes the manager LXC) is healthy we have to # bootstrap the Manager setup. The commands below were derived from doing a From 3e853baf3de4472bc5af42d48f7f684efd702525 Mon Sep 17 00:00:00 2001 From: Robert Gingras Date: Sat, 16 May 2026 10:19:37 -0400 Subject: [PATCH 06/16] fix: ensure job-runner uses the same environment file as container-creator --- create-a-container/systemd/job-runner.service | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/create-a-container/systemd/job-runner.service b/create-a-container/systemd/job-runner.service index d28e6443..7a3984de 100644 --- a/create-a-container/systemd/job-runner.service +++ b/create-a-container/systemd/job-runner.service @@ -1,6 +1,7 @@ [Unit] Description=Job Runner for create-a-container -After=network.target +After=network.target container-creator-init.service +Wants=container-creator-init.service [Service] Type=simple @@ -9,6 +10,7 @@ WorkingDirectory=/opt/opensource-server/create-a-container ExecStart=/usr/bin/node /opt/opensource-server/create-a-container/job-runner.js Restart=on-failure Environment=NODE_ENV=production +EnvironmentFile=/etc/default/container-creator [Install] WantedBy=multi-user.target From 864a841f94fbfd7f58b8e5553b56ecc632257a3f Mon Sep 17 00:00:00 2001 From: Robert Gingras Date: Sat, 16 May 2026 10:20:07 -0400 Subject: [PATCH 07/16] feat: remove obselete compose.yml from create-a-containers --- create-a-container/compose.yml | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 create-a-container/compose.yml diff --git a/create-a-container/compose.yml b/create-a-container/compose.yml deleted file mode 100644 index 8308c6c5..00000000 --- a/create-a-container/compose.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -services: - postgres: - image: postgres:18 - volumes: - - postgres-data:/var/lib/postgresql - environment: - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Missing password} - POSTGRES_USER: ${POSTGRES_USER:?Missing user} - POSTGRES_DB: ${POSTGRES_DATABASE:?Missing database} - ports: - - 5432:5432 - - ldap: - image: ghcr.io/mieweb/ldap-gateway:latest - env_file: compose.ldap.env - environment: - SQL_URI: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DATABASE} - -volumes: - postgres-data: From 13183ed82fe5303e39b7c575ebe27cb3beb4206a Mon Sep 17 00:00:00 2001 From: Robert Gingras Date: Sat, 16 May 2026 10:51:42 -0400 Subject: [PATCH 08/16] fix: use the correct cidr on the Manager IP --- images/proxmox-ve/create-manager.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/images/proxmox-ve/create-manager.service b/images/proxmox-ve/create-manager.service index 51ed3b83..2c600222 100644 --- a/images/proxmox-ve/create-manager.service +++ b/images/proxmox-ve/create-manager.service @@ -14,7 +14,7 @@ ExecStart=/bin/bash -ec '\ if [ -f /etc/pve/lxc/$$CTID.conf ]; then exit 0; fi; \ if [ ! -d /sys/class/net/$$BRIDGE ]; then exit 1; fi; \ if [ ! -f /var/lib/vz/template/cache/manager_$${MANAGER_TAG}.tar ]; then exit 1; fi; \ - pct create 100 local:vztmpl/manager_$${MANAGER_TAG}.tar --cores=4 --features=nesting=1 --hostname=manager --memory=8192 --net0=name=eth0,bridge=$$BRIDGE,gw=10.254.0.1,ip=10.254.0.2/24 --onboot=1 --ostype=debian --rootfs=local:50 --entrypoint="/sbin/init systemd.unit=emergency.target"; \ + pct create 100 local:vztmpl/manager_$${MANAGER_TAG}.tar --cores=4 --features=nesting=1 --hostname=manager --memory=8192 --net0=name=eth0,bridge=$$BRIDGE,gw=10.254.0.1,ip=10.254.0.2/16 --onboot=1 --ostype=debian --rootfs=local:50 --entrypoint="/sbin/init systemd.unit=emergency.target"; \ pct start 100; \ pct exec 100 -- openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -keyout /etc/ssl/private/localhost.key -out /etc/ssl/certs/localhost.crt -days 3650 -noenc -subj /CN=localhost; \ pct exec 100 -- mkdir -p /etc/systemd/system/container-creator.service.d; \ From 8d8a7d1766e7b877c461c2ff450f0de5313e3f2b Mon Sep 17 00:00:00 2001 From: Robert Gingras Date: Sat, 16 May 2026 14:29:04 -0400 Subject: [PATCH 09/16] chore: bump uv version --- compose.yml | 2 +- images/agent/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/compose.yml b/compose.yml index 36269720..77e38b9a 100644 --- a/compose.yml +++ b/compose.yml @@ -43,7 +43,7 @@ services: # not a hard dependency. The Proxmox service works just fine if the docs are # not getting rebuilt or even if they were never built in the first place. zensical: - image: astral/uv:python3.12-bookworm-slim + image: astral/uv:0.11.14-trixie-slim volumes: - ./:/opt/opensource-server working_dir: /opt/opensource-server/mie-opensource-landing diff --git a/images/agent/Dockerfile b/images/agent/Dockerfile index 1982b49a..df3cf944 100644 --- a/images/agent/Dockerfile +++ b/images/agent/Dockerfile @@ -42,7 +42,7 @@ RUN curl -fsSL https://get.acme.sh | sh \ && /root/.acme.sh/acme.sh --upgrade --auto-upgrade # Install uv for building the docs -ARG UV_VERSION=0.11.6 +ARG UV_VERSION=0.11.14 RUN curl -fsSL "https://github.com/astral-sh/uv/releases/download/${UV_VERSION}/uv-x86_64-unknown-linux-gnu.tar.gz" \ | tar -xzf - --strip-components=1 -C /usr/local/bin \ uv-x86_64-unknown-linux-gnu/uv uv-x86_64-unknown-linux-gnu/uvx From 644b5326cecfe868e3db0c83b67551093d543472 Mon Sep 17 00:00:00 2001 From: Robert Gingras Date: Sat, 16 May 2026 15:54:00 -0400 Subject: [PATCH 10/16] feat: prefer pulling image from local storage --- compose.yml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/compose.yml b/compose.yml index 77e38b9a..a8b52cbf 100644 --- a/compose.yml +++ b/compose.yml @@ -2,9 +2,6 @@ x-proxmox-service: &proxmox-service image: proxmox-ve:9 build: images/proxmox-ve - volumes: - - local:/var/lib/vz - - ./:/opt/opensource-server environment: MANAGER_TAG: ${MANAGER_TAG:-latest} @@ -12,16 +9,23 @@ services: # This service runs once when bringing the compose stack up to ensure the # latest manager image (overridable by .env) is installed into the Proxmox # template cache. This can run in parallel with other build tasks, slightly - # speeding up the overall time to a complete setup. + # speeding up the overall time to a complete setup. It prefers to pull from + # the local docker image storage, but if the requested tag isn't available + # there it will fallback to pulling from the registry. pull-image: <<: *proxmox-service + volumes: + - local:/var/lib/vz + - /var/run/docker.sock:/var/run/docker.sock entrypoint: ["/bin/bash", "-c"] command: - | MANAGER_TAG=$${MANAGER_TAG:-latest} FILENAME=/var/lib/vz/template/cache/manager_$${MANAGER_TAG}.tar + IMAGE_REF=ghcr.io/mieweb/opensource-server/manager:$${MANAGER_TAG} if [ ! -f "$$FILENAME" ]; then - skopeo copy docker://ghcr.io/mieweb/opensource-server/manager:$${MANAGER_TAG} oci-archive:$${FILENAME} + skopeo copy docker-daemon:$${IMAGE_REF} oci-archive:$${FILENAME} || \ + skopeo copy docker://$${IMAGE_REF} oci-archive:$${FILENAME} fi # This service runs once when bringing the compose stack up to ensure the From 2e94a27501b5c585200ade94a5d3f832addf3a63 Mon Sep 17 00:00:00 2001 From: Robert Gingras Date: Sun, 17 May 2026 15:22:56 -0400 Subject: [PATCH 11/16] docs: update admin docs to match with recent changes --- .../admins/core-concepts/external-domains.md | 25 +++-- .../docs/admins/core-concepts/nodes.md | 2 +- .../docs/admins/core-concepts/sites.md | 15 +-- .../docs/admins/installation.md | 106 +++++++++++++++--- 4 files changed, 112 insertions(+), 36 deletions(-) diff --git a/mie-opensource-landing/docs/admins/core-concepts/external-domains.md b/mie-opensource-landing/docs/admins/core-concepts/external-domains.md index 527b696b..256d753a 100644 --- a/mie-opensource-landing/docs/admins/core-concepts/external-domains.md +++ b/mie-opensource-landing/docs/admins/core-concepts/external-domains.md @@ -6,17 +6,18 @@ External domains expose container HTTP services to the internet. Domains are glo ## Prerequisites - A registered domain with Cloudflare DNS (only supported provider) -- Cloudflare API token with **Zone:DNS:Edit** permissions +- Cloudflare account email and a User API Token for DNS management ## Domain Properties | Field | Description | |-------|-------------| -| **Domain** | Top-level domain (e.g., `example.com`) | -| **Default Site** | Optional — the site whose DNS is assumed pre-configured (e.g., wildcard A record) | -| **ACME Email** | Certificate expiration notifications | -| **ACME Directory** | CA endpoint (Let's Encrypt Production/Staging) | -| **Cloudflare API Token** | For DNS-01 challenge authentication and cross-site DNS record management | +| **Domain Name** | Top-level domain (e.g., `example.com`) | +| **Default Site** | The site whose DNS is assumed pre-configured (e.g., wildcard A record). Selected explicitly when creating the domain. | +| **ACME Email** | Certificate expiration notifications (currently unused) | +| **ACME Directory** | CA endpoint, Let's Encrypt Production/Staging (currently unused) | +| **Cloudflare API Email** | Cloudflare account email, sent as the `X-Auth-Email` header — optional unless using Cross-Site DNS | +| **Cloudflare API Key** | Cloudflare **User API Token**, sent as `Authorization: Bearer `. Despite the field name, this is *not* the legacy Global API Key. Optional unless using Cross-Site DNS. | | **Auth Server URL** | Optional — URL of an authentication server for NGINX `auth_request`. See [Authentication](#authentication) | !!! tip @@ -25,12 +26,12 @@ External domains expose container HTTP services to the internet. Domains are glo ## Setup 1. Add your domain to Cloudflare and update nameservers -2. Create a Cloudflare API token with **Zone:DNS:Edit** permissions -3. In the admin interface, navigate to **External Domains** → **Create New External Domain** -4. Enter domain, ACME email, ACME directory, and Cloudflare API token -5. Save — the system validates Cloudflare API access automatically +2. (Optional, for Cross-Site DNS) Create a Cloudflare **User API Token** with `Zone:DNS:Edit` permission for the zone +3. In the admin interface, navigate to **External Domains** → **New External Domain** +4. Enter domain name, default site, ACME email, ACME directory, and Cloudflare email + API token (if used) +5. Select **Create External Domain** -The creating site is set as the domain's **default site**. Wildcard DNS (`*.example.com`) is assumed to point to the default site's IP. +Wildcard DNS (`*.example.com`) is assumed to point to the default site's IP. ## SSL Certificate Provisioning @@ -104,7 +105,7 @@ When creating a container service, users select an external domain and specify a ## Security -- Store Cloudflare API tokens with minimal permissions (Zone:DNS:Edit only) +- Issue Cloudflare User API Tokens with minimum scope (`Zone:DNS:Edit` for the target zone only) - Rotate tokens periodically; revoke immediately if compromised - Private keys never leave the cluster diff --git a/mie-opensource-landing/docs/admins/core-concepts/nodes.md b/mie-opensource-landing/docs/admins/core-concepts/nodes.md index 3e6ac691..fe08fa3a 100644 --- a/mie-opensource-landing/docs/admins/core-concepts/nodes.md +++ b/mie-opensource-landing/docs/admins/core-concepts/nodes.md @@ -7,7 +7,7 @@ Nodes are Proxmox VE servers within a site that host containers. - **Name**: Must exactly match the Proxmox hostname - **IP Address**: For internal DNS resolution -- **API URL**: e.g., `https://192.168.1.10:8006/api2/json` +- **API URL**: e.g., `https://192.168.1.10:8006` - **Authentication**: Username/password or API token - **TLS Verification**: Enable/disable certificate validation - **Template Storage**: Proxmox storage for CT template images (`vztmpl` content) diff --git a/mie-opensource-landing/docs/admins/core-concepts/sites.md b/mie-opensource-landing/docs/admins/core-concepts/sites.md index 3e29b6e3..039bdf6b 100644 --- a/mie-opensource-landing/docs/admins/core-concepts/sites.md +++ b/mie-opensource-landing/docs/admins/core-concepts/sites.md @@ -7,19 +7,20 @@ A site groups Proxmox nodes, defines network configuration, and manages containe | Field | Description | Example | |-------|-------------|---------| -| **Display Name** | Human-readable name | `Production Cluster` | +| **Site Name** | Human-readable name | `Production Cluster` | | **Internal Domain** | DNS domain for internal network | `cluster.example.internal` | -| **DHCP Range** | IP range for containers | `192.168.100.100-192.168.100.200` | +| **DHCP Range** | IP range for containers | `192.168.100.100,192.168.100.200` | | **Subnet Mask** | Network mask | `255.255.255.0` | -| **Gateway IP** | Default gateway | `192.168.100.1` | +| **Gateway** | Default gateway | `192.168.100.1` | | **DNS Forwarders** | Upstream DNS (comma-separated) | `8.8.8.8,1.1.1.1` | +| **External IP** | Public IP used for cross-site DNS records pointing to services on this site | `203.0.113.10` | ## Creating a Site -1. Navigate to **Sites** → **Create New Site** -2. Enter display name and internal domain -3. Configure DHCP range, subnet mask, gateway, and DNS forwarders -4. Save +1. Navigate to **Sites** → **New Site** +2. Enter site name and internal domain +3. Configure DHCP range, subnet mask, gateway, DNS forwarders, and external IP +4. Select **Create Site** !!! warning "Important" Ensure the DHCP range doesn't conflict with static IPs (Proxmox nodes, management container, infrastructure devices). diff --git a/mie-opensource-landing/docs/admins/installation.md b/mie-opensource-landing/docs/admins/installation.md index adc0456c..39f802f9 100644 --- a/mie-opensource-landing/docs/admins/installation.md +++ b/mie-opensource-landing/docs/admins/installation.md @@ -1,51 +1,125 @@ - # Installation Guide ## Prerequisites -- **Proxmox VE 13+** (required for OCI image support) +- **Proxmox VE 9+** (required for OCI image support) - **Isolated network** with no existing DHCP infrastructure - SSH and web UI access (port 8006) to the Proxmox host +## Assumptions + +The following instructions make several assumptions for clarity. You may have to adapt them for your deployment. + +1. You are using the Proxmox default `local` storage at `/var/lib/vz` for all operations. +2. You are using the Proxmox default `vmbr0` network on the `10.0.0.0/16` subnet. The gateway is at `10.0.0.1` and the Proxmox host is at `10.0.0.2`. +3. The Manager container uses container ID `100`. +4. The domains `example.org` and `*.example.org` have public DNS entries pointing to the firewall in front of this Proxmox cluster. +5. At least one Proxmox node is accessible at `https://example.org:8006` with a valid HTTPS certificate. + ## Installation Steps ### 1. Pull the OCI Image -```bash -apt update && apt install -y skopeo +Using the Proxmox Web UI or CLI: -skopeo copy docker://ghcr.io/mieweb/opensource-server/manager:latest \ - oci-archive:/var/lib/vz/template/cache/manager_latest.tar +```bash +skopeo copy docker://ghcr.io/mieweb/opensource-server/manager:latest oci-archive:/var/lib/vz/template/cache/manager_latest.tar ``` ### 2. Create the Management Container -In the Proxmox web interface (`https://your-proxmox-host:8006`): +In the Proxmox Web UI or CLI: -1. **Create CT** on your node -2. **Template**: Select `manager_latest.tar` -3. **Network**: Configure with a **static IP** in the same subnet as your Proxmox server(s) -4. **Resources**: Allocate CPU, memory, and storage as needed +```bash +pct create 100 local:vztmpl/manager_latest.tar --cores=4 --features=nesting=1 --hostname=manager --memory=8192 --net0=name=eth0,bridge=vmbr0,gw=10.0.0.1,ip=10.0.0.3/16 --onboot=1 --ostype=debian --rootfs=local:50 +``` !!! warning "Static IP Required" The management container must have a static IP. It runs a DNSMasq instance to manage DHCP within the network. ### 3. Configure Network Access -Forward at minimum **443/tcp** (HTTPS) to the management container. Optionally forward **22/tcp** (SSH) and **80/tcp** (HTTP redirect). +At your firewall, create the following port-forwards: + +| Port | Protocol | Destination | Purpose | +|---|---|---|---| +| 8006 | tcp | Proxmox Server | Web UI access to the Proxmox server. | +| 22 | tcp | Proxmox Server | SSH access to the Proxmox server. | +| 80 | tcp | Manager Container | HTTP -> HTTPS redirect served by the Manager. | +| 443 | tcp | Manager Container | HTTPS load balancer service provided by the Manager. | +| 443 | udp | Manager Container | HTTP/3.0 QUIC load balancer service provided by the Manager. | +| 2000-2999 | tcp and udp | Manager Container | Layer-4 load balancing service provided by the Manager. | ### 4. Start the Container +In the Proxmox Web UI or CLI: + ```bash -pct start +pct start 100 ``` ### 5. Initial Account Setup -Navigate to `https://your-external-address:443` and register an account. +1. Navigate to `https://example.org:443`. +2. Your web browser will warn about a self-signed certificate. Accept and bypass the warning. If your domain is in HSTS preload lists, you may need to use the IP address rather than the hostname until certificates are configured in a later step. +3. Select the "Register" link and create your account. !!! warning "First Account" The **first account registered** is automatically approved with full admin privileges. Register the intended administrator account first. -Once logged in, proceed to [Core Concepts](core-concepts/index.md) to configure your first site. - +### 6. Configure the First Site + +Further reading: [Sites](core-concepts/sites.md). + +1. Select "New Site". +2. Fill out the information: + 1. **Site Name**: `First Site` + 2. **Internal Domain**: `internal.example.org` + 3. **DHCP Range**: `10.0.1.1,10.0.254.254` + 4. **Subnet Mask**: `255.255.0.0` + 5. **Gateway**: `10.0.0.1` + 6. **DNS Forwarders**: `8.8.8.8,1.1.1.1` + 7. **External IP**: `your.ext.ernal.ip` +3. Select "Create Site". + +### 7. Import Proxmox Nodes + +Further reading: [Nodes](core-concepts/nodes.md). + +1. Select "Import Nodes". +2. Fill in the information: + 1. **API URL**: `https://example.org:8006` + 2. **Username**: `root@pam` + 3. **Password**: your Proxmox root password + 4. **TLS Verification**: Enable +3. Select "Import". + +### 8. Configure an External Domain + +Further reading: [External Domains](core-concepts/external-domains.md). + +1. Select "New External Domain". +2. Fill in the information: + 1. **Domain Name**: `example.org` + 2. **Default Site**: `First Site` + 3. **ACME Email** and **ACME Directory** are currently unused. + 4. **Cloudflare API Email** and **Key** are optional unless you are planning to use Cross-Site DNS. + 5. **Auth Server URL**: `https://manager.example.org` (see [Authentication](core-concepts/external-domains.md#authentication)). +3. Select "Create External Domain". +4. Refer to [SSL Certificate Provisioning](core-concepts/external-domains.md#ssl-certificate-provisioning) to configure an HTTPS certificate. + +### 9. Finalize the Manager Domain + +1. On the Manager container, select "Edit". +2. Expand the "Services" menu. +3. Select "Add Service". +4. Fill in the information: + 1. **Type**: HTTP + 2. **Internal Port**: `3000` + 3. **External Hostname**: `manager` + 4. **External Domain**: `example.org` + 5. **Require Auth**: false +5. Select "Update Container". + +!!! warning + After this configuration propagates to the load balancer, you'll no longer be able to access the manager on any other hostname. The bare domain `example.org` will show the documentation and the manager will only be accessible on `manager.example.org`. From 204b85db6be320d2eca6a706b99aa2fd62c9ff63 Mon Sep 17 00:00:00 2001 From: Robert Gingras Date: Sun, 17 May 2026 15:23:16 -0400 Subject: [PATCH 12/16] docs: update developer docs to explain the new compose setup --- .../docs/developers/contributing.md | 6 +- .../docs/developers/core-technologies.md | 2 +- .../docs/developers/development-workflow.md | 106 +++++++++++------- 3 files changed, 66 insertions(+), 48 deletions(-) diff --git a/mie-opensource-landing/docs/developers/contributing.md b/mie-opensource-landing/docs/developers/contributing.md index 9abcbba7..c8b754f6 100644 --- a/mie-opensource-landing/docs/developers/contributing.md +++ b/mie-opensource-landing/docs/developers/contributing.md @@ -10,7 +10,7 @@ ## Pull Requests -Before submitting: `npm test`, `npm run lint`, update docs if needed. +Before submitting: verify the stack runs (`docker compose up -d`), manually test affected flows, and update docs if needed. Include in your PR: summary of changes, related issues, how you tested, screenshots for UI changes, and any breaking changes. @@ -31,9 +31,7 @@ fix(auth): resolve LDAP authentication timeout issue ## Testing -- Write unit tests for new functions -- Test API endpoints end-to-end -- Manually verify container creation/deletion, DNS resolution, NGINX routing, and LDAP auth +There is currently no automated test suite. Manually verify your changes against a running stack (`docker compose up -d`) — container creation/deletion, DNS resolution, NGINX routing, and LDAP auth as relevant. ## License diff --git a/mie-opensource-landing/docs/developers/core-technologies.md b/mie-opensource-landing/docs/developers/core-technologies.md index 6194a3d5..738c3433 100644 --- a/mie-opensource-landing/docs/developers/core-technologies.md +++ b/mie-opensource-landing/docs/developers/core-technologies.md @@ -12,7 +12,7 @@ External documentation for core components. | **LDAP Gateway** | [GitHub](https://github.com/mieweb/LDAPServer) · [SSSD Docs](https://sssd.io/docs/) | | **DNSMasq** | [Docs](http://www.thekelleys.org.uk/dnsmasq/doc.html) · [Man Page](https://linux.die.net/man/8/dnsmasq) | | **NGINX** | [Docs](https://nginx.org/en/docs/) · [Reverse Proxy](https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/) | -| **Node.js** | [Docs](https://nodejs.org/docs/) — 18+ LTS recommended | +| **Node.js** | [Docs](https://nodejs.org/docs/) — 24.x LTS | | **Docker** | [Docs](https://docs.docker.com/) · [Compose](https://docs.docker.com/compose/) | ## Version Requirements diff --git a/mie-opensource-landing/docs/developers/development-workflow.md b/mie-opensource-landing/docs/developers/development-workflow.md index ba3e6165..c3f15dc8 100644 --- a/mie-opensource-landing/docs/developers/development-workflow.md +++ b/mie-opensource-landing/docs/developers/development-workflow.md @@ -1,73 +1,93 @@ - # Development Workflow +The entire stack — Proxmox, Manager, docs, and bootstrap — runs locally inside Docker. + ## Prerequisites -- **Node.js 18+**, **Git**, and **Docker** for PostgreSQL -- **Proxmox VE 13+** instance for testing +- [Docker Desktop](https://www.docker.com/products/docker-desktop/) -## Local Setup +## Start the Stack -### With Docker Compose (Recommended) +From the repository root: ```bash -git clone https://github.com/mieweb/opensource-server -cd opensource-server/create-a-container -cp example.env .env # Edit with your Proxmox/DB settings -docker compose up -d # Start PostgreSQL -npm install -npm run db:migrate -npm run dev +docker compose up -d ``` -### Without Docker +This brings up: + +| Service | Purpose | +|---|---| +| `npm ci` job | Installs all Node.js dependencies in the mounted workspace | +| `proxmox` | Virtualized Proxmox VE host | +| Manager container | The Manager application, running as a CT (`100`) inside the virtualized Proxmox | +| `zensical` | Rebuilds these docs on file changes | +| Bootstrap (one-shot) | Configures the Manager container to use the virtualized Proxmox | + +## Manager Image Selection -Requires a running PostgreSQL instance. +By default the compose stack deploys `ghcr.io/mieweb/opensource-server/manager:latest`. Override the tag by setting `MANAGER_TAG` in your environment or in a `.env` file alongside `compose.yml`: ```bash -git clone https://github.com/mieweb/opensource-server -cd opensource-server/create-a-container -cp example.env .env # Edit with your PostgreSQL connection settings -npm install -npm run db:migrate -npm run dev +MANAGER_TAG=my-feature-branch docker compose up -d ``` -## Key Directories +The template download step checks your **local** Docker images first, so a locally-built `manager:` image is used without pulling from GHCR — handy for testing in-progress changes to the Manager image itself. -``` -create-a-container/ -├── models/ # Sequelize database models -├── routers/ # Express API route handlers -├── middlewares/ # Authentication & authorization -├── migrations/ # Database schema migrations -├── views/ # Server-rendered EJS templates -└── public/ # Static assets +!!! note "Reusing a tag" + The downloaded template tarball is persisted in the named `local` volume so subsequent `docker compose up` cycles start quickly. To pick up a new build of the same tag, either: + + - Delete the cached tar file from the `local` volume, or + - Recreate the volume (e.g., `docker compose down -v`). + +## Endpoints + +| URL | Description | +|---|---| +| `https://localhost:8006` | Proxmox Web UI | +| `http://localhost` | Redirects to `https://localhost` | +| `https://localhost` | Documentation site | +| `https://manager.localhost` | Manager Web UI | + +## Credentials and Shell Access + +**Proxmox Web UI:** `root` / `root` + +**Proxmox CLI:** Use the **Shell** option in the Web UI, or: + +```bash +docker compose exec -it proxmox bash ``` -## Database Changes +**Manager container shell:** -Always create migrations for schema changes. Test both up and down. Use transactions for multi-step migrations. +```bash +docker compose exec -it proxmox pct enter 100 +``` -## Local Docker Image Build +**Manager Postgres:** ```bash -docker build -t opensource-server:dev . -docker run -d --privileged --name opensource-test \ - -p 80:80 -p 443:443 -p 53:53/udp opensource-server:dev +docker compose exec -it proxmox pct exec 100 -- sudo -u postgres psql cluster_manager ``` -Copies your local code (including uncommitted changes) — ideal for testing before pushing. +## Live Reload -## CI/CD +The local git repository is mounted **read-only** into `/opt/opensource-server` on the Manager container, so source changes on the host are visible immediately. -GitHub Actions builds and pushes Docker images on every push. Tags: branch name, `latest` (main only). Workflow: `.github/workflows/docker-build-push.yml`. +| Component | Reload behavior | +|---|---| +| Manager UI | Auto-reloaded by `nodemon` | +| Documentation | Auto-rebuilt by the `zensical` service | +| Job runner | Restart manually | +| Database migrations | Run manually in the proper server context (see below) | -## Debugging +### Run Database Migrations ```bash -DEBUG=* npm run dev # Verbose output -node --inspect index.js # Node inspector -DB_LOGGING=true npm run dev # Sequelize query logging +docker compose exec proxmox pct exec 100 -- \ + systemd-run \ + --working-directory=/opt/opensource-server/create-a-container \ + -p EnvironmentFile=/etc/default/container-creator \ + -P npx sequelize-cli db:migrate ``` - From 22b86c7d61368274dd953c7dbf829b2573ee68b5 Mon Sep 17 00:00:00 2001 From: Robert Gingras Date: Sun, 17 May 2026 15:27:05 -0400 Subject: [PATCH 13/16] docs: update README --- README.md | 132 +++++++++++------------------------------------------- 1 file changed, 25 insertions(+), 107 deletions(-) diff --git a/README.md b/README.md index 455b400c..d5d246b1 100644 --- a/README.md +++ b/README.md @@ -1,117 +1,35 @@ # opensource-server -Infrastructure management platform for automated LXC container hosting with Proxmox VE. +Self-service LXC container hosting on Proxmox VE — web UI, automated DNS/reverse proxy, LDAP authentication, and ACME TLS. -This repository provides a complete self-service container management system with web interface, automated configuration distribution, and integrated DNS/reverse proxy services. +Full documentation lives in [`mie-opensource-landing/docs/`](mie-opensource-landing/docs/) and is published to the project documentation site. -## Project Components +## Get Started -- [`create-a-container/`](create-a-container/README.md) - Web application for container lifecycle management -- [`pull-config/`](pull-config/README.md) - Automated configuration distribution system for nginx and dnsmasq -- [`mie-opensource-landing/`](mie-opensource-landing/README.md) - Landing page and documentation site -- [`manager-control-program/`](manager-control-program/README.md) - MCP server for AI-assisted container management -- [`packer/`](packer/README.md) - LXC container template creation -- [`ci-cd-automation/`](ci-cd-automation/README.md) - Proxmox API automation scripts -- [`LDAP/`](LDAP/README.md) - Centralized authentication infrastructure -- [`Wazuh/`](Wazuh/README.md) - Security monitoring and threat detection +| If you want to... | Read | +|---|---| +| Install and operate a production deployment | [Installation Guide](mie-opensource-landing/docs/admins/installation.md) | +| Run the full stack locally to develop or contribute | [Development Workflow](mie-opensource-landing/docs/developers/development-workflow.md) | +| Use a deployed cluster as an end user (create containers, etc.) | [User Getting Started](mie-opensource-landing/docs/users/getting-started.md) | +| Contribute changes | [Contributing](mie-opensource-landing/docs/developers/contributing.md) | +| Understand the system design | [System Architecture](mie-opensource-landing/docs/developers/system-architecture.md) | -## Installation +## Repository Layout -### Recommended: Proxmox 9+ OCI Container (Preferred) +| Path | Purpose | +|---|---| +| [`create-a-container/`](create-a-container/) | Manager web application (Node.js + Express + Sequelize) | +| [`pull-config/`](pull-config/) | Cron-driven config distribution for nginx and dnsmasq on agents — see [pull-config docs](mie-opensource-landing/docs/developers/pull-config.md) | +| [`images/`](images/) | Docker Bake definitions for the `base`, `nodejs`, `agent`, and `manager` images — see [Docker Images](mie-opensource-landing/docs/developers/docker-images.md) | +| [`manager-control-program/`](manager-control-program/) | MCP server for AI-assisted container management — see [MCP Server](mie-opensource-landing/docs/users/mcp-server.md) | +| [`mie-opensource-landing/`](mie-opensource-landing/) | Documentation site source | +| [`error-pages/`](error-pages/) | Static error pages served by NGINX | +| [`compose.yml`](compose.yml) | Local development stack (used by the Development Workflow guide) | -With Proxmox 9's native OCI container support, the easiest installation method is to deploy directly from GitHub Container Registry: +## Contributors -```bash -# Pull and run the container from GHCR -pct create ghcr.io/mieweb/opensource-server:latest \ - --hostname opensource-server \ - --net0 name=eth0,bridge=vmbr0,ip=dhcp \ - --features nesting=1 \ - --privileged 1 \ - --onboot 1 -``` + + Contributors + -> **Note**: Adjust the VMID, network configuration, and other parameters according to your Proxmox environment. - -### Alternative: Docker Container - -See the [`Dockerfile`](Dockerfile) in the repository root for building and running the container with Docker: - -```bash -docker build -t opensource-server . -docker run -d --privileged \ - -p 443:443 \ - -p 53:53/udp \ - --name opensource-server \ - opensource-server:latest -``` - -### Manual Installation (Legacy) - -For a traditional installation on a Debian-based system, see the [`Dockerfile`](Dockerfile) for the complete installation steps and dependencies. The Dockerfile serves as the canonical reference for system setup and configuration. - -Key steps include: -1. Install nginx (mainline from nginx's repo preferred) -2. Install dnsmasq with proper configuration -3. Clone repository and run `make install` - -For detailed configuration and usage instructions, refer to the individual component READMEs linked above. - -## Architecture Overview - -The system provides automated container hosting through three main components: - -1. **Container Management** (`create-a-container/`) - - Web-based interface for container lifecycle operations - - Proxmox VE API integration for LXC container provisioning - - Site-based organization with hierarchical node/container relationships - - Service port mapping and DNS configuration - -2. **Configuration Distribution** (`pull-config/`) - - Automated pulling of nginx and dnsmasq configurations - - ETag-based change detection for efficient updates - - Validation and automatic rollback on errors - - Multi-instance support via run-parts pattern - -3. **Infrastructure Services** - - nginx reverse proxy with SSL/TLS termination - - dnsmasq for DHCP and DNS services - - LDAP authentication for centralized user management - - Wazuh security monitoring and threat detection - -### Data Flow - -```mermaid -graph TD - User[User] --> WebUI[create-a-container Web UI] - WebUI --> DB[(PostgreSQL)] - WebUI --> PVE[Proxmox VE API] - PVE --> LXC[LXC Container] - - Cron[Cron Job] --> PullConfig[pull-config] - PullConfig --> WebUI - PullConfig --> Nginx[nginx config] - PullConfig --> Dnsmasq[dnsmasq config] - - Client[Client Request] --> Nginx - Nginx --> LXC - - DB --> Sites[Sites] - Sites --> Nodes[Nodes] - Nodes --> Containers[Containers] - Containers --> Services[Services] - - classDef user fill:#f57c00,stroke:#fff3e0,stroke-width:2px,color:#ffffff - classDef app fill:#1976d2,stroke:#e3f2fd,stroke-width:2px,color:#ffffff - classDef infra fill:#689f38,stroke:#f1f8e9,stroke-width:2px,color:#ffffff - classDef data fill:#7b1fa2,stroke:#f3e5f5,stroke-width:2px,color:#ffffff - - class User,Client user - class WebUI,PullConfig app - class PVE,LXC,Nginx,Dnsmasq,Cron infra - class DB,Sites,Nodes,Containers,Services data -``` - ---- - -Contributors: Carter Myers, Maxwell Klema, Anisha Pant, and Robert Gingras \ No newline at end of file +Made with [contrib.rocks](https://contrib.rocks). From f78c98e5e0f106b1d66711caec00d0d33bbcdc3d Mon Sep 17 00:00:00 2001 From: Robert Gingras Date: Sun, 17 May 2026 16:43:44 -0400 Subject: [PATCH 14/16] refactor: pull the create-manager script into it's own file for better readability --- images/proxmox-ve/create-manager.service | 18 +----- images/proxmox-ve/create-manager.sh | 76 ++++++++++++++++++++++++ 2 files changed, 77 insertions(+), 17 deletions(-) create mode 100644 images/proxmox-ve/create-manager.sh diff --git a/images/proxmox-ve/create-manager.service b/images/proxmox-ve/create-manager.service index 2c600222..f3e51188 100644 --- a/images/proxmox-ve/create-manager.service +++ b/images/proxmox-ve/create-manager.service @@ -6,23 +6,7 @@ Wants=network-online.target [Service] Type=oneshot PassEnvironment=MANAGER_TAG -ExecStart=/bin/bash -ec '\ - until [ -d /etc/pve/local ]; do sleep 0.5; done; \ - CTID=100; \ - BRIDGE=vmbr0; \ - MANAGER_TAG=$${MANAGER_TAG:-latest}; \ - if [ -f /etc/pve/lxc/$$CTID.conf ]; then exit 0; fi; \ - if [ ! -d /sys/class/net/$$BRIDGE ]; then exit 1; fi; \ - if [ ! -f /var/lib/vz/template/cache/manager_$${MANAGER_TAG}.tar ]; then exit 1; fi; \ - pct create 100 local:vztmpl/manager_$${MANAGER_TAG}.tar --cores=4 --features=nesting=1 --hostname=manager --memory=8192 --net0=name=eth0,bridge=$$BRIDGE,gw=10.254.0.1,ip=10.254.0.2/16 --onboot=1 --ostype=debian --rootfs=local:50 --entrypoint="/sbin/init systemd.unit=emergency.target"; \ - pct start 100; \ - pct exec 100 -- openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 -keyout /etc/ssl/private/localhost.key -out /etc/ssl/certs/localhost.crt -days 3650 -noenc -subj /CN=localhost; \ - pct exec 100 -- mkdir -p /etc/systemd/system/container-creator.service.d; \ - pct push 100 /opt/opensource-server/images/proxmox-ve/99-container-creator-dev.conf /etc/systemd/system/container-creator.service.d/99-container-creator-dev.conf; \ - pct shutdown 100; \ - pct set 100 --mp0=/opt/opensource-server,mp=/opt/opensource-server --entrypoint=/sbin/init; \ - pct start 100; \ -' +ExecStart=/bin/bash /opt/opensource-server/images/proxmox-ve/create-manager.sh [Install] WantedBy=multi-user.target diff --git a/images/proxmox-ve/create-manager.sh b/images/proxmox-ve/create-manager.sh new file mode 100644 index 00000000..85c33a4f --- /dev/null +++ b/images/proxmox-ve/create-manager.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +set -euo pipefail + +CTID="${CTID:-100}" +BRIDGE="${BRIDGE:-vmbr0}" +MANAGER_TAG="${MANAGER_TAG:-latest}" + +# Wait for pve-cluster.service to mount the Proxmox cluster filesystem +until [ -d /etc/pve/local ]; do + sleep 0.5 +done + +# Exit success if the specified container already exists +if [ -f "/etc/pve/lxc/${CTID}.conf" ]; then + exit 0 +fi + +# Ensure the specified network are available +if [ ! -d "/sys/class/net/${BRIDGE}" ]; then + echo "Bridge ${BRIDGE} does not exist!" + exit 1 +fi + +# Ensure the template for the specified tag is available +if [ ! -f "/var/lib/vz/template/cache/manager_${MANAGER_TAG}.tar" ]; then + echo "Template local:vztmpl/manager_${MANAGER_TAG}.tar does not exist!" + exit 1 +fi + +# Create the container, setting it to startup in emergency mode which will allow +# us to install our overrides into it's filesystem without the +# `container-creator-init.service` attempting to bootstrap the database before +# we're ready for it. +pct create 100 "local:vztmpl/manager_${MANAGER_TAG}.tar" \ + --cores=4 \ + --features=nesting=1 \ + --hostname=manager \ + --memory=8192 \ + --net0="name=eth0,bridge=${BRIDGE},gw=10.254.0.1,ip=10.254.0.2/16" \ + --onboot=1 \ + --ostype=debian \ + --rootfs=local:50 \ + --entrypoint="/sbin/init systemd.unit=emergency.target" \ + --start=1 + +# We need to do some initial setup for the development environment before boot- +# strapping. First we make some self-signed SSL certs for the NGINX to use since +# it checks specific paths so we can't just rely on the snakeoil cert. +pct exec 100 -- openssl req \ + -x509 \ + -newkey ec \ + -pkeyopt ec_paramgen_curve:prime256v1 \ + -keyout /etc/ssl/private/localhost.key \ + -out /etc/ssl/certs/localhost.crt \ + -days 3650 \ + -noenc \ + -subj /CN=localhost + +# Next we need to set up the systemd overrides so it will treat it as a develop- +# ment instance. +pct exec 100 -- mkdir -p /etc/systemd/system/container-creator.service.d +pct push 100 \ + /opt/opensource-server/images/proxmox-ve/99-container-creator-dev.conf \ + /etc/systemd/system/container-creator.service.d/99-container-creator-dev.conf + +# Now we can set the entrypoint back to normal so it'll boot up to the +# default systemd target. We also use this opportunity to add the directory +# mount. Doing it with the container online or during the create step causes all +# sorts of AppArmor and userns problems due to the nested Proxmox-in-Docker. +pct shutdown 100 +pct set 100 \ + --mp0=/opt/opensource-server,mp=/opt/opensource-server \ + --entrypoint=/sbin/init + +# Finally we start the container back up completing this service run. +pct start 100 From 45cef0be7c3b6017a15b959deb796a690ae1fe95 Mon Sep 17 00:00:00 2001 From: Robert Gingras Date: Sun, 17 May 2026 16:55:23 -0400 Subject: [PATCH 15/16] feat: build the Proxmox VE image in CI for more efficient local runs --- .github/workflows/build-images.yml | 15 +++++++++++++++ compose.yml | 2 +- images/docker-bake.hcl | 6 +++++- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-images.yml b/.github/workflows/build-images.yml index 59475d18..abcb07d9 100644 --- a/.github/workflows/build-images.yml +++ b/.github/workflows/build-images.yml @@ -110,6 +110,18 @@ jobs: type=ref,event=tag type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} + - name: Docker Meta (Proxmox VE) + id: meta-proxmox-ve + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ github.repository }}/proxmox-ve + bake-target: proxmox-ve + tags: | + type=sha + type=ref,event=branch + type=ref,event=tag + type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' }} + - name: Build and push uses: docker/bake-action@v5 with: @@ -122,6 +134,7 @@ jobs: ${{ steps.meta-docs.outputs.bake-file }} ${{ steps.meta-agent.outputs.bake-file }} ${{ steps.meta-manager.outputs.bake-file }} + ${{ steps.meta-proxmox-ve.outputs.bake-file }} set: | base.cache-from=type=gha,scope=base-${{ github.ref_name }} base.cache-to=type=gha,mode=max,scope=base-${{ github.ref_name }} @@ -133,6 +146,8 @@ jobs: agent.cache-to=type=gha,mode=max,scope=agent-${{ github.ref_name }} manager.cache-from=type=gha,scope=manager-${{ github.ref_name }} manager.cache-to=type=gha,mode=max,scope=manager-${{ github.ref_name }} + proxmox-ve.cache-from=type=gha,scope=proxmox-ve-${{ github.ref_name }} + proxmox-ve.cache-to=type=gha,mode=max,scope=proxmox-ve-${{ github.ref_name }} deploy-preview: if: github.event.action != 'closed' diff --git a/compose.yml b/compose.yml index a8b52cbf..a5402696 100644 --- a/compose.yml +++ b/compose.yml @@ -1,6 +1,6 @@ --- x-proxmox-service: &proxmox-service - image: proxmox-ve:9 + image: ghcr.io/mieweb/opensource-server/proxmox-ve:latest build: images/proxmox-ve environment: MANAGER_TAG: ${MANAGER_TAG:-latest} diff --git a/images/docker-bake.hcl b/images/docker-bake.hcl index 8d47e550..7e8f1b28 100644 --- a/images/docker-bake.hcl +++ b/images/docker-bake.hcl @@ -1,5 +1,5 @@ group "default" { - targets = ["base", "nodejs", "docs", "agent", "manager"] + targets = ["base", "nodejs", "docs", "agent", "manager", "proxmox-ve"] } target "base" { @@ -36,3 +36,7 @@ target "manager" { agent = "target:agent" } } + +target "proxmox-ve" { + context = "./proxmox-ve" +} From f6dbb1596fda80a4b04c98133e8a50d89a200569 Mon Sep 17 00:00:00 2001 From: Robert Gingras Date: Sun, 17 May 2026 21:40:52 -0400 Subject: [PATCH 16/16] feat: improve proxmox images size by 1G by remove kernel packages --- images/proxmox-ve/Dockerfile | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/images/proxmox-ve/Dockerfile b/images/proxmox-ve/Dockerfile index 3bb67a96..e7563226 100644 --- a/images/proxmox-ve/Dockerfile +++ b/images/proxmox-ve/Dockerfile @@ -16,7 +16,14 @@ COPY pve-install-repo.sources /etc/apt/sources.list.d/ # Install the Proxmox packages RUN apt-get update \ - && apt-get install -y proxmox-ve postfix open-iscsi chrony e2fsprogs \ + && apt-get install --no-install-recommends -y \ + openssh-server \ + openssh-client \ + proxmox-archive-keyring \ + pve-manager \ + systemd-sysv \ + e2fsprogs \ + skopeo \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* \ && rm /etc/apt/sources.list.d/pve-enterprise.sources /etc/network/interfaces.new