apply_schedule() was the actual behavioral fix in the prior commit but
shipped without tests. Six new pytest cases now cover its five+ state
combinations:
- manual mode with an existing crontab + live crond pid -> crontab
removed, SIGHUP sent
- manual mode with no crontab and no pid file -> no-op (function
returns cleanly)
- real schedule with a live pid -> crontab written, SIGHUP sent
- real schedule with a dead pid (ProcessLookupError) -> crontab written,
fresh crond spawned, new pid file
- real schedule with no pid file (e.g. container started in manual
mode, user enables a schedule via UI) -> crontab written, fresh
crond spawned
- real schedule with a garbage pid file (non-integer contents) ->
ValueError caught, fresh crond spawned
os.kill and subprocess.Popen are mocked so no real signals fire and no
real processes spawn during tests. CRONTAB_FILE / CROND_PID_FILE are
redirected to tmp paths via monkeypatch.
.gitignore: add __pycache__/, *.pyc, and .pytest_cache/ to prevent
future contributors from accidentally committing test artifacts.
Settings page saved to /config/settings.json but nothing downstream read
that file. Schedule changes were silently ignored; max_quality and
sleep_interval changes were silently ignored. The "Settings saved
successfully" flash was a lie.
Fix:
- sync.sh reads max_quality + sleep_interval from settings.json on each
run (jq -er ... // empty, falling back to env vars on missing/malformed
file)
- entrypoint.sh reads sync_schedule from settings.json before setting up
cron, and writes the crond PID to /var/run/crond.pid so Flask can
SIGHUP it
- app.py adds apply_schedule(): rewrites /etc/crontabs/root, signals
crond via the recorded PID, restarts crond if the PID is stale, drops
the crontab when schedule is set to "manual". save_settings_route
invokes it only when the schedule actually changed; any failure
flashes a warning so the save still succeeds with the user informed
- bare `except: pass` in get_settings replaced with explicit exception
types + stderr warning so debugging malformed settings is possible
- sync.sh: one bad channel no longer aborts the whole loop under set -e
- Dockerfile adds jq for the JSON reads in sync.sh / entrypoint.sh
- README: two stale github.com URLs fixed to Gitea; new Running Tests
section under Building From Source
- tests/test_settings.py: 3 pytest cases covering get_settings()'s
three branches (missing file, valid file, malformed JSON)
Settings hierarchy unchanged: env-var defaults seed the UI; settings.json
wins when present and parseable.
Timezone (TZ) is not applied live - tzdata is locked in at process start.
Same behavior as before; not in scope for this commit.
- Updated extract_channel_id() to use --flat-playlist instead of --playlist-items 0
- Append /videos to URLs to ensure playlist metadata is available
- Extract playlist_channel_id and playlist_channel from flat-playlist output
- Increased timeout to 15 seconds for network fetch
- All URL formats now working: direct IDs, /channel/ URLs, @handle URLs
Tested and verified in Docker container.
- Accept YouTube URLs in any format (@handle, /c/, /user/, /channel/)
- Auto-extract channel ID from URLs using yt-dlp
- Auto-detect channel name from URL (optional field)
- Support direct channel ID input (backwards compatible)
- Prevent duplicate channels
- Updated UI with better instructions and examples
- Improved user experience - just paste channel URL
- Flask-based web interface on port 8080
- Dashboard with channel statistics and sync status
- Channel management (add/remove channels via UI)
- Settings page for all configuration options
- Cookie file upload interface
- Real-time log viewer
- Manual sync trigger from web UI
- Updated Dockerfile to include Flask and web assets
- Updated Unraid template with WebUI port
- Updated README with web UI documentation