clean: remove Hermes cron artifacts, simplify for EasyPanel deploy
Some checks failed
Update Hugging Face Papers RSS Feed / update-feed (push) Has been cancelled
Update Hugging Face Papers Weekly RSS Feed / update-feed-weekly (push) Has been cancelled
Update Hugging Face Papers Monthly RSS Feed / update-feed-monthly (push) Has been cancelled

This commit is contained in:
2026-06-27 14:51:24 +07:00
parent 3ad39cb87d
commit b86665b7ff
4 changed files with 106 additions and 111 deletions

4
.gitignore vendored
View File

@@ -3,7 +3,3 @@ __pycache__/
*.py[cod] *.py[cod]
.venv/ .venv/
venv/ venv/
# Local test output
feeds/
test_*/

View File

@@ -1,47 +1,38 @@
# ───────────────────────────────────────────── # ─────────────────────────────────────────────
# HuggingFace Daily Paper RSS — Dockerfile # HuggingFace Daily Paper RSS — EasyPanel-ready Dockerfile
# ───────────────────────────────────────────── # ─────────────────────────────────────────────
# Two modes: # Single container that:
# 1. Run-once (default, for cron): # 1. Generates RSS feeds on startup
# docker run --rm -v ./feeds:/data hf-paper-rss # 2. Serves them via HTTP (port 8080, CORS enabled)
# 2. HTTP server (serve feeds): # 3. Auto-refreshes every 24h (background thread)
# docker run --rm -p 8080:8080 -v ./feeds:/data hf-paper-rss serve #
# Deploy on EasyPanel: health check + persistent volume
# ───────────────────────────────────────────── # ─────────────────────────────────────────────
FROM python:3.11-slim AS base FROM python:3.11-slim
WORKDIR /app WORKDIR /app
# Install system deps # System deps
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \ ca-certificates \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Install Python deps # Python deps
COPY requirements.txt . COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt RUN pip install --no-cache-dir -r requirements.txt
# Copy app code # App code
COPY parser.py rss_generator.py run.py run.sh ./ COPY parser.py rss_generator.py run.py server.py ./
# Create output directory # Volume for persistent feed output
RUN mkdir -p /data && chmod 777 /data VOLUME ["/data"]
# Health check # Health check — verify feed.xml exists (generated on startup)
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ HEALTHCHECK --interval=60s --timeout=10s --start-period=30s --retries=3 \
CMD python -c "import sys; sys.exit(0 if __import__('os').path.exists('/data/feed.xml') else 1)" \ CMD python -c "import os; exit(0 if os.path.exists('/data/feed.xml') else 1)"
|| true # Allow healthcheck to be optional
ENTRYPOINT ["python", "run.py"]
CMD ["--output-dir", "/data"]
# ─── Serve mode ───
FROM base AS serve
COPY server.py .
EXPOSE 8080 EXPOSE 8080
ENTRYPOINT ["python", "server.py"] ENTRYPOINT ["python", "server.py"]
CMD ["--dir", "/data", "--port", "8080"] CMD ["--dir", "/data", "--port", "8080", "--refresh", "24"]

View File

@@ -1,49 +0,0 @@
#!/bin/bash
set -euo pipefail
# ─────────────────────────────────────────────
# cron_update.sh — run daily via cron
# ─────────────────────────────────────────────
# 1. Generate RSS feeds
# 2. Commit & push updated feeds to Gitea
#
# Designed for Hermes cron or system cron.
# ─────────────────────────────────────────────
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$REPO_DIR"
# Setup Python
PYTHON=$(command -v python3 || command -v python)
# Try venv first, fallback to system python
if [[ -f .venv/bin/python ]]; then
PYTHON=".venv/bin/python"
fi
# Verify deps installed
echo "[cron] Using: $PYTHON ($($PYTHON --version))"
# Generate feeds (save in repo root — Gitea raw URL serves directly)
echo "[cron] Generating RSS feeds..."
$PYTHON run.py --output-dir "$REPO_DIR"
# Git commit & push if feed files changed
cd "$REPO_DIR"
CHANGED=false
for f in feed.xml feed_weekly.xml feed_monthly.xml; do
if [[ -f "$f" ]]; then
git add "$f" && CHANGED=true
fi
done
if $CHANGED && ! git diff --cached --quiet; then
echo "[cron] Changes detected — committing and pushing..."
git commit -m "bot: update RSS feeds $(date '+%Y-%m-%d')"
git push origin main
echo "[cron] ✅ Pushed updated feeds to Gitea."
else
echo "[cron] No changes to feeds."
fi
echo "[cron] Done."

117
server.py
View File

