804 lines
30 KiB
Markdown
804 lines
30 KiB
Markdown
# ENVIRONMENT.md
|
|
|
|
Specification for a Mechanical Compiler instance.
|
|
|
|
| | |
|
|
|---|---|
|
|
| Revision | 5.1 (2026-08-17) |
|
|
| Supersedes | Revisions 1 through 4 |
|
|
| Basis | Revision 4, reconciled against the proven staging build, then work order 002 |
|
|
| Scope | **Host-agnostic.** Applies to any instance. |
|
|
| Instance state | `STAGING-STATE.md`, and later `PRODUCTION-STATE.md` |
|
|
| Failure evidence | `FAILURES.md` |
|
|
| Project sequence | `ROADMAP.md` |
|
|
|
|
---
|
|
|
|
## 0. How to use this document
|
|
|
|
This describes what an instance must **be**. It does not describe what any
|
|
particular host currently **is** — that lives in the corresponding state file.
|
|
|
|
Revision 4 mixed the two, which is why it aged badly the moment a real host
|
|
disagreed with it. Everything specific to `srv-b` has been removed.
|
|
|
|
### Authority order
|
|
|
|
1. **The instance state file** — factual, wins on any question of what is true.
|
|
2. **`FAILURES.md`** — evidence from contact with hosts. Read this *before*
|
|
writing automation, not after.
|
|
3. **This document** — the specification, corrected whenever proven facts
|
|
invalidate it.
|
|
4. **`ROADMAP.md`** — product sequence. Environment work never invents
|
|
application code.
|
|
|
|
### Markers
|
|
|
|
| Marker | Meaning |
|
|
|---|---|
|
|
| **REQ** | Required. The work is blocked or wrong without it. |
|
|
| **PREF** | Preferred. Substitute freely, but record the substitution. |
|
|
| **PROVEN** | Established by a real build. Do not re-derive; see the cited failure. |
|
|
| **ASSUMED** | Decided without confirmation. Listed in §16. |
|
|
| **DEFERRED** | Deliberately postponed. Not a defect, not a gap. |
|
|
| **INSTANCE** | A value supplied per instance, not fixed here. |
|
|
|
|
### Provisioning method
|
|
|
|
**REQ — manual first.** One command group at a time. Failures are recorded
|
|
before they are corrected. The smallest corrective experiment is preferred over
|
|
the one that also fixes three adjacent worries.
|
|
|
|
Automation is **derived from a proven manual procedure**, never written ahead of
|
|
one. Revision 4 said the opposite. `FAILURES.md` is why it changed: three of
|
|
this document's assumptions were wrong in ways only a real host revealed, and
|
|
one of them (F-003) produced a container that booted, reported success, and had
|
|
four broken services.
|
|
|
|
---
|
|
|
|
## 1. Five decisions that shape everything below
|
|
|
|
### 1.1 The 2D path and the 3D path are separable
|
|
|
|
The catalogue's product is a 2D cross-section rendered to SVG. Shapely plus our
|
|
own code covers that completely. CadQuery/OCCT is needed only for STEP and mesh
|
|
export.
|
|
|
|
They stay separate — separate requirements files, separate code paths, and a CI
|
|
job running the suite with the CAD dependencies absent. Three reasons, none of
|
|
which is packaging politics:
|
|
|
|
1. Part II §16 requires the control-plane / execution-plane separation
|
|
regardless of distribution.
|
|
2. A slow OCCT import must never land in the request path for a page that only
|
|
draws a cross-section.
|
|
3. Test-suite speed — the difference between running 123 fixtures on every save
|
|
and only before commit.
|
|
|
|
**CadQuery is a default-installed dependency.** Revision 1 restricted it as a
|
|
defence against YunoHost's "resource-hungry" criterion; that was overcalibrated.
|
|
The criterion reads "compared to their features" and is aimed at marginal apps.
|
|
The boundary stays; the apology is gone.
|
|
|
|
### 1.2 YunoHost and Docker are parallel targets, not sequential
|
|
|
|
YunoHost apps install natively — apt, venv, systemd, nginx — and the project
|
|
does not want Docker inside YunoHost. Neither blocks the other, and neither
|
|
should be built "in order to" reach the other.
|
|
|
|
### 1.3 Every instance mirrors production topology, not production exposure
|
|
|
|
An instance runs **two containers**: an application container that never
|
|
terminates TLS, and a reverse proxy that does. A single container serving
|
|
directly would never exercise the `X-Forwarded-Proto` path.
|
|
|
|
Staging is not publicly reachable and does not use the public FQDN. That forces
|
|
the promotion path to be exercised rather than assumed.
|
|
|
|
### 1.4 Directory layout mirrors YunoHost conventions
|
|
|
|
`install_dir`, `data_dir`, a dedicated system user, a port treated as data. The
|
|
eventual `manifest.toml` resource block then describes what already exists.
|
|
|
|
### 1.5 Configuration comes from the environment, never the filesystem
|
|
|
|
No path, port, URL or secret is hardcoded or discovered by convention. One env
|
|
file; the same variables become `ENV` in a Dockerfile and `ynh_add_config`
|
|
substitutions in a package.
|
|
|
|
The check: moving an instance to a different hostname is a one-variable change.
|
|
|
|
---
|
|
|
|
## 2. Host requirements
|
|
|
|
**REQ** — Proxmox VE 8.x, Debian 12 base.
|
|
|
|
**REQ** — Two bridges (§3). **REQ** — Directory storage for backups and
|
|
templates; LVM-thin or equivalent for container volumes.
|
|
|
|
**PREF** — At least 12 CPU threads and 24 GiB RAM if the instance is also a
|
|
development host. The geometry solvers are single-threaded, so single-thread
|
|
performance governs fixture regeneration while thread count governs test
|
|
parallelism. An older CPU without AVX is acceptable; all compiled wheels target
|
|
an SSE2 baseline.
|
|
|
|
**REQ** — Time zone and NTP configured; clock synchronised.
|
|
|
|
**INSTANCE** — Hostname, addressing, storage pool names, template path.
|
|
|
|
---
|
|
|
|
## 3. Network
|
|
|
|
### 3.1 Two bridges
|
|
|
|
**REQ** — A **management bridge** with a physical port, carrying the host's own
|
|
address. The host uses it for egress and administration.
|
|
|
|
**REQ** — A **service bridge with no physical port**, carrying the host at
|
|
`.1`. This is the only network the containers sit on.
|
|
|
|
A portless bridge needs no cable, no switch configuration and no spare NIC. It
|
|
exists so the containers cannot be reached from the physical network at all.
|
|
|
|
### 3.2 Containers have exactly one interface
|
|
|
|
**PROVEN (F-017)** — Each container has a single interface on the **service
|
|
bridge only**, with the host as gateway. No management-network interface.
|
|
|
|
Revision 4 gave each container an address on both bridges, on the unstated
|
|
assumption that a workstation on the physical LAN would browse the instance.
|
|
That assumption was wrong and put both containers on the operator's home
|
|
network.
|
|
|
|
**PROVEN (F-021)** — After any interface change, the guest's
|
|
`/etc/network/interfaces` must be inspected. `pct set --delete netN` removes the
|
|
LXC interface but leaves the stanza in the guest, and `ifup -a` then fails at
|
|
boot on a device that no longer exists. See §15 gate 1 for the assertion that
|
|
catches this.
|
|
|
|
### 3.3 Isolation is routing, not interfaces
|
|
|
|
**PROVEN (F-018)** — Removing an interface removes an address, not a route. With
|
|
forwarding enabled and a masquerade for internet access, containers reach the
|
|
physical LAN through the host, translated to its address.
|
|
|
|
**REQ** — These rules, in this order:
|
|
|
|
```
|
|
nat POSTROUTING
|
|
-s <SVC_NET> -d <LAN_NET> -j RETURN # before any masquerade
|
|
-s <SVC_NET> -o <WG_IF> -j MASQUERADE
|
|
-s <SVC_NET> -o <MGMT> -j MASQUERADE
|
|
|
|
filter FORWARD
|
|
-s <SVC_NET> -d <SVC_NET> -j ACCEPT
|
|
-s <SVC_NET> -d <LAN_NET> -j DROP
|
|
```
|
|
|
|
**REQ** — Rule order is load-bearing. `RETURN` must precede both masquerades.
|
|
Rules must be persisted, and the persisted set verified to match the live set.
|
|
|
|
**REQ** — The isolation must be asserted **negatively**: the LAN gateway is
|
|
unreachable from both containers. Asserting that the interface is gone is not
|
|
sufficient — that is exactly the mistake F-018 records.
|
|
|
|
**Expected and correct:** containers *can* reach the host itself. A container
|
|
addressing its own gateway takes the INPUT path and never enters FORWARD. This
|
|
is required — the host is their router and their SSH entry point — and is not a
|
|
leak.
|
|
|
|
### 3.4 Names
|
|
|
|
**REQ** — Names resolve through `/etc/hosts` on the host and both containers.
|
|
|
|
**REQ — do not stand up an internal DNS server.** Two containers and one host do
|
|
not justify one, and it would create a second source of truth for names that
|
|
production will not share.
|
|
|
|
**REQ** — The service FQDN names **the proxy**, which has exactly one address.
|
|
Guest hostnames are separate entries.
|
|
|
|
```
|
|
<SERVICE_FQDN> -> <proxy service address>
|
|
<app hostname> -> <app service address>
|
|
<proxy hostname> -> <proxy service address>
|
|
```
|
|
|
|
**PROVEN (F-020)** — This value tracked topology through three states before
|
|
settling. The reasoning matters more than the value.
|
|
|
|
### 3.5 No DHCP
|
|
|
|
**REQ** — Static addressing. Two containers on a portless bridge is the entire
|
|
address space; a DHCP server would add a daemon, a lease database and a failure
|
|
mode in exchange for nothing.
|
|
|
|
### 3.6 Firewall
|
|
|
|
**ASSUMED** — The Proxmox firewall may remain disabled where the service bridge
|
|
is portless and the application has no listener outside it. **REQ** — Production
|
|
must revisit this; see §15 gate 5.
|
|
|
|
---
|
|
|
|
## 4. Containers
|
|
|
|
### 4.1 Roles and features
|
|
|
|
| | Application | Proxy |
|
|
|---|---|---|
|
|
| Role | application, worker | reverse proxy, TLS |
|
|
| Unprivileged | yes | yes |
|
|
| Features | `nesting=1,keyctl=1` | `nesting=1` |
|
|
|
|
**PROVEN (F-003)** — **Every Debian 12 unprivileged container requires
|
|
`nesting=1`**, including one running nothing but nginx. Without it,
|
|
`systemd-logind`, `systemd-networkd`, `systemd-timedated` and
|
|
`systemd-networkd.socket` fail with `226/NAMESPACE`. Revision 4 said the proxy
|
|
needed no features.
|
|
|
|
`keyctl=1` is required only where Docker runs. Tested separately rather than
|
|
copied across; it is genuinely unnecessary on the proxy.
|
|
|
|
### 4.2 Sizing
|
|
|
|
**INSTANCE.** Sizing is a host capacity decision, not a specification value.
|
|
|
|
**REQ** — The application container's data volume is a **real mount point** with
|
|
`backup=0`. Backup of application data is a separate tier (§10), and including a
|
|
large data volume in a container snapshot is how a backup store fills.
|
|
|
|
**REQ** — Do not over-commit a thin pool. Thin-pool exhaustion is an ugly
|
|
failure mode.
|
|
|
|
### 4.3 Base OS
|
|
|
|
**REQ** — Debian 12 bookworm, both containers. It matches YunoHost 12 stable and
|
|
ships Python 3.11, which has the most reliable OCP/CadQuery wheel coverage.
|
|
|
|
**REQ (F-005)** — `apt full-upgrade` immediately after first boot, before
|
|
anything else is installed. Templates are not current, and discovering a
|
|
52-package backlog midway through provisioning is avoidable.
|
|
|
|
**REQ (F-004)** — Locale must be **generated**, not merely configured. Install
|
|
`locales`, enable the locale, run `locale-gen`, then `update-locale`. Setting
|
|
`LANG` alone leaves the system with only `C` and `POSIX`, silently, until
|
|
something depends on collation.
|
|
|
|
**REQ** — Time zone matching the host.
|
|
|
|
---
|
|
|
|
## 5. Names and TLS
|
|
|
|
### 5.1 Three names, never conflated
|
|
|
|
| Name | Scope |
|
|
|---|---|
|
|
| Service FQDN | What users type. Differs per instance. |
|
|
| Infrastructure token | System user, paths, units, YunoHost app id. **Same everywhere.** |
|
|
| Project name | Documents, README, catalog entry |
|
|
|
|
YunoHost app ids accept lowercase alphanumerics and underscores only, so a
|
|
hyphenated FQDN cannot serve as the token.
|
|
|
|
### 5.2 Non-production instances
|
|
|
|
**REQ** — An internal service FQDN. **REQ** — No public DNS record and no
|
|
publicly-trusted certificate: it consumes rate limit against a name production
|
|
needs clean, and puts a development host on the internet.
|
|
|
|
**PROVEN** — A locally generated CA is sufficient and is the fallback whenever a
|
|
site CA has no discoverable issuance path. The point of the TLS step is
|
|
exercising termination and header forwarding, not the trust chain. Replacing the
|
|
leaf later is two file copies and a reload.
|
|
|
|
**REQ** — The CA root is installed into the trust store of the host, both
|
|
containers, and any client used to browse the instance.
|
|
|
|
**REQ** — Record the leaf's expiry somewhere that gets read. Nothing renews a
|
|
hand-made certificate.
|
|
|
|
### 5.3 Production
|
|
|
|
| | Value |
|
|
|---|---|
|
|
| FQDN | `mechanical-compiler.manufacturing.kane-il.us` |
|
|
| Zone | Labels within `kane-il.us`. **Not delegated.** No new DS record. |
|
|
| Certificate | Let's Encrypt, **per-name, no wildcard** |
|
|
| Challenge | **ASSUMED** HTTP-01. Match existing practice if it is DNS-01; never run both. |
|
|
|
|
`manufacturing.kane-il.us` is a namespace, not a service. `kane-il.us` stays
|
|
reserved for cross-cutting infrastructure and must not acquire project records.
|
|
|
|
The leaf names the service, not the discipline, because the parent already
|
|
carries the discipline and the namespace must hold siblings:
|
|
|
|
```
|
|
mechanical-compiler.manufacturing.kane-il.us this application
|
|
field-metrology.manufacturing.kane-il.us Utility One, later
|
|
stock-processing.manufacturing.kane-il.us Utility Two, later
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Reverse proxy
|
|
|
|
**REQ** — nginx in the proxy container. Terminates TLS, proxies to the
|
|
application container's service address.
|
|
|
|
**REQ** — Debian's `default` site removed. Only the project vhost enabled.
|
|
|
|
**REQ** — `X-Forwarded-Proto` is mandatory. Getting this wrong is what broke
|
|
Hubzilla sessions.
|
|
|
|
**REQ** — `default_server` declared explicitly on both TLS listeners. With one
|
|
vhost the implicit default is correct today; the moment a second is added, which
|
|
block catches unmatched requests becomes a function of file ordering. This is
|
|
independent hardening and **does not close F-012**.
|
|
|
|
```nginx
|
|
server {
|
|
listen 443 ssl default_server;
|
|
listen [::]:443 ssl default_server;
|
|
server_name <SERVICE_FQDN>;
|
|
|
|
ssl_certificate <cert>;
|
|
ssl_certificate_key <key>;
|
|
|
|
location / {
|
|
proxy_pass http://<APP_SVC_ADDR>:<PORT>;
|
|
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_read_timeout 300s;
|
|
client_max_body_size 8m;
|
|
}
|
|
}
|
|
|
|
server {
|
|
listen 80;
|
|
listen [::]:80;
|
|
server_name <SERVICE_FQDN>;
|
|
return 301 https://$host$request_uri;
|
|
}
|
|
```
|
|
|
|
**REQ** — This vhost differs between instances in exactly two places:
|
|
`server_name` and the certificate paths. Any further divergence means the
|
|
non-production instance has stopped testing production.
|
|
|
|
**PREF** — `proxy_read_timeout 300s`. Compile jobs are asynchronous, but a cold
|
|
synchronous render can take tens of seconds and the 60s default leaves no
|
|
headroom.
|
|
|
|
---
|
|
|
|
## 7. Users, directories, permissions
|
|
|
|
**REQ** — Dedicated system user, no login shell:
|
|
|
|
```
|
|
user:group mechcomp:mechcomp
|
|
shell /usr/sbin/nologin
|
|
home <install_dir>
|
|
```
|
|
|
|
**REQ (F-016 context)** — The service user's home is the install directory, not
|
|
a nonexistent `/home/<user>`. Tooling run as that user writes to `$HOME`;
|
|
`ProtectHome=true` makes it moot at runtime but not during provisioning.
|
|
|
|
**REQ** — Layout, matching what a YunoHost package would provision:
|
|
|
|
| Path | Owner | Mode | YunoHost equivalent |
|
|
|---|---|---|---|
|
|
| `/var/www/mechcomp` | `mechcomp:mechcomp` | `0750` | `install_dir` |
|
|
| `/var/lib/mechcomp` | `mechcomp:mechcomp` | `0750` | `data_dir` |
|
|
| `/var/log/mechcomp` | `mechcomp:mechcomp` | `0750` | — |
|
|
| `/etc/mechcomp/mechcomp.env` | `root:mechcomp` | `0640` | `ynh_add_config` |
|
|
|
|
```
|
|
/var/lib/mechcomp/
|
|
├── db/ SQLite database
|
|
├── artifacts/ generated SVG, STL, STEP, manifests
|
|
├── fixtures/ frozen acceptance oracles, read-mostly
|
|
└── cache/ safe to delete at any time
|
|
```
|
|
|
|
The `cache/` guarantee matters: anything not reproducible from `db/` plus
|
|
`artifacts/` must not live there.
|
|
|
|
**REQ (F-007)** — **Never `chown -R` a mount point root.** `data_dir` is a
|
|
filesystem root containing `lost+found`, owned by `nobody:nogroup` at `0700`,
|
|
created by `mkfs` and not ours to manage. Enumerate the application directories
|
|
explicitly.
|
|
|
|
**REQ (F-008)** — All repository operations run as the owning service user. Do
|
|
not add a root `safe.directory` exception — it would mask every future instance
|
|
of the same mistake.
|
|
|
|
**REQ** — The admin account exists in both containers with an authorized key.
|
|
**REQ (F-014)** — If the admin is placed in the service group, that group must be
|
|
created explicitly in the proxy container, where no service user creates it as a
|
|
side effect.
|
|
|
|
---
|
|
|
|
## 8. Software
|
|
|
|
### 8.1 Application container
|
|
|
|
**REQ**
|
|
|
|
```
|
|
python3 python3-venv python3-dev
|
|
build-essential pkg-config
|
|
git curl ca-certificates
|
|
libgeos-dev
|
|
sqlite3
|
|
```
|
|
|
|
**REQ (F-015)** — The proxy container needs its own minimal toolset, at least
|
|
`curl` and `ca-certificates`. Do not assume packages installed in one container
|
|
exist in the other.
|
|
|
|
**REQ — do not install** `openscad`, `openscad-nightly`, or any Qt or X11
|
|
package. The running application never invokes OpenSCAD, and §8.2 confines it.
|
|
|
|
### 8.2 The pinned reference toolchain
|
|
|
|
**REQ** — One Docker image with the exact toolchain the fixture oracle was
|
|
frozen against, and nothing else:
|
|
|
|
```dockerfile
|
|
FROM debian:12-slim
|
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
openscad git ca-certificates \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
RUN git clone https://github.com/BelfrySCAD/BOSL2.git /BOSL2 \
|
|
&& git -C /BOSL2 checkout 92d697c2856de2fed93a33e858068589cefc2898
|
|
```
|
|
|
|
Tag `mechcomp/reference-toolchain:8.0.0`. Its only job is to run
|
|
`make_fixtures.py` and prove the oracle reproduces. The container has no
|
|
OpenSCAD, so its version cannot drift.
|
|
|
|
**Acceptance** — must reproduce exactly:
|
|
|
|
```
|
|
sha256 strap-beam-fixtures-8.0.0.json
|
|
= ddd0f1548379205dd0c652ec07285b0dae331e52ff0a0437005dfc6cddcc2cb2
|
|
```
|
|
|
|
A drifting oracle is worse than no oracle.
|
|
|
|
### 8.3 Python
|
|
|
|
**REQ** — A virtualenv inside `install_dir`. Debian 12 enforces PEP 668; do not
|
|
use `--break-system-packages`.
|
|
|
|
**REQ** — `venv/` is in `.gitignore`. It is a legitimate artifact in
|
|
`install_dir` and simply should not be tracked.
|
|
|
|
**REQ** — Pinned, hash-checked dependencies in two files, both installed:
|
|
|
|
| File | Contents |
|
|
|---|---|
|
|
| `requirements-base.txt` | shapely, fastapi, uvicorn, pydantic, sqlalchemy, jinja2, pytest, pytest-xdist |
|
|
| `requirements-cad.txt` | cadquery / build123d |
|
|
|
|
CI runs the suite twice — with both, then with base alone — and the second run
|
|
must pass. That is the mechanism keeping §1.1 honest.
|
|
|
|
---
|
|
|
|
## 9. Services
|
|
|
|
**REQ** — Two units, split along the control-plane / execution-plane boundary:
|
|
|
|
| Unit | Role |
|
|
|---|---|
|
|
| `mechcomp.service` | FastAPI/uvicorn. Serves the catalogue, accepts jobs, returns cached artifacts. **Never runs geometry.** |
|
|
| `mechcomp-worker.service` | Consumes the queue, runs generators, writes artifacts. |
|
|
|
|
**REQ** — Both:
|
|
|
|
```ini
|
|
[Service]
|
|
User=mechcomp
|
|
Group=mechcomp
|
|
EnvironmentFile=/etc/mechcomp/mechcomp.env
|
|
WorkingDirectory=/var/www/mechcomp
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
|
|
NoNewPrivileges=true
|
|
PrivateTmp=true
|
|
ProtectSystem=strict
|
|
ProtectHome=true
|
|
ReadWritePaths=/var/lib/mechcomp /var/log/mechcomp
|
|
ProtectKernelTunables=true
|
|
ProtectControlGroups=true
|
|
RestrictSUIDSGID=true
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
**REQ** — Worker only: a `MemoryMax` bound, a `CPUQuota`, `TimeoutStopSec=30`.
|
|
A runaway geometry job must become a clean OOM-kill of one worker, not a dead
|
|
container.
|
|
|
|
**REQ** — Logging to journald. No application-managed rotation.
|
|
|
|
**REQ** — Job queue is SQLite-backed and in-process. No Redis, no Celery, no
|
|
RabbitMQ. A table with a status column and a claim query is correct at this
|
|
scale and adds no services to either packaging target.
|
|
|
|
**REQ (F-019)** — **Anything a proof depends on must be supervised.** This
|
|
includes temporary scaffolding. A placeholder backend used to prove the proxy
|
|
chain before the application exists is a systemd unit, obviously named and
|
|
obviously temporary — otherwise the proof expires at the next reboot and the
|
|
following session diagnoses a proxy fault that does not exist.
|
|
|
|
**REQ (F-011)** — Do not use `/tmp` for state shared across users.
|
|
`fs.protected_regular` makes that fail in ways that look like permission bugs.
|
|
Moot under `PrivateTmp=true`.
|
|
|
|
---
|
|
|
|
## 10. Backup
|
|
|
|
**DEFERRED — 2026-08-16, operator decision.** The backup strategy may change.
|
|
This section is retained as a design sketch and is **not authoritative**; no
|
|
acceptance gate depends on it until a strategy is chosen.
|
|
|
|
The sketch, for whoever revisits it:
|
|
|
|
| Tier | What | Retention |
|
|
|---|---|---|
|
|
| Routine | container snapshot, **rootfs only** | measured, not guessed |
|
|
| Application | tar stream of `db/`, `artifacts/`, `fixtures/`, and the env file | short, rolling |
|
|
| Gold | routine + application + manifest, on removable encrypted media | indefinite, offline |
|
|
|
|
Three constraints that survive any strategy change:
|
|
|
|
1. **Data volumes carry `backup=0`.** A container snapshot store on the root
|
|
filesystem is how a Proxmox host fills up.
|
|
2. **Pull, never push.** The container has no access to any backup destination.
|
|
The alternative hands a network-facing container write access to the last
|
|
line of defence, and it avoids idmap ownership problems.
|
|
3. **Encrypt removable media.** The backup set includes the env file, which
|
|
includes the secret key. Media that travels is media that gets lost.
|
|
|
|
**Standing recommendation regardless of strategy:** take one manual snapshot of
|
|
both containers before any significant change. It presupposes nothing and yields
|
|
the archive size that retention decisions need.
|
|
|
|
---
|
|
|
|
## 11. Configuration contract
|
|
|
|
**REQ** — One env file configures the application:
|
|
|
|
```bash
|
|
MECHCOMP_ENV= # staging | production
|
|
MECHCOMP_BIND= # single address — see below
|
|
MECHCOMP_PORT=
|
|
MECHCOMP_BASE_URL=
|
|
MECHCOMP_DATA_DIR=/var/lib/mechcomp
|
|
MECHCOMP_LOG_LEVEL=info
|
|
MECHCOMP_DB_URL=sqlite:////var/lib/mechcomp/db/mechcomp.sqlite3
|
|
MECHCOMP_WORKER_CONCURRENCY=
|
|
MECHCOMP_CAD_BACKEND=none # none | cadquery
|
|
MECHCOMP_ARTIFACT_RETENTION_DAYS=30
|
|
MECHCOMP_SECRET_KEY= # generated on first provision, never committed
|
|
```
|
|
|
|
**REQ** — `MECHCOMP_BIND` is a **single address**. Loopback is not additionally
|
|
bound.
|
|
|
|
Revision 4 said "the service-network address, plus `127.0.0.1`," which a scalar
|
|
cannot express. Resolved toward one address: `uvicorn` takes one `--host`, and
|
|
two sockets would mean either `0.0.0.0` — defeating the isolation — or
|
|
multi-socket setup for no benefit. Local checks inside the container can use the
|
|
service address. The scalar also generalises correctly: under YunoHost, where
|
|
nginx and the application share a host, the same variable takes `127.0.0.1`.
|
|
|
|
**REQ** — The application fails loudly at startup if a required variable is
|
|
missing, and never falls back to a compiled-in default path.
|
|
|
|
**REQ** — The secret is generated on first provision and never overwritten by a
|
|
re-run. It is never committed and never logged.
|
|
|
|
Promotion changes `MECHCOMP_ENV`, `MECHCOMP_BIND` and `MECHCOMP_BASE_URL`.
|
|
Nothing else.
|
|
|
|
---
|
|
|
|
## 12. Mail and monitoring
|
|
|
|
**REQ** — **SMTP 250 from a relay and an empty local queue prove handoff, not
|
|
delivery.** Alerting acceptance requires demonstrated end-to-end receipt, twice,
|
|
with full headers captured. This is the whole content of F-023: a message can be
|
|
accepted by every hop and still be rejected at the last one.
|
|
|
|
**REQ** — Diagnose relay problems with **RCPT-only probes, over every address
|
|
family, before and after any change**, with no message body sent. A `554`
|
|
before and a `250` after proves the change caused the fix rather than
|
|
coinciding with it.
|
|
|
|
**REQ (F-024)** — On a Proxmox host, inspect mail logs with
|
|
`journalctl -u postfix@-`. There is no `rsyslog` and no `/var/log/mail.log`.
|
|
Automation that greps a logfile path finds nothing silently, which is worse than
|
|
failing.
|
|
|
|
**REQ** — Disk monitoring must be configured **explicitly per device** where the
|
|
controller does not present members to automatic scanning. An active
|
|
`smartmontools.service` monitoring zero devices is not monitoring, and is easy
|
|
to mistake for success. Acceptance is a delivered test alert, not an active
|
|
unit.
|
|
|
|
### 12.1 Downstream infrastructure is out of bounds
|
|
|
|
**REQ** — An instance may configure its own relay client. It may not alter the
|
|
mail infrastructure it relays through. Where a downstream change is genuinely
|
|
required, it is escalated with evidence, not made locally.
|
|
|
|
Two standing constraints apply to this estate:
|
|
|
|
| Host | Constraint |
|
|
|---|---|
|
|
| The final mailbox host | Runs YunoHost. Its mail configuration must not be altered — which is the reason a separate relay exists. |
|
|
| The relay MTA | Uses DANE. Its TLS and certificate configuration must not be broken. Client-trust changes such as `mynetworks` do not interact with DANE; certificate changes do. |
|
|
|
|
**REQ (F-025)** — Record what a trust change **grants**, not only what it
|
|
repairs. `permit_mynetworks` is destination-agnostic: authorising a client to
|
|
fix one rejected recipient authorises it for every destination. Where an
|
|
instance's containers have no reason to originate mail, block SMTP egress at the
|
|
instance boundary rather than relying on downstream policy to refuse it.
|
|
|
|
## 13. Instance parameters
|
|
|
|
**INSTANCE** — Every value below is supplied per instance and recorded in that
|
|
instance's state file, not here.
|
|
|
|
```
|
|
host hostname, storage pools, bridges, template
|
|
management network host address, gateway
|
|
service network network, host address, container addresses
|
|
wireguard interface, address, allowed networks
|
|
containers ids, hostnames, sizing
|
|
service FQDN per instance
|
|
TLS CA and leaf source
|
|
admin username, authorized key
|
|
mail relay endpoint, root alias
|
|
```
|
|
|
|
**REQ** — These live in exactly one file per instance, sourced by every
|
|
provisioning step. No literals scattered through scripts. The file is not
|
|
committed; an example with empty values is.
|
|
|
|
---
|
|
|
|
## 14. Constraints the application code will follow
|
|
|
|
1. No hardcoded absolute paths. Everything derives from `MECHCOMP_DATA_DIR`.
|
|
2. No writes outside `MECHCOMP_DATA_DIR`. Enforced by `ProtectSystem=strict`.
|
|
3. No dependency on systemd from application code. Signal handling only.
|
|
4. The app listens on one TCP address, never `0.0.0.0`.
|
|
5. No OpenSCAD, Qt or X11 dependency in the running application.
|
|
6. The 3D backend is reached only through an interface in `src/mechcomp/cad/`.
|
|
No other module imports CadQuery or OCP directly.
|
|
7. Schema migrations are explicit and forward-only.
|
|
8. Artifacts are addressed by content hash of **inputs** — parameter set plus
|
|
generator revision — never by hash of output bytes. Mesh output is not
|
|
reproducible across toolchain versions; input hashing keeps a qualification
|
|
valid across an upgrade that did not change the geometry.
|
|
9. Every generated artifact carries the generator revision that produced it.
|
|
10. The test suite passes with `requirements-cad.txt` uninstalled.
|
|
11. AGPL-3.0 §13 requires network users be offered the source. The web tier
|
|
carries a visible source link to the repository. A licence obligation.
|
|
|
|
---
|
|
|
|
## 15. Acceptance gates
|
|
|
|
**REQ** — Five independent gates. Revision 4 ran them as one list, which meant
|
|
an honest infrastructure build appeared to fail because application code did not
|
|
exist yet.
|
|
|
|
### Gate 1 — Infrastructure
|
|
|
|
Host and both containers `running` with **zero failed units**, asserted **after a
|
|
reboot**. Locale generated. Data volume is a real mount. Ownership correct with
|
|
`lost+found` untouched. Forbidden packages absent. Container egress works;
|
|
**the LAN gateway is unreachable from both containers**; the host remains
|
|
reachable from them; the management console remains reachable from the LAN. NAT
|
|
and FORWARD rules present live and persisted, in order. Proxy serves the service
|
|
FQDN over TLS without an insecure bypass, redirects HTTP, and
|
|
**`X-Forwarded-Proto: https` is observed at the backend**. Any supervised
|
|
scaffolding survives a reboot.
|
|
|
|
**PROVEN (F-021)** — the reboot is not optional. Connectivity can be perfect
|
|
while boot is degraded.
|
|
|
|
### Gate 2 — Operational services
|
|
|
|
Mail delivered **end to end and received twice**, with full headers captured, an
|
|
empty queue and no deferred entries. Disk monitoring reporting a **non-zero
|
|
device count** and a delivered test alert. Container SMTP egress blocked where
|
|
the containers have no reason to originate mail. Not gated on gate 1.
|
|
|
|
### Gate 3 — Application runtime
|
|
|
|
Dependencies installed from committed manifests. Real service and worker units
|
|
active. Reference toolchain image reproduces `ddd0f154…`. Scaffolding removed.
|
|
|
|
### Gate 4 — Backup
|
|
|
|
Undefined pending a strategy decision.
|
|
|
|
### Gate 5 — Production automation
|
|
|
|
Idempotent scripts derived from a proven manual procedure. Firewall posture
|
|
revisited. Publicly-trusted certificate issued and renewing.
|
|
|
|
---
|
|
|
|
## 16. Assumptions
|
|
|
|
| § | Assumption | If wrong |
|
|
|---|---|---|
|
|
| 3.6 | Firewall may stay disabled on a non-production instance | Enable per-container; production revisits regardless |
|
|
| 5.3 | Production uses HTTP-01 | Switch to DNS-01 if that is existing practice; never both |
|
|
| 11 | `MECHCOMP_BIND` is a single address | Revisit only if a real requirement for two sockets appears |
|
|
| 15 | Five gates are the right split | Merge or split further as evidence warrants |
|
|
|
|
---
|
|
|
|
## 17. Provenance disclosure
|
|
|
|
This project's documents, code and roadmap are LLM-generated under human
|
|
direction. Stated plainly here, in the README, and in any catalog submission.
|
|
|
|
**The YunoHost policy is a quality bar with a disclosure requirement, not a
|
|
ban.** The catalog rejects generated packages *that do not follow the
|
|
`example_ynh` template*, citing verbose code, hallucinated helpers and
|
|
non-standard directory architectures — then explicitly permits AI use where the
|
|
maintainer is transparent and can explain every line. The failure mode guarded
|
|
against is sprawl, not provenance.
|
|
|
|
**Package provenance is not upstream provenance.** The policy concerns the
|
|
`_ynh` repository — a few hundred lines of shell and one TOML file.
|
|
|
|
**Disclosure is right; headlining it is a tactical mistake.** If provenance
|
|
becomes the pitch, the project is judged on that axis rather than on whether it
|
|
makes distributed manufacturing capacity legible. State it in the README; keep
|
|
the pitch about manufacturing.
|
|
|
|
---
|
|
|
|
## 18. Packaging, when it comes
|
|
|
|
**DISCOVER** — Unknown territory, documented as it is walked: `manifest.toml` v2
|
|
with helpers 2.1, the `scripts/` set, `conf/` templates, `tests.toml`,
|
|
`package_check` levels.
|
|
|
|
The useful comparison for acceptable weight is `paperless-ngx_ynh` or
|
|
`fab-manager_ynh`, not a hello-world. The package is drafted against
|
|
`example_ynh` and reviewed by CIVICVS as his own work.
|
|
|
|
---
|
|
|
|
## 19. First application work item
|
|
|
|
Port `sb-geom` to Shapely; `pytest -n auto` green against the 123 frozen cases in
|
|
`strap-beam-fixtures-8.0.0.json`. The ten rejected cases are part of the
|
|
contract: a port that accepts them is wrong.
|