Archive remaining SRS development drafts from a.mx
This commit is contained in:
@@ -0,0 +1,127 @@
|
||||
# Selective SRS Installer – Review Draft 0.9
|
||||
|
||||
Stand: 18.10.2026. Target: Debian 13, PostgreSQL/PostfixAdmin; reference implementation `mx.warenform.de`.
|
||||
|
||||
This directory is a **review bundle**, not a production-tested release. Copy its templates into the corresponding location of the shared `mailsystem` Git repository (`files/srs/templates/`) before the installer can be run in its normal script directory. The four Python programs and `helpers/srs-common.sh` in the bundle are unmodified reference snapshots from the user's repository/uploads.
|
||||
|
||||
## Files
|
||||
|
||||
- `install-update-srs.sh` — draft with read-only `--check` and an **unapproved** `--install` implementation.
|
||||
- `files/srs/templates/delivery-main.cf.in` — reference delivery main.cf with selected Postfix parameters templated.
|
||||
- `files/srs/templates/delivery-master.cf` — reference delivery master.cf, unchanged.
|
||||
- `files/srs/templates/transport.in` — reference transport file with SRS domain placeholder.
|
||||
- `files/srs/templates/postsrsd.conf.in` — reference PostSRSd configuration with SRS domain placeholder.
|
||||
- `files/srs/templates/*.service` — reference systemd units, unchanged.
|
||||
|
||||
## Conventions
|
||||
|
||||
- The product Postfix instance `/etc/postfix/main.cf` and `/etc/postfix/master.cf` is **never modified** by this draft. Separate `activate-srs.sh`/`deactivate-srs.sh` do that.
|
||||
- Initial PRE-SRS state is derived once; existing state is preserved.
|
||||
- PostSRSd CMake 2.4.1 options match the working reference.
|
||||
- The Debian unit `postfix@srs-delivery.service` decodes its `%I` instance token `srs-delivery` as `srs/delivery`, yielding `/etc/postfix-srs/delivery`. No special directory symlink is necessary.
|
||||
- Three reference Python programs have their single `srs.warenform.de` string replaced by the locally configured `_SRS_DOMAIN` when installed, without modifying the Git sources.
|
||||
- In this initial draft, active-service updates to another PostSRSd version are **refused**; coordinated restarts, full rollback of queue initialisation, semantic rendering parity tests and cross-host integration tests remain to be implemented.
|
||||
- No attempt is made to install Debian build dependencies. On `a.mx.oopen.de`, `cmake` was found missing during inventory.
|
||||
|
||||
## Safe first check
|
||||
|
||||
Run only `--check` after placing the script and templates in the repository. This path does not run `init_srs_runtime()`, create a lock, install files, build anything or touch services.
|
||||
|
||||
**Do not run `--install` on any production mail server yet.** Syntax validation alone is insufficient; the installation stage and its rollback require offline/staging testing and explicit approval.
|
||||
|
||||
## Findings after systemd template inspection (18 October 2026)
|
||||
|
||||
- `postfix@srs-delivery.service` uses `Environment=MAIL_CONFIG=/etc/postfix-%I`. The unescaped `%I` for instance `srs-delivery` is `srs/delivery`; thus the intended configuration path is `/etc/postfix-srs/delivery`. There is NO `/etc/postfix-srs-delivery` symlink on the working reference server and none is needed.
|
||||
- The SRS private configuration directory is now created with root:root mode `0700`; an already-existing directory must match these permissions.
|
||||
- Snapshotting existing files records a rollback entry only AFTER the backup is successful, and installation to a symlinked destination is rejected.
|
||||
- **Remaining blockers:** package provisioning (CMake missing on a.mx), semantic config parity validation, coordinated active-version upgrades, complete rollback of newly created directories/queue initialization and failure-injection testing. `--install` is NOT approved.
|
||||
|
||||
## Changes in draft 0.3
|
||||
|
||||
- The `--check` report correctly distinguishes an available Debian `postfix@.service` template from an installed, ready SRS delivery instance.
|
||||
- Rollback removes a freshly installed PostSRSd version prefix only after the previous stable link has been restored and only if it is not selected.
|
||||
- An initial installation aborts if SRS configuration files already exist without a stable PostSRSd symlink, rather than overwriting an ambiguous partial installation.
|
||||
|
||||
**Still NOT production ready:** CMake package provisioning, comprehensive rollback of new directories and Postfix queue creation, service upgrade orchestration, DNS checks, and integration tests are not yet implemented.
|
||||
|
||||
## Changes in draft 0.4
|
||||
|
||||
- Both `--check` and `--install` **first ask interactively for the SRS domain**, before inspecting prerequisites. For the target `a.mx.oopen.de`, enter `srs.oopen.de`. This value is **not** hardcoded into the shared Git source.
|
||||
- If a local `conf/srs.conf` contains a straightforward domain assignment, that domain is offered as the default. The hint is read as plain text, not sourced.
|
||||
- `--check` displays the selected domain and detects an evident conflict with a previously configured domain; it never writes `conf/srs.conf`.
|
||||
- First `--install` now stages and installs `conf/srs.conf` (`root:root`, `0644`) after user confirmation. Existing configuration is validated using `load_srs_config()` and must match the selected domain; automatic domain migration is refused.
|
||||
- Add `conf/srs.conf` to the Git repository's `.gitignore`; only `conf/srs.conf.sample` belongs in Git.
|
||||
- **Still a REVIEW DRAFT, NOT approved for production `--install`.** Other known blockers from 0.3 remain.
|
||||
|
||||
- After domain selection, `--check` prints informational DNS TXT/MX/A/AAAA lookups when `dig` is available. These outputs are **not** a completed SPF/MX validation.
|
||||
|
||||
## Changes in review draft 0.5 (18 October 2026)
|
||||
|
||||
- Adds **`helpers/srs-dns-check.py`**. Both modes ask for the domain first. `--check` uses it without any writes and reports readiness; **`--install` stops before changes if the public-DNS preflight fails**.
|
||||
- The DNS helper resolves delegation via public DNS resolvers, then directly queries **every discovered authoritative nameserver**. This bypasses the local `/etc/hosts` override where `a.mx.oopen.de` resolves to `127.0.1.1` via systemd-resolved on this machine.
|
||||
- The agreed **strict single-host DNS profile** is checked: precisely one MX pointing to Postfix `myhostname`, exactly one SPF record equal to `v=spf1 mx -all`, and public MX-target A/AAAA answers containing the explicit `smtp_bind_address` and `smtp_bind_address6` values. For `a.mx.oopen.de`, that means MX `a.mx.oopen.de`, IPv4 `95.217.204.247`, IPv6 `2a01:4f9:4a:47e5::247`. No server domain or IP has been hardcoded into the installer or helper.
|
||||
- This is **not a complete SPF evaluator**, an SMTP connectivity test, or a guarantee of bounce routing through arbitrary topologies. DNS setups using other valid SPF expressions, shared SRS domains, multiple MX hosts or hosts without explicit IPv4/IPv6 bind addresses require a considered implementation extension, rather than being silently accepted.
|
||||
- Reorders `--install` so it performs root/command/source/DNS and installation-state preflight **before** asking the operator to approve preparation and **before** acquiring the lock or writing files. `--check` stays read-only. It checks for `dig` as a required installation dependency.
|
||||
- Add the new helper to the common Git repository at `helpers/srs-dns-check.py` alongside the updated `install-update-srs.sh`. Keep `conf/srs.conf` ignored in Git.
|
||||
- Tests run for this draft: Bash syntax check, Python syntax compilation, three isolated DNS-positive/negative cases, and an end-to-end **mocked** `--check` with two nameservers. No productive SRS installation, delivery, failure-injection or rollback testing has been performed.
|
||||
|
||||
**Known blockers remain:** CMake and CTest absent on `a.mx.oopen.de`; Postfix delivery config semantics and safe rollback/active upgrades need additional testing. **Do not run `--install` in production yet.**
|
||||
|
||||
## Changes in review draft 0.6 (18 October 2026)
|
||||
|
||||
- The operator's `git status --short --untracked-files=all` listed precisely the eight expected new files. `bash -n` of the installer and `compile()` of the DNS helper passed on `a.mx.oopen.de`. `conf/srs.conf` is already ignored by `.gitignore` (`conf/*.conf`). No Git commit has been performed.
|
||||
- Preflight now rejects symlinked/non-directory SRS destination paths and a nonprivate existing `/etc/postsrsd-srs` directory BEFORE the build and filesystem-deployment confirmation.
|
||||
- Newly created configuration directories are tracked separately and removed on failed installation **only if empty** (after tracked file rollback).
|
||||
- Newly created `/var/spool/postfix-srs-delivery` and `/var/lib/postfix-srs-delivery` roots are remembered before `postfix -c ... check`. Rollback tries only `rmdir`; it **never recursively deletes Postfix runtime data**. If Postfix created a nonempty hierarchy, that hierarchy remains for manual inspection. Thus rollback is **not fully automatic for initialized Postfix queue/data**.
|
||||
- Unexpected process EXIT after the file-deployment phase begins is routed through tracked-file rollback (normal success and explicit rollback disarm this trap). This does not guarantee recovery from SIGKILL, power loss, process crash without shell handling, or full operational restore.
|
||||
- No changes to the six reference templates, the four reference SRS Python scripts, `helpers/srs-common.sh`, or the existing activation/deactivation tools.
|
||||
|
||||
**Release status: NOT approved for `--install`.** Active upgrades with running SRS services are deliberately rejected and still need a separate safe restart/rollback design. CMake/CTest provisioning, live first-install, Postfix config semantic comparison, and injected-failure/queue behavior tests remain open. Do not commit this draft as a production-ready release.
|
||||
|
||||
## Changes in review draft 0.7 (18 October 2026)
|
||||
|
||||
- `apt-get -s install cmake` on `a.mx.oopen.de` proposed exactly four **new** Debian 13 packages: `cmake`, `cmake-data`, `libjsoncpp26`, `librhash1`; zero upgrades and removals. No backports packages were proposed.
|
||||
- `--check` still never changes packages or SRS state; it reports missing CMake/CTest and prints an `apt-get -s --no-install-recommends` simulation when CMake is missing.
|
||||
- `--install` first asks for the SRS domain, then performs read-only preflight and simulates the APT transaction. The simulated transaction must have exactly zero upgrades and zero removals. A general preparation approval and a **separate, explicit yes** for CMake package installation are required. APT is run only after the shared SRS lock has been acquired.
|
||||
- Re-simulates the APT transaction immediately before the actual install and refuses changed plans. Does **not** run `apt-get update` or request backports. Validates that `cmake >= 3.24` and `ctest` are available after package installation. APT package changes are intentionally **not reverted** by SRS file rollback; an interrupted APT transaction requires manual inspection.
|
||||
- These checks are a safety guard, not an absolute guarantee against concurrent external changes in APT repositories, package indexes or package states.
|
||||
|
||||
**Still not approved for production `--install`.** No live package installation, PostSRSd build, first-install or failure-injection/rollback test has been performed for v0.7.
|
||||
|
||||
## Changes in review draft 0.8 (18 October 2026)
|
||||
|
||||
- Fixed actual delivery `main.cf` template rendering: template placeholders must be resolved by the **exact case-sensitive Postfix parameter name on the left-hand side** of the template assignment, not `placeholder.lower()`. `smtp_tls_CAfile` was erroneously queried as `smtp_tls_cafile`, causing a Postfix warning and an empty `smtp_tls_CAfile` in the generated delivery configuration.
|
||||
- The rendering step now refuses placeholders not standing alone in a Postfix assignment and refuses `postconf` errors/warnings instead of silently accepting an empty value. Existing valid empty settings remain possible. No reference template changed.
|
||||
- Still a review draft; no production `--install` approval.
|
||||
|
||||
## Changes in review draft 0.9 (18 October 2026)
|
||||
|
||||
- **Same-version maintenance update:** If `/usr/local/postsrsd` already resolves to the selected `/usr/local/postsrsd-<version>`, no Git/CMake/CTest build, no new installation prefix, and no symlink switch are performed. Instead, the installer renders and updates the four integration Python programs, PostSRSd config, separate Postfix delivery config/map and two systemd unit files with existing file snapshots and rollback.
|
||||
- **Version-change update:** When the selected version differs from the existing symlink target, the previous immutable version prefix is retained; the new version is built, staged and selected only after the prepared integration files have been copied. Failed deployment attempts restore the previous symlink and tracked files and remove the new prefix, provided it is not selected. This requires services to be inactive.
|
||||
- **Strict inactive-only updates:** For both maintenance updates and version changes, any SRS managed block in either productive Postfix `main.cf` or `master.cf`, or any running SRS systemd service, causes an immediate refusal. The operator must coordinate deactivation and downtime separately; **live/zero-downtime updates remain unsupported**. This draft never restarts or activates services.
|
||||
- Existing SRS secret and PRE-SRS state are **never regenerated** during an update. A missing secret or state on an existing installation is treated as an error before any writes.
|
||||
- CMake/CTest and Debian package provisioning are **not required** for same-version integration-only updates. They remain required for initial installation or PostSRSd version changes. The requested version defaults to 2.4.1; use `--version X.Y.Z` to specify another release.
|
||||
- Installation directory backups are created only after the final deployment confirmation; operator cancellation before deployment does not create an empty backup snapshot.
|
||||
- The change-tracking flags for new prefixes and symlink switches are set immediately before the respective filesystem operation to improve interruption handling (but SIGKILL, power loss and concurrent external changes remain outside the rollback guarantee).
|
||||
- A controlled offline mock test was run with all install paths remapped into a temporary directory, with fake `systemctl`, `postfix`, `postmap`, `postconf`, `git`, `cmake`, `ctest` and DNS checks. Cases that passed: same-version maintenance update, failed maintenance update with rollback of the helper and preservation of secret/symlink, version-change update (2.4.1 → simulated 2.4.2), failed version-change update (simulated 2.4.3) with symlink restoration and removal of new prefix. **These were mock tests, not real PostSRSd builds, Postfix validation or service starts.**
|
||||
- The six reference configuration templates, the four reference Python programs, `helpers/srs-common.sh`, `helpers/srs-dns-check.py` and `activate-srs.sh`/`deactivate-srs.sh` are unchanged. No Git commit has been made on `a.mx.oopen.de` by this development process.
|
||||
|
||||
**NOT production-ready yet:** First-install and rollback tests with actual Postfix and PostSRSd in a disposable Debian 13 environment, source-build verification and runtime end-to-end delivery checks remain necessary. Do **not** execute `--install` on a production mail server yet.
|
||||
|
||||
## Changes in review draft 0.10 – display and offline test status (18 October 2026)
|
||||
|
||||
- Shortens the APT dry-run output in both `--check` and `--install` preflight: report the transaction summary and the packages in `Inst` lines. The actual `apt-get -s` output remains the source of truth; the `--install` safety gate still requires zero upgrades and removals and still compares the full plan before installing packages. If the simulation cannot be summarized, the full text is printed for diagnosis and the `--install` plan is rejected.
|
||||
- No changes to the six Postfix/PostSRSd templates, to the four SRS Python integration programs, or to the SRS common and DNS helper scripts.
|
||||
- A simulated end-to-end `--check` in a temporary directory, using fake `postconf`, `apt-get`, `systemctl` and DNS helper responses, passed. It detected CMake/CTest as missing, summarized four proposed packages and left the test project files unchanged. Bash syntax passed.
|
||||
- This environment is Debian 13, and CMake/CTest are available, but actual PostSRSd 2.4.1 source retrieval failed because outbound DNS/network access to GitHub was unavailable; no genuine PostSRSd build, `ctest` of the real source or real Postfix `check` was performed. Postfix binaries are unavailable here. The production target `a.mx.oopen.de` was **not** modified.
|
||||
- As before, **do not run `--install` on production** without a genuinely isolated Debian-13 test exercising real PostSRSd and Postfix, including failure injection and rollback.
|
||||
|
||||
|
||||
## Changes in review draft 0.11 – directory mode and reproducible offline tests (18 October 2026)
|
||||
|
||||
- A newly created `/etc/postsrsd-srs` directory must be exactly `root:root` mode `0700`. On directories under a setgid parent, `install -d -m 0700` may leave an inherited SGID bit and produce mode `2700`. This caused the subsequent maintenance update to fail its strict permission preflight in the isolated test.
|
||||
- After creating and registering the new directory for rollback, the installer now applies a **five-digit octal chmod (`00700`)**, which removes inherited setgid/sticky flags, then confirms owner/group and exact mode. This applies only to newly created paths using a requested explicit mode; existing directory permissions are not silently changed.
|
||||
- The new test runner `tests/test_installer_mock.py` relocates all managed paths into a temporary fixture tree (which deliberately inherits SGID), mocks Git/CMake/CTest, DNS, Postfix and systemd, and supplies a pseudo-terminal to exercise the complete installer flow.
|
||||
- Successfully tested scenarios: simulated first install, same-version maintenance update, failure of maintenance update with file rollback, version upgrade, failed version upgrade with symlink/prefix rollback, update refusal when SRS managed block exists, failed initial Postfix check retaining runtime queue directories, and failed `postmap` with tracked-file rollback.
|
||||
- Reproduce the mocked scenarios on an isolated development machine by extracting this archive and executing `python3 tests/test_installer_mock.py`. **This is not a real PostSRSd source build or a Postfix integration test. The fake programs do not validate Postfix syntax, actual LMTP routing, service listeners or mail delivery.**
|
||||
- **Production use remains forbidden pending real integration testing in a disposable Debian 13 VM/container.** No changes were made to the productive mailserver `a.mx.oopen.de`.
|
||||
@@ -168,3 +168,26 @@ Postfix-Mailbetrieb haben.
|
||||
|
||||
Eine unveraenderte Wiederaufnahme der hier
|
||||
archivierten Implementierung wird nicht empfohlen.
|
||||
|
||||
## Zusaetzliche historische Entwicklungsdateien
|
||||
|
||||
Auf a.mx.oopen.de befanden sich nach der Archivierung
|
||||
noch zwei lokale, bisher unversionierte Dateien.
|
||||
|
||||
Diese wurden ebenfalls in das Archiv uebernommen:
|
||||
|
||||
- README-DRAFT.md
|
||||
Historische Entwicklungsnotizen und Review-Staende
|
||||
des SRS-Installers vom Oktober 2026.
|
||||
|
||||
- tests/test_installer_mock_a-mx.py
|
||||
Aeltere Version des Installer-Mock-Tests von
|
||||
a.mx.oopen.de. Die spaetere Version
|
||||
test_installer_mock.py enthaelt zusaetzliche
|
||||
Pruefungen zur Behandlung von Postfix-Warnungen
|
||||
und fehlerhaften Konfigurationsparametern.
|
||||
|
||||
Diese Dateien dienen ausschliesslich der
|
||||
historischen Nachvollziehbarkeit und sind
|
||||
nicht als aktuelle Installationsanleitung oder
|
||||
freigegebene Testsuite zu verstehen.
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Offline, path-relocated installer contract tests. All external commands mocked.
|
||||
Do not equate these tests with genuine Postfix/PostSRSd integration.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import os, subprocess, tempfile, zipfile, re, sys, shutil
|
||||
|
||||
SOURCE = Path(sys.argv[1]).resolve() if len(sys.argv) > 1 else Path(__file__).resolve().parents[1]
|
||||
|
||||
def putexe(file, content):
|
||||
file.parent.mkdir(parents=True, exist_ok=True)
|
||||
file.write_text('#!/usr/bin/env bash\n' + content)
|
||||
file.chmod(0o755)
|
||||
|
||||
def setup(root):
|
||||
root.mkdir(parents=True, exist_ok=True)
|
||||
repo = root / 'repo'
|
||||
repo.mkdir()
|
||||
if SOURCE.is_file():
|
||||
with zipfile.ZipFile(SOURCE) as z:
|
||||
z.extractall(repo)
|
||||
else:
|
||||
for source_file in SOURCE.rglob('*'):
|
||||
if source_file.is_file():
|
||||
dst = repo / source_file.relative_to(SOURCE)
|
||||
dst.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(source_file, dst)
|
||||
for path in (repo/'install-update-srs.sh', repo/'helpers/srs-common.sh',
|
||||
*list((repo/'files/srs').glob('*.py')),
|
||||
*list((repo/'files/srs/templates').glob('*'))):
|
||||
s = path.read_text()
|
||||
# Keep all managed install/config/queue paths in an unprivileged fixture tree.
|
||||
# The postconf absolute command must also be relocated.
|
||||
s = s.replace('/usr/sbin/postconf', str(root/'bin/postconf'))
|
||||
for prefix in ['/etc/', '/usr/local/', '/var/', '/run/lock/', '/root/srs-installer-backups']:
|
||||
new = str(root) + prefix
|
||||
s = s.replace(prefix, new)
|
||||
# prevent reading the real original root Postfix config in our fixtures
|
||||
path.write_text(s)
|
||||
(repo/'install-update-srs.sh').chmod(0o755)
|
||||
for p in ['etc/postfix','usr/local/sbin','run/lock','var/spool','var/lib','etc/systemd/system','flags']:
|
||||
(root/p).mkdir(parents=True, exist_ok=True)
|
||||
(root/'etc/postfix/main.cf').write_text('myhostname = mail.example.org\n')
|
||||
(root/'etc/postfix/master.cf').write_text('smtp inet n - y - - smtpd\nlocalhost:10025 inet n - y - - smtpd\n -o receive_override_options=no_unknown_recipient_checks\n')
|
||||
(repo/'conf').mkdir(exist_ok=True)
|
||||
# All expected lookup paths are extracted from the (relocated) reference source.
|
||||
alias = (repo/'files/srs/srs-alias-decision.py').read_text()
|
||||
for typ,name in set(re.findall(r'"(pgsql|btree):([^" ]+)"', alias)):
|
||||
f = Path(name + ('.db' if typ == 'btree' else ''))
|
||||
f.parent.mkdir(parents=True,exist_ok=True)
|
||||
f.touch()
|
||||
bin = root/'bin'
|
||||
bin.mkdir()
|
||||
putexe(bin/'postconf', '''
|
||||
name="${@: -1}"
|
||||
case "$name" in
|
||||
myhostname) echo mail.example.org;;
|
||||
mydomain) echo example.org;;
|
||||
myorigin) echo /etc/mailname;;
|
||||
mydestination) echo 'mail.example.org localhost';;
|
||||
relay_domains) echo '\u0024mydestination';;
|
||||
virtual_alias_maps) echo 'pgsql:some-alias';;
|
||||
virtual_alias_domains) echo 'btree:some-domain';;
|
||||
virtual_mailbox_maps) echo 'pgsql:some-mailbox';;
|
||||
virtual_mailbox_domains) echo 'pgsql:some-domain';;
|
||||
smtp_bind_address) echo 192.0.2.22;;
|
||||
smtp_bind_address6) echo 2001:db8::22;;
|
||||
smtp_dns_support_level) echo dnssec;;
|
||||
smtp_host_lookup) echo dns;;
|
||||
smtp_tls_CAfile) echo /etc/ssl/certs/ca-certificates.crt;;
|
||||
smtp_tls_mandatory_protocols|smtp_tls_protocols) echo '>=TLSv1.2';;
|
||||
smtp_tls_security_level) echo dane;;
|
||||
*) echo "unknown $name" >&2; exit 1;;
|
||||
esac
|
||||
''')
|
||||
putexe(bin/'systemctl', '''
|
||||
if [[ "$1" == is-active ]]; then exit 3; fi
|
||||
exit 0
|
||||
''')
|
||||
putexe(bin/'git', '''
|
||||
if [[ "$1" == clone ]]; then
|
||||
mkdir -p "${@: -1}"; exit 0
|
||||
fi
|
||||
exec /usr/bin/git "$@"
|
||||
''')
|
||||
putexe(bin/'cmake', '''
|
||||
if [[ "$1" == --version ]]; then echo 'cmake version 3.31.6'; exit 0; fi
|
||||
if [[ "$1" == -S ]]; then
|
||||
for ((i=1; i<=$#; i++)); do
|
||||
arg="${!i}"
|
||||
if [[ "$arg" == -B ]]; then n=$((i+1)); build="${!n}"; fi
|
||||
if [[ "$arg" == -DCMAKE_INSTALL_PREFIX=* ]]; then prefix="${arg#*=}"; fi
|
||||
done
|
||||
mkdir -p "$build"; printf '%s\\n' "$prefix" > "$build/prefix"; exit 0
|
||||
fi
|
||||
if [[ "$1" == --build ]]; then exit 0; fi
|
||||
if [[ "$1" == --install ]]; then
|
||||
prefix="$(cat "$2/prefix")"
|
||||
mkdir -p "${DESTDIR}${prefix}/sbin"
|
||||
printf '#!/bin/sh\\nexit 0\\n' > "${DESTDIR}${prefix}/sbin/postsrsd"
|
||||
chmod +x "${DESTDIR}${prefix}/sbin/postsrsd"
|
||||
exit 0
|
||||
fi
|
||||
exit 9
|
||||
''')
|
||||
putexe(bin/'ctest','exit 0\n')
|
||||
putexe(bin/'dig','exit 0\n')
|
||||
putexe(bin/'postmap','''
|
||||
case "$1" in
|
||||
btree:*) dest="${1#btree:}"; touch "${dest}.db";;
|
||||
*) exit 1;;
|
||||
esac
|
||||
if [[ -f "${SRS_TEST_ROOT}/flags/fail_postmap" ]]; then exit 1; fi
|
||||
exit 0
|
||||
''')
|
||||
putexe(bin/'postfix','''
|
||||
if [[ "$*" == *check* ]]; then
|
||||
mkdir -p "${SRS_TEST_ROOT}/var/spool/postfix-srs-delivery/private" "${SRS_TEST_ROOT}/var/lib/postfix-srs-delivery"
|
||||
if [[ -f "${SRS_TEST_ROOT}/flags/fail_postfix" ]]; then echo 'forced postfix check error' >&2; exit 1; fi
|
||||
fi
|
||||
exit 0
|
||||
''')
|
||||
(repo/'helpers/srs-dns-check.py').write_text('print(" Simulated authoritative DNS: OK")\n')
|
||||
return repo
|
||||
|
||||
def run(root, repo, *, fail=None, version=None):
|
||||
if fail: (root/'flags'/('fail_'+fail)).touch()
|
||||
cmd = f"{repo/'install-update-srs.sh'} --install"
|
||||
if version: cmd += f' --version {version}'
|
||||
env = os.environ.copy()
|
||||
env.update({'PATH':str(root/'bin') + ':' + env['PATH'], 'SRS_TEST_ROOT':str(root), 'LC_ALL':'C'})
|
||||
# Running inside util-linux script supplies a real PTY for init_srs_runtime.
|
||||
result = subprocess.run(['script','-q','-e','-c',cmd,'/dev/null'], input='srs.example.org\nyes\nyes\n',
|
||||
text=True, capture_output=True, timeout=35, env=env)
|
||||
return result
|
||||
|
||||
def assert_ok(condition, msg):
|
||||
if not condition: raise AssertionError(msg)
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix='oopen-srs-test-') as tmp:
|
||||
base=Path(tmp)
|
||||
os.chmod(base, 0o2770) # Deliberately inherit SGID on fixture subdirectories.
|
||||
root=base/'initial'
|
||||
repo=setup(root)
|
||||
result=run(root,repo)
|
||||
print('initial install', result.returncode)
|
||||
if result.returncode: print(result.stdout[-2500:])
|
||||
assert_ok(result.returncode == 0,'initial installation failed')
|
||||
assert_ok((root/'usr/local/postsrsd').is_symlink(), 'missing symlink')
|
||||
assert_ok((root/'usr/local/sbin/srs-forward.py').exists(), 'missing srs forward')
|
||||
assert_ok((root/'etc/postsrsd-srs/secret').stat().st_mode & 0o777 == 0o600, 'secret mode')
|
||||
assert_ok('smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt' in (root/'etc/postfix-srs/delivery/main.cf').read_text(),'rendered TLS CA')
|
||||
print(" secret dir mode:", oct((root/"etc/postsrsd-srs").stat().st_mode & 0o777), "uid", (root/"etc/postsrsd-srs").stat().st_uid)
|
||||
print(" stat check:", subprocess.check_output(["stat", "-c", "%u:%g:%a", str(root/"etc/postsrsd-srs")], text=True).strip())
|
||||
print(' initial install: state, secret, config, symlink, CAfile verified')
|
||||
original_secret=(root/'etc/postsrsd-srs/secret').read_bytes()
|
||||
original_forward=(root/'usr/local/sbin/srs-forward.py').read_bytes()
|
||||
original_link=os.readlink(root/'usr/local/postsrsd')
|
||||
result=run(root,repo)
|
||||
print('maintenance update', result.returncode)
|
||||
if result.returncode: print(result.stdout[-2500:])
|
||||
assert_ok(result.returncode == 0,'maintenance update failed')
|
||||
assert_ok((root/'etc/postsrsd-srs/secret').read_bytes()==original_secret,'secret changed')
|
||||
assert_ok(os.readlink(root/'usr/local/postsrsd')==original_link,'link changed')
|
||||
print(' maintenance update: original secret and binary retained')
|
||||
# Force late failure, after Postfix configuration and all units have been deployed.
|
||||
source=repo/'files/srs/srs-forward.py'
|
||||
source.write_text(source.read_text()+'\n# injected test change\n')
|
||||
result=run(root,repo,fail='postfix')
|
||||
print('failed maintenance rollback', result.returncode)
|
||||
if result.returncode==0: print(result.stdout[-2500:])
|
||||
assert_ok(result.returncode != 0,'injected failure not detected')
|
||||
assert_ok((root/'usr/local/sbin/srs-forward.py').read_bytes()==original_forward,'rollback did not restore file')
|
||||
assert_ok((root/'etc/postsrsd-srs/secret').read_bytes()==original_secret,'rollback changed secret')
|
||||
assert_ok(os.readlink(root/'usr/local/postsrsd')==original_link,'rollback changed symlink')
|
||||
print(' failure rollback: file restored; secret and symlink preserved')
|
||||
(root/'flags/fail_postfix').unlink()
|
||||
result=run(root,repo,version='2.4.2')
|
||||
print('version upgrade', result.returncode)
|
||||
if result.returncode: print(result.stdout[-2500:])
|
||||
assert_ok(result.returncode == 0,'version upgrade failed')
|
||||
assert_ok((root/'usr/local/postsrsd-2.4.1').exists(),'prior prefix missing')
|
||||
assert_ok(os.readlink(root/'usr/local/postsrsd')=='postsrsd-2.4.2','new link incorrect')
|
||||
print(' version upgrade: previous prefix retained; link updated')
|
||||
(root/'flags/fail_postfix').touch()
|
||||
result=run(root,repo,version='2.4.3')
|
||||
print('failed version upgrade rollback', result.returncode)
|
||||
assert_ok(result.returncode != 0,'injected failure not detected')
|
||||
assert_ok(os.readlink(root/'usr/local/postsrsd')=='postsrsd-2.4.2','previous version link not restored')
|
||||
assert_ok(not (root/'usr/local/postsrsd-2.4.3').exists(),'failed version prefix not removed')
|
||||
print(' failed version upgrade: symlink restored, new prefix removed')
|
||||
|
||||
# Active SRS configuration must prohibit any maintenance update before writes.
|
||||
(root/'flags/fail_postfix').unlink()
|
||||
active_marker = '# BEGIN OOPEN-SRS-MANAGED'
|
||||
existing_main=(root/'etc/postfix/main.cf')
|
||||
original_main=existing_main.read_text()
|
||||
existing_main.write_text(original_main + active_marker + '\n')
|
||||
before=(root/'usr/local/sbin/srs-forward.py').read_bytes()
|
||||
result=run(root,repo,version='2.4.2')
|
||||
print('active integration guard', result.returncode)
|
||||
assert_ok(result.returncode != 0,'active SRS was not refused')
|
||||
assert_ok((root/'usr/local/sbin/srs-forward.py').read_bytes() == before, 'active check changed scripts')
|
||||
existing_main.write_text(original_main)
|
||||
print(' active integration guard: blocked before file changes')
|
||||
|
||||
# Independent initial installation with late failure. New SRS files must be removed.
|
||||
root=base/'failed-first'
|
||||
repo=setup(root)
|
||||
result=run(root,repo,fail='postfix')
|
||||
print('failed first install', result.returncode)
|
||||
assert_ok(result.returncode != 0,'injected first-install failure not detected')
|
||||
assert_ok(not (root/'usr/local/postsrsd').exists(), 'first-install rollback left symlink')
|
||||
assert_ok(not (root/'usr/local/postsrsd-2.4.1').exists(), 'first-install rollback left prefix')
|
||||
assert_ok(not (root/'etc/postsrsd-srs/secret').exists(), 'first-install rollback left secret')
|
||||
assert_ok(not (root/'etc/postfix-srs/delivery/main.cf').exists(), 'first-install rollback left config')
|
||||
assert_ok((root/'var/spool/postfix-srs-delivery/private').is_dir(), 'test Postfix runtime queue unexpectedly deleted')
|
||||
print(' failed first install: tracked files removed; Postfix runtime directory safely preserved')
|
||||
|
||||
# Fail at postmap, before systemd and Postfix checks.
|
||||
root=base/'failed-map'
|
||||
repo=setup(root)
|
||||
result=run(root,repo,fail='postmap')
|
||||
print('failed postmap rollback', result.returncode)
|
||||
assert_ok(result.returncode != 0, 'injected postmap failure not detected')
|
||||
assert_ok(not (root/'etc/postfix-srs/delivery/transport.db').exists(),'failed postmap index remains')
|
||||
assert_ok(not (root/'etc/postsrsd-srs/secret').exists(),'failed postmap secret remains')
|
||||
assert_ok(not (root/'usr/local/postsrsd').exists(),'failed postmap link remains')
|
||||
print(' failed postmap: generated index and secret removed')
|
||||
|
||||
print('ALL OFFLINE MOCK CONTRACT TESTS PASSED')
|
||||
Reference in New Issue
Block a user