Release v1.4.0 - #34
Merged
Merged
Conversation
* Make rate limiting configurable via environment variable
Add RATELIMIT_ENABLE environment variable (default: true) to allow
disabling rate limits for development/testing without code changes.
Usage: RATELIMIT_ENABLE=false to disable all rate limits
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
* Merge add_ip scripts into single script
Combine run_add_ip.sh wrapper into add_ip_to_scaleway_allowlist.sh.
The merged script now handles .env loading internally and can be
called directly without the wrapper.
Usage: ./scripts/add_ip_to_scaleway_allowlist.sh
or: SCALEWAY_* env vars already set before calling
* Move startup scripts into scripts directory
Consolidate all scripts into the scripts/ directory for better organization.
- dev_startup.sh: Development server startup
- startup.sh: Production startup (migrations, cache, gunicorn)
- add_ip_to_scaleway_allowlist.sh: IP allowlist management
* Use startup script in Dockerfile instead of inlining commands
Replace inlined startup commands with call to scripts/startup.sh for
better maintainability and consistency. Also ensure gunicorn binds to
port 8080 as specified in Dockerfile EXPOSE.
Changes:
- Dockerfile CMD now calls: sh scripts/startup.sh
- startup.sh: Added explicit 8080 port binding
* Fix startup scripts to work from scripts/ directory
Both scripts now:
- Change to project root directory before running
- Use consistent shebang (#!/bin/sh)
- Allow running from any directory (not just project root)
Usage:
./scripts/dev_startup.sh (from project root or anywhere)
/full/path/to/scripts/dev_startup.sh
* Fix dev_startup.sh to work with sh (not just bash)
Changes:
- Replace bash-specific 'source' with sh-compatible '. '
- Gracefully handle missing venv (check if it exists first)
- Only activate venv if it's available
- Allow script to run without venv (e.g., if using system Python)
This ensures the script works with /bin/sh as specified in shebang.
* Bump server version to 1.3.2
Changes in this release:
- Make rate limiting configurable via RATELIMIT_ENABLE env var
- Reorganize scripts into scripts/ directory
- Replace inlined Docker startup with script call
- Fix startup scripts to work from scripts/ directory
- Improve script portability and error handling
* Fix RATELIMIT_ENABLE to handle common truthy values
Make the RATELIMIT_ENABLE setting case-insensitive and accept common
truthy values (true, 1, yes, on) in any case variation. This prevents
silent misconfigurations where typos or environment setup errors could
unintentionally disable rate limiting.
Fixes Copilot finding: case-sensitive comparison was risky
* Address Copilot review: env.bool parsing + fix stale script refs
- Use env.bool() for RATELIMIT_ENABLE so invalid values fail fast
instead of silently disabling rate limiting
- Update Makefile 'allowlist' target to call the merged
add_ip_to_scaleway_allowlist.sh (run_add_ip.sh was removed)
- Update README to point to ./scripts/dev_startup.sh after the move
* Gracefully stop Django-Q worker on shutdown + document RATELIMIT_ENABLE
startup.sh previously ran 'qcluster &' then 'exec gunicorn', making
Gunicorn PID 1 so only it received SIGTERM; the Django-Q worker was
orphaned and force-killed on container stop. (Pre-existing: the old
inline Dockerfile CMD and top-level startup.sh had the same pattern.)
Now run both as child processes and trap TERM/INT to forward the signal
to both, so the worker stops gracefully. Verified with a dash harness:
docker-stop and gunicorn-self-exit both tear down both processes with no
leftovers (exit 143 on SIGTERM, the conventional code).
Also document RATELIMIT_ENABLE in .env.sample so the new toggle is
discoverable.
* Wait for Gunicorn drain on shutdown + add 1.3.2 changelog entry
startup.sh: after forwarding SIGTERM, wait for BOTH children to fully
exit before PID 1 does. Previously the script returned as soon as the
qcluster worker exited, so if Gunicorn was still draining connections it
got force-killed when the container stopped. Also preserve Gunicorn's
exit code so a crash still propagates (container can restart).
Verified with a dash harness: slow-drain SIGTERM (script waits for the
full drain, exits 143, no orphan) and Gunicorn crash (exit 3 propagates,
worker reaped).
CHANGELOG.md: add the 1.3.2 entry to match the __version__ bump, per the
repo's paired version+changelog release convention.
* Self-review fixes: strict RATELIMIT_ENABLE parsing, script exec bits, venv path
Three defects found by re-auditing my own changes on this branch:
1. settings.py: env.bool() does NOT fail fast — verified against
django-environ 0.13.0: any unrecognized string ('enabled', typos)
silently becomes False, i.e. rate limiting silently disabled. That is
exactly the risk the review flagged, and the previous commit only
fixed the case-sensitivity half while claiming fail-fast. Replace
with strict parsing: true/1/yes/on and false/0/no/off accepted
(case-insensitive), anything else raises ImproperlyConfigured at
startup. Verified by importing real settings with 9 values.
2. scripts/*.sh were tracked mode 644, so the README instruction
'./scripts/dev_startup.sh' failed with Permission denied. Set +x on
dev_startup.sh and startup.sh (Docker's 'sh scripts/startup.sh' was
unaffected).
3. dev_startup.sh looked for .venv/ but this repo's virtualenv is
venv/ — activation never fired and it fell back to system python.
Now tries venv/ then .venv/. Verified: direct invocation boots the
dev server with the repo venv.
Also correct the CHANGELOG wording that repeated the env.bool claim.
* Address review: venv comment accuracy + document bare-wait semantics
- dev_startup.sh: the comment claimed 'this repo uses venv/' but the
README setup instructions create .venv/. Neutralize the comment and
check .venv/ (documented convention) before venv/ (fallback).
- startup.sh: no functional change for the set -e concern — a bare
'wait' (no operands) always returns 0 per POSIX regardless of child
exit statuses, verified in dash and bash with children exiting 5/7
(script reached its final exit). Added a comment pinning this down so
a future refactor to the operand form 'wait "$pid"' (which DOES
return the child's status and would abort under set -e) doesn't
reintroduce the issue.
Re-ran the harness: SIGTERM drain exits 143, gunicorn crash propagates
exit 3 even with the worker exiting 9, no orphaned processes.
* Supervise both children in startup.sh; exit non-zero on worker death
Previously the script only blocked on Gunicorn, so a crashed qcluster
worker went unnoticed while the container kept serving HTTP with
notifications/background jobs silently dead (known limitation flagged in
review).
Now a supervision loop (interruptible 5s poll — POSIX sh has no wait -n)
exits as soon as either child dies or a signal lands, tears down the
survivor, still waits out Gunicorn's connection drain, and propagates:
- gunicorn crash -> its exit code (unchanged)
- worker crash -> forced exit 1 even if Gunicorn stopped cleanly, so
the orchestrator restarts the container
- docker stop -> Gunicorn's graceful status (0), signal path excluded
from the forced-nonzero rule via a _signaled flag
Harness (real script + faithful stubs, dash): SIGTERM with 2s drain ->
exit 0, no orphans, no stderr noise; gunicorn crash -> exit 3; worker
crash -> detected in ~5s, teardown clean, exit 1.
---------
Co-authored-by: veeck <gitkraken@veeck.de>
Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
* Add podcastFeed proxy endpoint for the podcast feed Fetches the podcast RSS feed — the Volksverpetzer Podigee feed by default, configurable via the PODCAST_FEED_URL env var (e.g. for the Mimikama deployment or a feed move) — parses episodes (title, description, date, link, mp3 enclosure, cover, duration incl. HH:MM:SS) into JSON and serves them at /proxy/podcastFeed for the app's podcast home-feed section. Hardened from the start: - Controlled 502 on upstream or parse failures instead of an uncaught 500 (errors are never cached, so a transient outage is not replayed) - Constant cache key (30 min TTL): the endpoint takes no parameters, so query strings can neither bypass the cache nor evict other entries from the shared cache - Naive RFC 2822 "-0000" pubDates are emitted as UTC so the app never parses them as device-local time - Nonsense itunes:duration values (negatives, >3 parts) are rejected - guid/link are stripped (guid feeds the app's de-duplication); episodes without an audio enclosure are dropped; episode covers fall back to the channel image Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * chore(release): prepare v1.4.0 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: veeck <gitkraken@veeck.de> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
… YT_CHANNEL_ID config) (#29) * Make the YouTube channel configurable via YT_CHANNEL_ID The ytAPI endpoint hardcoded the Volksverpetzer channel. Read it from the YT_CHANNEL_ID env var (documented in .env.sample), falling back to the Volksverpetzer channel when unset, so the Mimikama deployment can point at its own channel without a code change — mirroring how the podcast feed URL is configured. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * Strip whitespace from configurable env URLs/IDs A stray-whitespace env value (e.g. " UC…" or "PODCAST_FEED_URL= ") is truthy, so `os.environ.get(...) or DEFAULT` did not fall back to the default and passed the malformed value downstream: - YT_CHANNEL_ID → the YouTube API 400s, and ytAPI has no error handling, so it surfaced as an uncaught 500 - PODCAST_FEED_URL → fetched as a malformed URL (caught as a 502, but still wrong) .strip() both so a whitespace-only value falls back to the default. Also guard the YouTube test's os.environ.pop inside patch.dict so it can't leak to other tests, and cover the whitespace fallback for both. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: veeck <gitkraken@veeck.de> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Every allowed host is a potential Host-header cache-poisoning vector: replace_media_urls() builds absolute URLs from the request host and the results land in the shared response cache. - Remove "pruefpunkt.org": it is the WordPress site's domain and never legitimately reaches this server as a Host header — the site-scoped analytics select the site via the ?site= query parameter, not the vhost - Move "127.0.0.1"/"localhost" behind DEBUG and add "10.0.2.2" (the Android emulator's host-loopback alias) there, so local emulator testing works without editing settings - Keep the Azure entries for the Mimikama App Service deployment with a TODO to verify the raw inbound IP against the probe config Co-authored-by: veeck <gitkraken@veeck.de> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Bumps [actions/setup-python](https://github.com/actions/setup-python) from 6 to 7. - [Release notes](https://github.com/actions/setup-python/releases) - [Commits](actions/setup-python@v6...v7) --- updated-dependencies: - dependency-name: actions/setup-python dependency-version: '7' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…escription tag (#31) The old filter dropped a video only if its description literally contained "#shorts", which missed genuine Shorts uploaded without that tag. Now fetches contentDetails alongside snippet/player and filters using the video's actual duration against YouTube's own <=3min Shorts threshold, and forwards the duration to consumers (e.g. the Divi5 ContentOverview widget) for their own synchronous filtering. Co-authored-by: veeck <gitkraken@veeck.de> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
#28 moved these behind `if DEBUG`, so production (DEBUG=disabled) no longer allows them. If any production health/liveness check reaches the container over loopback with a bare Host: 127.0.0.1 or localhost, that now 400s (DisallowedHost) instead of returning a real health response. Keep them in the base list; only the Android-emulator alias (10.0.2.2) is dev-only. Co-authored-by: veeck <gitkraken@veeck.de> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* Bump version to 1.4.0 to match CHANGELOG __init__.py was still at 1.3.2 while the CHANGELOG's top entry (and this release) is 1.4.0 — the app would have reported the wrong version. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Fold the phantom v1.3.2 entry into v1.4.0 There was never a v1.3.2 release/tag (only v1.3.0 and v1.3.1 exist) — the Startup/Rate-limiting/Scripts changes from #26 never shipped on their own, so they belong under the same 1.4.0 heading as the Podcast feature from #27, not a separate prior version. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * Add CHANGELOG entries for #28-#31 and #33 These were already merged into prerelease (ALLOWED_HOSTS tightening, YT_CHANNEL_ID/whitespace hardening, setup-python CI bump, ytAPI duration-based Shorts filter, and the loopback-hosts follow-up) but were never reflected in the CHANGELOG's 1.4.0 entry. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: veeck <gitkraken@veeck.de> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.