Skip to content
Closed
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
63 changes: 63 additions & 0 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Docker Build & Push

on:
push:
branches: [main]

jobs:
server:
name: Build mk-server
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v6

- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- uses: docker/setup-buildx-action@v3

- uses: docker/build-push-action@v6
with:
context: packages/engine-rs
push: true
cache-from: type=gha,scope=mk-server
cache-to: type=gha,mode=max,scope=mk-server
tags: |
ghcr.io/${{ github.repository_owner }}/mk-server:latest
ghcr.io/${{ github.repository_owner }}/mk-server:${{ github.sha }}

client:
name: Build mk-client
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v6

- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- uses: docker/setup-buildx-action@v3

- uses: docker/build-push-action@v6
with:
context: .
file: packages/client/Dockerfile
push: true
cache-from: type=gha,scope=mk-client
cache-to: type=gha,mode=max,scope=mk-client
build-args: |
VITE_ASSETS_BASE_URL=https://assets.mageknightdigital.app/mageknight/v1/assets
tags: |
ghcr.io/${{ github.repository_owner }}/mk-client:latest
ghcr.io/${{ github.repository_owner }}/mk-client:${{ github.sha }}
26 changes: 26 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
services:
mk-server:
image: ${SERVER_IMAGE:-ghcr.io/mage-knight-digital/mk-server:latest}
restart: unless-stopped
environment:
PORT: "3030"

mk-client:
image: ${CLIENT_IMAGE:-ghcr.io/mage-knight-digital/mk-client:latest}
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- caddy_data:/data
- caddy_config:/config
environment:
APP_DOMAIN: ${APP_DOMAIN}
API_DOMAIN: ${API_DOMAIN}
depends_on:
- mk-server

volumes:
caddy_data:
caddy_config:
9 changes: 9 additions & 0 deletions packages/client/Caddyfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{$APP_DOMAIN} {
root * /srv
try_files {path} /index.html
file_server
}

{$API_DOMAIN} {
reverse_proxy mk-server:3030
}
25 changes: 25 additions & 0 deletions packages/client/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
FROM oven/bun:1 AS builder
WORKDIR /build

# Install step — cache this layer separately from source
COPY package.json bun.lock ./
COPY packages/client/package.json packages/client/
COPY packages/shared/package.json packages/shared/
COPY packages/mage-dev/package.json packages/mage-dev/
RUN bun install

# Source
COPY packages/client/ packages/client/
COPY packages/shared/ packages/shared/
COPY packages/mage-dev/ packages/mage-dev/

ARG VITE_ASSETS_BASE_URL=https://assets.mageknightdigital.app/mageknight/v1/assets
ENV VITE_ASSETS_BASE_URL=$VITE_ASSETS_BASE_URL

RUN bun run --filter @mage-knight/shared build && \
bun run --filter @mage-knight/client build

FROM caddy:2-alpine
COPY --from=builder /build/packages/client/dist /srv
COPY packages/client/Caddyfile /etc/caddy/Caddyfile
EXPOSE 80 443
11 changes: 11 additions & 0 deletions packages/engine-rs/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM rust:1-slim-bookworm AS builder
WORKDIR /build
COPY . .
RUN cargo build --release -p mk-server

FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
COPY --from=builder /build/target/release/mk-server /usr/local/bin/mk-server
ENV PORT=3030
EXPOSE 3030
CMD ["mk-server"]
3 changes: 1 addition & 2 deletions terraform/environments/dev/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,5 @@ module "hetzner_app" {
repo_branch = var.repo_branch
app_domain = "dev.${local.domain_name}"
api_domain = "api-dev.${local.domain_name}"
assets_base_url = local.assets_base_url
server_port = 3030
ghcr_token = var.ghcr_token
}
7 changes: 7 additions & 0 deletions terraform/environments/dev/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,10 @@ variable "repo_branch" {
type = string
default = "main"
}

