Add nginx and firewall baseline

This commit is contained in:
Nick Beckley 2026-07-10 18:42:43 +00:00
parent d07b5cc575
commit 4dafddf8e5
15 changed files with 343 additions and 17 deletions

View File

@ -41,6 +41,8 @@ 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`.
`config/nginx/` contains the temporary Atlas default HTTP site configuration. Live nginx configuration is deployed under `/etc/nginx/`.
## 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.

15
config/nginx/README.md Normal file
View File

@ -0,0 +1,15 @@
# nginx Configuration
This directory contains version-controlled nginx configuration for Atlas.
`atlas-default.conf` is the temporary default HTTP site. It serves a small static status page from `/var/www/atlas` and exposes `/health` for local validation.
Deployment locations:
- Source configuration: `config/nginx/atlas-default.conf`
- Live configuration: `/etc/nginx/sites-available/atlas-default.conf`
- Enabled site: `/etc/nginx/sites-enabled/atlas-default.conf`
- Static content: `/var/www/atlas`
- Health check: `/health`
Do not store TLS private keys, live certificates, secrets, or PlotDirector-specific proxy credentials here.

View File

@ -0,0 +1,20 @@
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
root /var/www/atlas;
index index.html;
access_log /var/log/nginx/atlas-access.log;
error_log /var/log/nginx/atlas-error.log;
location = /health {
add_header Content-Type text/plain;
return 200 "ok\n";
}
location / {
try_files $uri $uri/ =404;
}
}

View File

@ -8,7 +8,7 @@ services:
env_file:
- /etc/atlas/sql-server.env
ports:
- "1433:1433"
- "192.168.10.10:1433:1433"
volumes:
- /sql/mssql:/var/opt/mssql
healthcheck:
@ -26,4 +26,3 @@ services:
options:
max-size: "10m"
max-file: "5"

View File

@ -8,4 +8,3 @@ ACCEPT_EULA=Y
MSSQL_PID=Developer
MSSQL_TCP_PORT=1433
MSSQL_SA_PASSWORD=CHANGE_ME_DO_NOT_COMMIT

View File

@ -34,7 +34,9 @@ The intended model separates operating system state, SQL data, and development d
- Primary network interface: `enp3s0`
- Gitea SSH remote for this repository: `git@git.catherinelynwood.com:atlas-bot/AI-Development-Server.git`
DNS, firewall, public exposure, and TLS policy are `TODO: Confirm during the relevant implementation phase.`
UFW is active with default deny incoming and default allow outgoing. Inbound SSH `22`, HTTP `80`, and HTTPS `443` are allowed. SQL Server `1433` is allowed only from `192.168.10.0/24` and is bound to `192.168.10.10`.
Public DNS, TLS, and router exposure policy are `TODO: Confirm during the relevant implementation phase.`
### Repository and Source-Control Flow
@ -95,14 +97,16 @@ Specific PlotDirector service names, ports, deployment paths, database names, an
### Role of nginx
nginx is planned as the likely local reverse proxy for HTTP and HTTPS applications. Its future responsibilities may include:
nginx is installed from the standard Ubuntu 26.04 repositories and currently serves a temporary Atlas HTTP status site.
- Routing requests to application services.
- TLS termination.
- Static file serving where appropriate.
- Request size, timeout, and header policy.
- Package version: nginx `1.28.3`
- Service: `nginx`
- Site configuration: `/etc/nginx/sites-available/atlas-default.conf`
- Version-controlled source: `config/nginx/atlas-default.conf`
- Static site root: `/var/www/atlas`
- Health endpoint: `http://192.168.10.10/health`
nginx installation, site layout, TLS certificate handling, and exposure rules are `TODO: Confirm during the relevant implementation phase.`
HTTPS, TLS certificates, and PlotDirector reverse-proxy configuration are not configured yet.
### Role of SQL Server
@ -185,8 +189,8 @@ flowchart TB
Tests[Future smoke tests<br>tests/]
SQL[(SQL data volume<br>/sql)]
Deploy[Future deployed applications]
Nginx[Future nginx reverse proxy]
Docker[Future Docker runtime]
Nginx[nginx reverse proxy]
Docker[Docker runtime]
end
Repo --> Install

View File

