fix(demo): fix PrestaShop container-network post-install fatal on PS 9 - #1372
Conversation
Live re-test of #1369 on a fresh PrestaShop 9.0.2 install surfaced two bugs in 40-configure-container-network.php that broke the container's boot (set -e in the wrapper turned the PHP fatal into a hard container exit): 1. ShopUrl::getShopUrls() returns ShopUrl objects under PS 9, not arrays — $row['domain'] threw "Cannot use object of type ShopUrl as array", aborting post-install and exiting the prestashop container (255). Replaced with a direct Db::getInstance()->getValue() existence check (matches the ObjectModel/legacy-bootstrap style already used in this script and its 20-set-default-currency.php sibling). 2. Db::getValue() appends its own trailing `LIMIT 1` internally (via getRow()); the query's own explicit `LIMIT 1` doubled it into `... LIMIT 1 LIMIT 1`, a SQL syntax error (exit 255 again). Verified end-to-end on a from-scratch PrestaShop 9.0.2 install (fresh volume + fresh database): post-install now logs "ShopUrl added for domain 'prestashop'", ps_shop_url carries both the main localhost row and the new non-main 'prestashop' row, PS_CANONICAL_REDIRECT=0, the container stays up (exit 0), and the API container's webservice probe to http://prestashop/api/ returns 401 (reached, no redirect) instead of the prior 301/302 to the canonical localhost domain. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
The documented .env recipe (README + the setup guide) did `cp .env.example .env` then `echo "KEY=..." >> .env`. .env.example already ships an empty `OPENLINKER_CREDENTIALS_ENCRYPTION_KEY=` line, so appending leaves TWO lines with the same key — harmless (env-file parsing is last-wins) but confusing to anyone editing .env by hand, and flagged in the #1369 review. The guide's proposed fix (sed -i '...') introduced a second problem: GNU sed and BSD sed (macOS) have incompatible -i syntax (BSD requires -i '' <script>), so the documented command would fail outright on macOS. Replaced both with a portable, dependency-free two-liner that drops the example's empty key line and appends the generated one: grep -v '^OPENLINKER_CREDENTIALS_ENCRYPTION_KEY=' .env.example > .env echo "OPENLINKER_CREDENTIALS_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> .env Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
Tech Lead ReviewNote: this is a single review pass (running the standard ContextThis PR touches Both fixes are well-verified: the PR description documents a full from-scratch reproduction (fresh volumes + fresh DB) with concrete before/after evidence (container exit code,
Findings[SUGGESTION] -
[SUGGESTION] -
Positive observations
Final AssessmentSummary: A well-verified, narrowly-scoped follow-up to #1369 that fixes two real boot-breaking bugs in a PrestaShop 9 post-install script (an object/array type-shape mismatch and a doubled SQL Merge Readiness: Approve - ready to merge as-is. Priority fixes: None blocking. The two SUGGESTION items above are optional follow-ups, not required before merge. |
Addresses PR #1372 review suggestions: revert the idempotency check to ShopUrl::getShopUrls() with property access ($row->domain) instead of a hand-built Db::getInstance()->getValue() raw SQL string. Same idempotency semantics, no manual SQL/pSQL() escaping, and stays consistent with the sibling script's ObjectModel-only convention (also sidesteps the flagged double-quoted SQL string literal / ANSI_QUOTES portability risk, since there's no raw SQL left in this block at all). Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com>
|
Addressed both review suggestions in f729a4c. Both SUGGESTION findings on
Verified against a live PrestaShop 9.0.2 demo container (not just Also double-checked the |
…staShop) (#1365) * feat(demo): one-command Docker demo environment (API/Web/Worker + PrestaShop) Add a dedicated demo overlay that boots the full OpenLinker stack in Docker with a single command, on top of the existing infra services. - Dockerfile: new worker build target; also fixes a pre-existing blocker where the ksef/infakt workspace manifests + dist were never copied, so the image build failed outright (unnoticed because CI does not build the image). - apps/web/Dockerfile + nginx.conf: static SPA build served by nginx with SPA-fallback routing; VITE_API_BASE_URL baked at build time. - package.json: demo:up / demo:down / demo:logs scripts. - README.md: demo section (URLs, credentials, manual PS<->OL connection). Closes #1352 Signed-off-by: jakubret <jakub.retajczyk@blockydevs.com> * feat(demo): one-command Docker demo environment (API/Web/Worker + PrestaShop) Add a dedicated demo overlay that boots the full OpenLinker stack in Docker with a single command, on top of the existing infra services. - Dockerfile: new worker build target + apk add bash in base (migrate uses the repo migration:run script, which invokes typeorm via bash); also fixes a pre-existing blocker where the ksef/infakt workspace manifests + dist were never copied, so the image build failed outright (unnoticed because CI does not build the image). - apps/web/Dockerfile + nginx.conf: static SPA build served by nginx with SPA-fallback routing; VITE_API_BASE_URL baked at build time. - package.json: demo:up / demo:down / demo:logs scripts (demo:up includes phpmyadmin). - README.md: demo section (URLs incl. phpMyAdmin, credentials, manual PS<->OL connection). Closes #1352 Signed-off-by: jakubret <jakub.retajczyk@blockydevs.com> * fix(demo): unblock one-command Docker demo boot + PrestaShop networking (#1368) (#1369) Clean-checkout boot of the one-command demo (#1352, PR #1365) surfaced several infra/docs gaps. This is infra + docs only, no domain code. - A2: add start_period (180s) to the mysql healthcheck so first-boot init is not counted against the retry window on slower hosts. - A3: set OL_CORS_ORIGIN=http://localhost:8090 on the demo api service so the web UI (published on :8090) logs in without a CORS NetworkError. - A1: source OPENLINKER_CREDENTIALS_ENCRYPTION_KEY from the environment on migrate/api/worker with a required-var (:?) guard that fails the boot with a clear message; add a root .env.example and README pre-step documenting it (generate with openssl rand -base64 32). - B1: add a PrestaShop post-install step (40-configure-container-network) that registers a prestashop-domain ps_shop_url row and disables the canonical redirect, so the app-tier containers can reach the shop by its compose service name; also document the container-reachable Shop URL. - B2: document the container-reachable Storefront URL requirement for server-side offer image upload, with the browser-thumbnail trade-off noted as a tracked follow-up. - Docs: correct the PrestaShop admin path (/admin -> /admin-dev) and add a louder warning about the demo sharing Compose project + volumes with dev:stack:up. Closes #1368 Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> * docs(demo): add one-command demo setup guide (#1371) Detailed walkthrough for the #1352/#1365 Docker demo: prerequisites, example .env (required OPENLINKER_CREDENTIALS_ENCRYPTION_KEY), boot, service URLs, manual PrestaShop + Allegro connection wiring, end-to-end verification, and a troubleshooting table. Linked from the README demo section. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> * fix(demo): fix PrestaShop container-network post-install fatal on PS 9 (#1372) * fix(demo): fix PrestaShop container-network post-install fatal on PS 9 Live re-test of #1369 on a fresh PrestaShop 9.0.2 install surfaced two bugs in 40-configure-container-network.php that broke the container's boot (set -e in the wrapper turned the PHP fatal into a hard container exit): 1. ShopUrl::getShopUrls() returns ShopUrl objects under PS 9, not arrays — $row['domain'] threw "Cannot use object of type ShopUrl as array", aborting post-install and exiting the prestashop container (255). Replaced with a direct Db::getInstance()->getValue() existence check (matches the ObjectModel/legacy-bootstrap style already used in this script and its 20-set-default-currency.php sibling). 2. Db::getValue() appends its own trailing `LIMIT 1` internally (via getRow()); the query's own explicit `LIMIT 1` doubled it into `... LIMIT 1 LIMIT 1`, a SQL syntax error (exit 255 again). Verified end-to-end on a from-scratch PrestaShop 9.0.2 install (fresh volume + fresh database): post-install now logs "ShopUrl added for domain 'prestashop'", ps_shop_url carries both the main localhost row and the new non-main 'prestashop' row, PS_CANONICAL_REDIRECT=0, the container stays up (exit 0), and the API container's webservice probe to http://prestashop/api/ returns 401 (reached, no redirect) instead of the prior 301/302 to the canonical localhost domain. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * docs(demo): fix .env key recipe (duplicate line, sed portability) The documented .env recipe (README + the setup guide) did `cp .env.example .env` then `echo "KEY=..." >> .env`. .env.example already ships an empty `OPENLINKER_CREDENTIALS_ENCRYPTION_KEY=` line, so appending leaves TWO lines with the same key — harmless (env-file parsing is last-wins) but confusing to anyone editing .env by hand, and flagged in the #1369 review. The guide's proposed fix (sed -i '...') introduced a second problem: GNU sed and BSD sed (macOS) have incompatible -i syntax (BSD requires -i '' <script>), so the documented command would fail outright on macOS. Replaced both with a portable, dependency-free two-liner that drops the example's empty key line and appends the generated one: grep -v '^OPENLINKER_CREDENTIALS_ENCRYPTION_KEY=' .env.example > .env echo "OPENLINKER_CREDENTIALS_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> .env Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(demo): use ShopUrl object property access instead of raw SQL Addresses PR #1372 review suggestions: revert the idempotency check to ShopUrl::getShopUrls() with property access ($row->domain) instead of a hand-built Db::getInstance()->getValue() raw SQL string. Same idempotency semantics, no manual SQL/pSQL() escaping, and stays consistent with the sibling script's ObjectModel-only convention (also sidesteps the flagged double-quoted SQL string literal / ANSI_QUOTES portability risk, since there's no raw SQL left in this block at all). Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> --------- Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> * fix(demo): address PR #1365 review findings Addresses every /pr-review finding from @piotrswierzy, including suggestions: commit .dockerignore (was gitignored, so a fresh clone built with no ignore rules at all — this also uncovered and fixed a real tsc -b failure from a stray host tsconfig.tsbuildinfo leaking into the build context), a CI docker-build smoke job guarding the Dockerfile's per-package COPY lists, loopback-bound demo ports for api/web (default admin/admin ships with no redaction), nginx gzip/cache/security headers, and comments tying the enumerated COPY lists to their source of truth. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * feat(demo): parameterize domain/secret env vars via .env (#1375) Wires OL_CORS_ORIGIN, VITE_API_BASE_URL, PS_DOMAIN, OL_BOOTSTRAP_ADMIN_PASSWORD, JWT_SECRET, JWT_EXPIRES_IN, and OL_PII_HASH_SALT through ${VAR:-default} interpolation instead of hardcoded literals, so a devops team can point the demo at a real public domain (reverse proxy + TLS) and rotate the throwaway dev secrets purely via .env, without editing the compose files. Unlike OPENLINKER_CREDENTIALS_ENCRYPTION_KEY (${VAR:?required} — no safe default exists for a credentials-encryption key), every variable here defaults to today's exact literal value, so a plain `pnpm demo:up` / `pnpm dev:stack:up` with no .env changes needs no behaviour change. VITE_API_BASE_URL is a Vite build-time arg — .env.example calls out that overriding it only takes effect on the next --build, not a plain restart. PS_DOMAIN is documented as the PUBLIC browser-facing domain only; it does not affect how api/worker reach PrestaShop internally (always the compose service name, http://prestashop). .env.example gains a clearly-separated OPTIONAL section (commented out by default) documenting each variable's purpose and default. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> --------- Signed-off-by: jakubret <jakub.retajczyk@blockydevs.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> Co-authored-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…staShop) (#1365) * feat(demo): one-command Docker demo environment (API/Web/Worker + PrestaShop) Add a dedicated demo overlay that boots the full OpenLinker stack in Docker with a single command, on top of the existing infra services. - Dockerfile: new worker build target; also fixes a pre-existing blocker where the ksef/infakt workspace manifests + dist were never copied, so the image build failed outright (unnoticed because CI does not build the image). - apps/web/Dockerfile + nginx.conf: static SPA build served by nginx with SPA-fallback routing; VITE_API_BASE_URL baked at build time. - package.json: demo:up / demo:down / demo:logs scripts. - README.md: demo section (URLs, credentials, manual PS<->OL connection). Closes #1352 Signed-off-by: jakubret <jakub.retajczyk@blockydevs.com> * feat(demo): one-command Docker demo environment (API/Web/Worker + PrestaShop) Add a dedicated demo overlay that boots the full OpenLinker stack in Docker with a single command, on top of the existing infra services. - Dockerfile: new worker build target + apk add bash in base (migrate uses the repo migration:run script, which invokes typeorm via bash); also fixes a pre-existing blocker where the ksef/infakt workspace manifests + dist were never copied, so the image build failed outright (unnoticed because CI does not build the image). - apps/web/Dockerfile + nginx.conf: static SPA build served by nginx with SPA-fallback routing; VITE_API_BASE_URL baked at build time. - package.json: demo:up / demo:down / demo:logs scripts (demo:up includes phpmyadmin). - README.md: demo section (URLs incl. phpMyAdmin, credentials, manual PS<->OL connection). Closes #1352 Signed-off-by: jakubret <jakub.retajczyk@blockydevs.com> * fix(demo): unblock one-command Docker demo boot + PrestaShop networking (#1368) (#1369) Clean-checkout boot of the one-command demo (#1352, PR #1365) surfaced several infra/docs gaps. This is infra + docs only, no domain code. - A2: add start_period (180s) to the mysql healthcheck so first-boot init is not counted against the retry window on slower hosts. - A3: set OL_CORS_ORIGIN=http://localhost:8090 on the demo api service so the web UI (published on :8090) logs in without a CORS NetworkError. - A1: source OPENLINKER_CREDENTIALS_ENCRYPTION_KEY from the environment on migrate/api/worker with a required-var (:?) guard that fails the boot with a clear message; add a root .env.example and README pre-step documenting it (generate with openssl rand -base64 32). - B1: add a PrestaShop post-install step (40-configure-container-network) that registers a prestashop-domain ps_shop_url row and disables the canonical redirect, so the app-tier containers can reach the shop by its compose service name; also document the container-reachable Shop URL. - B2: document the container-reachable Storefront URL requirement for server-side offer image upload, with the browser-thumbnail trade-off noted as a tracked follow-up. - Docs: correct the PrestaShop admin path (/admin -> /admin-dev) and add a louder warning about the demo sharing Compose project + volumes with dev:stack:up. Closes #1368 Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> * docs(demo): add one-command demo setup guide (#1371) Detailed walkthrough for the #1352/#1365 Docker demo: prerequisites, example .env (required OPENLINKER_CREDENTIALS_ENCRYPTION_KEY), boot, service URLs, manual PrestaShop + Allegro connection wiring, end-to-end verification, and a troubleshooting table. Linked from the README demo section. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> * fix(demo): fix PrestaShop container-network post-install fatal on PS 9 (#1372) * fix(demo): fix PrestaShop container-network post-install fatal on PS 9 Live re-test of #1369 on a fresh PrestaShop 9.0.2 install surfaced two bugs in 40-configure-container-network.php that broke the container's boot (set -e in the wrapper turned the PHP fatal into a hard container exit): 1. ShopUrl::getShopUrls() returns ShopUrl objects under PS 9, not arrays — $row['domain'] threw "Cannot use object of type ShopUrl as array", aborting post-install and exiting the prestashop container (255). Replaced with a direct Db::getInstance()->getValue() existence check (matches the ObjectModel/legacy-bootstrap style already used in this script and its 20-set-default-currency.php sibling). 2. Db::getValue() appends its own trailing `LIMIT 1` internally (via getRow()); the query's own explicit `LIMIT 1` doubled it into `... LIMIT 1 LIMIT 1`, a SQL syntax error (exit 255 again). Verified end-to-end on a from-scratch PrestaShop 9.0.2 install (fresh volume + fresh database): post-install now logs "ShopUrl added for domain 'prestashop'", ps_shop_url carries both the main localhost row and the new non-main 'prestashop' row, PS_CANONICAL_REDIRECT=0, the container stays up (exit 0), and the API container's webservice probe to http://prestashop/api/ returns 401 (reached, no redirect) instead of the prior 301/302 to the canonical localhost domain. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * docs(demo): fix .env key recipe (duplicate line, sed portability) The documented .env recipe (README + the setup guide) did `cp .env.example .env` then `echo "KEY=..." >> .env`. .env.example already ships an empty `OPENLINKER_CREDENTIALS_ENCRYPTION_KEY=` line, so appending leaves TWO lines with the same key — harmless (env-file parsing is last-wins) but confusing to anyone editing .env by hand, and flagged in the #1369 review. The guide's proposed fix (sed -i '...') introduced a second problem: GNU sed and BSD sed (macOS) have incompatible -i syntax (BSD requires -i '' <script>), so the documented command would fail outright on macOS. Replaced both with a portable, dependency-free two-liner that drops the example's empty key line and appends the generated one: grep -v '^OPENLINKER_CREDENTIALS_ENCRYPTION_KEY=' .env.example > .env echo "OPENLINKER_CREDENTIALS_ENCRYPTION_KEY=$(openssl rand -base64 32)" >> .env Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * fix(demo): use ShopUrl object property access instead of raw SQL Addresses PR #1372 review suggestions: revert the idempotency check to ShopUrl::getShopUrls() with property access ($row->domain) instead of a hand-built Db::getInstance()->getValue() raw SQL string. Same idempotency semantics, no manual SQL/pSQL() escaping, and stays consistent with the sibling script's ObjectModel-only convention (also sidesteps the flagged double-quoted SQL string literal / ANSI_QUOTES portability risk, since there's no raw SQL left in this block at all). Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> --------- Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> * fix(demo): address PR #1365 review findings Addresses every /pr-review finding from @piotrswierzy, including suggestions: commit .dockerignore (was gitignored, so a fresh clone built with no ignore rules at all — this also uncovered and fixed a real tsc -b failure from a stray host tsconfig.tsbuildinfo leaking into the build context), a CI docker-build smoke job guarding the Dockerfile's per-package COPY lists, loopback-bound demo ports for api/web (default admin/admin ships with no redaction), nginx gzip/cache/security headers, and comments tying the enumerated COPY lists to their source of truth. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> * feat(demo): parameterize domain/secret env vars via .env (#1375) Wires OL_CORS_ORIGIN, VITE_API_BASE_URL, PS_DOMAIN, OL_BOOTSTRAP_ADMIN_PASSWORD, JWT_SECRET, JWT_EXPIRES_IN, and OL_PII_HASH_SALT through ${VAR:-default} interpolation instead of hardcoded literals, so a devops team can point the demo at a real public domain (reverse proxy + TLS) and rotate the throwaway dev secrets purely via .env, without editing the compose files. Unlike OPENLINKER_CREDENTIALS_ENCRYPTION_KEY (${VAR:?required} — no safe default exists for a credentials-encryption key), every variable here defaults to today's exact literal value, so a plain `pnpm demo:up` / `pnpm dev:stack:up` with no .env changes needs no behaviour change. VITE_API_BASE_URL is a Vite build-time arg — .env.example calls out that overriding it only takes effect on the next --build, not a plain restart. PS_DOMAIN is documented as the PUBLIC browser-facing domain only; it does not affect how api/worker reach PrestaShop internally (always the compose service name, http://prestashop). .env.example gains a clearly-separated OPTIONAL section (commented out by default) documenting each variable's purpose and default. Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> --------- Signed-off-by: jakubret <jakub.retajczyk@blockydevs.com> Signed-off-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> Co-authored-by: norbert-kulus-blockydevs <norbert.kulus@blockydevs.com> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Summary
Follow-up to #1369. A live re-test of the merged #1369 fix — full teardown + fresh rebuild from a clean checkout, PrestaShop 9.0.2 — surfaced two bugs in
docker/prestashop/post-install-lib/40-configure-container-network.phpthat broke the container's boot entirely (the wrapper'sset -eturned a PHP fatal into a hard container exit, code 255):ShopUrl::getShopUrls()returnsShopUrlobjects under PrestaShop 9, not arrays —$row['domain']threwCannot use object of type ShopUrl as array, aborting the post-install script and killing theprestashopcontainer. This is exactly the risk flagged in the fix(demo): unblock one-command Docker demo boot + PrestaShop networking (#1368) #1369 review (idempotency-guard shape assumption), except it's a hard boot-break rather than a silent duplicate-row issue.Db::getInstance()->getValue(...)existence check (matches the ObjectModel/legacy-bootstrap style this script and its20-set-default-currency.phpsibling already use).Db::getValue()appends its own trailingLIMIT 1internally (viagetRow()); the query's own explicitLIMIT 1doubled it into... LIMIT 1 LIMIT 1— a SQL syntax error (exit 255 again). Caught on the second reproduction attempt after fixing (1).Verification
Full from-scratch reproduction: tore down all previously-built demo containers/volumes/images, rebuilt the images, and reset both the PrestaShop file volume and its MySQL database to force a true clean install (not just a container recreate).
* ShopUrl added for domain 'prestashop' (id_shop=1)/* App-tier containers can now reach the shop at http://prestashopps_shop_urlcarries both the operator's mainlocalhost:18080row and the new non-mainprestashoprow;PS_CANONICAL_REDIRECT=0.prestashopcontainer status:running, exit code0(previously exited 255).apicontainer:fetch('http://prestashop/api/')→401(reached, no redirect) — previously a301/302back to the canonicallocalhostdomain.migrateexits 0 automatically via the.env-sourcedOPENLINKER_CREDENTIALS_ENCRYPTION_KEY(no manual key insertion needed),mysqlreaches healthy without the prior healthcheck race, UI login succeeds with no CORS error, and the fix(allegro,web): advertise OfferManager sub-capabilities so bulk wizard shows Allegro category params #1370 Allegro manifest fix is present in the rebuilt web bundle.Test plan
bash -n docker/prestashop/post-install/40-configure-container-network.sh— OK (unchanged wrapper).prestashopstays up, post-install succeeds, webservice reachable from the app tier by service name.🤖 Generated with Claude Code