@@ -1,26 +1,80 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Minimal HTTP server to serve generated RSS feed files. RSS Feed HTTP Server generates feeds on startup + serves them via HTTP.
For EasyPanel deployment: single entrypoint that handles everything.
Usage: Usage:
python server.py # port 8080, dir ./feeds python server.py # port 8080, dir /data
python server.py --dir /data --port 8080 python server.py --dir /data --port 8080
python server.py --dir /data --port 8080 --refresh 24
""" """
import argparse import argparse
import http.server
import os
import sys
import datetime import datetime
import http.server
import logging
import os
import subprocess
import sys
import threading
import time
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)
logger = logging.getLogger("hf-rss")
# ── Feed generation ──
def generate_feeds(output_dir: str):
"""Run run.py to generate RSS feeds."""
script_dir = os.path.dirname(os.path.abspath(__file__))
run_py = os.path.join(script_dir, "run.py")
python = sys.executable
logger.info("Generating RSS feeds...")
try:
result = subprocess.run(
[python, run_py, "--output-dir", output_dir],
capture_output=True, text=True, timeout=60,
cwd=script_dir,
)
if result.returncode == 0:
logger.info(f"Feeds generated successfully in {output_dir}")
for f in sorted(os.listdir(output_dir)):
if f.endswith(".xml"):
fpath = os.path.join(output_dir, f)
size = os.path.getsize(fpath)
logger.info(f" {f} ({size:,} bytes)")
else:
logger.error(f"Feed generation failed (exit {result.returncode})")
logger.error(result.stderr[:500] if result.stderr else result.stdout[:500])
except Exception as e:
logger.error(f"Feed generation error: {e}")
def background_refresh(output_dir: str, interval_hours: int):
"""Regenerate feeds periodically."""
if interval_hours <= 0:
return
interval_seconds = interval_hours * 3600
logger.info(f"Background refresh every {interval_hours}h (next in {interval_hours}h)")
while True:
time.sleep(interval_seconds)
generate_feeds(output_dir)
# ── HTTP Server ──
class RSSFeedHandler(http.server.SimpleHTTPRequestHandler): class RSSFeedHandler(http.server.SimpleHTTPRequestHandler):
"""Custom handler with CORS headers and auto-refresh.""" """Custom handler with CORS headers."""
def __init__(self, *args, directory=None, **kwargs): def __init__(self, *args, directory=None, **kwargs):
super().__init__(*args, directory=directory, **kwargs) super().__init__(*args, directory=directory, **kwargs)
def end_headers(self): def end_headers(self):
# CORS — allow RSS readers to fetch from any origin
self.send_header("Access-Control-Allow-Origin", "*") self.send_header("Access-Control-Allow-Origin", "*")
self.send_header("Access-Control-Allow-Methods", "GET, OPTIONS") self.send_header("Access-Control-Allow-Methods", "GET, OPTIONS")
self.send_header("Cache-Control", "no-cache, no-store, must-revalidate") self.send_header("Cache-Control", "no-cache, no-store, must-revalidate")
@@ -32,49 +86,52 @@ class RSSFeedHandler(http.server.SimpleHTTPRequestHandler):
self.send_response(204) self.send_response(204)
self.end_headers() self.end_headers()
def log_message(self, format, *args):
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
sys.stderr.write(f"[{timestamp}] {args[0]} {args[1]} {args[2]}\n")
# ── Main ──
def main(): def main():
parser = argparse.ArgumentParser(description="RSS Feed HTTP Server") parser = argparse.ArgumentParser(description="HF Daily Paper RSS HTTP Server")
parser.add_argument("--dir", default=os.path.join(os.getcwd(), "feeds"), parser.add_argument("--dir", default="/data",
help="Directory containing RSS feeds") help="Feed output directory (default: /data)")
parser.add_argument("--port", type=int, default=8080, parser.add_argument("--port", type=int, default=8080,
help="Port to serve on") help="HTTP server port (default: 8080)")
parser.add_argument("--host", default="0.0.0.0", parser.add_argument("--host", default="0.0.0.0",
help="Host to bind to") help="Bind address (default: 0.0.0.0)")
parser.add_argument("--refresh", type=int, default=24,
help="Auto-refresh interval in hours (0=disabled, default: 24)")
args = parser.parse_args() args = parser.parse_args()
feed_dir = os.path.abspath(args.dir) feed_dir = os.path.abspath(args.dir)
if not os.path.exists(feed_dir):
print(f"[server.py] Creating feed directory: {feed_dir}")
os.makedirs(feed_dir, exist_ok=True) os.makedirs(feed_dir, exist_ok=True)
# Ensure at least one feed exists # Generate feeds on startup
feeds = sorted(f for f in os.listdir(feed_dir) if f.endswith(".xml")) generate_feeds(feed_dir)
if feeds:
print(f"[server.py] Found {len(feeds)} feed(s): {', '.join(feeds)}")
else:
print(f"[server.py] No feeds yet in {feed_dir}. Run 'run.py' first or wait for cron.")
# Start background refresh thread
if args.refresh > 0:
t = threading.Thread(
target=background_refresh,
args=(feed_dir, args.refresh),
daemon=True,
)
t.start()
# Start HTTP server
class Handler(RSSFeedHandler): class Handler(RSSFeedHandler):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, directory=feed_dir, **kwargs) super().__init__(*args, directory=feed_dir, **kwargs)
server = http.server.HTTPServer((args.host, args.port), Handler) server = http.server.HTTPServer((args.host, args.port), Handler)
print(f"[server.py] Serving RSS feeds from {feed_dir}") logger.info(f"Serving RSS feeds from {feed_dir}")
print(f"[server.py] Listening on http://{args.host}:{args.port}") logger.info(f"Listening on http://{args.host}:{args.port}")
print(f"[server.py] Feed URLs:") logger.info(f" GET /feed.xml (daily)")
print(f" http://localhost:{args.port}/feed.xml (daily)") logger.info(f" GET /feed_weekly.xml (weekly, Mondays)")
print(f" http://localhost:{args.port}/feed_weekly.xml (weekly)") logger.info(f" GET /feed_monthly.xml (monthly, 1st)")
print(f" http://localhost:{args.port}/feed_monthly.xml (monthly)")
try: try:
server.serve_forever() server.serve_forever()
except KeyboardInterrupt: except KeyboardInterrupt:
print("\n[server.py] Shutting down...") logger.info("Shutting down...")
server.server_close() server.server_close()