@ -10,6 +10,59 @@ This file records chronological Atlas build and infrastructure milestones. Do no
### Phase
Phase 2 nginx and firewall baseline.
### Objective
Install nginx, deploy a minimal Atlas status site, restrict SQL Server binding, and enable a safe UFW baseline.
### Changes
- Added `install/phases/40-nginx.sh`.
- Installed nginx `1.28.3` from Ubuntu `resolute-updates`.
- Added Atlas default nginx site under `config/nginx/`.
- Deployed static site content to `/var/www/atlas`.
- Enabled `/health` endpoint.
- Enabled UFW with default deny incoming and default allow outgoing.
- Allowed inbound TCP `22`, `80`, and `443`.
- Allowed inbound TCP `1433` only from `192.168.10.0/24`.
- Changed SQL Server container binding to `192.168.10.10:1433:1433`.
### Validation
- `nginx -t` passed.
- HTTP checks passed for `127.0.0.1` and `192.168.10.10`.
- `/health` returned HTTP 200.
- nginx restart test passed.
- SQL Server remained healthy after container recreation.
- SQL data persisted after container recreation.
- SQL LAN connection test passed.
- Atlas discovery and validation passed.
### Problems
- Initial nginx deployment encountered a duplicate default-server config while the Ubuntu default site was still enabled. The installer now validates before and after disabling the default site.
### Decisions
- Keep nginx HTTP-only until a later TLS phase.
- Keep the temporary Atlas status site separate from future PlotDirector proxy configuration.
- Do not rely on UFW alone for Docker-published SQL Server; bind SQL Server to the Atlas LAN address.
### Git Commit
Not yet committed.
### Operator
Codex.
### Date
2026-07-10
### Phase
Phase 2 Docker and SQL Server container platform.
### Objective

View File

@ -11,6 +11,7 @@ This guide describes the planned rebuild path for Atlas. At Phase 1, the reposit
- .NET installation script: present.
- Docker installation script: present.
- SQL Server container installation script: present.
- nginx installation script: present.
- Other component installation scripts: not yet created.
- Configuration templates: not yet created.
- Operational scripts: not yet created.
@ -118,8 +119,10 @@ Planned bootstrap flow:
7. Run `install/phases/10-dotnet.sh` when .NET SDKs are required.
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.
10. Run `install/phases/40-nginx.sh`.
11. Apply the UFW baseline rules.
12. Run additional ordered installation scripts from `install/phases/` when they exist.
13. Run smoke tests from `tests/` when they exist.
Bootstrap command sequence is `TODO: Confirm during the relevant implementation phase.`
@ -154,7 +157,22 @@ 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:
Current nginx validation commands:
```bash
sudo nginx -t
systemctl is-active nginx
curl -fsS http://127.0.0.1/health
curl -fsS http://192.168.10.10/health
```
Current firewall inspection command:
```bash
sudo ufw status numbered
```
Installation scripts must:
- Be idempotent.
- Validate prerequisites.

View File

