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
35 changes: 35 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# ============================================================================
# Dungeon Master's Vault — Docker Environment Configuration
#
# Copy this file to .env and update the values:
# cp .env.example .env
#
# Or run the setup script to generate .env with secure random values:
# ./docker-setup.sh
# ============================================================================

# --- Application ---
PORT=8890

# --- Datomic Database ---
# ADMIN_PASSWORD secures the Datomic admin interface
# DATOMIC_PASSWORD is used by the application to connect to Datomic
# The password in DATOMIC_URL must match DATOMIC_PASSWORD
ADMIN_PASSWORD=change-me-admin
DATOMIC_PASSWORD=change-me-datomic
DATOMIC_URL=datomic:free://datomic:4334/orcpub?password=change-me-datomic

# --- Security ---
# Secret used to sign JWT tokens (20+ characters recommended)
SIGNATURE=change-me-to-something-unique-and-long

# --- Email (SMTP) ---
# Leave EMAIL_SERVER_URL empty to disable email functionality
EMAIL_SERVER_URL=
EMAIL_ACCESS_KEY=
EMAIL_SECRET_KEY=
EMAIL_SERVER_PORT=587
EMAIL_FROM_ADDRESS=
EMAIL_ERRORS_TO=
EMAIL_SSL=FALSE
EMAIL_TLS=FALSE
222 changes: 222 additions & 0 deletions .github/workflows/docker-integration.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
name: Docker Integration Test

on:
pull_request:
branches: [develop]
paths:
- 'docker/**'
- 'docker-compose*.yaml'
- 'docker-setup.sh'
- 'docker-user.sh'
- 'deploy/**'
- '.github/workflows/docker-integration.yml'
workflow_dispatch:

jobs:
docker-test:
name: Docker Setup & User Management
runs-on: ubuntu-latest
timeout-minutes: 10

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Lint shell scripts
run: |
sudo apt-get update -qq && sudo apt-get install -y -qq shellcheck
shellcheck docker-setup.sh docker-user.sh

- name: Run docker-setup.sh --auto
run: |
./docker-setup.sh --auto
echo "--- Generated .env (secrets redacted) ---"
sed 's/=.*/=***/' .env

- name: Validate .env password consistency
run: |
PW=$(grep '^DATOMIC_PASSWORD=' .env | cut -d= -f2)
URL_PW=$(grep '^DATOMIC_URL=' .env | sed 's/.*password=//')
if [ "$PW" != "$URL_PW" ]; then
echo "FAIL: DATOMIC_PASSWORD and DATOMIC_URL password mismatch"
exit 1
fi
echo "OK: Passwords match"

- name: Start datomic (no deps)
run: |
docker compose pull
docker compose up -d --no-deps datomic
echo "Datomic container started, waiting for health..."

- name: Wait for datomic healthy
run: |
for i in $(seq 1 90); do
CID=$(docker compose ps -q datomic 2>/dev/null) || true
if [ -z "$CID" ]; then
echo " [$i/90] datomic container not found yet"
sleep 2
continue
fi
RUNNING=$(docker inspect --format='{{.State.Running}}' "$CID" 2>/dev/null || echo "false")
STATUS=$(docker inspect --format='{{.State.Health.Status}}' "$CID" 2>/dev/null || echo "starting")
echo " [$i/90] running=$RUNNING health=$STATUS"
if [ "$STATUS" = "healthy" ]; then
echo "Datomic is healthy (after ~$((i * 2))s)"
break
fi
if [ "$RUNNING" = "false" ]; then
echo "WARN: datomic container stopped — dumping logs"
docker compose logs datomic
echo "Container will restart (restart: always), continuing to wait..."
fi
if [ "$i" -eq 90 ]; then
echo "FAIL: Datomic did not become healthy within 180s"
echo "=== container state ==="
docker inspect --format='{{json .State}}' "$CID" | python3 -m json.tool || true
echo "=== datomic logs ==="
docker compose logs datomic
exit 1
fi
sleep 2
done

