# GhostGrid Deployment This document describes how to deploy GhostGrid inside a Proxmox LXC container. GhostGrid runs as a single Node.js process. In production mode, the Express backend serves both the built frontend from `dist/` and the API on `0.0.0.0`. The container runs two GhostGrid instances in parallel. Each instance has its own working directory, SQLite database, port, and systemd service. | Instance | Branch | Directory | Port | systemd Service | | --- | --- | --- | --- | --- | | Production | `main` | `/opt/ghostgrid` | `3000` | `ghostgrid` | | Staging | `dev` | `/opt/ghostgrid-dev` | `3001` | `ghostgrid-dev` | ## Deployment Model GhostGrid uses a manual deployment model: ```bash git pull npm run build systemctl restart ``` The helper script `deploy/deploy.sh` automates this flow. It accepts the target branch as an argument: ```bash deploy/deploy.sh main deploy/deploy.sh dev ``` If no argument is provided, the script defaults to `main`. ## Access Both instances are exposed directly in the LAN: | Instance | URL | | --- | --- | | Production | `http://:3000` | | Staging | `http://:3001` | There is currently no reverse proxy and no TLS termination. ## Recommended Setup: Automated Proxmox Installer The recommended installation method is the standalone helper script: ```text deploy/proxmox-ghostgrid.sh ``` It follows the style of the Proxmox VE helper scripts and performs the full setup automatically. The installer creates the LXC container, installs Node.js 20, clones both branches, builds both instances, and configures two systemd services: | Service | Branch | Port | | --- | --- | --- | | `ghostgrid` | `main` | `3000` | | `ghostgrid-dev` | `dev` | `3001` | Each instance receives its own randomly generated `JWT_SECRET`. Run the installer on the Proxmox host as `root`. Example using the script directly from the repository: ```bash bash <(curl -fsSL https://git.airit.rocks/jbrueckner/GhostGrid/raw/branch/main/deploy/proxmox-ghostgrid.sh) ``` Alternatively, copy the script locally and run it: ```bash bash proxmox-ghostgrid.sh ``` The script asks for the container ID, hostname, resources, network settings, and a read-only Gitea access token. The token is only required while the repository is private. If the repository is public, the token can be left empty. At the end of the installation, the script prints both application URLs: ```text http://:3000 http://:3001 ``` Updates after the initial installation are handled through: ```bash deploy/deploy.sh main deploy/deploy.sh dev ``` ## Manual Installation The following steps describe a manual installation of the production instance on branch `main`. The staging instance follows the same pattern, using branch `dev`, directory `/opt/ghostgrid-dev`, service `ghostgrid-dev`, and port `3001`. ## 1. Create the LXC Container Create the container on the Proxmox host. Recommended baseline: - Debian 12 template - Unprivileged container - 1–2 vCPU - 1 GB RAM - 8 GB disk - Static IP or DHCP reservation in the LAN Example using the Proxmox CLI: ```bash pct create local:vztmpl/debian-12-standard_*.tar.zst \ --hostname ghostgrid \ --cores 2 \ --memory 1024 \ --rootfs local-lvm:8 \ --net0 name=eth0,bridge=vmbr0,ip=dhcp \ --unprivileged 1 pct start pct enter ``` Replace `` with the desired container ID. ## 2. Install Base Packages and Node.js 20 LTS Run the following commands inside the container: ```bash apt update apt install -y curl git ca-certificates build-essential python3 curl -fsSL https://deb.nodesource.com/setup_20.x | bash - apt install -y nodejs node --version ``` The expected Node.js version is `v20.x`. `build-essential` and `python3` are fallback dependencies in case `better-sqlite3` cannot use a prebuilt binary and needs to compile locally. ## 3. Create the Service User and Clone the Repository Create a dedicated system user: ```bash useradd --system \ --create-home \ --home-dir /opt/ghostgrid \ --shell /usr/sbin/nologin \ ghostgrid ``` Repository: ```text https://git.airit.rocks/jbrueckner/GhostGrid.git ``` For unattended deployments, the `ghostgrid` user needs read-only repository access. There are two recommended options. ### Option A: SSH Deploy Key Create an SSH key for the service user: ```bash sudo -u ghostgrid ssh-keygen -t ed25519 -N "" -f /opt/ghostgrid/.ssh/id_ed25519 sudo -u ghostgrid cat /opt/ghostgrid/.ssh/id_ed25519.pub ``` Add the public key in Gitea: ```text Repository → Settings → Deploy Keys → Add Deploy Key ``` Keep the deploy key read-only. Accept the host key and clone the repository via SSH: ```bash sudo -u ghostgrid ssh-keyscan git.airit.rocks >> /opt/ghostgrid/.ssh/known_hosts sudo -u ghostgrid git clone git@git.airit.rocks:jbrueckner/GhostGrid.git /opt/ghostgrid ``` If Gitea uses a non-default SSH port, configure it either with `-p ` or through `/opt/ghostgrid/.ssh/config`. When the repository is cloned via SSH, `deploy/deploy.sh` can use the configured Git remote without further changes. ### Option B: HTTPS with Read-Only Access Token Create a Gitea access token with read-only repository permissions, for example with the `read:repository` scope. Clone the repository using the token: ```bash sudo -u ghostgrid git clone \ https://oauth2:@git.airit.rocks/jbrueckner/GhostGrid.git \ /opt/ghostgrid ``` Protect the Git config because the token is stored in `.git/config`: ```bash chmod 600 /opt/ghostgrid/.git/config ``` ## 4. Configure the Application Secret Create `/opt/ghostgrid/.env`. The application loads this file through `dotenv`. ```bash printf 'JWT_SECRET="%s"\n' "$(openssl rand -hex 32)" > /opt/ghostgrid/.env chown ghostgrid:ghostgrid /opt/ghostgrid/.env chmod 600 /opt/ghostgrid/.env ``` The `JWT_SECRET` must be unique and must not be committed to Git. ## 5. Build the Application and Enable the systemd Service ```bash cd /opt/ghostgrid sudo -u ghostgrid npm ci sudo -u ghostgrid npm run build cp deploy/ghostgrid.service /etc/systemd/system/ghostgrid.service systemctl daemon-reload systemctl enable --now ghostgrid systemctl status ghostgrid ``` The service should be `active (running)`. ## 6. Allow Controlled Service Restarts for Deployments The unprivileged `ghostgrid` user needs permission to restart the GhostGrid services during deployment. Create the sudoers file: ```bash nano /etc/sudoers.d/ghostgrid ``` Add the following rule: ```text ghostgrid ALL=(root) NOPASSWD: /usr/bin/systemctl restart ghostgrid, /usr/bin/systemctl restart ghostgrid-dev ``` Validate the sudoers file if desired: ```bash visudo -c ``` ## 7. Future Deployments Deploy production: ```bash sudo -u ghostgrid /opt/ghostgrid/deploy/deploy.sh main ``` Deploy staging: ```bash sudo -u ghostgrid /opt/ghostgrid-dev/deploy/deploy.sh dev ``` Running `deploy.sh` without an argument deploys `main`. ## Verification After installation or deployment, verify the following: 1. Both services are running: ```bash systemctl status ghostgrid ghostgrid-dev ``` 2. Recent logs do not show crashes: ```bash journalctl -u ghostgrid -n 50 journalctl -u ghostgrid-dev -n 50 ``` 3. Both applications are reachable in the browser: ```text http://:3000 http://:3001 ``` 4. Login or registration loads correctly. 5. Create a device and reload the page. The device should remain available, confirming SQLite persistence. 6. Restart the service or reboot the LXC container. Login and data should still be available. 7. Verify the deployment flow: ```text local change → push to dev → deploy.sh dev → test → merge to main → deploy.sh main ``` ## Manual Setup of the Staging Instance The automated installer creates the staging instance automatically. For a manual setup, repeat the production steps with the following changes: | Setting | Production | Staging | | --- | --- | --- | | Branch | `main` | `dev` | | Directory | `/opt/ghostgrid` | `/opt/ghostgrid-dev` | | Port | `3000` | `3001` | | Service | `ghostgrid` | `ghostgrid-dev` | | Database | `/opt/ghostgrid/ghostgrid.db` | `/opt/ghostgrid-dev/ghostgrid.db` | Example: ```bash sudo -u ghostgrid git clone --branch dev /opt/ghostgrid-dev printf 'JWT_SECRET="%s"\n' "$(openssl rand -hex 32)" > /opt/ghostgrid-dev/.env chown ghostgrid:ghostgrid /opt/ghostgrid-dev/.env chmod 600 /opt/ghostgrid-dev/.env cd /opt/ghostgrid-dev sudo -u ghostgrid npm ci sudo -u ghostgrid npm run build cp deploy/ghostgrid-dev.service /etc/systemd/system/ghostgrid-dev.service systemctl daemon-reload systemctl enable --now ghostgrid-dev ``` ## Notes Each instance uses its own SQLite database: | Instance | Database | | --- | --- | | Production | `/opt/ghostgrid/ghostgrid.db` | | Staging | `/opt/ghostgrid-dev/ghostgrid.db` | The database files are intentionally not tracked in Git. For backups, include the SQLite database and its related WAL/SHM files if present: ```text ghostgrid.db ghostgrid.db-wal ghostgrid.db-shm ``` Ports `3000` and `3001` are exposed directly in the LAN. If a reverse proxy or TLS termination is required later, add it separately, for example with nginx or Caddy forwarding to: ```text 127.0.0.1:3000 127.0.0.1:3001 ``` Device status is provided by CheckMK. To enable periodic status synchronization, configure the following variables in the relevant `.env` file: ```env CHECKMK_API_URL= CHECKMK_API_SECRET= ``` If these variables are not set, CheckMK synchronization is disabled. Devices remain in status `unknown` and are not available for booking.