# PiFeed

PiFeed is a single-file microblogging server and RSS aggregator built with Python, Bottle, and SQLite. The entire application (routes, templates, static rendering, database models, and background feed processing) lives in `pifeed.py`.

## Features

- **Microblogging and Threading:** Markdown-style formatting, threaded replies, emoji reactions, user mentions (`@user`), and customizable greeting cards with photo attachments.
- **Circles:** Group and topic feeds where posts can be directed to specific circles or kept on the main timeline.
- **Feed Aggregation:** Subscribe to external RSS and Atom feeds. Items appear in dedicated feed views or merged into your timeline, complete with embeds for YouTube, XKCD, and asciinema.
- **Media and Document Lightbox:** Attach images, PDFs, and HTML archives. Media files open in a responsive lightbox, and uploaded HTML archives are served in an isolated iframe sandbox.
- **Podcasts:** Enclosure detection, inline audio streaming, and local episode downloading.
- **Full RSS Compatibility:** Every timeline (public, user profile, circle, and private home feed) generates standard Atom XML feeds for RSS readers.
- **Flexible Access:** Admin-controlled user creation and single-token invite links with conflict detection and one-time password generation.
- **No Build Pipelines:** Vanilla JavaScript, pure CSS toggles, and zero frontend toolchains.

## Architecture and Runtime Modes

PiFeed stores everything in a local SQLite database (`pifeed.db`) and local directories for file uploads and static cache.

It supports two operational modes:

1. **Standalone Server:** Run directly from the terminal or managed by systemd/supervisor. In server mode, PiFeed uses a multithreaded WSGI server and starts an internal background thread that refreshes external feeds every 15 minutes.
2. **CGI Application:** Run behind Apache, lighttpd, or BusyBox httpd. When `GATEWAY_INTERFACE` is present in the environment, PiFeed processes the incoming request and exits immediately. Feed fetches in CGI mode are scheduled via cron.

## Building

PiFeed can run straight from `pifeed.py` with dependencies installed, or packaged into a deployment artifact using `./deps.sh`.

Prerequisites: Python 3 and [`uv`](https://docs.astral.sh/uv/).

Run `./deps.sh` to run tests and compile artifacts:

```bash
./deps.sh          # Runs audit, check, test, zipapp, and nuitka
```

### Option 1: Python Zipapp (`pifeed.pyz`)

A standalone zipapp bundles application code and locked dependencies into an executable zip archive that runs on any system with a compatible Python 3 interpreter:

```bash
./deps.sh zipapp
```

This generates `pifeed.pyz`.

### Option 2: Native Compiled Binary (`pifeed.bin`)

Using Nuitka, PiFeed compiles into a self-contained ELF executable with an embedded Python interpreter. This eliminates interpreter startup overhead, making it well-suited for CGI environments:

```bash
./deps.sh nuitka
```

This generates `pifeed.bin`. Compile on the same OS family and architecture as your deployment server.

## Deployment

### Standalone Server

To run directly on port 3141:

```bash
# Using source
python3 pifeed.py

# Using the zipapp
python3 pifeed.pyz

# Using the compiled binary
./pifeed.bin
```

When started for the first time, PiFeed creates `pifeed.db`, initializes tables, generates an `admin` user, and outputs a one-time password to standard error:

```text
*** BOOTSTRAP: Admin user created. Username: 'admin' | Password: '<generated-password>' ***
```

To run behind a reverse proxy like Nginx or Caddy, proxy traffic to `http://127.0.0.1:3141`.

### CGI Application (Apache)

CGI mode hosts PiFeed on shared hosting or Apache without running a persistent daemon process.

#### Installation

Copy either `pifeed.pyz` or `pifeed.bin` to your web root, rename it to `index.cgi`, and grant execution permissions:

```bash
cp pifeed.bin /var/www/html/index.cgi
chmod +x /var/www/html/index.cgi
```

#### Web Server Configuration (.htaccess)

Place an `.htaccess` file in your document root to route requests into `index.cgi`, protect the database file, and optionally serve static cache files directly:

```apache
Options +ExecCGI -Indexes
AddHandler cgi-script .cgi
DirectoryIndex index.cgi

RewriteEngine On
RewriteBase /

# Static cache shortcuts (serve public feeds directly from disk if cached)
RewriteCond %{HTTP_COOKIE} !session= [NC]
RewriteCond %{QUERY_STRING} ^$
RewriteCond %{DOCUMENT_ROOT}/cache/public.html -f
RewriteRule ^public/?$ cache/public.html [L]

RewriteCond %{HTTP_COOKIE} !session= [NC]
RewriteCond %{QUERY_STRING} ^$
RewriteCond %{DOCUMENT_ROOT}/cache/public.xml -f
RewriteRule ^public\.xml$ cache/public.xml [L]

# Route all non-file requests into index.cgi
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.cgi/$1 [L,QSA]

# Block direct web downloads of the SQLite database
<Files "pifeed.db">
    Require all denied
</Files>

<Files "index.cgi">
    Require all granted
</Files>
```

#### Scheduled Feed Polling (Cron)

Because CGI processes terminate after responding, background threads do not run. Schedule external feed fetching and cache culling every 15 minutes using cron:

```cron
*/15 * * * * /var/www/html/index.cgi --task-fetch >/dev/null 2>&1
```

#### Subdirectory Hosting

When served from a subdirectory (such as `/cgi-bin/pifeed.cgi`), PiFeed detects `SCRIPT_NAME` and rewrites links and redirects automatically. To override detection for reverse-proxy setups, set the `PIFEED_BASE_PATH` environment variable:

```bash
export PIFEED_BASE_PATH=/my-prefix
```

## Configuration

PiFeed is configured through environment variables and the web admin UI:

### Environment Variables

| Variable | Default | Purpose |
| --- | --- | --- |
| `PORT` | `3141` | Server listen port |
| `PIFEED_DB` | `./pifeed.db` | SQLite database file location |
| `PIFEED_UPLOADS` | `./uploads` | Directory for image, PDF, and HTML uploads |
| `PIFEED_CACHE` | `./cache` | Directory for generated static feed caches |
| `PIFEED_PODCASTS` | `./podcasts` | Directory for downloaded podcast episodes |
| `PIFEED_BASE_PATH` | (detected) | URL prefix override when running behind a prefix proxy |
| `PIFEED_ALLOW_LOCAL_REQS` | `0` | Set to `1` to allow RSS fetching from private or loopback IP ranges |

### Admin Interface

Log in as `admin` and visit `/admin` to configure instance settings:
- Toggle public timelines, circles, and RSS aggregation.
- Adjust session durations and feed retention culling rules.
- Set instance-wide themes and visitor Message of the Day (MOTD).
- Generate, copy, regenerate, or delete private registration invite links.
- Reset user passwords and rotate RSS security tokens.

If you ever lose the admin password, run:

```bash
python3 pifeed.py --reset-admin
```

This outputs a new temporary password directly to the console.

## Development and Testing

Run unit and integration tests using pytest:

```bash
pytest test_pifeed.py pifeed_tests.py
```

Tests run against isolated temporary SQLite databases and mock upload directories without modifying your production data.
