diff --git a/config/README.md b/config/README.md
index a5b8ebc..7b5b746 100644
--- a/config/README.md
+++ b/config/README.md
@@ -41,7 +41,9 @@ 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/`.
+`config/nginx/` contains the temporary Atlas default HTTP site configuration and the PlotDirector development reverse-proxy configuration. Live nginx configuration is deployed under `/etc/nginx/`.
+
+`config/systemd/` contains source-controlled systemd unit templates. `plotdirector-dev.service` is deployed to `/etc/systemd/system/plotdirector-dev.service`.
## Security Considerations
diff --git a/config/nginx/README.md b/config/nginx/README.md
index fa5a89c..61d0e95 100644
--- a/config/nginx/README.md
+++ b/config/nginx/README.md
@@ -4,6 +4,8 @@ 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.
+`plotdirector-dev.conf` proxies `dev.plotdirector.com` to PlotDirector on `http://127.0.0.1:5050` and provides a lightweight `/health` endpoint. It is intentionally HTTP-only until the TLS phase.
+
Deployment locations:
- Source configuration: `config/nginx/atlas-default.conf`
@@ -11,5 +13,9 @@ Deployment locations:
- Enabled site: `/etc/nginx/sites-enabled/atlas-default.conf`
- Static content: `/var/www/atlas`
- Health check: `/health`
+- PlotDirector source configuration: `config/nginx/plotdirector-dev.conf`
+- PlotDirector live configuration: `/etc/nginx/sites-available/plotdirector-dev.conf`
+- PlotDirector enabled site: `/etc/nginx/sites-enabled/plotdirector-dev.conf`
+- PlotDirector hostname: `dev.plotdirector.com`
Do not store TLS private keys, live certificates, secrets, or PlotDirector-specific proxy credentials here.
diff --git a/config/nginx/plotdirector-dev.conf b/config/nginx/plotdirector-dev.conf
new file mode 100644
index 0000000..030b087
--- /dev/null
+++ b/config/nginx/plotdirector-dev.conf
@@ -0,0 +1,30 @@
+server {
+ listen 80;
+ listen [::]:80;
+
+ server_name dev.plotdirector.com;
+
+ access_log /var/log/nginx/plotdirector-dev-access.log;
+ error_log /var/log/nginx/plotdirector-dev-error.log;
+
+ client_max_body_size 100m;
+
+ location = /health {
+ add_header Content-Type text/plain;
+ return 200 "ok\n";
+ }
+
+ location / {
+ proxy_pass http://127.0.0.1:5050;
+ proxy_http_version 1.1;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ proxy_read_timeout 120s;
+ proxy_send_timeout 120s;
+ proxy_connect_timeout 30s;
+ }
+}
diff --git a/config/systemd/plotdirector-dev.service b/config/systemd/plotdirector-dev.service
new file mode 100644
index 0000000..5de6ba6
--- /dev/null
+++ b/config/systemd/plotdirector-dev.service
@@ -0,0 +1,21 @@
+[Unit]
+Description=PlotDirector development web application
+After=network-online.target docker.service
+Wants=network-online.target
+Requires=docker.service
+
+[Service]
+Type=simple
+User=plotdirector
+Group=plotdirector
+WorkingDirectory=/srv/apps/plotdirector-dev/current
+EnvironmentFile=/etc/plotdirector/plotdirector.env
+ExecStart=/usr/bin/dotnet /srv/apps/plotdirector-dev/current/PlotLine.dll
+Restart=on-failure
+RestartSec=10
+SyslogIdentifier=plotdirector-dev
+NoNewPrivileges=true
+PrivateTmp=true
+
+[Install]
+WantedBy=multi-user.target
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index c406932..d6f57fa 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -23,6 +23,8 @@ Atlas is a single physical development server.
- Development volume: SSD mounted at `/srv`
- Repository root: `/srv/repos`
- Infrastructure repository: `/srv/repos/AI-Development-Server`
+- PlotDirector source repository: `/srv/repos/PlotDirector`
+- PlotDirector development deployment: `/srv/apps/plotdirector-dev/current`
The intended model separates operating system state, SQL data, and development data. This supports rebuild and recovery by reducing reliance on the OS volume for mutable project data.
@@ -80,20 +82,32 @@ Atlas is intended to support:
Only components verified during implementation should be recorded as installed.
-### Intended PlotDirector Deployment Topology
+### PlotDirector Development Deployment Topology
-PlotDirector deployment topology is not yet established.
+PlotDirector is deployed for development from the `onboarding` branch.
-Current assumptions:
+- Repository: `git@git.catherinelynwood.com:nick/PlotDirector.git`
+- Source path: `/srv/repos/PlotDirector`
+- Entry project: `PlotLine/PlotLine.csproj`
+- Target framework: `net10.0`
+- Output assembly: `PlotLine.dll`
+- Published path: `/srv/apps/plotdirector-dev/current`
+- Uploads path: `/srv/apps/plotdirector-dev/uploads`
+- Runtime environment file: `/etc/plotdirector/plotdirector.env`
+- Service account: `plotdirector`
+- systemd service: `plotdirector-dev`
+- Kestrel endpoint: `http://127.0.0.1:5050`
+- nginx hostname: `dev.plotdirector.com`
+- Development database: `PlotDirector_Development`
+- SQL login: `plotdirector_dev`
-- Source repositories should live under `/srv/repos`.
-- Build artefacts should be generated outside this infrastructure repository.
-- Deployed applications should be separated from source repositories.
-- SQL data should use `/sql` once SQL Server paths are confirmed.
-- nginx may act as the reverse proxy if the application is exposed over HTTP or HTTPS.
-- Docker may host one or more services if containerisation is adopted.
+Windows LAN clients can test the development site with this hosts-file entry:
-Specific PlotDirector service names, ports, deployment paths, database names, and rollback procedures are `TODO: Confirm during the relevant implementation phase.`
+```text
+192.168.10.10 dev.plotdirector.com
+```
+
+SMTP, Stripe, and OpenAI are intentionally unconfigured in the Atlas development runtime unless explicitly provided through protected runtime configuration.
### Role of nginx
@@ -105,8 +119,10 @@ nginx is installed from the standard Ubuntu 26.04 repositories and currently ser
- Version-controlled source: `config/nginx/atlas-default.conf`
- Static site root: `/var/www/atlas`
- Health endpoint: `http://192.168.10.10/health`
+- PlotDirector development proxy source: `config/nginx/plotdirector-dev.conf`
+- PlotDirector development hostname: `dev.plotdirector.com`
-HTTPS, TLS certificates, and PlotDirector reverse-proxy configuration are not configured yet.
+HTTPS and TLS certificates are not configured yet.
### Role of SQL Server
@@ -141,6 +157,10 @@ The intended storage and responsibility model is:
- Source repositories: `/srv/repos`
- Infrastructure repository: `/srv/repos/AI-Development-Server`
+- PlotDirector source repository: `/srv/repos/PlotDirector`
+- PlotDirector published application: `/srv/apps/plotdirector-dev/current`
+- PlotDirector runtime configuration: `/etc/plotdirector/plotdirector.env`
+- PlotDirector uploads: `/srv/apps/plotdirector-dev/uploads`
- Installer logs: `/var/log/atlas/install/`
- Discovery and validation reports: `/var/log/atlas/reports/`
- Future installer state and markers: `/var/lib/atlas/`
@@ -163,6 +183,7 @@ Known trust boundaries:
- Repository content versus live secret-bearing configuration.
- SQL data on `/sql` versus source and scripts on `/srv`.
- Future public-facing application traffic through nginx, if enabled.
+- PlotDirector Kestrel listens only on loopback and is reached through nginx.
Unresolved trust-boundary details:
@@ -188,9 +209,10 @@ flowchart TB
Scripts[Future operational scripts
scripts/]
Tests[Future smoke tests
tests/]
SQL[(SQL data volume
/sql)]
- Deploy[Future deployed applications]
- Nginx[nginx reverse proxy]
+ Deploy[PlotDirector dev deployment
/srv/apps/plotdirector-dev/current]
+ Nginx[nginx reverse proxy
dev.plotdirector.com]
Docker[Docker runtime]
+ SqlContainer[SQL Server container
atlas-sql-server]
end
Repo --> Install
@@ -198,7 +220,8 @@ flowchart TB
Repo --> Scripts
Repo --> Tests
Source --> Deploy
- Deploy --> SQL
+ Deploy --> SqlContainer
+ SqlContainer --> SQL
Nginx --> Deploy
Docker --> Deploy
```
diff --git a/docs/BUILD-LOG.md b/docs/BUILD-LOG.md
index e758add..259a0d0 100644
--- a/docs/BUILD-LOG.md
+++ b/docs/BUILD-LOG.md
@@ -10,6 +10,63 @@ This file records chronological Atlas build and infrastructure milestones. Do no
### Phase
+Phase 2 PlotDirector development deployment.
+
+### Objective
+
+Build and run the PlotDirector `onboarding` branch on Atlas behind nginx using the local SQL Server container.
+
+### Changes
+
+- Cloned PlotDirector at `/srv/repos/PlotDirector` from `git@git.catherinelynwood.com:nick/PlotDirector.git`.
+- Confirmed branch `onboarding` tracks `origin/onboarding`.
+- Built `PlotLine/PlotLine.csproj` targeting `net10.0`.
+- Created runtime configuration at `/etc/plotdirector/plotdirector.env`.
+- Created SQL database `PlotDirector_Development`.
+- Created SQL login `plotdirector_dev`.
+- Applied numbered SQL scripts `001` through `131`; `Stripe_Price_IDs_Prod.sql` was not run.
+- Published the application to `/srv/apps/plotdirector-dev/current`.
+- Added systemd unit template `config/systemd/plotdirector-dev.service`.
+- Added nginx proxy configuration `config/nginx/plotdirector-dev.conf`.
+- Added deployment phase `install/phases/50-plotdirector-dev.sh`.
+
+### Validation
+
+- `dotnet restore` passed.
+- `dotnet build --configuration Release` passed with zero warnings.
+- `dotnet publish --configuration Release` passed.
+- `plotdirector-dev` systemd service started and listened on `127.0.0.1:5050`.
+- nginx proxied `dev.plotdirector.com` to Kestrel.
+- `/health` returned HTTP 200 from nginx.
+- The development database contained the expected schema after script application.
+
+### Problems
+
+- SQL script execution required `sqlcmd -I` from script `076` onward so `QUOTED_IDENTIFIER` was enabled.
+- `/srv` was `0700`, which prevented the non-root service account from traversing to `/srv/apps`. Atlas now grants execute-only traversal on `/srv`.
+- The repository contains legacy hard-coded connection strings. They were not copied into Atlas runtime configuration.
+
+### Decisions
+
+- Run the application as the non-root `plotdirector` service account.
+- Keep Kestrel bound to `127.0.0.1:5050`.
+- Use nginx hostname `dev.plotdirector.com` for LAN development.
+- Leave SMTP, Stripe, and OpenAI unconfigured for smoke testing.
+
+### Git Commit
+
+Not yet committed.
+
+### Operator
+
+Codex.
+
+### Date
+
+2026-07-10
+
+### Phase
+
Phase 2 nginx and firewall baseline.
### Objective
diff --git a/docs/REBUILD-GUIDE.md b/docs/REBUILD-GUIDE.md
index f0ac66a..01399b5 100644
--- a/docs/REBUILD-GUIDE.md
+++ b/docs/REBUILD-GUIDE.md
@@ -12,6 +12,7 @@ This guide describes the planned rebuild path for Atlas. At Phase 1, the reposit
- Docker installation script: present.
- SQL Server container installation script: present.
- nginx installation script: present.
+- PlotDirector development deployment script: present.
- Other component installation scripts: not yet created.
- Configuration templates: not yet created.
- Operational scripts: not yet created.
@@ -121,8 +122,12 @@ Planned bootstrap flow:
9. Run `install/phases/20-sql-server.sh` after Docker is available.
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.
+12. Clone PlotDirector to `/srv/repos/PlotDirector` on branch `onboarding`.
+13. Create protected PlotDirector runtime configuration at `/etc/plotdirector/plotdirector.env`.
+14. Initialise the `PlotDirector_Development` database using the numbered SQL scripts.
+15. Run `install/phases/50-plotdirector-dev.sh`.
+16. Run additional ordered installation scripts from `install/phases/` when they exist.
+17. Run smoke tests from `tests/` when they exist.
Bootstrap command sequence is `TODO: Confirm during the relevant implementation phase.`
@@ -135,6 +140,7 @@ install/phases/10-dotnet.sh
install/phases/20-sql-server.sh
install/phases/30-docker.sh
install/phases/40-nginx.sh
+install/phases/50-plotdirector-dev.sh
```
Current Docker and SQL Server commands:
@@ -172,6 +178,41 @@ Current firewall inspection command:
sudo ufw status numbered
```
+Current PlotDirector development deployment command:
+
+```bash
+install/phases/50-plotdirector-dev.sh
+```
+
+PlotDirector runtime details:
+
+- Source repository: `/srv/repos/PlotDirector`
+- Required branch: `onboarding`
+- Entry project: `PlotLine/PlotLine.csproj`
+- Published path: `/srv/apps/plotdirector-dev/current`
+- Runtime environment file: `/etc/plotdirector/plotdirector.env`
+- Service: `plotdirector-dev`
+- Kestrel endpoint: `http://127.0.0.1:5050`
+- nginx hostname: `dev.plotdirector.com`
+- Database: `PlotDirector_Development`
+- SQL login: `plotdirector_dev`
+
+Use this Windows hosts-file entry for LAN testing:
+
+```text
+192.168.10.10 dev.plotdirector.com
+```
+
+PlotDirector validation commands:
+
+```bash
+systemctl is-active plotdirector-dev
+journalctl -u plotdirector-dev --no-pager -n 100
+curl --noproxy '*' -fsS http://127.0.0.1:5050/ >/dev/null
+curl --noproxy '*' -fsS -H 'Host: dev.plotdirector.com' http://127.0.0.1/ >/dev/null
+sudo nginx -t
+```
+
Installation scripts must:
- Be idempotent.
diff --git a/install/README.md b/install/README.md
index 21419e8..2f37cb3 100644
--- a/install/README.md
+++ b/install/README.md
@@ -55,6 +55,7 @@ install/phases/10-dotnet.sh
install/phases/20-sql-server.sh
install/phases/30-docker.sh
install/phases/40-nginx.sh
+install/phases/50-plotdirector-dev.sh
```
The .NET installer installs only the required .NET SDK packages and verifies them with `dotnet --list-sdks`.
@@ -70,11 +71,17 @@ Docker is installed from Docker's official signed Ubuntu APT repository. SQL Ser
nginx is installed from Ubuntu repositories and serves the temporary Atlas status site until PlotDirector routing is configured.
+PlotDirector development deployment uses `/srv/repos/PlotDirector` on branch `onboarding`, publishes `PlotLine.dll` to `/srv/apps/plotdirector-dev/current`, runs as the `plotdirector` service account, and is proxied by nginx as `dev.plotdirector.com`.
+
+The PlotDirector runtime environment file is `/etc/plotdirector/plotdirector.env`. It contains secrets and must never be committed.
+
Runtime output belongs outside the repository:
- `/var/log/atlas/install/`
- `/var/log/atlas/reports/`
- `/var/lib/atlas/`
+- `/srv/apps/plotdirector-dev/current`
+- `/srv/apps/plotdirector-dev/uploads`
## Security Considerations
diff --git a/install/phases/50-plotdirector-dev.sh b/install/phases/50-plotdirector-dev.sh
new file mode 100755
index 0000000..a990484
--- /dev/null
+++ b/install/phases/50-plotdirector-dev.sh
@@ -0,0 +1,218 @@
+#!/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/files.sh
+source "${INSTALL_DIR}/lib/files.sh"
+
+atlas_set_script_name "50-plotdirector-dev.sh"
+atlas_install_error_trap
+
+APP_REPO="/srv/repos/PlotDirector"
+APP_BRANCH="onboarding"
+APP_PROJECT="${APP_REPO}/PlotLine/PlotLine.csproj"
+PUBLISH_DIR="/srv/apps/plotdirector-dev/current"
+UPLOADS_DIR="/srv/apps/plotdirector-dev/uploads"
+ENV_FILE="/etc/plotdirector/plotdirector.env"
+SERVICE_USER="plotdirector"
+SERVICE_NAME="plotdirector-dev"
+SYSTEMD_SOURCE="${REPO_ROOT}/config/systemd/plotdirector-dev.service"
+SYSTEMD_DEST="/etc/systemd/system/plotdirector-dev.service"
+NGINX_SOURCE="${REPO_ROOT}/config/nginx/plotdirector-dev.conf"
+NGINX_DEST="/etc/nginx/sites-available/plotdirector-dev.conf"
+NGINX_LINK="/etc/nginx/sites-enabled/plotdirector-dev.conf"
+NGINX_BACKUP_DIR="/etc/nginx/atlas-backups"
+ALLOW_DIRTY=0
+
+usage() {
+ cat <<'USAGE'
+Usage: install/phases/50-plotdirector-dev.sh [options]
+
+Publish and run the PlotDirector onboarding branch as the Atlas development
+instance behind nginx. This script does not modify PlotDirector source,
+create Git commits, or store secrets.
+
+Prerequisites:
+ - /srv/repos/PlotDirector exists on branch onboarding.
+ - /etc/plotdirector/plotdirector.env exists and contains runtime settings.
+ - The PlotDirector development database has already been initialised.
+
+Options:
+ --help Show this help text.
+ --version Show script/framework version.
+ --dry-run Show intended actions without making changes.
+ --verbose Print additional diagnostic output.
+ --allow-dirty Permit deployment from a dirty PlotDirector working tree.
+
+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
+ ;;
+ --allow-dirty)
+ ALLOW_DIRTY=1
+ ;;
+ *)
+ atlas_usage_error "Unknown option: $1"
+ exit "${ATLAS_EXIT_USAGE}"
+ ;;
+ esac
+ shift
+done
+
+require_prerequisites() {
+ atlas_require_command git
+ atlas_require_command dotnet
+ atlas_require_command curl
+ atlas_require_sudo_noninteractive || {
+ atlas_log_error "Passwordless non-interactive sudo is required"
+ exit "${ATLAS_EXIT_PREREQ}"
+ }
+
+ if [[ ! -d "${APP_REPO}/.git" ]]; then
+ atlas_log_error "PlotDirector repository not found at ${APP_REPO}"
+ exit "${ATLAS_EXIT_PREREQ}"
+ fi
+
+ local current_branch
+ current_branch="$(git -C "$APP_REPO" branch --show-current)"
+ if [[ "$current_branch" != "$APP_BRANCH" ]]; then
+ atlas_log_error "Expected ${APP_REPO} to be on ${APP_BRANCH}; found ${current_branch}"
+ exit "${ATLAS_EXIT_UNSAFE}"
+ fi
+
+ if [[ "$ALLOW_DIRTY" != "1" ]] && [[ -n "$(git -C "$APP_REPO" status --porcelain)" ]]; then
+ atlas_log_error "PlotDirector working tree is dirty. Use --allow-dirty only for deliberate local deployments."
+ exit "${ATLAS_EXIT_UNSAFE}"
+ fi
+
+ if ! sudo -n test -f "$ENV_FILE"; then
+ atlas_log_error "Runtime environment file is missing: ${ENV_FILE}"
+ exit "${ATLAS_EXIT_PREREQ}"
+ fi
+}
+
+ensure_runtime_user_and_dirs() {
+ if atlas_is_dry_run; then
+ atlas_log_info "Dry-run mode: would ensure ${SERVICE_USER} user and deployment directories"
+ return 0
+ fi
+
+ if ! id "$SERVICE_USER" >/dev/null 2>&1; then
+ sudo -n useradd --system --home-dir /srv/apps/plotdirector-dev --shell /usr/sbin/nologin --create-home "$SERVICE_USER"
+ fi
+
+ sudo -n install -d -m 0750 -o "$SERVICE_USER" -g "$SERVICE_USER" /srv/apps/plotdirector-dev "$PUBLISH_DIR" "$UPLOADS_DIR"
+ local srv_mode
+ srv_mode="$(stat -c '%a' /srv)"
+ if (( (8#$srv_mode & 0001) == 0 )); then
+ sudo -n chmod o+x /srv
+ fi
+ sudo -n chown root:"$SERVICE_USER" /etc/plotdirector
+ sudo -n chmod 0750 /etc/plotdirector
+ sudo -n chown root:"$SERVICE_USER" "$ENV_FILE"
+ sudo -n chmod 0640 "$ENV_FILE"
+}
+
+publish_application() {
+ if atlas_is_dry_run; then
+ atlas_log_info "Dry-run mode: would restore and publish ${APP_PROJECT} to ${PUBLISH_DIR}"
+ return 0
+ fi
+
+ local publish_tmp
+ publish_tmp="$(mktemp -d)"
+ dotnet restore "$APP_PROJECT"
+ dotnet publish "$APP_PROJECT" --configuration Release --output "$publish_tmp"
+ sudo -n rsync -a --delete "${publish_tmp}/" "${PUBLISH_DIR}/"
+ sudo -n chown -R "${SERVICE_USER}:${SERVICE_USER}" "$PUBLISH_DIR" "$UPLOADS_DIR"
+ sudo -n find "$PUBLISH_DIR" -type d -exec chmod 0750 {} +
+ sudo -n find "$PUBLISH_DIR" -type f -exec chmod 0640 {} +
+ rm -rf "$publish_tmp"
+}
+
+deploy_systemd_unit() {
+ if atlas_is_dry_run; then
+ atlas_log_info "Dry-run mode: would deploy ${SYSTEMD_DEST}"
+ atlas_log_info "Dry-run mode: would enable ${SERVICE_NAME}"
+ return 0
+ fi
+
+ if [[ -f "$SYSTEMD_DEST" ]] && cmp -s "$SYSTEMD_SOURCE" "$SYSTEMD_DEST"; then
+ atlas_log_ok "No change required for ${SYSTEMD_DEST}"
+ else
+ sudo -n install -m 0644 -o root -g root "$SYSTEMD_SOURCE" "$SYSTEMD_DEST"
+ atlas_log_ok "Updated ${SYSTEMD_DEST}"
+ fi
+ sudo -n systemctl daemon-reload
+ sudo -n systemctl enable "$SERVICE_NAME"
+}
+
+deploy_nginx_site() {
+ if atlas_is_dry_run; then
+ atlas_log_info "Dry-run mode: would deploy nginx site ${NGINX_DEST}"
+ 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
+ sudo -n install -m 0644 "$NGINX_SOURCE" "$NGINX_DEST"
+ sudo -n ln -sfn "$NGINX_DEST" "$NGINX_LINK"
+ sudo -n nginx -t
+}
+
+restart_and_verify() {
+ if atlas_is_dry_run; then
+ atlas_log_info "Dry-run mode: would restart ${SERVICE_NAME}, reload nginx, and verify HTTP responses"
+ return 0
+ fi
+
+ sudo -n systemctl restart "$SERVICE_NAME"
+
+ local attempt
+ for attempt in {1..30}; do
+ if curl -fsS http://127.0.0.1:5050/ >/dev/null 2>&1; then
+ break
+ fi
+ sleep 2
+ done
+
+ curl -fsS http://127.0.0.1:5050/ >/dev/null
+ sudo -n nginx -t
+ sudo -n systemctl reload nginx
+ curl -fsS -H "Host: dev.plotdirector.com" http://127.0.0.1/health >/dev/null
+ atlas_log_ok "PlotDirector development deployment verified"
+}
+
+main() {
+ require_prerequisites
+ ensure_runtime_user_and_dirs
+ publish_application
+ deploy_systemd_unit
+ deploy_nginx_site
+ restart_and_verify
+}
+
+main
diff --git a/install/phases/README.md b/install/phases/README.md
index b2dc3b7..e7d6a06 100644
--- a/install/phases/README.md
+++ b/install/phases/README.md
@@ -24,6 +24,7 @@ Each phase must source the shared framework under `install/lib/`, support dry-ru
- `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.
+- `50-plotdirector-dev.sh`: publishes and restarts the PlotDirector development instance from `/srv/repos/PlotDirector`.
Installed SDKs after the current Phase 2 run:
@@ -36,3 +37,15 @@ Current SQL Server deployment:
- Digest: `sha256:698682bab57c02c42bc0aa274b158aeb242d8e9104149a7489628d5535805816`
- Container: `atlas-sql-server`
- Compose project: `atlas-sql`
+
+Current PlotDirector development deployment:
+
+- Repository: `/srv/repos/PlotDirector`
+- Branch: `onboarding`
+- Entry project: `PlotLine/PlotLine.csproj`
+- Target framework: `net10.0`
+- Assembly: `PlotLine.dll`
+- Published path: `/srv/apps/plotdirector-dev/current`
+- Service: `plotdirector-dev`
+- nginx hostname: `dev.plotdirector.com`
+- Database: `PlotDirector_Development`