variable "ghcr_token" {
description = "GitHub PAT with read:packages scope for pulling GHCR images. Leave null for public packages."
type = string
sensitive = true
default = null
}
3 changes: 1 addition & 2 deletions terraform/environments/prod/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,5 @@ module "hetzner_app" {
repo_branch = var.repo_branch
app_domain = "play.${local.domain_name}"
api_domain = "api.${local.domain_name}"
assets_base_url = local.assets_base_url
server_port = 3030
ghcr_token = var.ghcr_token
}
7 changes: 7 additions & 0 deletions terraform/environments/prod/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -75,3 +75,10 @@ variable "repo_branch" {
type = string
default = "main"
}

variable "ghcr_token" {
description = "GitHub PAT with read:packages scope for pulling GHCR images. Leave null for public packages."
type = string
sensitive = true
default = null
}
7 changes: 3 additions & 4 deletions terraform/modules/hetzner_app/main.tf
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
locals {
bootstrap_script = templatefile("${path.module}/templates/bootstrap.sh.tftpl", {
repo_url = var.repo_url
repo_branch = var.repo_branch
app_domain = var.app_domain
api_domain = var.api_domain
assets_base_url = var.assets_base_url
server_port = var.server_port
deploy_interval_sec = var.deploy_interval_sec
ghcr_token = var.ghcr_token
server_image = var.server_image
client_image = var.client_image
})
}

Expand Down
153 changes: 65 additions & 88 deletions terraform/modules/hetzner_app/templates/bootstrap.sh.tftpl
Original file line number Diff line number Diff line change
Expand Up @@ -4,104 +4,95 @@ set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
export HOME=/root

REPO_DIR=/opt/mage-knight
REPO_URL="${repo_url}"
REPO_BRANCH="${repo_branch}"
APP_DOMAIN="${app_domain}"
API_DOMAIN="${api_domain}"
ASSETS_BASE_URL="${assets_base_url}"
SERVER_PORT="${server_port}"
DEPLOY_INTERVAL_SEC="${deploy_interval_sec}"
SERVER_IMAGE="${server_image}"
CLIENT_IMAGE="${client_image}"
%{ if ghcr_token != null ~}
GHCR_TOKEN="${ghcr_token}"
%{ endif ~}

apt-get update
apt-get install -y ca-certificates curl git unzip build-essential pkg-config libssl-dev caddy

if ! command -v bun >/dev/null 2>&1; then
curl -fsSL https://bun.sh/install | bash
fi

export BUN_INSTALL=/root/.bun
export PATH="$BUN_INSTALL/bin:$PATH"

if ! command -v rustup >/dev/null 2>&1; then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
fi

source /root/.cargo/env
rustup default stable

if [ ! -d "$REPO_DIR/.git" ]; then
rm -rf "$REPO_DIR"
git clone --branch "$REPO_BRANCH" "$REPO_URL" "$REPO_DIR"
fi

cat >/etc/profile.d/mage-knight-path.sh <<'EOF'
export BUN_INSTALL=/root/.bun
export PATH=/root/.cargo/bin:/root/.bun/bin:$PATH
EOF

cat >/etc/systemd/system/mage-knight-server.service <<EOF
[Unit]
Description=Mage Knight WebSocket Server
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
WorkingDirectory=$REPO_DIR
Environment=PATH=/root/.cargo/bin:/root/.bun/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
Environment=PORT=$SERVER_PORT
ExecStart=/root/.cargo/bin/cargo run --release -p mk-server --manifest-path packages/engine-rs/Cargo.toml
Restart=always
RestartSec=5
DEPLOY_DIR=/opt/mage-knight

[Install]
WantedBy=multi-user.target
# Install Docker
apt-get update
apt-get install -y ca-certificates curl
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
>/etc/apt/sources.list.d/docker.list
apt-get update
apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
systemctl enable --now docker

mkdir -p "$DEPLOY_DIR"

cat >"$DEPLOY_DIR/docker-compose.yml" <<'COMPOSE_EOF'
services:
mk-server:
image: ${server_image}
restart: unless-stopped
environment:
PORT: "3030"

