Gitea act runner v0.6.1 ignores `shell: bash` step overrides and always
executes with `sh -e {0}`. The `set -euo pipefail` on line 2 caused sh to
exit immediately with "Illegal option -o pipefail" before any curl/openssl
ran. Replace with POSIX-compatible `set -eu` in both api-build and
admin-api-build trust steps so the diagnostic curl output is finally visible.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
set -euo pipefail is bash-only; Gitea act runner used sh by default so
the step crashed on line 1 before curl even ran. Adding shell: bash
lets the step actually execute and surface the real AIA/cert output.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previous attempt used curl -sf which silently swallows failures, so
we never knew if ISRG Root YR was actually fetched. This run:
• set -euo pipefail → step fails fast and loudly on any error
• curl -v → shows connection result / error in log
• openssl verify → confirms cert bundle is good before restore
• openssl s_client → shows full chain verify against live mirror
If the AIA URL (http://yr.i.lencr.org/) is unreachable from the
runner, the step will fail HERE rather than silently at dotnet restore.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The prior Trust step added only the YR2 intermediate to the OS trust
store. dotnet's X.509 chain builder requires a self-signed ROOT as the
trust anchor (it does not enable OpenSSL's X509_V_FLAG_PARTIAL_CHAIN),
so intermediate-only still caused PartialChain.
New approach (two jobs: api-build, admin-api-build):
1. curl http://yr.i.lencr.org/ (plain HTTP AIA) → ISRG Root YR DER
→ convert to PEM → add to /usr/local/share/ca-certificates/
2. cp YR2 intermediate (docker/nexus-mirror-ca.crt) → same dir
3. update-ca-certificates (OS method)
4. cat both certs >> /etc/ssl/certs/ca-certificates.crt
(belt-and-suspenders: directly appends to the OpenSSL bundle
dotnet reads on Linux, works even if step 3 is a no-op)
If the AIA fetch fails (network block) step 4 still appends the
intermediate, which may work if dotnet ever enables partial chains.
Fetch failure is non-fatal (echo warning + continue).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The mirror's Let's Encrypt cert renewed under the new ISRG Root YR root,
which isn't in the dotnet SDK image's trust store. `dotnet restore` validates
TLS and fails (NU1301 / unable to get local issuer certificate), so both
backend CI jobs fail and the deploy is skipped. The npm jobs are unaffected
because they already pass --strict-ssl=false.
Pin the mirror's intermediate (CN=YR2, CA:TRUE, valid to Sept 2028) and add it
as a trust anchor before restore in:
- CI api-build + admin-api-build jobs (.gitea/workflows/ci-cd.yml)
- docker/api/Dockerfile + docker/admin-api/Dockerfile (deploy image builds)
Also set NUGET_CERT_REVOCATION_MODE=offline in the CI restore steps to avoid
CRL/OCSP fetches to lencr.org (filtered from Iran).
Permanent fix is server-side (re-chain to ISRG Root X1 or update trust stores);
this unblocks CI/deploys without depending on that.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Removes <none>-tagged images left over from previous builds.
Only affects untagged images (dangling=true) — never touches other
projects' named images (soroushasadi-site, drsousan, etc.).
Also logs disk usage after prune.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Start api alone first; web/admin-api each wait for their own step
so a health-check wait never blocks unrelated services
- Detect crash-loops via RestartCount > 1 (restart:unless-stopped hides
the exited state behind rapid restarts — count is reliable)
- Dump up to 120 lines of api logs immediately on crash/timeout
- Log infra network state (json) in attach step + failure dump so we
can see exactly which aliases are registered on meezi_default
- admin-api and admin-web are now started in separate steps, same pattern
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without --alias, meezi-db joins meezi_default but is only reachable
as "meezi-db". The API uses Host=postgres — DNS lookup fails after
~5s, migration throws, container crashes.
Fix: disconnect first, then reconnect with service-name aliases
so "postgres" and "redis" resolve correctly on meezi_default.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
postgres/redis were created before the compose project name was locked
to "meezi", so they're on a different Docker network. New app containers
join meezi_default — the API crashes immediately because it can't reach
Host=postgres.
Fix: create meezi_default if needed, then docker network connect
meezi-db and meezi-redis to it before starting the app containers.
Also dump API and admin-api logs on failure to make future failures
easier to diagnose.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Existing containers lack compose project labels so Compose cannot claim
them — it tries to CREATE alongside them and hits a name conflict.
Fix: stop + rm only the 6 meezi app containers by name before compose up.
Postgres, redis, and all other projects are never touched.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The meezi-redis container was created before the name:meezi label existed
in docker-compose.yml, so Compose doesn't recognise it as its own project
container and tries to CREATE a new one, causing the name conflict.
Real fix: postgres and redis are persistent infrastructure — CI should
never restart them. Remove them from all deploy steps entirely.
Only api, web, website, koja, admin-api, admin-web are cycled on deploy.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
docker compose up without --no-recreate tries to recreate postgres/redis
when it detects a config change or finds a stopped container, which causes
"container name already in use" when the container is still running.
Fix: infrastructure (postgres, redis) uses --no-recreate so a healthy
container is never touched. App services (api, web, website, koja,
admin-api, admin-web) use --force-recreate so freshly-built images are
always applied.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- dashboard layout: wait for Zustand _hasHydrated before redirecting to /login
(was redirecting on first render before localStorage was read)
- admin shell: same fix using new _hasHydrated on admin auth store
- admin-auth.store: add _hasHydrated + onRehydrateStorage to mirror merchant store
- AdminPlansScreen: replace direct cache mutation with per-plan PlanCard component
that owns its own useState — fixes other plans disappearing after save
- AdminSettingsScreen: detect boolean values and render iOS-style Toggle switches
- AdminIntegrationsScreen: replace all <input type=checkbox> with Toggle switches;
replace OpenAI model text input with <select> dropdown (gpt-4o-mini/4o/4-turbo/4/3.5)
- blog editor: fix form never syncing existing post data into state (editing was broken);
all fields now use local form state, save uses form directly
- blog links: fix broken relative hrefs (website/blog/new → /admin/website/blog/new)
and back button using proper Link components
- ci-cd: remove image prune step entirely — never removes containers or images
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prevents runner workspace collisions with other projects (DrSousan etc.)
causing containers to be treated as orphans and stopped on deploy.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Docker daemon reaches the Nexus Docker group over the dedicated
connector port 8087 (its registry mirror), not the main 8081 HTTP port,
which caused HTTPS-to-HTTP pull failures in CI. Repoint all image refs to
171.22.25.73:8087 at the connector root; npm and NuGet stay on 8081.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Point Docker, NuGet, and npm pulls at the Nexus group repos on
171.22.25.73:8081 for both CI/CD and local builds, so the pipeline and
developers no longer depend on Docker Hub, MCR, nuget.org, or npmjs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rebrand the public café-discovery app: directories web/finder→web/koja and
docker/finder→docker/koja, plus all service wiring (docker-compose, Caddy
subdomain koja.meezi.ir, env vars KOJA_PORT / NEXT_PUBLIC_KOJA_URL, CI
workflows) and the app's display name (Koja / کجا).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- aspnet:10.0, postgres:16-alpine, redis:7-alpine all fail on first
fetch through Nexus proxy (OCI manifest format bug in Nexus)
- Change DOTNET_ASPNET_IMAGE default to mcr-mirror.liara.ir directly
- Change postgres/redis service images to docker-mirror.liara.ir
- CI service containers (api-build job) also use Liara directly
- All images parameterized so ENV_FILE can override for any registry
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
act runner (host mode) inherits a minimal PATH from the process
environment — docker is not found even though it is installed.
Explicitly include all standard locations plus /snap/bin.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Node.js is not in PATH on the self-hosted:host runner, so JS actions
(actions/checkout@v4) fail with "cannot find node". Use the same shell
git init/fetch/checkout pattern used in all other jobs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
python3 is not in PATH inside dotnet/sdk:10.0 container — replace the
"Write NuGet config" step with a cat heredoc which works in any container.
Also syncs GitHub with the Gitea-side changes:
- All images pulled from local Nexus mirrors (no internet round-trip)
171.22.25.73:5000 → docker-hub-proxy (node, postgres, redis)
171.22.25.73:5002 → mcr-proxy (dotnet/sdk)
- npm steps already on npm-group (Liara + Runflare fallback)
- docker-compose.mirror.yml: expose port 5002 for mcr-proxy
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
apk add git downloads from dl-cdn.alpinelinux.org (Fastly CDN) which is
slow/blocked in Iran — caused 6m+ checkout times.
New approach: wget the repo tarball from Gitea's archive API endpoint.
wget + tar (busybox) are already in node:20-alpine — no package install.
Gitea is on the same machine as the runner = download is instant.
GET /api/v1/repos/{owner}/{repo}/archive/{sha}.tar.gz
Authorization: Bearer {token}
dotnet/sdk jobs unchanged — Debian base has git pre-installed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
NuGet 10 blocks HTTP sources by default. allowInsecureConnections=true
must be set in a config file — the --source CLI flag doesn't support it.
Write the config to /tmp/nuget.ci.config inline in the step so there is
no dependency on any file existing in the workspace.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
--configfile nuget.mirror.config fails when the file isn't present in
the workspace (e.g. when Gitea is behind GitHub on commits).
--source inline URL is simpler, self-contained, and replaces all
configured sources — no extra file dependency in CI.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
github.server_url returns 'http://gitea:3000' (Gitea ROOT_URL using Docker
service name). CI job containers run on an isolated network and can't resolve
the 'gitea' hostname.
host-gateway maps to the Docker bridge IP (172.17.0.1). Gitea publishes
port 3000 on all interfaces, so http://gitea:3000 becomes reachable inside
every job container via the bridge.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
actions/checkout@v4 is a JS action executed inside the job container:
- dotnet/sdk:10.0 has no Node.js → exit 127
- node:20-alpine has no git → checkout fails
Fix: manual git clone via shell using http.extraheader for token auth.
Token never appears in process list or git log. deploy job (self-hosted:host)
keeps actions/checkout — the act_runner image has both node and git.
Also removes defaults.run.working-directory from Node.js jobs (the checkout
step must start in workspace root, not web/<app>).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Switch CI jobs to container: image: overrides so jobs run inside official
SDK containers (dotnet/sdk:10.0, node:20-alpine) instead of the bare
runner container. This bypasses blocked CDN downloads for dotnet/node.
Deploy job stays on self-hosted:host where Docker CLI is available.
Update workflow comments to explain the required runner label config:
ubuntu-latest:docker://node:20-alpine (CI jobs)
self-hosted:host (deploy)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause: actions/setup-dotnet@v4 downloads .NET from
download.visualstudio.microsoft.com and actions/setup-node@v4 downloads
Node from nodejs.org — both CDNs are blocked from Iran so jobs hang at 0s.
Fix:
- All .NET jobs: add container: mcr.microsoft.com/dotnet/sdk:10.0
so .NET is already inside the image — no download needed.
Remove actions/setup-dotnet step entirely.
- All Node.js jobs: add container: node:20-alpine
so Node/npm are already inside the image — no download needed.
Remove actions/setup-node step entirely.
- api-build: add postgres + redis service containers + env vars so
dotnet test can actually connect to a database (was silently failing).
- deploy job: change back to runs-on: self-hosted
ubuntu-latest containers don't have Docker CLI — docker compose
commands would fail immediately. Deploy MUST run on the server.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
.gitea/workflows/ci-cd.yml:
- Triggers on push to main and PRs
- CI jobs: dotnet build/test, dashboard tsc, finder tsc (all self-hosted)
- Deploy job: only on push to main, needs all CI jobs to pass
- Writes .env from ENV_FILE secret (set in Gitea repo settings)
- docker compose build --parallel with BuildKit
- Rolling restart (postgres/redis untouched)
- Health-check poll: waits up to 2min for meezi-api healthy
- Auto-prunes old images on success
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>