Initial commit
This commit is contained in:
394
DEPLOY.md
Normal file
394
DEPLOY.md
Normal file
@ -0,0 +1,394 @@
|
||||
# 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
|
||||
- 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 <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.
|
||||
Reference in New Issue
Block a user