- name: Start orcpub and web
run: |
docker compose up -d
docker compose ps

- name: Wait for orcpub healthy
run: |
for i in $(seq 1 90); do
CID=$(docker compose ps -q orcpub 2>/dev/null) || true
if [ -z "$CID" ]; then
echo " [$i/90] orcpub container not found yet"
sleep 2
continue
fi
STATUS=$(docker inspect --format='{{.State.Health.Status}}' "$CID" 2>/dev/null || echo "starting")
echo " [$i/90] orcpub health=$STATUS"
if [ "$STATUS" = "healthy" ]; then
echo "orcpub is healthy (after ~$((i * 2))s)"
break
fi
if [ "$STATUS" = "unhealthy" ]; then
echo "FAIL: orcpub reported unhealthy"
echo "=== all logs ==="
docker compose logs
exit 1
fi
if [ "$i" -eq 90 ]; then
echo "FAIL: orcpub did not become healthy within 180s"
docker compose logs
exit 1
fi
sleep 2
done
docker compose ps

- name: Test — create user
run: |
./docker-user.sh create testadmin admin@test.local SecurePass123
echo "Exit code: $?"

- name: Test — check user exists
run: |
OUTPUT=$(./docker-user.sh check testadmin)
echo "$OUTPUT"
echo "$OUTPUT" | grep -q "testadmin"
echo "$OUTPUT" | grep -q "admin@test.local"
echo "$OUTPUT" | grep -q "true" # verified

- name: Test — list includes user
run: |
OUTPUT=$(./docker-user.sh list)
echo "$OUTPUT"
echo "$OUTPUT" | grep -q "testadmin"

- name: Test — duplicate user fails
run: |
if ./docker-user.sh create testadmin admin@test.local SecurePass123 2>&1; then
echo "FAIL: Should have rejected duplicate user"
exit 1
fi
echo "OK: Duplicate user correctly rejected"

- name: Test — create second user
run: ./docker-user.sh create player2 player2@test.local AnotherPass456

- name: Test — list shows both users
run: |
OUTPUT=$(./docker-user.sh list)
echo "$OUTPUT"
echo "$OUTPUT" | grep -q "testadmin"
echo "$OUTPUT" | grep -q "player2"

- name: Test — verify already-verified user is idempotent
run: |
OUTPUT=$(./docker-user.sh verify testadmin)
echo "$OUTPUT"
echo "$OUTPUT" | grep -q "already verified"

- name: Test — check nonexistent user fails
run: |
if ./docker-user.sh check nobody@nowhere.com 2>&1; then
echo "FAIL: Should have reported user not found"
exit 1
fi
echo "OK: Nonexistent user correctly not found"

- name: Test — created user can log in via HTTP
run: |
# Use nginx (port 443) since orcpub:8890 is not exposed to host
RESPONSE=$(curl -sk -X POST https://localhost/login \
-H "Content-Type: application/json" \
-d '{"username":"testadmin","password":"SecurePass123"}' \
-w "\n%{http_code}" 2>&1) || true

HTTP_CODE=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | sed '$d')

echo "HTTP $HTTP_CODE"
echo "$BODY"

if [ "$HTTP_CODE" = "200" ]; then
echo "OK: Login succeeded"
echo "$BODY" | grep -q "token"
echo "OK: Response contains JWT token"
else
echo "FAIL: Expected HTTP 200, got $HTTP_CODE"
exit 1
fi

- name: Test — wrong password is rejected
run: |
HTTP_CODE=$(curl -sk -o /dev/null -w "%{http_code}" \
-X POST https://localhost/login \
-H "Content-Type: application/json" \
-d '{"username":"testadmin","password":"WrongPassword"}' 2>&1) || true

