Adding a few

This commit is contained in:
2026-04-12 00:17:29 +02:00
parent 50660db16b
commit 578812a727
9 changed files with 339 additions and 96 deletions
+1 -1
View File
@@ -36,7 +36,7 @@ ssh ${SERVER} "docker exec gnommo-db psql -U \$(grep ^POSTGRES_USER ${REMOTE_DIR
# Build and restart containers # Build and restart containers
echo "==> Building and restarting containers..." echo "==> Building and restarting containers..."
ssh ${SERVER} "cd ${REMOTE_DIR} && docker compose -f docker-compose.prod.yml --env-file .env.prod up -d --build" ssh ${SERVER} "cd ${REMOTE_DIR} && bash start.sh --build"
# Wait for backend to be ready # Wait for backend to be ready
echo "==> Waiting for backend to start..." echo "==> Waiting for backend to start..."
+112
View File
@@ -0,0 +1,112 @@
=== dev.sh started 2026-04-11 22:15:47 UTC ===
Starting GnommoEditor...
Frontend : http://localhost:5173
Backend : http://localhost:3001
Log file : /Users/jenstandstad/Projects/gnommoeditor/dev.log
✓ Node v21
✓ Loaded .env
✓ PostgreSQL is running
✓ MinIO is running
✓ Database gnommoeditor already exists
Running migrations...
> gnommoeditor-backend@1.0.0 migrate:up
> node run-migrations.cjs up
Can't determine timestamp for 002
Can't determine timestamp for 001
Can't determine timestamp for 003
Can't determine timestamp for 002
Can't determine timestamp for 004
Can't determine timestamp for 003
Can't determine timestamp for 005
Can't determine timestamp for 004
Can't determine timestamp for 006
Can't determine timestamp for 005
Can't determine timestamp for 007
Can't determine timestamp for 006
Can't determine timestamp for 008
Can't determine timestamp for 007
Can't determine timestamp for 009
Can't determine timestamp for 008
Can't determine timestamp for 010
Can't determine timestamp for 009
Can't determine timestamp for 011
Can't determine timestamp for 010
Can't determine timestamp for 012
Can't determine timestamp for 011
Can't determine timestamp for 013
Can't determine timestamp for 012
Can't determine timestamp for 014
Can't determine timestamp for 013
Can't determine timestamp for 015
Can't determine timestamp for 014
Can't determine timestamp for 016
Can't determine timestamp for 015
Can't determine timestamp for 017
Can't determine timestamp for 016
Can't determine timestamp for 018
Can't determine timestamp for 017
Can't determine timestamp for 019
Can't determine timestamp for 018
Can't determine timestamp for 001
Can't determine timestamp for 002
Can't determine timestamp for 003
Can't determine timestamp for 004
Can't determine timestamp for 005
Can't determine timestamp for 006
Can't determine timestamp for 007
Can't determine timestamp for 008
Can't determine timestamp for 009
Can't determine timestamp for 010
Can't determine timestamp for 011
Can't determine timestamp for 012
Can't determine timestamp for 013
Can't determine timestamp for 014
Can't determine timestamp for 015
Can't determine timestamp for 016
Can't determine timestamp for 017
Can't determine timestamp for 018
Can't determine timestamp for 019
No migrations to run!
Migrations complete!
✓ Migrations up to date
Starting backend on port 3001...
> gnommoeditor-backend@1.0.0 dev
> node --watch src/index.js
(node:16183) ExperimentalWarning: Watch mode is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
node:internal/modules/esm/resolve:844
throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base), null);
^
Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'jsonwebtoken' imported from /Users/jenstandstad/Projects/gnommoeditor/backend/src/middleware/jwtAuth.js
at packageResolve (node:internal/modules/esm/resolve:844:9)
at moduleResolve (node:internal/modules/esm/resolve:901:20)
at defaultResolve (node:internal/modules/esm/resolve:1121:11)
at ModuleLoader.defaultResolve (node:internal/modules/esm/loader:396:12)
at ModuleLoader.resolve (node:internal/modules/esm/loader:365:25)
at ModuleLoader.getModuleJob (node:internal/modules/esm/loader:240:38)
at ModuleWrap.<anonymous> (node:internal/modules/esm/module_job:85:39)
at link (node:internal/modules/esm/module_job:84:36) {
code: 'ERR_MODULE_NOT_FOUND'
}
Node.js v21.5.0
Failed running 'src/index.js'
Starting frontend on port 5173...
> gnommoeditor@0.1.0 dev
> vite --port 5173
VITE v5.4.21 ready in 150 ms
➜ Local: http://localhost:5173/
➜ Network: http://192.168.10.57:5173/
➜ Network: http://192.168.64.1:5173/
Shutting down...
=== dev.sh stopped 2026-04-11 22:16:25 UTC ===
gu_common services left running.
Executable
+163
View File
@@ -0,0 +1,163 @@
#!/bin/bash
# dev.sh — GnommoEditor local development
# Requires gu_common to be running (postgres :5432, minio :9000).
#
# Usage:
# ./dev.sh [--port <frontend_port>] [--api-port <backend_port>]
#
# Services (gu_common): This script runs natively:
# - PostgreSQL :5432 - Express backend :<BACKEND_PORT>
# - MinIO :9000 - Vite frontend :<FRONTEND_PORT>
set -e
# ── Args ───────────────────────────────────────────────────────────────────────
FRONTEND_PORT=5173
BACKEND_PORT=3001
while [[ $# -gt 0 ]]; do
case "$1" in
--port) FRONTEND_PORT="$2"; shift 2 ;;
--api-port) BACKEND_PORT="$2"; shift 2 ;;
*) echo "Unknown option: $1" >&2; exit 1 ;;
esac
done
# ── Setup ──────────────────────────────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOG_FILE="$SCRIPT_DIR/dev.log"
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'
log() { local c="$1"; shift; echo -e "${c}$*${NC}"; echo "$*" >> "$LOG_FILE"; }
echo "=== dev.sh started $(date -u '+%Y-%m-%d %H:%M:%S UTC') ===" > "$LOG_FILE"
log "$GREEN" "Starting GnommoEditor..."
log "$GREEN" " Frontend : http://localhost:${FRONTEND_PORT}"
log "$GREEN" " Backend : http://localhost:${BACKEND_PORT}"
log "$GREEN" " Log file : ${LOG_FILE}"
# ── Node version guard ─────────────────────────────────────────────────────────
REQUIRED_NODE=20
CURRENT_NODE=$(node --version 2>/dev/null | sed 's/v//' | cut -d. -f1)
if [ -z "$CURRENT_NODE" ]; then
log "$RED" "Error: node not found."; log "$YELLOW" "Run: nvm use ${REQUIRED_NODE}"; exit 1
fi
if [ "$CURRENT_NODE" -lt "$REQUIRED_NODE" ]; then
log "$RED" "Error: Node ${CURRENT_NODE} too old (need >= ${REQUIRED_NODE})."
log "$YELLOW" "Fix: nvm use ${REQUIRED_NODE}"; exit 1
fi
log "$GREEN" "✓ Node v${CURRENT_NODE}"
# ── Load .env ──────────────────────────────────────────────────────────────────
if [ -f "$SCRIPT_DIR/.env" ]; then
set -a; source "$SCRIPT_DIR/.env"; set +a
log "$GREEN" "✓ Loaded .env"
fi
# ── npm install ────────────────────────────────────────────────────────────────
if [ ! -d "$SCRIPT_DIR/node_modules" ]; then
log "$YELLOW" "Installing frontend dependencies..."
npm install --prefix "$SCRIPT_DIR" >> "$LOG_FILE" 2>&1
log "$GREEN" "✓ Frontend deps installed"
fi
if [ ! -d "$SCRIPT_DIR/backend/node_modules" ]; then
log "$YELLOW" "Installing backend dependencies..."
npm install --prefix "$SCRIPT_DIR/backend" >> "$LOG_FILE" 2>&1
log "$GREEN" "✓ Backend deps installed"
fi
# ── Check gu_common services ───────────────────────────────────────────────────
if ! docker ps --format "{{.Names}}" | grep -qE "gnommo.?db"; then
log "$RED" "Error: postgres (gnommo-db) is not running."
log "$YELLOW" "Start gu_common first: cd ../gu_common && ./dev.sh"
exit 1
fi
log "$GREEN" "✓ PostgreSQL is running"
if ! docker ps --format "{{.Names}}" | grep -q "gnommo-minio"; then
log "$YELLOW" "⚠ MinIO not running — file uploads won't work"
else
log "$GREEN" "✓ MinIO is running"
fi
# ── Ensure gnommoeditor database exists ────────────────────────────────────────
DB_CONTAINER=$(docker ps --format "{{.Names}}" | grep -E "gnommo.?db" | head -1)
docker exec "$DB_CONTAINER" psql -U gnommo \
-c "CREATE DATABASE gnommoeditor;" 2>/dev/null \
&& log "$GREEN" "✓ Database gnommoeditor created" \
|| log "$GREEN" "✓ Database gnommoeditor already exists"
# ── Environment ────────────────────────────────────────────────────────────────
export NODE_ENV=development
export PORT=$BACKEND_PORT
export DATABASE_URL="postgresql://gnommo:${POSTGRES_PASSWORD:-gnommo_secret}@localhost:5432/gnommoeditor"
export CORS_ORIGIN="http://localhost:${FRONTEND_PORT}"
export INGEST_API_KEY="${INGEST_API_KEY:-dev-ingest-key-change-me}"
export JWT_SECRET="${JWT_SECRET:-dev-jwt-secret-change-me}"
export MINIO_ENDPOINT="http://localhost:9000"
export MINIO_PUBLIC_URL="http://localhost:9000"
export MINIO_BUCKET="${MINIO_BUCKET:-glitch-university}"
export MINIO_ROOT_USER="${MINIO_ROOT_USER:-minioadmin}"
export MINIO_ROOT_PASSWORD="${MINIO_ROOT_PASSWORD:-minioadmin}"
# ── Cleanup trap ───────────────────────────────────────────────────────────────
cleanup() {
echo ""
log "$YELLOW" "Shutting down..."
kill $BACKEND_PID 2>/dev/null || true
kill $FRONTEND_PID 2>/dev/null || true
echo "=== dev.sh stopped $(date -u '+%Y-%m-%d %H:%M:%S UTC') ===" >> "$LOG_FILE"
log "$YELLOW" "gu_common services left running."
exit 0
}
trap cleanup SIGINT SIGTERM
# ── Migrations ─────────────────────────────────────────────────────────────────
log "$GREEN" "Running migrations..."
cd "$SCRIPT_DIR/backend"
npm run migrate:up >> "$LOG_FILE" 2>&1 \
&& log "$GREEN" "✓ Migrations up to date" \
|| { log "$RED" "Migration failed — check dev.log"; exit 1; }
# ── Start backend ──────────────────────────────────────────────────────────────
log "$GREEN" "Starting backend on port ${BACKEND_PORT}..."
npm run dev >> "$LOG_FILE" 2>&1 &
BACKEND_PID=$!
sleep 2
# ── Start frontend ─────────────────────────────────────────────────────────────
log "$GREEN" "Starting frontend on port ${FRONTEND_PORT}..."
cd "$SCRIPT_DIR"
npm run dev -- --port "$FRONTEND_PORT" >> "$LOG_FILE" 2>&1 &
FRONTEND_PID=$!
echo ""
echo -e "${GREEN}============================================${NC}"
echo -e "${GREEN} GnommoEditor is running!${NC}"
echo -e "${GREEN}============================================${NC}"
echo ""
echo -e " Frontend : ${YELLOW}http://localhost:${FRONTEND_PORT}${NC}"
echo -e " Backend : ${YELLOW}http://localhost:${BACKEND_PORT}${NC}"
echo -e " Database : ${YELLOW}localhost:5432 / gnommoeditor${NC}"
echo -e " MinIO : ${YELLOW}localhost:9000${NC}"
echo -e " Log file : ${YELLOW}${LOG_FILE}${NC}"
echo ""
echo -e " Press ${RED}Ctrl+C${NC} to stop (gu_common keeps running)"
echo ""
wait
+3 -4
View File
@@ -1,8 +1,7 @@
# GnommoEditor — Production # GnommoEditor — Production
# Usage: ./deploy.sh # Usage: ./deploy.sh (local) or bash start.sh (on the server)
# Prerequisites: # The `gnommo` network is created automatically by start.sh if it doesn't exist.
# docker network create gnommo (run once on the server — shared with gu_common/gnommoweb) # gu_common must be running to provide gnommo-db (postgres) and gnommo-minio.
# gu_common must be running (provides gnommo-db postgres and gnommo-minio)
services: services:
backend: backend:
+10 -63
View File
@@ -1,25 +1,8 @@
# GnommoEditor — standalone development # GnommoEditor — development
# Requires gu_common to be running (provides gnommo-db and gnommo-minio).
# Usage: docker compose up # Usage: docker compose up
services: services:
db:
image: postgres:16-alpine
container_name: gnommoeditor-db
restart: unless-stopped
environment:
POSTGRES_DB: gnommoeditor
POSTGRES_USER: gnommoeditor
POSTGRES_PASSWORD: gnommoeditor_secret
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5433:5432"
healthcheck:
test: ["CMD-SHELL", "pg_isready -U gnommoeditor"]
interval: 5s
timeout: 5s
retries: 5
backend: backend:
build: build:
context: ./backend context: ./backend
@@ -33,58 +16,22 @@ services:
- ./backend/migrations:/app/migrations - ./backend/migrations:/app/migrations
command: npm run dev command: npm run dev
environment: environment:
DATABASE_URL: postgresql://gnommoeditor:gnommoeditor_secret@db:5432/gnommoeditor?sslmode=disable DATABASE_URL: postgresql://${POSTGRES_USER:-gnommo}:${POSTGRES_PASSWORD:-gnommo_secret}@gnommo_db:5432/gnommoeditor?sslmode=disable
INGEST_API_KEY: ${INGEST_API_KEY:-dev-ingest-key-change-me} INGEST_API_KEY: ${INGEST_API_KEY:-dev-ingest-key-change-me}
JWT_SECRET: ${JWT_SECRET:-dev-jwt-secret-change-me} JWT_SECRET: ${JWT_SECRET:-dev-jwt-secret-change-me}
CORS_ORIGIN: http://localhost:5173 CORS_ORIGIN: http://localhost:5173
PORT: 3001 PORT: 3001
MINIO_ENDPOINT: http://minio:9000 MINIO_ENDPOINT: http://gnommo-minio:9000
MINIO_PUBLIC_URL: http://localhost:9000 MINIO_PUBLIC_URL: http://localhost:9000
MINIO_BUCKET: glitch-university MINIO_BUCKET: glitch-university
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin} MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin} MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin}
ports: ports:
- "3001:3001" - "3001:3001"
depends_on: networks:
db: - default
condition: service_healthy - gnommo
minio:
condition: service_healthy
minio: networks:
image: minio/minio:latest gnommo:
container_name: gnommoeditor-minio external: true
restart: unless-stopped
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:-minioadmin}
command: server /data --console-address ":9001"
ports:
- "9000:9000" # S3 API
- "9001:9001" # Web console http://localhost:9001
volumes:
- minio_data:/data
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 10s
timeout: 5s
retries: 5
minio-setup:
image: minio/mc
depends_on:
minio:
condition: service_healthy
entrypoint: >
/bin/sh -c "
mc alias set local http://minio:9000 $${MINIO_ROOT_USER:-minioadmin} $${MINIO_ROOT_PASSWORD:-minioadmin};
mc mb --ignore-existing local/glitch-university;
mc anonymous set download local/glitch-university;
echo 'MinIO bucket ready.';
exit 0;
"
restart: on-failure
volumes:
postgres_data:
minio_data:
+1 -6
View File
@@ -6,12 +6,7 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview"
"services": "docker compose up",
"services:down": "docker compose down",
"services:logs": "docker compose logs -f",
"migrate": "docker compose exec backend npm run migrate:up",
"migrate:down": "docker compose exec backend npm run migrate:down"
}, },
"dependencies": { "dependencies": {
"react": "^18.2.0", "react": "^18.2.0",
+21 -21
View File
@@ -150,7 +150,7 @@
.btn-delete { .btn-delete {
background: transparent; background: transparent;
border: none; border: none;
color: #444444; color: #666666;
cursor: pointer; cursor: pointer;
font-size: 10px; font-size: 10px;
padding: 2px 6px; padding: 2px 6px;
@@ -343,7 +343,7 @@
line-height: 2; line-height: 2;
} }
.td-id { color: #555555; font-size: 8px; } .td-id { color: #888888; font-size: 9px; }
.td-count { color: #ffff00; } .td-count { color: #ffff00; }
.td a { color: #ffffff; text-decoration: none; } .td a { color: #ffffff; text-decoration: none; }
@@ -353,7 +353,7 @@
.td-course { .td-course {
margin-left: 10px; margin-left: 10px;
font-size: 8px; font-size: 8px;
color: #555555; color: #888888;
} }
.status-badge { .status-badge {
@@ -417,7 +417,7 @@
} }
.muted { .muted {
color: #444444; color: #777777;
font-size: 8px; font-size: 8px;
margin: 0; margin: 0;
line-height: 2; line-height: 2;
@@ -476,23 +476,23 @@
} }
.strip-num { .strip-num {
font-size: 7px; font-size: 8px;
color: #444444; color: #666666;
min-width: 16px; min-width: 16px;
text-align: right; text-align: right;
flex-shrink: 0; flex-shrink: 0;
} }
.strip-id { .strip-id {
font-size: 7px; font-size: 8px;
color: #ffff00; color: #ffff00;
font-weight: 700; font-weight: 700;
flex-shrink: 0; flex-shrink: 0;
} }
.strip-type { .strip-type {
font-size: 7px; font-size: 8px;
color: #444444; color: #666666;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
@@ -520,12 +520,12 @@
flex-shrink: 0; flex-shrink: 0;
} }
.strip-confirm-yes { .strip-confirm-yes {
font-size: 7px; padding: 1px 4px; font-size: 8px; padding: 1px 4px;
background: #ff0000; color: #000; border: none; cursor: pointer; background: #ff0000; color: #000; border: none; cursor: pointer;
} }
.strip-confirm-no { .strip-confirm-no {
font-size: 7px; padding: 1px 4px; font-size: 8px; padding: 1px 4px;
background: transparent; color: #888; border: 1px solid #444; cursor: pointer; background: transparent; color: #888; border: 1px solid #555; cursor: pointer;
} }
.strip-add { .strip-add {
@@ -557,8 +557,8 @@
.slide-editor-empty { .slide-editor-empty {
padding: 24px 0; padding: 24px 0;
color: #444444; color: #666666;
font-size: 8px; font-size: 9px;
line-height: 2; line-height: 2;
} }
@@ -592,7 +592,7 @@
.slide-num { .slide-num {
font-size: 8px; font-size: 8px;
color: #444444; color: #666666;
min-width: 20px; min-width: 20px;
text-align: right; text-align: right;
font-weight: 700; font-weight: 700;
@@ -757,7 +757,7 @@
.tmpl-thumbnail--fullscreen .glitch-slide-thumbnail__surface { aspect-ratio: 16 / 9; } .tmpl-thumbnail--fullscreen .glitch-slide-thumbnail__surface { aspect-ratio: 16 / 9; }
.tmpl-label { font-size: 8px; font-weight: 700; color: #ffffff; padding: 5px 8px 2px; line-height: 1.8; } .tmpl-label { font-size: 8px; font-weight: 700; color: #ffffff; padding: 5px 8px 2px; line-height: 1.8; }
.tmpl-desc { font-size: 7px; color: #888888; padding: 0 8px 6px; line-height: 1.8; } .tmpl-desc { font-size: 8px; color: #aaaaaa; padding: 0 8px 6px; line-height: 1.8; }
/* ── Asset picker ────────────────────────────────────────────────────────────── */ /* ── Asset picker ────────────────────────────────────────────────────────────── */
@@ -789,8 +789,8 @@
image-rendering: pixelated; image-rendering: pixelated;
} }
.asset-name { font-size: 7px; color: #888888; margin-top: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .asset-name { font-size: 8px; color: #aaaaaa; margin-top: 4px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.asset-meta { font-size: 7px; color: #444444; margin-top: 2px; } .asset-meta { font-size: 8px; color: #777777; margin-top: 2px; }
.check-badge { .check-badge {
position: absolute; position: absolute;
@@ -831,7 +831,7 @@
.auth-indicator:hover { border-color: #555555; } .auth-indicator:hover { border-color: #555555; }
.auth-indicator__name { color: #00ffff; } .auth-indicator__name { color: #00ffff; }
.auth-indicator__guest { color: #555555; } .auth-indicator__guest { color: #888888; }
.auth-token-popup { .auth-token-popup {
position: absolute; position: absolute;
@@ -890,8 +890,8 @@
} }
.proposal-card__by { .proposal-card__by {
font-size: 7px; font-size: 8px;
color: #888888; color: #aaaaaa;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
+1 -1
View File
@@ -16,7 +16,7 @@ html, body {
background: #000000; background: #000000;
color: #ffffff; color: #ffffff;
font-family: 'Press Start 2P', 'Courier New', monospace; font-family: 'Press Start 2P', 'Courier New', monospace;
font-size: 10px; font-size: 11px;
line-height: 2.2; line-height: 2.2;
image-rendering: pixelated; image-rendering: pixelated;
} }
Executable
+27
View File
@@ -0,0 +1,27 @@
#!/bin/bash
# start.sh — Start GnommoEditor on the server.
# Safe to run repeatedly (e.g. after a reboot).
# The `gnommo` network is shared with gu_common/gnommoweb; create it here
# if it doesn't already exist so startup is not order-dependent.
set -e
# Ensure shared external volumes exist
for vol in glitch_postgres_data glitch_minio_data; do
if ! docker volume inspect "$vol" >/dev/null 2>&1; then
echo "==> Creating volume: $vol"
docker volume create "$vol"
fi
done
# Ensure the shared gnommo network exists
if ! docker network inspect gnommo >/dev/null 2>&1; then
echo "==> Creating shared Docker network: gnommo"
docker network create gnommo
fi
# Bring up services
echo "==> Starting GnommoEditor..."
docker compose -f docker-compose.prod.yml --env-file .env.prod up -d "$@"
echo "==> Done."