Skip to content

Commit bda41e4

Browse files
aberohamclaude
andcommitted
feat: docker compose support + env var config overrides (#19)
docker compose up gets the API running against MariaDB 11 on node:22-trixie-slim. generate-env.sh creates .env with random passwords, same convention as NicTool 2's generate-env.sh. config.js now checks env vars (DB_HOST, DB_PASSWORD, HTTP_HOST, etc.) after parsing TOML -- fully backwards compatible, the TOML values are just defaults now. config tests save/clear/restore the env vars so they should pass in both CI and docker. init-mysql.sh picks up MYSQL_ROOT_PASSWORD when running inside the mariadb container, prefers the mariadb client over mysql, and supports DB_USER/DB_NAME/SQL_DIR env vars. quick start: ./generate-env.sh && docker compose up --build Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 55f22b4 commit bda41e4

8 files changed

Lines changed: 198 additions & 24 deletions

File tree

.dockerignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
node_modules/
2+
.git/
3+
.github/
4+
dist/
5+
*.md
6+
.env
7+
.env.example
8+
.eslintrc*
9+
eslint.config*

.env.example

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# NicTool API environment configuration
2+
# Copy to .env, or run: ./dist/setup/generate-env.sh
3+
4+
# --- Database (MariaDB/MySQL) ---
5+
DB_ROOT_PASSWORD=changeme
6+
NICTOOL_DB_NAME=nictool
7+
NICTOOL_DB_USER=nictool
8+
NICTOOL_DB_USER_PASSWORD=changeme
9+
10+
# --- API config overrides (optional, override conf.d/*.toml defaults) ---
11+
# DB_HOST=127.0.0.1
12+
# DB_PORT=3306
13+
# DB_USER=nictool
14+
# DB_PASSWORD=
15+
# DB_NAME=nictool
16+
# HTTP_HOST=localhost
17+
# HTTP_PORT=3000
18+
19+
# --- Docker Compose port mapping ---
20+
# DB_PORT=3307
21+
# API_PORT=3000

Dockerfile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
FROM node:22-trixie-slim
2+
WORKDIR /app
3+
COPY package*.json .
4+
RUN npm install --omit=dev
5+
COPY . .
6+
EXPOSE 3000
7+
CMD ["node", "server.js"]

docker-compose.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
services:
2+
db:
3+
image: mariadb:11
4+
environment:
5+
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
6+
MYSQL_ROOT_HOST: '%'
7+
MYSQL_DATABASE: ${NICTOOL_DB_NAME:-nictool}
8+
MYSQL_USER: ${NICTOOL_DB_USER:-nictool}
9+
MYSQL_PASSWORD: ${NICTOOL_DB_USER_PASSWORD}
10+
SQL_DIR: /sql
11+
volumes:
12+
- db-data:/var/lib/mysql
13+
- ./sql:/sql:ro
14+
- ./sql/init-mysql.sh:/docker-entrypoint-initdb.d/init-mysql.sh:ro
15+
ports:
16+
- "${DB_PORT:-3307}:3306"
17+
healthcheck:
18+
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
19+
interval: 5s
20+
timeout: 5s
21+
retries: 10
22+
23+
api:
24+
build: .
25+
ports:
26+
- "${API_PORT:-3000}:3000"
27+
depends_on:
28+
db:
29+
condition: service_healthy
30+
environment:
31+
NODE_ENV: development
32+
DB_HOST: db
33+
DB_USER: ${NICTOOL_DB_USER:-nictool}
34+
DB_PASSWORD: ${NICTOOL_DB_USER_PASSWORD}
35+
DB_NAME: ${NICTOOL_DB_NAME:-nictool}
36+
HTTP_HOST: "0.0.0.0"
37+
healthcheck:
38+
test: ["CMD", "node", "-e", "fetch('http://localhost:3000/documentation').then(r=>{if(!r.ok)process.exit(1)}).catch(()=>process.exit(1))"]
39+
interval: 10s
40+
timeout: 5s
41+
retries: 10
42+
start_period: 15s
43+
44+
volumes:
45+
db-data:

generate-env.sh

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/bin/bash
2+
set -e
3+
4+
ENV_FILE="$(cd "$(dirname "$0")/../.." && pwd)/.env"
5+
6+
if [ -f "$ENV_FILE" ]; then
7+
echo ".env already exists, not overwriting." >&2
8+
exit 0
9+
fi
10+
11+
DB_ROOT_PW=$(openssl rand -base64 24)
12+
NT_DB_PW=$(openssl rand -base64 24)
13+
14+
cat > "$ENV_FILE" <<EOF
15+
# NicTool API environment configuration
16+
# Generated by dist/setup/generate-env.sh
17+
18+
# --- Database (MariaDB/MySQL) ---
19+
DB_ROOT_PASSWORD=$DB_ROOT_PW
20+
NICTOOL_DB_NAME=nictool
21+
NICTOOL_DB_USER=nictool
22+
NICTOOL_DB_USER_PASSWORD=$NT_DB_PW
23+
24+
# --- API config overrides (optional, override conf.d/*.toml defaults) ---
25+
# DB_HOST=127.0.0.1
26+
# DB_PORT=3306
27+
# DB_USER=nictool
28+
# DB_PASSWORD=
29+
# DB_NAME=nictool
30+
# HTTP_HOST=localhost
31+
# HTTP_PORT=3000
32+
33+
# --- Docker Compose port mapping ---
34+
# DB_PORT=3307
35+
# API_PORT=3000
36+
EOF
37+
38+
echo "Generated $ENV_FILE with random passwords."

lib/config.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class Config {
1717

1818
const str = await fs.readFile(`./conf.d/${name}.toml`, 'utf8')
1919
const cfg = parse(str)
20+
applyEnvOverrides(name, cfg)
2021
if (this.debug) console.debug(cfg)
2122

2223
if (name === 'http') {
@@ -35,6 +36,7 @@ class Config {
3536

3637
const str = fsSync.readFileSync(`./conf.d/${name}.toml`, 'utf8')
3738
const cfg = parse(str)
39+
applyEnvOverrides(name, cfg)
3840
if (this.debug) console.debug(cfg)
3941

4042
if (name === 'http') {
@@ -47,6 +49,20 @@ class Config {
4749
}
4850
}
4951

52+
function applyEnvOverrides(name, cfg) {
53+
if (name === 'mysql') {
54+
if (process.env.DB_HOST) cfg.host = process.env.DB_HOST
55+
if (process.env.DB_PORT) cfg.port = parseInt(process.env.DB_PORT)
56+
if (process.env.DB_USER) cfg.user = process.env.DB_USER
57+
if (process.env.DB_PASSWORD) cfg.password = process.env.DB_PASSWORD
58+
if (process.env.DB_NAME) cfg.database = process.env.DB_NAME
59+
}
60+
if (name === 'http') {
61+
if (process.env.HTTP_HOST) cfg.host = process.env.HTTP_HOST
62+
if (process.env.HTTP_PORT) cfg.port = parseInt(process.env.HTTP_PORT)
63+
}
64+
}
65+
5066
async function loadPEM(dir) {
5167
let entries
5268
try {

lib/config.test.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,29 @@
11
import assert from 'node:assert/strict'
2-
import { describe, it } from 'node:test'
2+
import { describe, it, before, after } from 'node:test'
33

44
import Config from './config.js'
55

6+
const envOverrideKeys = ['DB_HOST', 'DB_PORT', 'DB_USER', 'DB_PASSWORD', 'DB_NAME', 'HTTP_HOST', 'HTTP_PORT']
7+
68
describe('config', () => {
9+
const savedEnv = {}
10+
11+
before(() => {
12+
for (const key of envOverrideKeys) {
13+
savedEnv[key] = process.env[key]
14+
delete process.env[key]
15+
}
16+
Config.cfg = {}
17+
})
18+
19+
after(() => {
20+
for (const key of envOverrideKeys) {
21+
if (savedEnv[key] !== undefined) process.env[key] = savedEnv[key]
22+
else delete process.env[key]
23+
}
24+
Config.cfg = {}
25+
})
26+
727
describe('get', () => {
828
it(`loads mysql config`, async () => {
929
const cfg = await Config.get('mysql')

sql/init-mysql.sh

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,55 @@
11
#!/bin/sh
22

3+
DB_USER="${DB_USER:-root}"
4+
DB_NAME="${DB_NAME:-nictool}"
35
MYSQL_BIN=""
46

5-
if [ "$MYSQL_PWD" = "" ];
6-
then
7-
export MYSQL_PWD=root
8-
9-
# configure MySQL in the GitHub workflow runners
10-
case "$(uname -s)" in
11-
Linux*)
12-
;;
13-
Darwin*)
14-
MYSQL_BIN=/opt/homebrew/opt/mysql@8.4/bin/
15-
${MYSQL_BIN}mysqladmin --user=root --password='' --protocol=tcp password 'root'
16-
;;
17-
CYGWIN*|MINGW*|MINGW32*|MSYS*)
18-
mysqladmin --user=root --password='' --protocol=tcp password 'root'
19-
# export MYSQL_PWD=""
20-
;;
21-
esac
7+
if [ "$MYSQL_PWD" = "" ]; then
8+
if [ -n "$MYSQL_ROOT_PASSWORD" ]; then
9+
# Docker: MYSQL_ROOT_PASSWORD is set by the MariaDB container
10+
export MYSQL_PWD="$MYSQL_ROOT_PASSWORD"
11+
else
12+
export MYSQL_PWD=root
13+
14+
# configure MySQL in the GitHub workflow runners
15+
case "$(uname -s)" in
16+
Linux*)
17+
;;
18+
Darwin*)
19+
MYSQL_BIN=/opt/homebrew/opt/mysql@8.4/bin/
20+
${MYSQL_BIN}mysqladmin --user=root --password='' --protocol=tcp password 'root'
21+
;;
22+
CYGWIN*|MINGW*|MINGW32*|MSYS*)
23+
mysqladmin --user=root --password='' --protocol=tcp password 'root'
24+
# export MYSQL_PWD=""
25+
;;
26+
esac
27+
fi
2228
fi
2329

24-
# AUTH="--defaults-extra-file=./sql/my-gha.cnf"
30+
if [ -z "$MYSQL_CMD" ]; then
31+
# prefer mariadb client if available (MariaDB 11+ dropped the mysql symlink)
32+
if [ -z "$MYSQL_BIN" ] && command -v mariadb >/dev/null 2>&1; then
33+
MYSQL_CMD="mariadb --user=$DB_USER"
34+
elif [ -n "$MYSQL_BIN" ]; then
35+
MYSQL_CMD="${MYSQL_BIN}mysql --user=$DB_USER"
36+
else
37+
MYSQL_CMD="mysql --user=$DB_USER"
38+
fi
39+
fi
2540

2641
if [ "$1" = "drop" ]; then
27-
${MYSQL_BIN}mysql --user=root -e 'DROP DATABASE IF EXISTS nictool;' || exit 1
42+
$MYSQL_CMD -e "DROP DATABASE IF EXISTS $DB_NAME;" || exit 1
2843
fi
29-
${MYSQL_BIN}mysql --user=root -e 'CREATE DATABASE nictool;' || exit 1
3044

31-
for f in ./sql/*.sql;
45+
$MYSQL_CMD -e "CREATE DATABASE IF NOT EXISTS $DB_NAME;" || exit 1
46+
47+
SQL_DIR="${SQL_DIR:-./sql}"
48+
49+
for f in "$SQL_DIR"/*.sql;
3250
do
33-
echo "cat $f | ${MYSQL_BIN}mysql nictool"
34-
cat $f | ${MYSQL_BIN}mysql --user=root nictool || exit 1
51+
echo "$f"
52+
$MYSQL_CMD "$DB_NAME" < "$f" || exit 1
3553
done
3654

3755
exit 0

0 commit comments

Comments
 (0)