Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/workflows/build-images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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 }}
Expand All @@ -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'
Expand Down
132 changes: 25 additions & 107 deletions README.md
Original file line number Diff line number Diff line change
@@ -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 <VMID> ghcr.io/mieweb/opensource-server:latest \
--hostname opensource-server \
--net0 name=eth0,bridge=vmbr0,ip=dhcp \
--features nesting=1 \
--privileged 1 \
--onboot 1
```
<a href="https://github.com/mieweb/opensource-server/graphs/contributors">
<img src="https://contrib.rocks/image?repo=mieweb/opensource-server" alt="Contributors" />
</a>

> **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
Made with [contrib.rocks](https://contrib.rocks).
121 changes: 121 additions & 0 deletions compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
---
x-proxmox-service: &proxmox-service
image: ghcr.io/mieweb/opensource-server/proxmox-ve:latest
build: images/proxmox-ve
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. 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-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
# 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

# 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:0.11.14-trixie-slim
volumes:
- ./:/opt/opensource-server
working_dir: /opt/opensource-server/mie-opensource-landing
command: uv run --active zensical serve
environment:
VIRTUAL_ENV: /opt/zensical
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
# 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

# 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:
21 changes: 0 additions & 21 deletions create-a-container/compose.yml

This file was deleted.

16 changes: 11 additions & 5 deletions create-a-container/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -63,8 +64,6 @@ async function main() {
db: sequelize,
});

const isProduction = process.env.NODE_ENV === 'production';

app.use(session({
secret: await getSessionSecrets(),
store: sessionStore,
Expand All @@ -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
};
}
}));
Expand Down
4 changes: 3 additions & 1 deletion create-a-container/systemd/job-runner.service
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Loading
Loading