Four workflows, none of which existed:
* `tests.yml` — backend, desktop, `:core` and the web app's typecheck+build, in
parallel. `:core` needs no Android SDK at all, because settings.gradle.kts
leaves `:app` out when there is none; `:app`'s Robolectric suite therefore
lives in the APK workflow, where the SDK is installed anyway. Nothing needs a
running backend — the suites feed on committed fixtures, which is the whole
reason they were written that way.
* `lint.yml` — ruff, which the repo had been configured for and never run.
* `docker.yml` — builds the image, *starts* it against SQLite and waits for
/api/health before pushing, then publishes `:latest` and `:<short-sha>` to
git.timka.su. A tag nobody can run is worse than no tag.
* `android-release.yml` — the release APK on every push to master, signed from
secrets, with `apksigner verify --print-certs` reporting which key was really
used and failing if it was the debug one.
Three things the repo needed before any of that could work:
`versionCode` was pinned at 1, so every APK looked like the same version to the
phone and would not install over its predecessor. It now comes from the run
number, with `versionName` from a property, both via one `setting()` helper that
reads a Gradle property or an environment variable.
The release build fell back to the *debug* signing key whenever the keystore was
missing — silently. That is a trap: the APK installs, looks fine, and can never
be upgraded by a properly signed build. `KADENCE_REQUIRE_SIGNING=1` makes that
case fail the build instead, and the release job sets it whenever a keystore
secret is present.
The compose files built an image with no name, so there was nothing to pull.
`image:` now names the registry tag while `up --build` still builds locally.
Also: `android/ci/install-sdk.sh` (idempotent, cacheable, and the one place that
knows `platforms;android-37.0` is spelled with the minor version), the runner as
a compose service with its config and a setup checklist, six make targets, and a
README section. No runner is registered yet — `make ci-runner` is step two of
that checklist.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
deploy.sh lives in the repo it updates. `git reset --hard` therefore replaced
the file bash was reading; bash keeps a byte offset into the script and resumed
at that offset in the new file, skipping whatever the diff had shifted.
That is how the previous commit's renewal hook ended up present on the server
and never executed: `grep` found it in deploy.sh, the hook directory was empty.
Stage one now updates the checkout and re-execs a copy under /tmp, which no
later `git reset` can touch; stage two does the work and removes the copy.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`certbot certonly` replaces the files on disk but signals nothing, so nginx
would have gone on serving the old certificate until some unrelated reload
happened — which is to say the cert would have quietly expired in January.
`--nginx` installs this hook itself; keeping the vhost in git means we
install it ourselves.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The server runs nginx 1.24.0; the standalone `http2 on;` directive only
arrived in 1.25.1, so `nginx -t` rejected the vhost as an unknown directive.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three pieces so kadence.timka.su can be stood up and updated from the repo
rather than from shell history on the box:
* `docker-compose.prod.yml` — publishes the app on loopback only. The base
file's `${APP_PORT}:8000` binds every interface, which on this host both
collides with remnawave (already on 8000) and would serve the API beside
the proxy without TLS. Compose *appends* merged port lists, so the override
needs the `!override` tag or the public binding survives. uvicorn also gets
`--proxy-headers`, without which every request looks like plain HTTP from
127.0.0.1 and the session cookie loses its `Secure` flag.
* `deploy/nginx/kadence.timka.su.conf` — TLS, HSTS, the forwarded headers the
cookie logic reads, and a 120s read timeout because one task refresh fans
out to four third-party APIs.
* `deploy/deploy.sh` — idempotent install-or-update. Generates API_TOKEN and
the database password once and never rotates them afterwards (that would
lock out the phone and the desktop app), treats `.env.example`'s
`change-me` placeholders as unset, and uses `certbot certonly --webroot`
rather than `--nginx` so the vhost in git stays the truth.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>