diff --git a/.github/workflows/masto-rss.yml b/.github/workflows/masto-rss.yml index 4b2e886..c65e441 100644 --- a/.github/workflows/masto-rss.yml +++ b/.github/workflows/masto-rss.yml @@ -5,16 +5,55 @@ on: branches: [ main ] pull_request: branches: [ main ] - + jobs: build: - runs-on: ubuntu-latest + permissions: + contents: read + packages: write steps: - - uses: actions/checkout@v1 - - name: Build & Push Image - run: | - echo "${{ secrets.DH_PASSWORD }}" | docker login -u "amitserper" --password-stdin - docker build -t amitserper/masto-rss . - docker push amitserper/masto-rss + - uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: amitserper + password: ${{ secrets.DH_PASSWORD }} + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: | + amitserper/masto-rss + ghcr.io/aserper/masto-rss + tags: | + type=ref,event=branch + type=sha,prefix={{branch}}- + type=raw,value=latest,enable={{is_default_branch}} + + - name: Build and push multiarch image + uses: docker/build-push-action@v5 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/Dockerfile b/Dockerfile index 324712f..c10a348 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,14 +4,15 @@ FROM alpine:3.18 # Set the working directory inside the container WORKDIR /app -# Copy the entire current directory into the container at /app +# Install Python dependencies in a single layer +RUN apk add --no-cache python3 py3-pip + +# Copy requirements first for better layer caching +COPY requirements.txt /app/ +RUN pip install --no-cache-dir -r requirements.txt + +# Copy the application code COPY . /app -# Install any Python dependencies - -RUN apk add python3 -RUN apk add py3-pip -RUN pip install -r requirements.txt - # Run Python script CMD ["python", "main.py"] diff --git a/README.md b/README.md index 9c4c8e6..ed6e3c0 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,216 @@ -# Masto-rss +![Masto-RSS Header](header.jpg) -A simple Mastodon bot written in python that posts updates from an RSS feed to a Mastodon account. -This project is meant to be built to a docker container, so all of the options need to be set as environment variables: +# Masto-RSS -MASTODON_CLIENT_ID = Mastodon client ID +[![Build Status](https://img.shields.io/github/actions/workflow/status/aserper/masto-rss/masto-rss.yml?style=for-the-badge&logo=github&label=Build)](https://github.com/aserper/masto-rss/actions/workflows/masto-rss.yml) +[![Docker Hub](https://img.shields.io/badge/docker%20hub-amitserper%2Fmasto--rss-blue?style=for-the-badge&logo=docker&logoColor=white)](https://hub.docker.com/r/amitserper/masto-rss) +[![GHCR](https://img.shields.io/badge/ghcr.io-masto--rss-blue?style=for-the-badge&logo=docker&logoColor=white)](https://github.com/aserper/masto-rss/pkgs/container/masto-rss) +[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-yellow.svg?style=for-the-badge)](LICENSE) +[![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg?style=for-the-badge&logo=python&logoColor=white)](https://www.python.org/downloads/) +[![GitHub stars](https://img.shields.io/github/stars/aserper/masto-rss.svg?style=social)](https://github.com/aserper/masto-rss) -MASTODON_CLIENT_SECRET = Mastodon client secret +A simple, lightweight Mastodon bot that automatically posts updates from RSS feeds to the Fediverse. Built with Python and designed to run seamlessly in Docker with multiarch support (amd64 & arm64). -MASTODON_ACCESS_TOKEN = Mastodon access token +## Features -MASTODON_INSTANCE_URL = Mastodon instance URL +- Automatically monitors RSS/Atom feeds and posts new items to Mastodon +- Persistent state tracking to avoid duplicate posts +- Configurable post visibility (public, unlisted, private, direct) +- Lightweight Alpine-based Docker image +- Multiarch support (amd64 & arm64) for broad compatibility +- Continuous monitoring with configurable check intervals -RSS_FEED_URL = URL of RSS/xml feed +## Quick Start -TOOT_VISIBILITY = 'public', 'unlisted', 'private', or 'direct' +### Using Docker (Recommended) -The best way to use this project is by using [its docker container](https://hub.docker.com/r/amitserper/masto-rss) -When using docker, make a bind mount between /state on the container to whatever directory you want on your machine in order to keep the state of the feeds that were already posted -![image](https://github.com/aserper/masto-rss/actions/workflows/masto-rss.yml/badge.svg) +The easiest way to run Masto-RSS is using the pre-built multiarch Docker images available on both Docker Hub and GitHub Container Registry. + +#### Pull from Docker Hub + +```bash +docker pull amitserper/masto-rss:latest +``` + +#### Pull from GitHub Container Registry + +```bash +docker pull ghcr.io/aserper/masto-rss:latest +``` + +#### Run the Bot + +```bash +docker run -d \ + --name masto-rss-bot \ + -e MASTODON_CLIENT_ID="your_client_id" \ + -e MASTODON_CLIENT_SECRET="your_client_secret" \ + -e MASTODON_ACCESS_TOKEN="your_access_token" \ + -e MASTODON_INSTANCE_URL="https://mastodon.social" \ + -e RSS_FEED_URL="https://example.com/feed.xml" \ + -e TOOT_VISIBILITY="public" \ + -e CHECK_INTERVAL="300" \ + -v /path/to/state:/state \ + amitserper/masto-rss:latest +``` + +> **Important:** Use a bind mount for `/state` to persist the list of processed feed items across container restarts. + +### Using Docker Compose + +Create a `docker-compose.yml`: + +```yaml +version: '3.8' + +services: + masto-rss: + image: amitserper/masto-rss:latest + # Or use GHCR: ghcr.io/aserper/masto-rss:latest + container_name: masto-rss-bot + restart: unless-stopped + environment: + MASTODON_CLIENT_ID: "your_client_id" + MASTODON_CLIENT_SECRET: "your_client_secret" + MASTODON_ACCESS_TOKEN: "your_access_token" + MASTODON_INSTANCE_URL: "https://mastodon.social" + RSS_FEED_URL: "https://example.com/feed.xml" + TOOT_VISIBILITY: "public" + CHECK_INTERVAL: "300" + volumes: + - ./state:/state +``` + +Then run: + +```bash +docker-compose up -d +``` + +## Configuration + +All configuration is done via environment variables: + +| Variable | Description | Required | Example | +|----------|-------------|----------|---------| +| `MASTODON_CLIENT_ID` | Mastodon application client ID | Yes | `abc123...` | +| `MASTODON_CLIENT_SECRET` | Mastodon application client secret | Yes | `xyz789...` | +| `MASTODON_ACCESS_TOKEN` | Mastodon access token | Yes | `token123...` | +| `MASTODON_INSTANCE_URL` | URL of your Mastodon instance | Yes | `https://mastodon.social` | +| `RSS_FEED_URL` | URL of the RSS/Atom feed to monitor | Yes | `https://example.com/feed.xml` | +| `TOOT_VISIBILITY` | Post visibility level | Yes | `public`, `unlisted`, `private`, or `direct` | +| `CHECK_INTERVAL` | Seconds between feed checks | Yes | `300` (5 minutes) | + +### Getting Mastodon API Credentials + +1. Log into your Mastodon instance +2. Go to **Settings** → **Development** → **New Application** +3. Give it a name (e.g., "RSS Bot") +4. Set scopes to `write:statuses` +5. Save and copy the client ID, client secret, and access token + +## Building from Source + +### Build Locally + +```bash +git clone https://github.com/aserper/masto-rss.git +cd masto-rss +docker build -t masto-rss . +``` + +### Build Multiarch Images + +```bash +# Set up buildx +docker buildx create --use + +# Build for both architectures +docker buildx build \ + --platform linux/amd64,linux/arm64 \ + -t yourusername/masto-rss:latest \ + --push \ + . +``` + +## Running Without Docker + +If you prefer to run the bot directly with Python: + +```bash +# Clone the repository +git clone https://github.com/aserper/masto-rss.git +cd masto-rss + +# Install dependencies +pip install -r requirements.txt + +# Set environment variables +export MASTODON_CLIENT_ID="your_client_id" +export MASTODON_CLIENT_SECRET="your_client_secret" +export MASTODON_ACCESS_TOKEN="your_access_token" +export MASTODON_INSTANCE_URL="https://mastodon.social" +export RSS_FEED_URL="https://example.com/feed.xml" +export TOOT_VISIBILITY="public" +export CHECK_INTERVAL="300" + +# Run the bot +python main.py +``` + +> **Note:** When running without Docker, the bot stores its state in `/state/processed_entries.txt`. Make sure this directory exists or modify [main.py](main.py#L15) to use a different path. + +## How It Works + +1. The bot fetches the RSS feed at regular intervals (defined by `CHECK_INTERVAL`) +2. For each feed item, it checks if the item's URL has been processed before +3. If the item is new, it posts to Mastodon with the format: `{title}\n\n{link}` +4. The item URL is saved to prevent duplicate posts +5. The process repeats indefinitely + +## Architecture + +- **Base Image:** Alpine Linux 3.18 (minimal footprint) +- **Python Version:** 3.10+ +- **Platforms:** linux/amd64, linux/arm64 +- **Dependencies:** feedparser, mastodon.py (see [requirements.txt](requirements.txt)) + +## State Persistence + +The bot maintains state in `/state/processed_entries.txt` to track which feed items have already been posted. This prevents duplicate posts across restarts. + +**Important:** Always mount `/state` as a volume to preserve this state file. + +## CI/CD + +The project uses GitHub Actions for automated multiarch builds and deployments: + +- Builds on every push to `main` +- Creates images for both amd64 and arm64 architectures +- Automatically pushes to Docker Hub and GitHub Container Registry +- Uses Docker layer caching for faster builds + +See [.github/workflows/masto-rss.yml](.github/workflows/masto-rss.yml) for the full pipeline. + +## Contributing + +Contributions are welcome! Feel free to: + +- Report bugs by opening an issue +- Submit pull requests for improvements +- Suggest new features or enhancements + +## License + +This project is licensed under the GNU General Public License v3.0 - see the [LICENSE](LICENSE) file for details. + +## Support + +If you find this project useful, please consider giving it a star on GitHub! + +## Links + +- [Docker Hub Repository](https://hub.docker.com/r/amitserper/masto-rss) +- [GitHub Container Registry](https://github.com/aserper/masto-rss/pkgs/container/masto-rss) +- [Source Code](https://github.com/aserper/masto-rss) +- [Issues](https://github.com/aserper/masto-rss/issues) diff --git a/header.jpg b/header.jpg new file mode 100644 index 0000000..c5d4edd Binary files /dev/null and b/header.jpg differ