Skip to content

Commit 3496e97

Browse files
committed
feat(deploy): containerization + WP-specific deploy + config examples
- Containerfile: top-level container build (Chainguard-base per estate convention). - deploy/: compose.yaml + setup.sh + verify-sqli.sh + local-test.sh + DEPLOY.adoc + Containerfile.ols-local. - examples/: wharf-cli.toml + yacht-agent.toml — sample configs. - eclexiaiser.toml: eclexia (sustainabot policy engine) config. Per the 2026-04-26 graduation plan (AI-WORK-todo.md item -4).
1 parent 01431b5 commit 3496e97

214 files changed

Lines changed: 6161 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

Lines changed: 3311 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Containerfile

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# SPDX-License-Identifier: PMPL-1.0-or-later
2+
# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
4+
# Multi-stage Containerfile for Project Wharf
5+
# Builds both wharf-cli and yacht-agent binaries
6+
# Uses Chainguard images for minimal attack surface
7+
8+
# Builder stage — Wolfi-base with Rust toolchain
9+
FROM cgr.dev/chainguard/wolfi-base:latest AS builder
10+
11+
RUN apk add --no-cache rust pkgconf openssl-dev gcc glibc-dev
12+
13+
WORKDIR /build
14+
15+
# Copy workspace files
16+
COPY Cargo.toml Cargo.lock ./
17+
COPY crates/ crates/
18+
COPY bin/ bin/
19+
# Dummy xtask for workspace resolution
20+
RUN mkdir -p xtask/src && echo 'fn main() {}' > xtask/src/main.rs
21+
COPY xtask/Cargo.toml xtask/Cargo.toml
22+
23+
# Build release binaries
24+
RUN cargo build --release --bin wharf --bin yacht-agent
25+
26+
# Runtime image for wharf-cli
27+
FROM cgr.dev/chainguard/wolfi-base:latest AS wharf-cli
28+
29+
COPY --from=builder /build/target/release/wharf /usr/local/bin/wharf
30+
31+
ENTRYPOINT ["wharf"]
32+
CMD ["--help"]
33+
34+
# Runtime image for yacht-agent (wolfi-base — glibc required)
35+
FROM cgr.dev/chainguard/wolfi-base:latest AS yacht-agent
36+
37+
COPY --from=builder /build/target/release/yacht-agent /yacht-agent
38+
39+
EXPOSE 3306 33060 9001
40+
41+
ENTRYPOINT ["/yacht-agent"]
42+
CMD ["--help"]

