Initial commit: SQL Server 2025 local dev stack
This commit is contained in:
11
.env.example
Normal file
11
.env.example
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
# Copy to .env and adjust values.
|
||||||
|
# IMPORTANT: Use a strong SA password (complexity rules apply).
|
||||||
|
MSSQL_SA_PASSWORD=ChangeMe_UseARealP@ssw0rd!
|
||||||
|
|
||||||
|
# Optional: host port (default 1433)
|
||||||
|
MSSQL_PORT=1433
|
||||||
|
|
||||||
|
# IMPORTANT (SQL Server 2025):
|
||||||
|
# Valid examples: Developer, DeveloperStandard, Express, Evaluation, Standard, Enterprise
|
||||||
|
# For local dev: Developer (free, non-prod)
|
||||||
|
MSSQL_PID=Developer
|
||||||
20
.gitea/ISSUE_TEMPLATE/bug.yml
Normal file
20
.gitea/ISSUE_TEMPLATE/bug.yml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
name: Bug report
|
||||||
|
description: Report a problem
|
||||||
|
title: "[bug] "
|
||||||
|
labels: ["bug"]
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: what
|
||||||
|
attributes:
|
||||||
|
label: What happened?
|
||||||
|
description: What did you expect to happen?
|
||||||
|
placeholder: Steps, logs, screenshots...
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: repro
|
||||||
|
attributes:
|
||||||
|
label: Reproduction
|
||||||
|
description: Minimal steps to reproduce
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
12
.gitea/ISSUE_TEMPLATE/feature.yml
Normal file
12
.gitea/ISSUE_TEMPLATE/feature.yml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
name: Feature request
|
||||||
|
description: Suggest an improvement
|
||||||
|
title: "[feat] "
|
||||||
|
labels: ["enhancement"]
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: idea
|
||||||
|
attributes:
|
||||||
|
label: Idea
|
||||||
|
description: What should we add/change?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
11
.gitea/PULL_REQUEST_TEMPLATE/default.md
Normal file
11
.gitea/PULL_REQUEST_TEMPLATE/default.md
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
## What
|
||||||
|
Describe what this PR changes.
|
||||||
|
|
||||||
|
## Why
|
||||||
|
Why is this change needed?
|
||||||
|
|
||||||
|
## How to test
|
||||||
|
- [ ] `./scripts/start.sh`
|
||||||
|
- [ ] `./scripts/seed.sh`
|
||||||
|
- [ ] `./scripts/migrate.sh`
|
||||||
|
- [ ] T-SQL smoke test in VS Code
|
||||||
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# --- SQL Server container persistent data (local only) ---
|
||||||
|
data/
|
||||||
|
backup/
|
||||||
|
|
||||||
|
# --- secrets / local overrides ---
|
||||||
|
.env
|
||||||
|
|
||||||
|
# --- editor / OS ---
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# --- logs ---
|
||||||
|
*.log
|
||||||
67
README.md
Normal file
67
README.md
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# SQL Server 2025 on Arch Linux (Docker) — localdev-ready
|
||||||
|
|
||||||
|
This repo provides a reproducible local SQL Server 2025 setup using Docker Compose,
|
||||||
|
plus convenience scripts for start/stop and database provisioning ("betanken").
|
||||||
|
|
||||||
|
## Layout / Mini Convention
|
||||||
|
|
||||||
|
- `compose.yml`
|
||||||
|
Docker Compose definition (SQL Server 2025 container).
|
||||||
|
- `.env.example` → copy to `.env` (not committed)
|
||||||
|
Holds your `MSSQL_SA_PASSWORD` and optional settings (port, edition).
|
||||||
|
- `scripts/`
|
||||||
|
Convenience scripts:
|
||||||
|
- `start.sh` — start container and wait until healthy
|
||||||
|
- `stop.sh` — stop container
|
||||||
|
- `down.sh` — remove container (keeps data on disk)
|
||||||
|
- `seed.sh` — run **all** `db/seed/*.sql` in order
|
||||||
|
- `migrate.sh` — run **all** `db/migrations/*.sql` in order
|
||||||
|
- `reset.sh` — delete `./data` (DESTROYS DB files), then start + seed + migrate
|
||||||
|
- `db/seed/` (idempotent)
|
||||||
|
Scripts that can be re-run safely. Typical content:
|
||||||
|
- create database(s) if missing
|
||||||
|
- create logins/users if missing
|
||||||
|
- minimal reference data
|
||||||
|
Naming: `001_*.sql`, `010_*.sql`, ...
|
||||||
|
- `db/migrations/` (forward-only)
|
||||||
|
Schema/data changes over time. Each file should be **run exactly once** per environment.
|
||||||
|
Naming: `2026-01-30_001_create_tables.sql`, `2026-02-05_002_add_index.sql`, ...
|
||||||
|
- `data/` (gitignored)
|
||||||
|
SQL Server data files mounted from the host.
|
||||||
|
- `backup/` (gitignored)
|
||||||
|
Place `.bak` files here if you want to restore from backups.
|
||||||
|
|
||||||
|
### Recommended workflow
|
||||||
|
|
||||||
|
1. Copy env:
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
# edit .env (set strong password!)
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Start server:
|
||||||
|
```bash
|
||||||
|
./scripts/start.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Provision baseline:
|
||||||
|
```bash
|
||||||
|
./scripts/seed.sh
|
||||||
|
./scripts/migrate.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Work with T-SQL (VS Code):
|
||||||
|
- Install extension: **SQL Server (mssql)** (Microsoft)
|
||||||
|
- Connect:
|
||||||
|
- Server: `localhost`
|
||||||
|
- Port: value from `MSSQL_PORT` (default 1433)
|
||||||
|
- Auth: SQL Login
|
||||||
|
- User: `sa`
|
||||||
|
- Password: `MSSQL_SA_PASSWORD`
|
||||||
|
- Open any `.sql` file and run **Execute Query**.
|
||||||
|
|
||||||
|
## Gitea-ready notes
|
||||||
|
|
||||||
|
- `.env` is ignored (secrets).
|
||||||
|
- `data/` and `backup/` are ignored (local artifacts).
|
||||||
|
- Use the issue/PR templates under `.gitea/` for consistent collaboration.
|
||||||
35
compose.yml
Normal file
35
compose.yml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
services:
|
||||||
|
mssql:
|
||||||
|
image: mcr.microsoft.com/mssql/server:2025-RTM-ubuntu-22.04
|
||||||
|
container_name: mssql2025
|
||||||
|
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
|
||||||
|
environment:
|
||||||
|
ACCEPT_EULA: "Y"
|
||||||
|
# IMPORTANT (SQL Server 2025):
|
||||||
|
# Use "Developer" or "DeveloperStandard" (NOT "Enterprise Developer")
|
||||||
|
MSSQL_PID: "${MSSQL_PID:-Developer}"
|
||||||
|
|
||||||
|
# Workaround for CPU topology/NUMA assert (sosnumap.cpp)
|
||||||
|
cpuset: "0-15"
|
||||||
|
cpus: "16.0"
|
||||||
|
|
||||||
|
ports:
|
||||||
|
- "${MSSQL_PORT:-1433}:1433"
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- ./data:/var/opt/mssql
|
||||||
|
- ./backup:/var/opt/mssql/backup
|
||||||
|
- ./db/seed:/seed:ro
|
||||||
|
- ./db/migrations:/migrations:ro
|
||||||
|
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P \"$$MSSQL_SA_PASSWORD\" -Q \"SELECT 1\" || exit 1"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 30
|
||||||
|
start_period: 25s
|
||||||
|
|
||||||
|
restart: unless-stopped
|
||||||
12
db/migrations/2026-01-30_001_create_customer.sql
Normal file
12
db/migrations/2026-01-30_001_create_customer.sql
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
USE DevDb;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF OBJECT_ID('dbo.Customer', 'U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.Customer(
|
||||||
|
CustomerId int IDENTITY(1,1) NOT NULL PRIMARY KEY,
|
||||||
|
Name nvarchar(200) NOT NULL,
|
||||||
|
CreatedAt datetime2 NOT NULL CONSTRAINT DF_Customer_CreatedAt DEFAULT (sysutcdatetime())
|
||||||
|
);
|
||||||
|
END
|
||||||
|
GO
|
||||||
5
db/seed/001_create_db.sql
Normal file
5
db/seed/001_create_db.sql
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
IF DB_ID('DevDb') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE DATABASE DevDb;
|
||||||
|
END
|
||||||
|
GO
|
||||||
9
db/seed/020_seed_customers.sql
Normal file
9
db/seed/020_seed_customers.sql
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
USE DevDb;
|
||||||
|
GO
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.Customer WHERE Name = N'Alice')
|
||||||
|
INSERT INTO dbo.Customer(Name) VALUES (N'Alice');
|
||||||
|
|
||||||
|
IF NOT EXISTS (SELECT 1 FROM dbo.Customer WHERE Name = N'Bob')
|
||||||
|
INSERT INTO dbo.Customer(Name) VALUES (N'Bob');
|
||||||
|
GO
|
||||||
4
scripts/down.sh
Executable file
4
scripts/down.sh
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
docker compose down
|
||||||
36
scripts/fix-perms.sh
Executable file
36
scripts/fix-perms.sh
Executable file
@@ -0,0 +1,36 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
ts() { date +"%Y-%m-%d %H:%M:%S"; }
|
||||||
|
log() { echo "[$(ts)] [INFO] $*"; }
|
||||||
|
warn(){ echo "[$(ts)] [WARN] $*" >&2; }
|
||||||
|
err() { echo "[$(ts)] [ERROR] $*" >&2; }
|
||||||
|
die() { err "$*"; exit 1; }
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
if ! command -v docker >/dev/null 2>&1; then
|
||||||
|
die "docker is not installed or not in PATH."
|
||||||
|
fi
|
||||||
|
if ! docker info >/dev/null 2>&1; then
|
||||||
|
die "docker daemon not reachable. Start docker service first."
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Stopping/removing any existing stack (safe to run repeatedly)..."
|
||||||
|
# Not fatal if nothing is running
|
||||||
|
docker compose down --remove-orphans >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
# Additionally remove a stray container with the same name (outside this compose project)
|
||||||
|
if docker ps -a --format '{{.Names}}' | grep -qx 'mssql2025'; then
|
||||||
|
warn "Found stray container named mssql2025; removing it..."
|
||||||
|
docker rm -f mssql2025 >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Ensuring ./data and ./backup exist..."
|
||||||
|
mkdir -p ./data ./backup
|
||||||
|
|
||||||
|
log "Fixing permissions for ./data and ./backup (UID 10001)..."
|
||||||
|
sudo chown -R 10001:0 ./data ./backup
|
||||||
|
sudo chmod -R ug+rwx ./data ./backup
|
||||||
|
|
||||||
|
log "Permissions fixed ✅"
|
||||||
93
scripts/migrate.sh
Executable file
93
scripts/migrate.sh
Executable file
@@ -0,0 +1,93 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
ts() { date +"%Y-%m-%d %H:%M:%S"; }
|
||||||
|
log() { echo "[$(ts)] [INFO] $*"; }
|
||||||
|
warn(){ echo "[$(ts)] [WARN] $*" >&2; }
|
||||||
|
err() { echo "[$(ts)] [ERROR] $*" >&2; }
|
||||||
|
die() { err "$*"; exit 1; }
|
||||||
|
|
||||||
|
trap 'err "Migration failed. Showing last 200 log lines:"; docker logs --tail=200 mssql2025 2>/dev/null || true' ERR
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
[[ -f .env ]] || die "Missing .env. Create it first: cp .env.example .env"
|
||||||
|
|
||||||
|
set -a
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source .env
|
||||||
|
set +a
|
||||||
|
|
||||||
|
[[ -n "${MSSQL_SA_PASSWORD:-}" ]] || die "MSSQL_SA_PASSWORD is empty in .env"
|
||||||
|
|
||||||
|
if ! docker ps --format '{{.Names}}' | grep -qx 'mssql2025'; then
|
||||||
|
die "Container mssql2025 is not running. Start it first: ./scripts/start.sh"
|
||||||
|
fi
|
||||||
|
|
||||||
|
health="$(docker inspect -f '{{.State.Health.Status}}' mssql2025 2>/dev/null || true)"
|
||||||
|
[[ "$health" == "healthy" ]] || die "Container is not healthy (health=$health). Run ./scripts/start.sh"
|
||||||
|
|
||||||
|
# Bootstrap: ensure DevDb + migration table exists
|
||||||
|
bootstrap_sql=$(
|
||||||
|
cat <<'SQL'
|
||||||
|
IF DB_ID('DevDb') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE DATABASE DevDb;
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
USE DevDb;
|
||||||
|
GO
|
||||||
|
IF OBJECT_ID('dbo.__SchemaMigrations','U') IS NULL
|
||||||
|
BEGIN
|
||||||
|
CREATE TABLE dbo.__SchemaMigrations(
|
||||||
|
Id int IDENTITY(1,1) NOT NULL PRIMARY KEY,
|
||||||
|
Filename nvarchar(260) NOT NULL UNIQUE,
|
||||||
|
AppliedAt datetime2 NOT NULL CONSTRAINT DF___SchemaMigrations_AppliedAt DEFAULT (sysutcdatetime())
|
||||||
|
);
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
SQL
|
||||||
|
)
|
||||||
|
|
||||||
|
log "Ensuring DevDb + dbo.__SchemaMigrations exist..."
|
||||||
|
docker exec -i mssql2025 /opt/mssql-tools18/bin/sqlcmd \
|
||||||
|
-S localhost -U sa -P "$MSSQL_SA_PASSWORD" \
|
||||||
|
-b -Q "$bootstrap_sql" < /dev/null
|
||||||
|
|
||||||
|
shopt -s nullglob
|
||||||
|
files=(db/migrations/*.sql)
|
||||||
|
|
||||||
|
if (( ${#files[@]} == 0 )); then
|
||||||
|
warn "No db/migrations/*.sql files found. Nothing to do."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Applying migrations (forward-only, each file once)."
|
||||||
|
for f in "${files[@]}"; do
|
||||||
|
bn="$(basename "$f")"
|
||||||
|
log "-> migration candidate: $bn"
|
||||||
|
|
||||||
|
check_sql="SET NOCOUNT ON; USE DevDb; SELECT COUNT(1) FROM dbo.__SchemaMigrations WHERE Filename = N'$bn';"
|
||||||
|
applied="$(docker exec -i mssql2025 /opt/mssql-tools18/bin/sqlcmd \
|
||||||
|
-S localhost -U sa -P "$MSSQL_SA_PASSWORD" -h -1 -W -Q "$check_sql" < /dev/null \
|
||||||
|
| tr -d '\r' | tail -n 1 | xargs || true)"
|
||||||
|
|
||||||
|
if [[ "${applied:-0}" != "0" ]]; then
|
||||||
|
log " (skip) already applied"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
log " (apply) running /migrations/$bn"
|
||||||
|
docker exec -i mssql2025 /opt/mssql-tools18/bin/sqlcmd \
|
||||||
|
-S localhost -U sa -P "$MSSQL_SA_PASSWORD" \
|
||||||
|
-b -i "/migrations/$bn" < /dev/null
|
||||||
|
|
||||||
|
record_sql="USE DevDb; INSERT INTO dbo.__SchemaMigrations(Filename) VALUES (N'$bn');"
|
||||||
|
docker exec -i mssql2025 /opt/mssql-tools18/bin/sqlcmd \
|
||||||
|
-S localhost -U sa -P "$MSSQL_SA_PASSWORD" \
|
||||||
|
-b -Q "$record_sql" < /dev/null
|
||||||
|
|
||||||
|
log " applied ✅"
|
||||||
|
done
|
||||||
|
|
||||||
|
log "Migrations completed ✅"
|
||||||
14
scripts/reset.sh
Executable file
14
scripts/reset.sh
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
echo "Stopping & removing container..."
|
||||||
|
docker compose down || true
|
||||||
|
|
||||||
|
echo "Deleting ./data (THIS REMOVES ALL DB DATA)..."
|
||||||
|
rm -rf ./data
|
||||||
|
mkdir -p ./data
|
||||||
|
|
||||||
|
./scripts/start.sh
|
||||||
|
./scripts/seed.sh
|
||||||
|
./scripts/migrate.sh
|
||||||
23
scripts/restart.sh
Executable file
23
scripts/restart.sh
Executable file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
ts() { date +"%Y-%m-%d %H:%M:%S"; }
|
||||||
|
log() { echo "[$(ts)] [INFO] $*"; }
|
||||||
|
warn(){ echo "[$(ts)] [WARN] $*" >&2; }
|
||||||
|
err() { echo "[$(ts)] [ERROR] $*" >&2; }
|
||||||
|
die() { err "$*"; exit 1; }
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
[[ -x ./scripts/start.sh ]] || die "Missing ./scripts/start.sh"
|
||||||
|
[[ -x ./scripts/fix-perms.sh ]] || die "Missing ./scripts/fix-perms.sh"
|
||||||
|
|
||||||
|
log "Restarting SQL Server stack (fresh recreate)..."
|
||||||
|
|
||||||
|
# Make sure perms are correct and old instance is cleaned
|
||||||
|
./scripts/fix-perms.sh
|
||||||
|
|
||||||
|
# Start fresh
|
||||||
|
./scripts/start.sh
|
||||||
|
|
||||||
|
log "Restart completed ✅"
|
||||||
47
scripts/seed.sh
Executable file
47
scripts/seed.sh
Executable file
@@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
ts() { date +"%Y-%m-%d %H:%M:%S"; }
|
||||||
|
log() { echo "[$(ts)] [INFO] $*"; }
|
||||||
|
warn(){ echo "[$(ts)] [WARN] $*" >&2; }
|
||||||
|
err() { echo "[$(ts)] [ERROR] $*" >&2; }
|
||||||
|
die() { err "$*"; exit 1; }
|
||||||
|
|
||||||
|
trap 'err "Seed failed. Showing last 200 log lines:"; docker logs --tail=200 mssql2025 2>/dev/null || true' ERR
|
||||||
|
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
[[ -f .env ]] || die "Missing .env. Create it first: cp .env.example .env"
|
||||||
|
|
||||||
|
set -a
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source .env
|
||||||
|
set +a
|
||||||
|
|
||||||
|
[[ -n "${MSSQL_SA_PASSWORD:-}" ]] || die "MSSQL_SA_PASSWORD is empty in .env"
|
||||||
|
|
||||||
|
if ! docker ps --format '{{.Names}}' | grep -qx 'mssql2025'; then
|
||||||
|
die "Container mssql2025 is not running. Start it first: ./scripts/start.sh"
|
||||||
|
fi
|
||||||
|
|
||||||
|
health="$(docker inspect -f '{{.State.Health.Status}}' mssql2025 2>/dev/null || true)"
|
||||||
|
[[ "$health" == "healthy" ]] || die "Container is not healthy (health=$health). Run ./scripts/start.sh"
|
||||||
|
|
||||||
|
shopt -s nullglob
|
||||||
|
files=(db/seed/*.sql)
|
||||||
|
|
||||||
|
if (( ${#files[@]} == 0 )); then
|
||||||
|
warn "No db/seed/*.sql files found. Nothing to do."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Executing ${#files[@]} seed file(s) (idempotent)."
|
||||||
|
for f in "${files[@]}"; do
|
||||||
|
bn="$(basename "$f")"
|
||||||
|
log "-> seed: $bn"
|
||||||
|
docker exec -i mssql2025 /opt/mssql-tools18/bin/sqlcmd \
|
||||||
|
-S localhost -U sa -P "$MSSQL_SA_PASSWORD" \
|
||||||
|
-b -i "/seed/$bn" < /dev/null
|
||||||
|
done
|
||||||
|
|
||||||
|
log "Seed completed ✅"
|
||||||
117
scripts/start.sh
Executable file
117
scripts/start.sh
Executable file
@@ -0,0 +1,117 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -Eeuo pipefail
|
||||||
|
|
||||||
|
# ---------- logging helpers ----------
|
||||||
|
ts() { date +"%Y-%m-%d %H:%M:%S"; }
|
||||||
|
log() { echo "[$(ts)] [INFO] $*"; }
|
||||||
|
warn(){ echo "[$(ts)] [WARN] $*" >&2; }
|
||||||
|
err() { echo "[$(ts)] [ERROR] $*" >&2; }
|
||||||
|
die() { err "$*"; exit 1; }
|
||||||
|
|
||||||
|
dump_diag() {
|
||||||
|
warn "Diagnostics:"
|
||||||
|
docker compose ps || true
|
||||||
|
echo
|
||||||
|
warn "docker ps (name=mssql2025):"
|
||||||
|
docker ps --filter "name=mssql2025" || true
|
||||||
|
echo
|
||||||
|
warn "Last 250 lines of container logs (mssql2025):"
|
||||||
|
docker logs --tail=250 mssql2025 2>/dev/null || warn "No logs available."
|
||||||
|
}
|
||||||
|
|
||||||
|
on_error() {
|
||||||
|
local exit_code=$?
|
||||||
|
err "Start failed (exit code=$exit_code)."
|
||||||
|
dump_diag
|
||||||
|
exit "$exit_code"
|
||||||
|
}
|
||||||
|
trap on_error ERR
|
||||||
|
|
||||||
|
# ---------- go to repo root ----------
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
# ---------- preflight ----------
|
||||||
|
command -v docker >/dev/null 2>&1 || die "docker is not installed or not in PATH."
|
||||||
|
docker info >/dev/null 2>&1 || die "docker daemon not reachable. Start docker service (sudo systemctl start docker)."
|
||||||
|
|
||||||
|
[[ -f .env ]] || die "Missing .env. Create it first: cp .env.example .env (then edit MSSQL_SA_PASSWORD)."
|
||||||
|
|
||||||
|
# Load .env
|
||||||
|
set -a
|
||||||
|
# shellcheck disable=SC1091
|
||||||
|
source .env
|
||||||
|
set +a
|
||||||
|
|
||||||
|
[[ -n "${MSSQL_SA_PASSWORD:-}" ]] || die "MSSQL_SA_PASSWORD is empty in .env"
|
||||||
|
|
||||||
|
# Validate MSSQL_PID for SQL Server 2025 containers
|
||||||
|
case "${MSSQL_PID:-Developer}" in
|
||||||
|
"Enterprise Developer"|"EnterpriseDeveloper"|"Enterprise_Developer")
|
||||||
|
cat >&2 <<'EOF'
|
||||||
|
[ERROR] MSSQL_PID is set to "Enterprise Developer", which SQL Server 2025 containers reject.
|
||||||
|
Fix: edit .env and set:
|
||||||
|
MSSQL_PID=Developer
|
||||||
|
(or: DeveloperStandard)
|
||||||
|
EOF
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
Developer|DeveloperStandard|Express|Evaluation|Standard|Enterprise)
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
warn "MSSQL_PID='${MSSQL_PID}' is unusual. If SQL Server errors about PID format, set MSSQL_PID=Developer."
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
log "Hard-resetting previous instance (if any)..."
|
||||||
|
# Bring down compose stack (remove orphans), but do NOT delete volumes because we use bind mounts (./data)
|
||||||
|
docker compose down --remove-orphans >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
# If there is a stray container with same name, kill it too (outside compose)
|
||||||
|
if docker ps -a --format '{{.Names}}' | grep -qx 'mssql2025'; then
|
||||||
|
warn "Found stray container named mssql2025; removing it..."
|
||||||
|
docker rm -f mssql2025 >/dev/null 2>&1 || true
|
||||||
|
fi
|
||||||
|
|
||||||
|
log "Starting fresh container..."
|
||||||
|
log "Settings: port=${MSSQL_PORT:-1433}, pid=${MSSQL_PID:-Developer}, cpuset=0-7, cpus=8.0"
|
||||||
|
|
||||||
|
# Ensure image is present (optional but nice)
|
||||||
|
docker compose pull >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
# Force recreate every time to avoid “stuck” states
|
||||||
|
docker compose up -d --force-recreate --remove-orphans
|
||||||
|
|
||||||
|
# ---------- wait for healthy ----------
|
||||||
|
log "Waiting for container healthcheck to become healthy..."
|
||||||
|
max_seconds=180
|
||||||
|
sleep_step=2
|
||||||
|
elapsed=0
|
||||||
|
|
||||||
|
while (( elapsed < max_seconds )); do
|
||||||
|
state="$(docker inspect -f '{{.State.Status}}' mssql2025 2>/dev/null || true)"
|
||||||
|
health="$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}no-healthcheck{{end}}' mssql2025 2>/dev/null || true)"
|
||||||
|
|
||||||
|
printf "\r[%s] state=%-10s health=%-12s elapsed=%3ss/%ss" "$(ts)" "${state:-unknown}" "${health:-unknown}" "$elapsed" "$max_seconds"
|
||||||
|
|
||||||
|
if [[ "$health" == "healthy" ]]; then
|
||||||
|
echo
|
||||||
|
log "SQL Server is healthy ✅"
|
||||||
|
log "Connect: localhost,${MSSQL_PORT:-1433} (user: sa)"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$state" == "exited" ]]; then
|
||||||
|
echo
|
||||||
|
err "Container exited before becoming healthy."
|
||||||
|
dump_diag
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep "$sleep_step"
|
||||||
|
elapsed=$((elapsed + sleep_step))
|
||||||
|
done
|
||||||
|
|
||||||
|
echo
|
||||||
|
err "Timed out waiting for SQL Server to become healthy (${max_seconds}s)."
|
||||||
|
dump_diag
|
||||||
|
exit 1
|
||||||
4
scripts/stop.sh
Executable file
4
scripts/stop.sh
Executable file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
docker compose stop
|
||||||
Reference in New Issue
Block a user