@ -54,6 +54,7 @@ Current Phase 2 installer:
install/phases/10-dotnet.sh
install/phases/20-sql-server.sh
install/phases/30-docker.sh
install/phases/40-nginx.sh
```
The .NET installer installs only the required .NET SDK packages and verifies them with `dotnet --list-sdks`.
@ -67,6 +68,8 @@ Current installed SDKs:
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.
nginx is installed from Ubuntu repositories and serves the temporary Atlas status site until PlotDirector routing is configured.
Runtime output belongs outside the repository:
- `/var/log/atlas/install/`

167
install/phases/40-nginx.sh Executable file
View File

@ -0,0 +1,167 @@
#!/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"
# shellcheck source=install/lib/packages.sh
source "${INSTALL_DIR}/lib/packages.sh"
atlas_set_script_name "40-nginx.sh"
atlas_install_error_trap
ATLAS_SITE_ROOT="/var/www/atlas"
ATLAS_SITE_CONF_SOURCE="${REPO_ROOT}/config/nginx/atlas-default.conf"
ATLAS_SITE_CONF_DEST="/etc/nginx/sites-available/atlas-default.conf"
ATLAS_SITE_LINK="/etc/nginx/sites-enabled/atlas-default.conf"
NGINX_DEFAULT_LINK="/etc/nginx/sites-enabled/default"
NGINX_BACKUP_DIR="/etc/nginx/atlas-backups"
usage() {
cat <<'USAGE'
Usage: install/phases/40-nginx.sh [options]
Install nginx from Ubuntu repositories and deploy the temporary Atlas default
HTTP status site. This does not configure HTTPS or PlotDirector proxying.
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
install_nginx() {
if atlas_package_installed nginx; then
atlas_log_ok "nginx is already installed"
else
atlas_install_packages nginx
fi
}
write_site_content() {
local tmp_file
tmp_file="$(mktemp)"
cat >"$tmp_file" <<'HTML'
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Atlas</title>
<style>
body { margin: 0; font-family: system-ui, sans-serif; background: #f6f7f9; color: #1f2933; }
main { max-width: 42rem; margin: 12vh auto; padding: 2rem; }
h1 { font-size: 2rem; margin: 0 0 1rem; }
p { line-height: 1.5; }
</style>
</head>
<body>
<main>
<h1>Atlas is operational</h1>
<p>This is the temporary Atlas status site. Application routing has not been configured yet.</p>
</main>
</body>
</html>
HTML
if [[ "${ATLAS_DRY_RUN}" == "1" ]]; then
atlas_log_info "Dry-run mode: would deploy static site content to ${ATLAS_SITE_ROOT}"
rm -f "$tmp_file"
return 0
fi
sudo -n install -d -m 0755 -o www-data -g www-data "$ATLAS_SITE_ROOT"
sudo -n install -m 0644 -o www-data -g www-data "$tmp_file" "${ATLAS_SITE_ROOT}/index.html"
rm -f "$tmp_file"
}
deploy_nginx_config() {
if [[ "${ATLAS_DRY_RUN}" == "1" ]]; then
atlas_log_info "Dry-run mode: would deploy ${ATLAS_SITE_CONF_DEST}"
atlas_log_info "Dry-run mode: would enable Atlas nginx site and disable Ubuntu default site"
return 0
fi
sudo -n install -d -m 0755 /etc/nginx/sites-available /etc/nginx/sites-enabled
sudo -n install -d -m 0750 "$NGINX_BACKUP_DIR"
sudo -n nginx -t
if [[ -e "$ATLAS_SITE_CONF_DEST" ]] && ! cmp -s "$ATLAS_SITE_CONF_SOURCE" "$ATLAS_SITE_CONF_DEST"; then
sudo -n cp -a "$ATLAS_SITE_CONF_DEST" "${NGINX_BACKUP_DIR}/atlas-default.conf.$(date -u '+%Y%m%dT%H%M%SZ').bak"
fi
sudo -n install -m 0644 "$ATLAS_SITE_CONF_SOURCE" "$ATLAS_SITE_CONF_DEST"
if [[ -e "$NGINX_DEFAULT_LINK" ]]; then
sudo -n cp -a "$NGINX_DEFAULT_LINK" "${NGINX_BACKUP_DIR}/default-link.$(date -u '+%Y%m%dT%H%M%SZ').bak"
sudo -n rm -f "$NGINX_DEFAULT_LINK"
fi
sudo -n ln -sfn "$ATLAS_SITE_CONF_DEST" "$ATLAS_SITE_LINK"
sudo -n nginx -t
}
enable_nginx() {
atlas_sudo_run systemctl enable nginx
atlas_sudo_run systemctl restart nginx
}
verify_nginx() {
if [[ "${ATLAS_DRY_RUN}" == "1" ]]; then
atlas_log_info "Dry-run mode: would verify nginx service and health endpoint"
return 0
fi
sudo -n nginx -t
systemctl is-enabled nginx >/dev/null
systemctl is-active nginx >/dev/null
curl -fsS http://127.0.0.1/health >/dev/null
curl -fsS http://192.168.10.10/health >/dev/null
atlas_log_ok "nginx verification passed"
}
main() {
atlas_require_sudo_noninteractive || {
atlas_log_error "Passwordless non-interactive sudo is required"
exit "${ATLAS_EXIT_PREREQ}"
}
install_nginx
write_site_content
deploy_nginx_config
enable_nginx
verify_nginx
}
main

View File

@ -23,6 +23,7 @@ Each phase must source the shared framework under `install/lib/`, support dry-ru
- `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.
- `40-nginx.sh`: installs nginx and deploys the temporary Atlas status site.
Installed SDKs after the current Phase 2 run:

View File

@ -53,3 +53,4 @@ Additional areas may be added when a repeated operational need exists.
- 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.
- Firewall inspection should use `sudo ufw status numbered`.