mk-client:
image: ${client_image}
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- caddy_data:/data
- caddy_config:/config
environment:
APP_DOMAIN: $APP_DOMAIN
API_DOMAIN: $API_DOMAIN
depends_on:
- mk-server

volumes:
caddy_data:
caddy_config:
COMPOSE_EOF

cat >"$DEPLOY_DIR/.env" <<EOF
APP_DOMAIN=$APP_DOMAIN
API_DOMAIN=$API_DOMAIN
SERVER_IMAGE=$SERVER_IMAGE
CLIENT_IMAGE=$CLIENT_IMAGE
EOF

cat >/usr/local/bin/mage-knight-deploy <<'DEPLOY_EOF'
#!/usr/bin/env bash
set -euo pipefail

REPO_DIR=/opt/mage-knight
DEPLOY_DIR=/opt/mage-knight
LOCK=/run/mage-knight-deploy.lock
LOG=/var/log/mage-knight-deploy.log
BRANCH="${repo_branch}"
ASSETS_BASE_URL="${assets_base_url}"

exec 200>"$LOCK"
flock -n 200 || exit 0

export BUN_INSTALL=/root/.bun
export PATH=/root/.cargo/bin:/root/.bun/bin:$PATH

cd "$REPO_DIR"
git fetch origin "$BRANCH" --quiet

LOCAL=$(git rev-parse HEAD)
REMOTE=$(git rev-parse "origin/$BRANCH")
%{ if ghcr_token != null ~}
echo "$GHCR_TOKEN" | docker login ghcr.io -u mage-knight-digital --password-stdin
%{ endif ~}

if [ "$LOCAL" != "$REMOTE" ]; then
git checkout "$BRANCH"
git reset --hard "$REMOTE"
fi
docker compose -f "$DEPLOY_DIR/docker-compose.yml" --env-file "$DEPLOY_DIR/.env" pull
docker compose -f "$DEPLOY_DIR/docker-compose.yml" --env-file "$DEPLOY_DIR/.env" up -d --remove-orphans

bun install >> "$LOG" 2>&1
bun run --filter @mage-knight/shared build >> "$LOG" 2>&1
VITE_ASSETS_BASE_URL="$ASSETS_BASE_URL" bun run --filter @mage-knight/client build >> "$LOG" 2>&1

systemctl restart mage-knight-server.service
systemctl reload caddy || systemctl restart caddy

echo "$(date -u +%FT%TZ) deployed $(git rev-parse --short HEAD)" >> "$LOG"
echo "$(date -u +%FT%TZ) deployed" >>"$LOG"
DEPLOY_EOF
chmod 0755 /usr/local/bin/mage-knight-deploy

cat >/etc/systemd/system/mage-knight-deploy.service <<'EOF'
[Unit]
Description=Deploy Mage Knight Digital
After=network-online.target
Wants=network-online.target
After=docker.service
Wants=docker.service

[Service]
Type=oneshot
Expand All @@ -110,7 +101,7 @@ EOF

cat >/etc/systemd/system/mage-knight-deploy.timer <<EOF
[Unit]
Description=Poll GitHub for Mage Knight Digital deploys
Description=Poll GHCR for Mage Knight Digital updates

[Timer]
OnBootSec=30
Expand All @@ -121,20 +112,6 @@ Unit=mage-knight-deploy.service
WantedBy=timers.target
EOF

cat >/etc/caddy/Caddyfile <<EOF
$APP_DOMAIN {
root * $REPO_DIR/packages/client/dist
try_files {path} /index.html
file_server
}

$API_DOMAIN {
reverse_proxy 127.0.0.1:$SERVER_PORT
}
EOF

systemctl daemon-reload
/usr/local/bin/mage-knight-deploy
systemctl enable --now mage-knight-server.service
systemctl enable --now caddy
systemctl enable --now mage-knight-deploy.timer
Loading