deploy/Containerfile.ols-local

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# SPDX-License-Identifier: PMPL-1.0-or-later
2+
# SPDX-FileCopyrightText: 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk>
3+
#
4+
# OpenLiteSpeed local dev image — WordPress-ready with MySQL extensions
5+
# EXCEPTION: No Chainguard OLS image exists — litespeedtech base required
6+
7+
FROM docker.io/litespeedtech/openlitespeed:1.8.5-lsphp83
8+
9+
# lsphp83-common (bundled in base) provides gd, mbstring, xml, zip
10+
# Only need to add packages not already included
11+
RUN apt-get update && apt-get install -y --no-install-recommends \
12+
lsphp83-mysql \
13+
lsphp83-curl \
14+
lsphp83-intl \
15+
lsphp83-imagick \
16+
lsphp83-redis \
17+
&& apt-get clean \
18+
&& rm -rf /var/lib/apt/lists/*
19+
20+
EXPOSE 8088
21+
22+
WORKDIR /var/www/vhosts/localhost/html

deploy/DEPLOY.adoc

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// SPDX-License-Identifier: PMPL-1.0-or-later
2+
= Project Wharf — Deployment Guide
3+
:toc: left
4+
:icons: font
5+
6+
== Prerequisites
7+
8+
* A Linux server (Debian 12+, Ubuntu 22.04+, Fedora 38+, or any distro with podman)
9+
* `podman` or `docker` installed
10+
* At least 2 GB RAM, 10 GB disk
11+
* Ports 8080, 8443, 9001 available
12+
13+
== Quick Start (5 minutes)
14+
15+
[source,bash]
16+
----
17+
# Clone the repo
18+
git clone https://github.com/hyperpolymath/project-wharf.git
19+
cd project-wharf/deploy
20+
21+
# Run the setup script
22+
sudo ./setup.sh --domain example.com
23+
24+
# Start the stack
25+
sudo podman compose -f ../infra/selur-compose.yaml up -d
26+
27+
# Verify
28+
curl http://localhost:9001/health
29+
# → {"status":"ok","moored":false,...}
30+
----
31+
32+
== What Gets Deployed
33+
34+
[cols="1,2,1"]
35+
|===
36+
| Service | Purpose | Port
37+
38+
| `yacht-agent` | Security enforcer (DB proxy, integrity, mooring API) | 3306, 9001
39+
| `yacht-nginx` | Reverse proxy (TLS termination, static files) | 8080, 8443
40+
| `mariadb` | Database (shadowed behind yacht-agent proxy) | 33060 (internal)
41+
|===
42+
43+
== Architecture
44+
45+
[source,text]
46+
----
47+
Internet → nginx:8080/8443 → PHP-FPM → yacht-agent:3306 → MariaDB:33060
48+
49+
AST-aware SQL filter
50+
(blocks injection)
51+
----
52+
53+
WordPress connects to the database on port 3306, which is the yacht-agent's proxy.
54+
The agent parses every SQL statement's AST and enforces table-level write policies.
55+
56+
== Managing Your Site
57+
58+
=== From Your Local Machine (The Wharf)
59+
60+
[source,bash]
61+
----
62+
# Install the wharf CLI
63+
cargo install --path bin/wharf-cli
64+
65+
# Initialize your fleet
66+
wharf init --adapter wordpress
67+
68+
# Push changes to the yacht
69+
wharf moor example.com
70+
71+
# Verify remote integrity
72+
wharf integrity verify --remote example.com
73+
----
74+
75+
=== On the Server (The Yacht)
76+
77+
[source,bash]
78+
----
79+
# Check agent status
80+
curl http://localhost:9001/stats
81+
82+
# View metrics (Prometheus format)
83+
curl http://localhost:9001/metrics
84+
85+
# Check health
86+
curl http://localhost:9001/health
87+
----
88+
89+
== Local Development (Podman Compose)
90+
91+
For a quick local proof-of-concept with WordPress behind the SQL proxy:
92+
93+
[source,bash]
94+
----
95+
cd deploy
96+
97+
# Automated: downloads WordPress, generates wp-config.php, builds + starts stack
98+
bash local-test.sh
99+
100+
# Visit WordPress setup wizard
101+
# http://localhost:8080
102+
103+
# Prove SQL injection is blocked (6 tests)
104+
bash verify-sqli.sh
105+
106+
# Check agent stats
107+
curl http://localhost:9001/stats
108+
109+
# Tear down
110+
podman compose down
111+
rm -rf wharf-local/ # removes all data
112+
----
113+
114+
=== Local Stack Architecture
115+
116+
[source,text]
117+
----
118+
Browser :8080 → OLS (web) → PHP/WordPress → agent:3306 → db:3306 (MariaDB)
119+
120+
AST SQL parser (sqlparser 0.39)
121+
Blocks writes to wp_users, wp_options, wp_posts
122+
Blocks DROP/ALTER/TRUNCATE always
123+
----
124+
125+
=== Services
126+
127+
[cols="1,2,1"]
128+
|===
129+
| Service | Purpose | Port
130+
131+
| `db` | MariaDB 10.11 (hidden, no host exposure) | 3306 (internal)
132+
| `agent` | yacht-agent SQL proxy + API | 3306 (proxy), 9001 (API)
133+
| `web` | OpenLiteSpeed 1.8.5 + PHP 8.3 | 80→8080 (host)
134+
|===
135+
136+
== Security Notes
137+
138+
* The default signature scheme is `ml-dsa-87-only` (FIPS 204, post-quantum safe)
139+
* To enable hybrid Ed448+ML-DSA-87 signatures (not yet audited):
140+
set `signature_scheme = "hybrid"` in `yacht-agent.toml`
141+
* WordPress is configured with `DISALLOW_FILE_EDIT` and `DISALLOW_FILE_MODS`
142+
* All code changes go through the mooring protocol (cryptographically signed)
143+
* The database proxy blocks writes to `wp_users`, `wp_options`, `wp_posts` tables
144+
* `DROP TABLE` and `ALTER TABLE` are always blocked regardless of table
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
# Project Wharf — Local Deployment Session Summary
2+
<!-- SPDX-License-Identifier: PMPL-1.0-or-later -->
3+
4+
**Date:** 2026-02-14
5+
**Status:** COMMITTED TO GITHUB. GitLab push pending (broken pack object).
6+
7+
---
8+
9+
## What Was Done
10+
11+
### Bug Fixes (3 files)
12+
13+
| File | Bug | Fix |
14+
|------|-----|-----|
15+
| `infra/containers/Containerfile.agent` | Wolfi doesn't have `musl-dev`, `cargo`, or `rustup` packages | Switched to glibc build: `rust pkgconf openssl-dev gcc glibc-dev`, removed musl target/RUSTFLAGS, build from `target/release/`, runtime `wolfi-base` |
16+
| `Containerfile` (line 35) | yacht-agent stage uses `static` image but binary is glibc-linked → segfault | Changed to `cgr.dev/chainguard/wolfi-base:latest` |
17+
| `adapters/wordpress-wharf/wharf-adapter.php` | Reads flat `$stats['queries_allowed']` but `/stats` returns nested `{"queries":{"allowed":N}}`. Also reads `moored`/`firewall_mode` from `/stats` but they're on `/status` | Rewrote `wharf_fetch_agent_stats()` to fetch both `/stats` and `/status`, maps nested JSON to flat format |
18+
19+
### New Files (4 files)
20+
21+
| File | Purpose |
22+
|------|---------|
23+
| `deploy/Containerfile.ols-local` | OLS 1.8.5 + lsphp83 with mysql/curl/intl/imagick/redis extensions |
24+
| `deploy/compose.yaml` | 3-service podman compose: db (MariaDB 10.11), agent (yacht-agent), web (OLS) |
25+
| `deploy/local-test.sh` | Automated: create dirs, download WP, fetch real salt keys, write wp-config.php (DB_HOST=agent:3306), copy adapter plugin, build containers, start stack, health check |
26+
| `deploy/verify-sqli.sh` | 6 SQL injection tests via `podman exec` through agent proxy |
27+
28+
### Documentation Updated (8 files)
29+
30+
| File | Changes |
31+
|------|---------|
32+
| `CHANGELOG.adoc` | Added "Local Deployment Proof" section |
33+
| `TOPOLOGY.md` | Added local deployment architecture diagram + `local-deploy` row in dashboard |
34+
| `AI.a2ml` | Added "Local Deployment" section |
35+
| `deploy/DEPLOY.adoc` | Added local dev instructions, architecture diagram, services table |
36+
| `.machine_readable/STATE.scm` | Added `local-deployment-proof` milestone with 5 sub-milestones |
37+
| `.machine_readable/ECOSYSTEM.scm` | Added `openlitespeed` and `mariadb` as related projects |
38+
| `.machine_readable/META.scm` | Added 2 ADRs: `glibc-runtime-containers`, `ols-local-deployment` |
39+
| `.gitignore` | Added `deploy/wharf-local/` |
40+
41+
### Verification Results
42+
43+
- **cargo test**: 63/63 passed (40 wharf-core + 19 wharf-cli + 4 yacht-agent)
44+
- **Stack**: All 3 containers healthy, WordPress installation wizard served at :8080
45+
- **Agent**: `/health` → OK, `/stats` → real counters (321 allowed, 3 blocked), `/status` → active
46+
- **verify-sqli.sh**: 6/6 passed
47+
- SELECT → PASS (forwarded)
48+
- INSERT INTO wp_users → BLOCKED
49+
- DROP TABLE → BLOCKED
50+
- ALTER TABLE → BLOCKED
51+
- TRUNCATE TABLE → BLOCKED
52+
- UNION SELECT injection → BLOCKED
53+
54+
---
55+
56+
## Commit
57+
58+
```
59+
ece13ac feat: local E2E deployment proof — WordPress behind yacht-agent SQL proxy
60+
```
61+
62+
15 files changed, 578 insertions(+), 38 deletions(-)
63+
64+
**Pushed to:** GitHub ✅
65+
**Pushed to:** GitLab ❌ (see below)
66+
67+
---
68+
69+
## Remaining: GitLab Push
70+
71+
**Two issues blocking GitLab push:**
72+
73+
### Issue 1: Diverged history
74+
GitLab has 3 old MR-based commits not in GitHub's history (`64fc965`, `5827c3f`, `bf1aac3`). Needs force-push to overwrite with canonical GitHub history.
75+
76+
### Issue 2: `main` branch is protected + API token expired
77+
- GitLab `main` branch is protected (no force push allowed)
78+
- GitLab API token (`glpat-dYz2_j--...`) is **EXPIRED**
79+
- SSH auth works but can't change branch protection via SSH
80+
- `glab` CLI also uses the expired token
81+
82+
### Fix (manual steps required)
83+
84+
```bash
85+
# Step 1: Refresh GitLab token
86+
# Go to: https://gitlab.com/-/user_settings/personal_access_tokens
87+
# Create new token with api + write_repository scopes
88+
# Update ~/.netrc with new token
89+
90+
# Step 2: Unprotect main
91+
GL_TOKEN="<new-token>"
92+
PROJECT_ID=$(curl -sf --header "PRIVATE-TOKEN: $GL_TOKEN" \
93+
"https://gitlab.com/api/v4/projects?search=project-wharf&owned=true" | python3 -c "import sys,json; print(json.load(sys.stdin)[0]['id'])")
94+
curl --request DELETE --header "PRIVATE-TOKEN: $GL_TOKEN" \
95+
"https://gitlab.com/api/v4/projects/$PROJECT_ID/protected_branches/main"
96+
97+
# Step 3: Force push from clean clone (already at /tmp/wharf-push.git)
98+
cd /tmp/wharf-push.git
99+
git push --force git@gitlab.com:hyperpolymath/project-wharf.git main
100+
101+
# Step 4: Re-protect main
102+
curl --request POST --header "PRIVATE-TOKEN: $GL_TOKEN" \
103+
"https://gitlab.com/api/v4/projects/$PROJECT_ID/protected_branches?name=main&push_access_level=40&merge_access_level=40"
104+
105+
# Step 5: Clean up
106+
rm -rf /tmp/wharf-push.git
107+
```
108+
109+
### Also fix the local repo's broken pack (optional)
110+
111+
The local `.git` has a broken pack object (`501faf18`) from the old GitLab fetch. Not urgent — local repo works fine for everything except `git gc`.
112+
113+
```bash
114+
cd /var$REPOS_DIR/project-wharf
115+
git remote remove gitlab
116+
git remote add gitlab git@gitlab.com:hyperpolymath/project-wharf.git
117+
# After GitLab is force-pushed with clean history:
118+
git fetch gitlab
119+
```
120+
121+
---
122+
123+
## How to Use the Local Stack
124+
125+
```bash
126+
cd /var$REPOS_DIR/project-wharf/deploy
127+
128+
# Start everything (downloads WP, builds containers, starts stack)
129+
bash local-test.sh
130+
131+
# WordPress setup wizard
132+
# http://localhost:8080
133+
134+
# Agent API
135+
curl http://localhost:9001/health
136+
curl http://localhost:9001/stats
137+
138+
# Prove SQL injection blocked
139+
bash verify-sqli.sh
140+
141+
# Tear down
142+
podman compose down
143+
rm -rf wharf-local/ # removes all runtime data
144+
```
145+
146+
### Key Architecture Detail
147+
148+
```
149+
Browser :8080 → OLS (port 80) → PHP/WordPress → agent:3306 (yacht-agent) → db:3306 (MariaDB)
150+
151+
AST SQL parser (sqlparser 0.39)
152+
Blocks: INSERT wp_users, DROP, ALTER, TRUNCATE, UNION
153+
```
154+
155+
WordPress `wp-config.php` has `DB_HOST = 'agent:3306'` — all SQL goes through the proxy.
156+
MariaDB is NOT exposed to the host network.
157+
158+
---
159+
160+
## Next Steps (Future Sessions)
161+
162+
1. **GitLab push** — use the fix above
163+
2. **Production VPS** — deploy to stamp-protocol.org with real TLS
164+
3. **WordPress setup** — complete the install wizard, activate Wharf adapter plugin
165+
4. **Bitbucket/Codeberg** — mirror when those forges are unblocked

0 commit comments

Comments
 (0)