View File

@ -31,4 +31,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 "$@"

View File

@ -131,6 +131,16 @@ sql_container_health() {
fi
}
nginx_status() {
if ! have nginx; then
printf 'not installed\n'
elif systemctl is-active --quiet nginx 2>/dev/null && sudo -n nginx -t >/dev/null 2>&1; then
printf 'active-config-valid\n'
else
printf 'installed-needs-attention\n'
fi
}
mount_status() {
local path="$1"
if have findmnt && findmnt -rn --target "$path" >/dev/null 2>&1; then
@ -243,6 +253,7 @@ human_report() {
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 ' nginx status: %s\n\n' "$(nginx_status)"
printf 'Services\n'
printf ' Failed systemd services:\n'
@ -305,6 +316,7 @@ json_report() {
--arg docker_version "$(component_version docker --version)" \
--arg docker_status "$(docker_status)" \
--arg sql_container_health "$(sql_container_health)" \
--arg nginx_status "$(nginx_status)" \
--arg hostname "$(hostname)" \
--arg host_overrides "$(local_host_overrides_present)" \
--arg firewall "$(firewall_summary)" \
@ -333,7 +345,8 @@ json_report() {
nginx: $nginx_version,
docker: $docker_version,
docker_status: $docker_status,
sql_server_container_health: $sql_container_health
sql_server_container_health: $sql_container_health,
nginx_status: $nginx_status
},
services: {failed_systemd_units: ($failed_services | split("\n") | map(select(length > 0)))},
network: {

View File

@ -246,6 +246,37 @@ check_docker_sql_state() {
fi
}
check_nginx_ufw_state() {
if command -v nginx >/dev/null 2>&1; then
systemctl is-active --quiet nginx && atlas_validation_result PASS "nginx service is active" || atlas_validation_result FAIL "nginx service is not active"
sudo -n nginx -t >/dev/null 2>&1 && atlas_validation_result PASS "nginx configuration is valid" || atlas_validation_result FAIL "nginx configuration is invalid"
curl -fsS http://127.0.0.1/health >/dev/null 2>&1 && atlas_validation_result PASS "Atlas nginx health endpoint is reachable" || atlas_validation_result FAIL "Atlas nginx health endpoint is not reachable"
else
atlas_validation_result SKIPPED "nginx is not installed"
fi
if command -v ufw >/dev/null 2>&1 && sudo -n ufw status | grep -q '^Status: active'; then
atlas_validation_result PASS "UFW is active"
sudo -n ufw status numbered | grep -q '22/tcp' && atlas_validation_result PASS "UFW allows SSH" || atlas_validation_result FAIL "UFW SSH rule missing"
sudo -n ufw status numbered | grep -q '80/tcp' && atlas_validation_result PASS "UFW allows HTTP" || atlas_validation_result FAIL "UFW HTTP rule missing"
sudo -n ufw status numbered | grep -q '443/tcp' && atlas_validation_result PASS "UFW allows HTTPS" || atlas_validation_result FAIL "UFW HTTPS rule missing"
sudo -n ufw status numbered | grep -q '1433/tcp.*192.168.10.0/24' && atlas_validation_result PASS "UFW restricts SQL Server to LAN subnet" || atlas_validation_result FAIL "UFW SQL LAN rule missing"
else
atlas_validation_result FAIL "UFW is not active"
fi
if ss -ltn | awk '{print $4}' | grep -qx '192.168.10.10:1433'; then
atlas_validation_result PASS "SQL Server is bound to Atlas LAN address"
else
atlas_validation_result FAIL "SQL Server is not bound to Atlas LAN address"
fi
if ss -ltn | awk '{print $4}' | grep -Eq '(^0\.0\.0\.0:1433$|^\[::\]:1433$)'; then
atlas_validation_result FAIL "SQL Server is bound to all interfaces"
else
atlas_validation_result PASS "SQL Server is not bound to all interfaces"
fi
}
atlas_validation_reset
printf 'Atlas Host Validation\n'
@ -290,6 +321,7 @@ check_free_space /sql "$MIN_SQL_FREE_GIB"
check_failed_services
check_docker_sql_state
check_nginx_ufw_state
if [[ -f /var/run/reboot-required ]]; then
atlas_validation_result WARNING "Reboot is pending"