Add Docker and SQL Server container phases
This commit is contained in:
parent
4655260986
commit
d07b5cc575
1
.gitignore
vendored
1
.gitignore
vendored
@ -3,6 +3,7 @@
|
||||
.env.*
|
||||
!.env.example
|
||||
!.env.*.example
|
||||
!*.env.example
|
||||
*.secret
|
||||
*.secrets
|
||||
secrets/
|
||||
|
||||
@ -87,8 +87,8 @@ Future component installation scripts will live under `install/phases/`. They wi
|
||||
```text
|
||||
install/phases/10-dotnet.sh
|
||||
install/phases/20-sql-server.sh
|
||||
install/phases/30-nginx.sh
|
||||
install/phases/40-docker.sh
|
||||
install/phases/30-docker.sh
|
||||
install/phases/40-nginx.sh
|
||||
```
|
||||
|
||||
These component scripts do not exist yet. Until they do, rebuild steps remain manual and are documented in [docs/REBUILD-GUIDE.md](docs/REBUILD-GUIDE.md).
|
||||
|
||||
@ -32,12 +32,15 @@ Current planned areas include:
|
||||
|
||||
```text
|
||||
config/nginx/
|
||||
config/sql-server/
|
||||
config/sqlserver/
|
||||
config/systemd/
|
||||
```
|
||||
|
||||
Additional service directories may be added when those services are implemented.
|
||||
|
||||
`config/sql-server/` contains the version-controlled Docker Compose definition and safe example environment file for the SQL Server container. The live environment file is stored outside the repository at `/etc/atlas/sql-server.env`.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
Configuration templates must not contain live secrets. Secret-bearing live files should be stored outside the repository and referenced by documented paths or environment variable names.
|
||||
|
||||
29
config/sql-server/compose.yml
Normal file
29
config/sql-server/compose.yml
Normal file
@ -0,0 +1,29 @@
|
||||
name: atlas-sql
|
||||
|
||||
services:
|
||||
sql-server:
|
||||
image: mcr.microsoft.com/mssql/server:2025-CU1-ubuntu-24.04
|
||||
container_name: atlas-sql-server
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- /etc/atlas/sql-server.env
|
||||
ports:
|
||||
- "1433:1433"
|
||||
volumes:
|
||||
- /sql/mssql:/var/opt/mssql
|
||||
healthcheck:
|
||||
test:
|
||||
[
|
||||
"CMD-SHELL",
|
||||
"/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P \"$${MSSQL_SA_PASSWORD}\" -C -Q \"SELECT 1\" -b -o /dev/null"
|
||||
]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 18
|
||||
start_period: 30s
|
||||
logging:
|
||||
driver: json-file
|
||||
options:
|
||||
max-size: "10m"
|
||||
max-file: "5"
|
||||
|
||||
11
config/sql-server/sql-server.env.example
Normal file
11
config/sql-server/sql-server.env.example
Normal file
@ -0,0 +1,11 @@
|
||||
# Example only. Do not put live SQL credentials in this repository.
|
||||
#
|
||||
# The live file is created at:
|
||||
# /etc/atlas/sql-server.env
|
||||
#
|
||||
# Required live variables:
|
||||
ACCEPT_EULA=Y
|
||||
MSSQL_PID=Developer
|
||||
MSSQL_TCP_PORT=1433
|
||||
MSSQL_SA_PASSWORD=CHANGE_ME_DO_NOT_COMMIT
|
||||
|
||||
@ -106,15 +106,30 @@ nginx installation, site layout, TLS certificate handling, and exposure rules ar
|
||||
|
||||
### Role of SQL Server
|
||||
|
||||
SQL Server is planned as the relational database platform for applications that require it. `/sql` is the intended data volume.
|
||||
Native SQL Server packages are not currently used because Microsoft does not publish a supported native SQL Server repository for Ubuntu 26.04. Atlas runs SQL Server 2025 Developer through Microsoft's official Linux container image instead.
|
||||
|
||||
SQL Server version, data paths, backup paths, service account model, authentication policy, maintenance jobs, and restore process are `TODO: Confirm during the relevant implementation phase.`
|
||||
- Compose project: `atlas-sql`
|
||||
- Container: `atlas-sql-server`
|
||||
- Image: `mcr.microsoft.com/mssql/server:2025-CU1-ubuntu-24.04`
|
||||
- Digest: `sha256:698682bab57c02c42bc0aa274b158aeb242d8e9104149a7489628d5535805816`
|
||||
- Edition: SQL Server 2025 Developer
|
||||
- Product version: `17.0.4006.2`
|
||||
- Persistent storage: `/sql/mssql`
|
||||
- Secret environment file: `/etc/atlas/sql-server.env`
|
||||
|
||||
The live SQL administrator password is not stored in Git.
|
||||
|
||||
### Role of Docker
|
||||
|
||||
Docker is planned as a possible application runtime and tooling boundary. It should be introduced only after the runtime policy is documented.
|
||||
Docker Engine is installed from Docker's official signed Ubuntu APT repository for `resolute`.
|
||||
|
||||
Docker installation method, user access policy, network policy, volume locations, image retention, and compose layout are `TODO: Confirm during the relevant implementation phase.`
|
||||
- Docker Engine: `29.6.1`
|
||||
- Docker Compose plugin: `v5.3.1`
|
||||
- Docker Buildx plugin: `v0.35.0`
|
||||
- containerd.io: `2.2.5`
|
||||
- User `nick` is a member of the `docker` group.
|
||||
|
||||
A new login may be required before all shells pick up Docker group membership.
|
||||
|
||||
## Separation of Concerns
|
||||
|
||||
@ -128,6 +143,7 @@ The intended storage and responsibility model is:
|
||||
- Build artefacts: `TODO: Confirm during the relevant implementation phase.`
|
||||
- Deployed applications: `TODO: Confirm during the relevant implementation phase.`
|
||||
- SQL data: `/sql`
|
||||
- SQL Server container storage: `/sql/mssql`
|
||||
- Backups: `TODO: Confirm during the relevant implementation phase.`
|
||||
- Temporary files: system temporary locations or script-created `mktemp` paths
|
||||
- Live secrets: outside this repository
|
||||
|
||||
@ -10,6 +10,61 @@ This file records chronological Atlas build and infrastructure milestones. Do no
|
||||
|
||||
### Phase
|
||||
|
||||
Phase 2 Docker and SQL Server container platform.
|
||||
|
||||
### Objective
|
||||
|
||||
Install Docker Engine and run SQL Server 2025 Developer using Microsoft's official container image with persistent storage on `/sql`.
|
||||
|
||||
### Changes
|
||||
|
||||
- Added `install/phases/30-docker.sh`.
|
||||
- Installed Docker Engine `29.6.1` from Docker's official Ubuntu `resolute` APT repository.
|
||||
- Installed Docker Compose plugin `v5.3.1` and Docker Buildx plugin `v0.35.0`.
|
||||
- Added user `nick` to the `docker` group.
|
||||
- Added `install/phases/20-sql-server.sh`.
|
||||
- Added SQL Server Compose configuration under `config/sql-server/`.
|
||||
- Added `scripts/sql/sqlcmd.sh`.
|
||||
- Deployed SQL Server 2025 Developer container `atlas-sql-server`.
|
||||
- Stored SQL Server data under `/sql/mssql`.
|
||||
- Stored live SQL Server environment secrets outside Git at `/etc/atlas/sql-server.env`.
|
||||
|
||||
### Validation
|
||||
|
||||
- Docker hello-world test passed.
|
||||
- Docker non-root validation passed with `sg docker`.
|
||||
- SQL Server container reached healthy state.
|
||||
- SQL version and edition queries passed.
|
||||
- Temporary database create, insert, read, and drop test passed.
|
||||
- Container restart and Docker service restart tests passed.
|
||||
- Container recreation with persistent storage passed.
|
||||
- SQL installer idempotency passed after correcting protected env-file detection.
|
||||
|
||||
### Problems
|
||||
|
||||
- Native SQL Server packages are not supported for Ubuntu 26.04, so the official container route was used.
|
||||
- Initial SQL container recreation exposed an env-file permissions issue. The installer now checks the protected env file through sudo.
|
||||
|
||||
### Decisions
|
||||
|
||||
- Use Docker's official APT repository rather than Ubuntu `docker.io`.
|
||||
- Use SQL Server image `mcr.microsoft.com/mssql/server:2025-CU1-ubuntu-24.04`.
|
||||
- Bind SQL Server port `1433` for LAN development access without changing UFW or router rules.
|
||||
|
||||
### Git Commit
|
||||
|
||||
Not yet committed.
|
||||
|
||||
### Operator
|
||||
|
||||
Codex.
|
||||
|
||||
### Date
|
||||
|
||||
2026-07-10
|
||||
|
||||
### Phase
|
||||
|
||||
Phase 2 .NET development platform.
|
||||
|
||||
### Objective
|
||||
|
||||
@ -9,6 +9,8 @@ This guide describes the planned rebuild path for Atlas. At Phase 1, the reposit
|
||||
- System discovery script: present.
|
||||
- Host validation script: present.
|
||||
- .NET installation script: present.
|
||||
- Docker installation script: present.
|
||||
- SQL Server container installation script: present.
|
||||
- Other component installation scripts: not yet created.
|
||||
- Configuration templates: not yet created.
|
||||
- Operational scripts: not yet created.
|
||||
@ -114,8 +116,10 @@ Planned bootstrap flow:
|
||||
5. Run `scripts/system/validate-host.sh`.
|
||||
6. Run `install/bootstrap.sh --dry-run`.
|
||||
7. Run `install/phases/10-dotnet.sh` when .NET SDKs are required.
|
||||
8. Run additional ordered installation scripts from `install/phases/` when they exist.
|
||||
9. Run smoke tests from `tests/` when they exist.
|
||||
8. Run `install/phases/30-docker.sh`.
|
||||
9. Run `install/phases/20-sql-server.sh` after Docker is available.
|
||||
10. Run additional ordered installation scripts from `install/phases/` when they exist.
|
||||
11. Run smoke tests from `tests/` when they exist.
|
||||
|
||||
Bootstrap command sequence is `TODO: Confirm during the relevant implementation phase.`
|
||||
|
||||
@ -126,10 +130,30 @@ Planned scripts may include:
|
||||
```text
|
||||
install/phases/10-dotnet.sh
|
||||
install/phases/20-sql-server.sh
|
||||
install/phases/30-nginx.sh
|
||||
install/phases/40-docker.sh
|
||||
install/phases/30-docker.sh
|
||||
install/phases/40-nginx.sh
|
||||
```
|
||||
|
||||
Current Docker and SQL Server commands:
|
||||
|
||||
```bash
|
||||
install/phases/30-docker.sh
|
||||
install/phases/20-sql-server.sh
|
||||
```
|
||||
|
||||
SQL Server operations:
|
||||
|
||||
```bash
|
||||
sudo docker compose -p atlas-sql -f config/sql-server/compose.yml up -d
|
||||
sudo docker compose -p atlas-sql -f config/sql-server/compose.yml stop
|
||||
sudo docker compose -p atlas-sql -f config/sql-server/compose.yml restart
|
||||
sudo docker ps --filter name=atlas-sql-server
|
||||
sudo docker logs atlas-sql-server
|
||||
sudo docker inspect -f '{{.State.Health.Status}}' atlas-sql-server
|
||||
```
|
||||
|
||||
SQL Server live secrets are stored at `/etc/atlas/sql-server.env` and must not be committed.
|
||||
|
||||
These scripts do not exist yet. When created, each script must:
|
||||
|
||||
- Be idempotent.
|
||||
|
||||
@ -30,8 +30,8 @@ Future phase scripts should use a two-digit numeric prefix followed by a short l
|
||||
```text
|
||||
phases/10-dotnet.sh
|
||||
phases/20-sql-server.sh
|
||||
phases/30-nginx.sh
|
||||
phases/40-docker.sh
|
||||
phases/30-docker.sh
|
||||
phases/40-nginx.sh
|
||||
phases/90-validate-platform.sh
|
||||
```
|
||||
|
||||
@ -52,6 +52,8 @@ Current Phase 2 installer:
|
||||
|
||||
```bash
|
||||
install/phases/10-dotnet.sh
|
||||
install/phases/20-sql-server.sh
|
||||
install/phases/30-docker.sh
|
||||
```
|
||||
|
||||
The .NET installer installs only the required .NET SDK packages and verifies them with `dotnet --list-sdks`.
|
||||
@ -63,6 +65,8 @@ Current installed SDKs:
|
||||
|
||||
.NET 8 is installed from the official Ubuntu .NET backports PPA. .NET 10 is installed from the built-in Ubuntu package feeds.
|
||||
|
||||
Docker is installed from Docker's official signed Ubuntu APT repository. SQL Server uses Microsoft's official SQL Server 2025 container image because native SQL Server packages are not supported on Ubuntu 26.04.
|
||||
|
||||
Runtime output belongs outside the repository:
|
||||
|
||||
- `/var/log/atlas/install/`
|
||||
|
||||
239
install/phases/20-sql-server.sh
Executable file
239
install/phases/20-sql-server.sh
Executable file
@ -0,0 +1,239 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
INSTALL_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
REPO_ROOT="$(cd "${INSTALL_DIR}/.." && pwd)"
|
||||
|
||||
# shellcheck source=install/lib/common.sh
|
||||
source "${INSTALL_DIR}/lib/common.sh"
|
||||
# shellcheck source=install/lib/logging.sh
|
||||
source "${INSTALL_DIR}/lib/logging.sh"
|
||||
|
||||
atlas_set_script_name "20-sql-server.sh"
|
||||
atlas_install_error_trap
|
||||
|
||||
COMPOSE_FILE="${REPO_ROOT}/config/sql-server/compose.yml"
|
||||
COMPOSE_PROJECT="atlas-sql"
|
||||
CONTAINER_NAME="atlas-sql-server"
|
||||
SQL_IMAGE="mcr.microsoft.com/mssql/server:2025-CU1-ubuntu-24.04"
|
||||
SQL_IMAGE_DIGEST="sha256:698682bab57c02c42bc0aa274b158aeb242d8e9104149a7489628d5535805816"
|
||||
SQL_ENV_DIR="/etc/atlas"
|
||||
SQL_ENV_FILE="${SQL_ENV_DIR}/sql-server.env"
|
||||
SQL_ROOT="/sql/mssql"
|
||||
SQL_DIRS=(data log secrets backup)
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
Usage: install/phases/20-sql-server.sh [options]
|
||||
|
||||
Deploy SQL Server 2025 Developer using Microsoft's official container image.
|
||||
Requires Docker Engine and Docker Compose.
|
||||
|
||||
Options:
|
||||
--help Show this help text.
|
||||
--version Show script/framework version.
|
||||
--dry-run Show intended actions without making changes.
|
||||
--verbose Print additional diagnostic output.
|
||||
|
||||
USAGE
|
||||
}
|
||||
|
||||
while [[ "$#" -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--help)
|
||||
usage
|
||||
exit "${ATLAS_EXIT_OK}"
|
||||
;;
|
||||
--version)
|
||||
atlas_print_version
|
||||
exit "${ATLAS_EXIT_OK}"
|
||||
;;
|
||||
--dry-run)
|
||||
atlas_enable_dry_run
|
||||
;;
|
||||
--verbose)
|
||||
atlas_enable_verbose
|
||||
;;
|
||||
*)
|
||||
atlas_usage_error "Unknown option: $1"
|
||||
exit "${ATLAS_EXIT_USAGE}"
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
docker_cmd() {
|
||||
if docker info >/dev/null 2>&1; then
|
||||
docker "$@"
|
||||
else
|
||||
sudo -n docker "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
compose_cmd() {
|
||||
if docker info >/dev/null 2>&1; then
|
||||
docker compose -p "$COMPOSE_PROJECT" -f "$COMPOSE_FILE" "$@"
|
||||
else
|
||||
sudo -n docker compose -p "$COMPOSE_PROJECT" -f "$COMPOSE_FILE" "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
require_prerequisites() {
|
||||
atlas_require_sudo_noninteractive || {
|
||||
atlas_log_error "Passwordless non-interactive sudo is required"
|
||||
exit "${ATLAS_EXIT_PREREQ}"
|
||||
}
|
||||
atlas_require_command docker
|
||||
docker compose version >/dev/null 2>&1 || {
|
||||
atlas_log_error "Docker Compose plugin is required"
|
||||
exit "${ATLAS_EXIT_PREREQ}"
|
||||
}
|
||||
systemctl is-active --quiet docker || {
|
||||
atlas_log_error "Docker service is not active"
|
||||
exit "${ATLAS_EXIT_PREREQ}"
|
||||
}
|
||||
findmnt -rn --target /sql >/dev/null 2>&1 || {
|
||||
atlas_log_error "/sql must be mounted before SQL Server deployment"
|
||||
exit "${ATLAS_EXIT_PREREQ}"
|
||||
}
|
||||
}
|
||||
|
||||
pull_image() {
|
||||
if docker_cmd image inspect "$SQL_IMAGE" >/dev/null 2>&1; then
|
||||
atlas_log_ok "SQL Server image already present: ${SQL_IMAGE}"
|
||||
elif [[ "${ATLAS_DRY_RUN}" == "1" ]]; then
|
||||
atlas_log_info "Dry-run mode: would pull ${SQL_IMAGE}"
|
||||
else
|
||||
docker_cmd pull "$SQL_IMAGE"
|
||||
fi
|
||||
}
|
||||
|
||||
inspect_sql_uid_gid() {
|
||||
if [[ "${ATLAS_DRY_RUN}" == "1" ]]; then
|
||||
printf '10001:10001\n'
|
||||
return 0
|
||||
fi
|
||||
docker_cmd run --rm --entrypoint /bin/bash "$SQL_IMAGE" -lc 'printf "%s:%s\n" "$(id -u mssql)" "$(id -g mssql)"'
|
||||
}
|
||||
|
||||
ensure_storage() {
|
||||
local uid_gid="$1"
|
||||
atlas_log_info "Ensuring SQL Server persistent storage under ${SQL_ROOT}"
|
||||
if [[ "${ATLAS_DRY_RUN}" == "1" ]]; then
|
||||
atlas_log_info "Dry-run mode: would create ${SQL_ROOT}/{data,log,secrets,backup}"
|
||||
atlas_log_info "Dry-run mode: would chown directories to ${uid_gid}"
|
||||
return 0
|
||||
fi
|
||||
sudo -n install -d -m 0770 "$SQL_ROOT"
|
||||
local dir
|
||||
for dir in "${SQL_DIRS[@]}"; do
|
||||
sudo -n install -d -m 0770 "${SQL_ROOT}/${dir}"
|
||||
done
|
||||
sudo -n chown "$uid_gid" "$SQL_ROOT" "${SQL_ROOT}/data" "${SQL_ROOT}/log" "${SQL_ROOT}/secrets" "${SQL_ROOT}/backup"
|
||||
sudo -n chmod 0770 "$SQL_ROOT" "${SQL_ROOT}/data" "${SQL_ROOT}/log" "${SQL_ROOT}/secrets" "${SQL_ROOT}/backup"
|
||||
}
|
||||
|
||||
generate_password() {
|
||||
python3 - <<'PY'
|
||||
import secrets
|
||||
alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789"
|
||||
print("A1a_" + "".join(secrets.choice(alphabet) for _ in range(28)))
|
||||
PY
|
||||
}
|
||||
|
||||
ensure_env_file() {
|
||||
if sudo -n test -f "$SQL_ENV_FILE"; then
|
||||
atlas_log_ok "SQL Server environment file already exists"
|
||||
return 0
|
||||
fi
|
||||
if [[ "${ATLAS_DRY_RUN}" == "1" ]]; then
|
||||
atlas_log_info "Dry-run mode: would create protected ${SQL_ENV_FILE}"
|
||||
return 0
|
||||
fi
|
||||
local password tmp_file
|
||||
password="$(generate_password)"
|
||||
tmp_file="$(mktemp)"
|
||||
cat >"$tmp_file" <<EOF
|
||||
ACCEPT_EULA=Y
|
||||
MSSQL_PID=Developer
|
||||
MSSQL_TCP_PORT=1433
|
||||
MSSQL_SA_PASSWORD=${password}
|
||||
EOF
|
||||
sudo -n install -d -m 0750 "$SQL_ENV_DIR"
|
||||
sudo -n install -m 0640 -o root -g docker "$tmp_file" "$SQL_ENV_FILE"
|
||||
rm -f "$tmp_file"
|
||||
atlas_log_warn "Created ${SQL_ENV_FILE}; the generated SQL administrator password was not printed"
|
||||
}
|
||||
|
||||
start_sql_server() {
|
||||
if [[ "${ATLAS_DRY_RUN}" == "1" ]]; then
|
||||
atlas_log_info "Dry-run mode: would run docker compose up -d"
|
||||
return 0
|
||||
fi
|
||||
if docker_cmd inspect "$CONTAINER_NAME" >/dev/null 2>&1; then
|
||||
local state image
|
||||
state="$(docker_cmd inspect -f '{{.State.Status}}' "$CONTAINER_NAME")"
|
||||
image="$(docker_cmd inspect -f '{{.Config.Image}}' "$CONTAINER_NAME")"
|
||||
if [[ "$image" == "$SQL_IMAGE" ]]; then
|
||||
if [[ "$state" == "running" ]]; then
|
||||
atlas_log_ok "SQL Server container is already running"
|
||||
else
|
||||
atlas_log_info "Starting existing SQL Server container"
|
||||
docker_cmd start "$CONTAINER_NAME" >/dev/null
|
||||
fi
|
||||
return 0
|
||||
fi
|
||||
atlas_log_error "Existing ${CONTAINER_NAME} uses unexpected image: ${image}"
|
||||
exit "${ATLAS_EXIT_UNSAFE}"
|
||||
fi
|
||||
compose_cmd up -d
|
||||
}
|
||||
|
||||
wait_for_health() {
|
||||
if [[ "${ATLAS_DRY_RUN}" == "1" ]]; then
|
||||
atlas_log_info "Dry-run mode: would wait for SQL Server health check"
|
||||
return 0
|
||||
fi
|
||||
local status
|
||||
for _ in {1..60}; do
|
||||
status="$(docker_cmd inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}no-healthcheck{{end}}' "$CONTAINER_NAME" 2>/dev/null || true)"
|
||||
case "$status" in
|
||||
healthy)
|
||||
atlas_log_ok "SQL Server container is healthy"
|
||||
return 0
|
||||
;;
|
||||
unhealthy)
|
||||
atlas_log_error "SQL Server container health check is unhealthy"
|
||||
docker_cmd logs --tail 80 "$CONTAINER_NAME" >&2 || true
|
||||
exit "${ATLAS_EXIT_VALIDATION}"
|
||||
;;
|
||||
esac
|
||||
sleep 5
|
||||
done
|
||||
atlas_log_error "Timed out waiting for SQL Server container health"
|
||||
docker_cmd logs --tail 80 "$CONTAINER_NAME" >&2 || true
|
||||
exit "${ATLAS_EXIT_VALIDATION}"
|
||||
}
|
||||
|
||||
verify_sqlcmd() {
|
||||
if [[ "${ATLAS_DRY_RUN}" == "1" ]]; then
|
||||
atlas_log_info "Dry-run mode: would verify sqlcmd connection"
|
||||
return 0
|
||||
fi
|
||||
"${REPO_ROOT}/scripts/sql/sqlcmd.sh" -Q "SELECT @@VERSION;" >/dev/null
|
||||
atlas_log_ok "SQL Server local sqlcmd verification passed"
|
||||
}
|
||||
|
||||
main() {
|
||||
require_prerequisites
|
||||
pull_image
|
||||
uid_gid="$(inspect_sql_uid_gid)"
|
||||
ensure_storage "$uid_gid"
|
||||
ensure_env_file
|
||||
start_sql_server
|
||||
wait_for_health
|
||||
verify_sqlcmd
|
||||
}
|
||||
|
||||
main
|
||||
210
install/phases/30-docker.sh
Executable file
210
install/phases/30-docker.sh
Executable file
@ -0,0 +1,210 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
INSTALL_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
||||
|
||||
# shellcheck source=install/lib/common.sh
|
||||
source "${INSTALL_DIR}/lib/common.sh"
|
||||
# shellcheck source=install/lib/logging.sh
|
||||
source "${INSTALL_DIR}/lib/logging.sh"
|
||||
# shellcheck source=install/lib/packages.sh
|
||||
source "${INSTALL_DIR}/lib/packages.sh"
|
||||
|
||||
atlas_set_script_name "30-docker.sh"
|
||||
atlas_install_error_trap
|
||||
|
||||
DOCKER_KEYRING="/etc/apt/keyrings/docker.asc"
|
||||
DOCKER_SOURCE="/etc/apt/sources.list.d/docker.list"
|
||||
DOCKER_PACKAGES=(docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin)
|
||||
DOCKER_CONFLICTS=(docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc)
|
||||
DOCKER_REPO_BASE="https://download.docker.com/linux/ubuntu"
|
||||
DOCKER_USER="nick"
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
Usage: install/phases/30-docker.sh [options]
|
||||
|
||||
Install Docker Engine from Docker's official signed Ubuntu APT repository.
|
||||
|
||||
Options:
|
||||
--help Show this help text.
|
||||
--version Show script/framework version.
|
||||
--dry-run Show intended actions without making changes.
|
||||
--verbose Print additional diagnostic output.
|
||||
|
||||
USAGE
|
||||
}
|
||||
|
||||
while [[ "$#" -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--help)
|
||||
usage
|
||||
exit "${ATLAS_EXIT_OK}"
|
||||
;;
|
||||
--version)
|
||||
atlas_print_version
|
||||
exit "${ATLAS_EXIT_OK}"
|
||||
;;
|
||||
--dry-run)
|
||||
atlas_enable_dry_run
|
||||
;;
|
||||
--verbose)
|
||||
atlas_enable_verbose
|
||||
;;
|
||||
*)
|
||||
atlas_usage_error "Unknown option: $1"
|
||||
exit "${ATLAS_EXIT_USAGE}"
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
load_os_release() {
|
||||
# shellcheck disable=SC1091
|
||||
. /etc/os-release
|
||||
if [[ "${ID:-}" != "ubuntu" || "${VERSION_CODENAME:-}" != "resolute" ]]; then
|
||||
atlas_log_error "Unsupported OS for this Docker phase: ${PRETTY_NAME:-unknown}"
|
||||
exit "${ATLAS_EXIT_PREREQ}"
|
||||
fi
|
||||
}
|
||||
|
||||
docker_repo_available() {
|
||||
curl -fsSL "${DOCKER_REPO_BASE}/dists/${VERSION_CODENAME}/Release" >/dev/null
|
||||
}
|
||||
|
||||
install_prerequisites() {
|
||||
local packages=(ca-certificates curl)
|
||||
local missing=()
|
||||
local package_name
|
||||
for package_name in "${packages[@]}"; do
|
||||
atlas_package_installed "$package_name" || missing+=("$package_name")
|
||||
done
|
||||
if [[ "${#missing[@]}" -gt 0 ]]; then
|
||||
atlas_install_packages "${missing[@]}"
|
||||
else
|
||||
atlas_log_ok "Docker repository prerequisites are already installed"
|
||||
fi
|
||||
}
|
||||
|
||||
remove_conflicts() {
|
||||
local installed=()
|
||||
local package_name
|
||||
for package_name in "${DOCKER_CONFLICTS[@]}"; do
|
||||
atlas_package_installed "$package_name" && installed+=("$package_name")
|
||||
done
|
||||
if [[ "${#installed[@]}" -eq 0 ]]; then
|
||||
atlas_log_ok "No conflicting Docker packages are installed"
|
||||
return 0
|
||||
fi
|
||||
if [[ "${ATLAS_DRY_RUN}" == "1" ]]; then
|
||||
atlas_log_info "Dry-run mode: would remove conflicting packages: ${installed[*]}"
|
||||
return 0
|
||||
fi
|
||||
atlas_log_info "Removing conflicting Docker packages: ${installed[*]}"
|
||||
atlas_sudo_run apt-get remove -y "${installed[@]}"
|
||||
}
|
||||
|
||||
configure_repository() {
|
||||
local arch repo_line
|
||||
arch="$(dpkg --print-architecture)"
|
||||
repo_line="deb [arch=${arch} signed-by=${DOCKER_KEYRING}] ${DOCKER_REPO_BASE} ${VERSION_CODENAME} stable"
|
||||
|
||||
if [[ -r "$DOCKER_SOURCE" ]] && grep -Fqx "$repo_line" "$DOCKER_SOURCE" && [[ -r "$DOCKER_KEYRING" ]]; then
|
||||
atlas_log_ok "Docker APT repository is already configured"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "${ATLAS_DRY_RUN}" == "1" ]]; then
|
||||
atlas_log_info "Dry-run mode: would install Docker GPG key to ${DOCKER_KEYRING}"
|
||||
atlas_log_info "Dry-run mode: would write Docker APT source ${DOCKER_SOURCE}"
|
||||
atlas_log_info "Dry-run mode: would run apt-get update"
|
||||
return 0
|
||||
fi
|
||||
|
||||
atlas_sudo_run install -m 0755 -d /etc/apt/keyrings
|
||||
tmp_key="$(mktemp)"
|
||||
curl -fsSL "${DOCKER_REPO_BASE}/gpg" -o "$tmp_key"
|
||||
atlas_sudo_run install -m 0644 "$tmp_key" "$DOCKER_KEYRING"
|
||||
rm -f "$tmp_key"
|
||||
printf '%s\n' "$repo_line" | sudo -n tee "$DOCKER_SOURCE" >/dev/null
|
||||
atlas_sudo_run apt-get update
|
||||
}
|
||||
|
||||
install_docker_packages() {
|
||||
local missing=()
|
||||
local package_name
|
||||
for package_name in "${DOCKER_PACKAGES[@]}"; do
|
||||
atlas_package_installed "$package_name" || missing+=("$package_name")
|
||||
done
|
||||
if [[ "${#missing[@]}" -eq 0 ]]; then
|
||||
atlas_log_ok "Docker packages are already installed"
|
||||
else
|
||||
atlas_install_packages "${missing[@]}"
|
||||
fi
|
||||
}
|
||||
|
||||
enable_docker_service() {
|
||||
atlas_log_info "Ensuring Docker service is enabled and active"
|
||||
atlas_sudo_run systemctl enable docker
|
||||
atlas_sudo_run systemctl start docker
|
||||
}
|
||||
|
||||
ensure_docker_group() {
|
||||
if id -nG "$DOCKER_USER" | tr ' ' '\n' | grep -qx docker; then
|
||||
atlas_log_ok "User ${DOCKER_USER} is already in the docker group"
|
||||
return 0
|
||||
fi
|
||||
if [[ "${ATLAS_DRY_RUN}" == "1" ]]; then
|
||||
atlas_log_info "Dry-run mode: would add ${DOCKER_USER} to docker group"
|
||||
return 0
|
||||
fi
|
||||
atlas_sudo_run usermod -aG docker "$DOCKER_USER"
|
||||
atlas_log_warn "User ${DOCKER_USER} was added to docker group; a new login may be required"
|
||||
}
|
||||
|
||||
verify_docker() {
|
||||
if [[ "${ATLAS_DRY_RUN}" == "1" ]]; then
|
||||
atlas_log_info "Dry-run mode: skipping Docker runtime verification"
|
||||
return 0
|
||||
fi
|
||||
docker --version
|
||||
docker compose version
|
||||
docker buildx version
|
||||
systemctl is-enabled docker >/dev/null
|
||||
systemctl is-active docker >/dev/null
|
||||
if docker info >/dev/null 2>&1; then
|
||||
atlas_log_ok "Docker is available to the current shell without sudo"
|
||||
else
|
||||
atlas_log_warn "Current shell cannot use Docker without sudo; a new login or sg docker may be required"
|
||||
sudo -n docker info >/dev/null
|
||||
fi
|
||||
atlas_log_ok "Docker verification passed"
|
||||
}
|
||||
|
||||
main() {
|
||||
load_os_release
|
||||
atlas_require_sudo_noninteractive || {
|
||||
atlas_log_error "Passwordless non-interactive sudo is required"
|
||||
exit "${ATLAS_EXIT_PREREQ}"
|
||||
}
|
||||
atlas_require_command curl
|
||||
atlas_require_command dpkg
|
||||
atlas_require_command apt-get
|
||||
atlas_require_command systemctl
|
||||
|
||||
docker_repo_available || {
|
||||
atlas_log_error "Docker official repository is not available for ${VERSION_CODENAME}"
|
||||
exit "${ATLAS_EXIT_PREREQ}"
|
||||
}
|
||||
|
||||
install_prerequisites
|
||||
remove_conflicts
|
||||
configure_repository
|
||||
install_docker_packages
|
||||
enable_docker_service
|
||||
ensure_docker_group
|
||||
verify_docker
|
||||
}
|
||||
|
||||
main
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
This directory is reserved for future ordered Atlas installation phases.
|
||||
|
||||
Phase 2A intentionally did not create speculative .NET, SQL Server, nginx, or Docker installer scripts. Phase 2 adds the .NET installer only.
|
||||
Phase 2A intentionally did not create speculative .NET, SQL Server, nginx, or Docker installer scripts. Phase 2 adds the .NET, Docker, and SQL Server container installers.
|
||||
|
||||
## Naming Convention
|
||||
|
||||
@ -11,8 +11,8 @@ Use a two-digit phase number and lower-case hyphenated component name:
|
||||
```text
|
||||
10-dotnet.sh
|
||||
20-sql-server.sh
|
||||
30-nginx.sh
|
||||
40-docker.sh
|
||||
30-docker.sh
|
||||
40-nginx.sh
|
||||
90-validate-platform.sh
|
||||
```
|
||||
|
||||
@ -21,8 +21,17 @@ Each phase must source the shared framework under `install/lib/`, support dry-ru
|
||||
## Current Phases
|
||||
|
||||
- `10-dotnet.sh`: installs the required .NET 8 and .NET 10 SDKs from official Ubuntu package sources.
|
||||
- `20-sql-server.sh`: deploys SQL Server 2025 Developer using Microsoft's official container image.
|
||||
- `30-docker.sh`: installs Docker Engine from Docker's official Ubuntu APT repository.
|
||||
|
||||
Installed SDKs after the current Phase 2 run:
|
||||
|
||||
- .NET 8 SDK `8.0.128`
|
||||
- .NET 10 SDK `10.0.109`
|
||||
|
||||
Current SQL Server deployment:
|
||||
|
||||
- Image: `mcr.microsoft.com/mssql/server:2025-CU1-ubuntu-24.04`
|
||||
- Digest: `sha256:698682bab57c02c42bc0aa274b158aeb242d8e9104149a7489628d5535805816`
|
||||
- Container: `atlas-sql-server`
|
||||
- Compose project: `atlas-sql`
|
||||
|
||||
@ -39,6 +39,7 @@ scripts/deploy/
|
||||
scripts/maintenance/
|
||||
scripts/monitoring/
|
||||
scripts/restore/
|
||||
scripts/sql/
|
||||
scripts/system/
|
||||
```
|
||||
|
||||
@ -51,3 +52,4 @@ Additional areas may be added when a repeated operational need exists.
|
||||
- Make destructive actions explicit and documented.
|
||||
- Include validation and rollback guidance for service-affecting scripts.
|
||||
- Discovery and validation scripts must not dump environment variables, private keys, tokens, or complete sensitive configuration files.
|
||||
- SQL helper scripts must not embed or print SQL administrator passwords.
|
||||
|
||||
34
scripts/sql/sqlcmd.sh
Executable file
34
scripts/sql/sqlcmd.sh
Executable file
@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env bash
|
||||
set -Eeuo pipefail
|
||||
|
||||
CONTAINER_NAME="atlas-sql-server"
|
||||
SQLCMD_PATH="/opt/mssql-tools18/bin/sqlcmd"
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
Usage: scripts/sql/sqlcmd.sh [sqlcmd arguments]
|
||||
|
||||
Runs sqlcmd inside the Atlas SQL Server container without embedding the SQL
|
||||
administrator password in the repository or command line.
|
||||
|
||||
Examples:
|
||||
scripts/sql/sqlcmd.sh -Q "SELECT @@VERSION;"
|
||||
scripts/sql/sqlcmd.sh -d master -Q "SELECT name FROM sys.databases;"
|
||||
|
||||
USAGE
|
||||
}
|
||||
|
||||
if [[ "${1:-}" == "--help" ]]; then
|
||||
usage
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
echo "ERROR: docker command not found" >&2
|
||||
exit 3
|
||||
fi
|
||||
|
||||
exec sudo -n docker exec -i "$CONTAINER_NAME" /bin/bash -lc \
|
||||
'SQLCMDPASSWORD="$MSSQL_SA_PASSWORD" exec /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -C "$@"' \
|
||||
sqlcmd "$@"
|
||||
|
||||
@ -113,6 +113,24 @@ component_version() {
|
||||
fi
|
||||
}
|
||||
|
||||
docker_status() {
|
||||
if ! have docker; then
|
||||
printf 'not installed\n'
|
||||
elif sudo -n docker info >/dev/null 2>&1 || docker info >/dev/null 2>&1; then
|
||||
printf 'active\n'
|
||||
else
|
||||
printf 'installed-not-active-or-not-accessible\n'
|
||||
fi
|
||||
}
|
||||
|
||||
sql_container_health() {
|
||||
if ! have docker; then
|
||||
printf 'docker unavailable\n'
|
||||
else
|
||||
sudo -n docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' atlas-sql-server 2>/dev/null || printf 'not present\n'
|
||||
fi
|
||||
}
|
||||
|
||||
mount_status() {
|
||||
local path="$1"
|
||||
if have findmnt && findmnt -rn --target "$path" >/dev/null 2>&1; then
|
||||
@ -223,6 +241,8 @@ human_report() {
|
||||
printf ' nginx: %s\n' "$(component_version nginx -v)"
|
||||
printf ' Docker: %s\n' "$(component_version docker --version)"
|
||||
printf ' Docker Compose: %s\n\n' "$(if have docker; then docker compose version 2>/dev/null | sed -n '1p'; else printf 'not installed'; fi)"
|
||||
printf ' Docker status: %s\n' "$(docker_status)"
|
||||
printf ' SQL Server container health: %s\n\n' "$(sql_container_health)"
|
||||
|
||||
printf 'Services\n'
|
||||
printf ' Failed systemd services:\n'
|
||||
@ -283,6 +303,8 @@ json_report() {
|
||||
--arg sqlserver_version "$(component_version sqlservr --version)" \
|
||||
--arg nginx_version "$(component_version nginx -v)" \
|
||||
--arg docker_version "$(component_version docker --version)" \
|
||||
--arg docker_status "$(docker_status)" \
|
||||
--arg sql_container_health "$(sql_container_health)" \
|
||||
--arg hostname "$(hostname)" \
|
||||
--arg host_overrides "$(local_host_overrides_present)" \
|
||||
--arg firewall "$(firewall_summary)" \
|
||||
@ -309,7 +331,9 @@ json_report() {
|
||||
dotnet: $dotnet_version,
|
||||
sql_server: $sqlserver_version,
|
||||
nginx: $nginx_version,
|
||||
docker: $docker_version
|
||||
docker: $docker_version,
|
||||
docker_status: $docker_status,
|
||||
sql_server_container_health: $sql_container_health
|
||||
},
|
||||
services: {failed_systemd_units: ($failed_services | split("\n") | map(select(length > 0)))},
|
||||
network: {
|
||||
|
||||
@ -217,6 +217,35 @@ check_failed_services() {
|
||||
fi
|
||||
}
|
||||
|
||||
check_docker_sql_state() {
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
atlas_validation_result SKIPPED "Docker is not installed"
|
||||
return
|
||||
fi
|
||||
|
||||
if systemctl is-active --quiet docker; then
|
||||
atlas_validation_result PASS "Docker service is active"
|
||||
else
|
||||
atlas_validation_result FAIL "Docker service is not active"
|
||||
fi
|
||||
|
||||
if docker compose version >/dev/null 2>&1; then
|
||||
atlas_validation_result PASS "Docker Compose plugin is available"
|
||||
else
|
||||
atlas_validation_result FAIL "Docker Compose plugin is not available"
|
||||
fi
|
||||
|
||||
local sql_health
|
||||
sql_health="$(sudo -n docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' atlas-sql-server 2>/dev/null || true)"
|
||||
if [[ -z "$sql_health" ]]; then
|
||||
atlas_validation_result SKIPPED "SQL Server container is not present"
|
||||
elif [[ "$sql_health" == "healthy" ]]; then
|
||||
atlas_validation_result PASS "SQL Server container is healthy"
|
||||
else
|
||||
atlas_validation_result FAIL "SQL Server container health is ${sql_health}"
|
||||
fi
|
||||
}
|
||||
|
||||
atlas_validation_reset
|
||||
|
||||
printf 'Atlas Host Validation\n'
|
||||
@ -260,6 +289,7 @@ check_free_space /srv "$MIN_SRV_FREE_GIB"
|
||||
check_free_space /sql "$MIN_SQL_FREE_GIB"
|
||||
|
||||
check_failed_services
|
||||
check_docker_sql_state
|
||||
|
||||
if [[ -f /var/run/reboot-required ]]; then
|
||||
atlas_validation_result WARNING "Reboot is pending"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user