Files
GhostGrid/DEPLOY.md
2026-06-03 15:20:06 +02:00

9.5 KiB
Raw Blame History

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:

git pull
npm run build
systemctl restart <service>

The helper script deploy/deploy.sh automates this flow. It accepts the target branch as an argument:

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://<lxc-ip>:3000
Staging http://<lxc-ip>:3001

There is currently no reverse proxy and no TLS termination.

The recommended installation method is the standalone helper script:

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 <(curl -fsSL https://git.airit.rocks/jbrueckner/GhostGrid/raw/branch/main/deploy/proxmox-ghostgrid.sh)

Alternatively, copy the script locally and run it:

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:

http://<lxc-ip>:3000
http://<lxc-ip>:3001

Updates after the initial installation are handled through:

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
  • 12 vCPU
  • 1 GB RAM
  • 8 GB disk
  • Static IP or DHCP reservation in the LAN

Example using the Proxmox CLI:

pct create <vmid> 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 <vmid>
pct enter <vmid>

Replace <vmid> with the desired container ID.

2. Install Base Packages and Node.js 20 LTS

Run the following commands inside the container:

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:

useradd --system \
  --create-home \
  --home-dir /opt/ghostgrid \
  --shell /usr/sbin/nologin \
  ghostgrid

Repository:

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:

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:

Repository → Settings → Deploy Keys → Add Deploy Key

Keep the deploy key read-only.

Accept the host key and clone the repository via SSH:

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 <port> 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:

sudo -u ghostgrid git clone \
  https://oauth2:<READ_ONLY_TOKEN>@git.airit.rocks/jbrueckner/GhostGrid.git \
  /opt/ghostgrid

Protect the Git config because the token is stored in .git/config:

chmod 600 /opt/ghostgrid/.git/config

4. Configure the Application Secret

Create /opt/ghostgrid/.env.

The application loads this file through dotenv.

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

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:

nano /etc/sudoers.d/ghostgrid

Add the following rule:

ghostgrid ALL=(root) NOPASSWD: /usr/bin/systemctl restart ghostgrid, /usr/bin/systemctl restart ghostgrid-dev

Validate the sudoers file if desired:

visudo -c

7. Future Deployments

Deploy production:

sudo -u ghostgrid /opt/ghostgrid/deploy/deploy.sh main

Deploy staging:

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:

    systemctl status ghostgrid ghostgrid-dev
    
  2. Recent logs do not show crashes:

    journalctl -u ghostgrid -n 50
    journalctl -u ghostgrid-dev -n 50
    
  3. Both applications are reachable in the browser:

    http://<lxc-ip>:3000
    http://<lxc-ip>: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:

    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:

sudo -u ghostgrid git clone --branch dev <REPO-URL> /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:

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:

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:

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.