Files
GhostGrid/DEPLOY.md

395 lines
9.5 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 <service>
```
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://<lxc-ip>:3000` |
| Staging | `http://<lxc-ip>: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://<lxc-ip>:3000
http://<lxc-ip>: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
- 12 vCPU
- 1 GB RAM
- 8 GB disk
- Static IP or DHCP reservation in the LAN
Example using the Proxmox CLI:
```bash
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:
```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 <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:
```bash
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`:
```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://<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:
```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 <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:
```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.