echo "HTTP $HTTP_CODE"
if [ "$HTTP_CODE" = "401" ]; then
echo "OK: Wrong password correctly rejected"
else
echo "FAIL: Expected HTTP 401, got $HTTP_CODE"
exit 1
fi

- name: Collect logs on failure
if: failure()
run: |
echo "=== docker compose ps ==="
docker compose ps
echo "=== datomic logs ==="
docker compose logs datomic
echo "=== orcpub logs ==="
docker compose logs orcpub
echo "=== web logs ==="
docker compose logs web

- name: Cleanup
if: always()
run: docker compose down -v
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pom.xml
orcpub.iml
profiles.clj
env.sh
.env
.repl
.nrepl-port
*~
Expand Down
50 changes: 29 additions & 21 deletions docker-compose-build.yaml
Original file line number Diff line number Diff line change
@@ -1,40 +1,47 @@
---
version: '3'
services:
orcpub:
build:
context: .
dockerfile: docker/orcpub/Dockerfile
environment:
PORT: 8890
EMAIL_SERVER_URL: ''
EMAIL_ACCESS_KEY: ''
EMAIL_SECRET_KEY: ''
EMAIL_SERVER_PORT: 587
# Email address to send from, will default to 'no-reply@orcpub.com'
EMAIL_FROM_ADDRESS: ''
# Email address to send errors to
EMAIL_ERRORS_TO: ''
EMAIL_SSL: 'TRUE'
EMAIL_TLS: 'FALSE'
# Datomic connection string - Make sure the <change this> matches the DATOMIC_PASSWORD below
DATOMIC_URL: datomic:free://datomic:4334/orcpub?password=<change this>
# The secret used to hash your password in the browser, 20+ characters recommended
SIGNATURE: '<change me to something unique>'
PORT: ${PORT:-8890}
EMAIL_SERVER_URL: ${EMAIL_SERVER_URL:-}
EMAIL_ACCESS_KEY: ${EMAIL_ACCESS_KEY:-}
EMAIL_SECRET_KEY: ${EMAIL_SECRET_KEY:-}
EMAIL_SERVER_PORT: ${EMAIL_SERVER_PORT:-587}
EMAIL_FROM_ADDRESS: ${EMAIL_FROM_ADDRESS:-}
EMAIL_ERRORS_TO: ${EMAIL_ERRORS_TO:-}
EMAIL_SSL: ${EMAIL_SSL:-FALSE}
EMAIL_TLS: ${EMAIL_TLS:-FALSE}
DATOMIC_URL: ${DATOMIC_URL:-datomic:free://datomic:4334/orcpub?password=change-me}
SIGNATURE: ${SIGNATURE:-change-me-to-something-unique}
depends_on:
- datomic
datomic:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8890/"]
interval: 5s
timeout: 3s
retries: 20
start_period: 15s
restart: always
datomic:
build:
context: .
dockerfile: docker/datomic/Dockerfile
environment:
ADMIN_PASSWORD: <change this as well!>
# Must match the <change this> in the DATOMIC_URL above.
DATOMIC_PASSWORD: <change this>
ADMIN_PASSWORD: ${ADMIN_PASSWORD:-change-me-admin}
DATOMIC_PASSWORD: ${DATOMIC_PASSWORD:-change-me}
volumes:
- ./data:/data
- ./logs:/logs
healthcheck:
test: ["CMD-SHELL", "grep -q ':10EE ' /proc/net/tcp || grep -q ':10EE ' /proc/net/tcp6"]
interval: 5s
timeout: 3s
retries: 30
start_period: 40s
restart: always
web:
image: nginx:alpine
Expand All @@ -46,5 +53,6 @@ services:
- ./deploy/snakeoil.crt:/etc/nginx/snakeoil.crt
- ./deploy/snakeoil.key:/etc/nginx/snakeoil.key
depends_on:
- orcpub
orcpub:
condition: service_healthy
restart: always
Loading
Loading