91 lines
3.1 KiB
Markdown
91 lines
3.1 KiB
Markdown
# The Meme Protocol
|
|
|
|
Small self-hosted meme gallery matching the `stitch_the_meme_protocol` desktop mockup direction.
|
|
|
|
## Run locally
|
|
|
|
```sh
|
|
npm start
|
|
```
|
|
|
|
The server listens on `http://localhost:8080` by default.
|
|
|
|
## Configuration
|
|
|
|
- `PORT`: HTTP port, default `8080`
|
|
- `HOST`: bind address, default `0.0.0.0`
|
|
- `DATA_DIR`: disk storage root, default `./data`
|
|
- `SITE_URL`: public canonical site URL used for SEO metadata, sitemaps, feeds, and `llms.txt`
|
|
- `SEED_DEMO_MEMES`: set to `false` to disable generated demo memes on first boot
|
|
- `ADMIN_TOKEN`: secret review URL token. If omitted, one is generated at boot and printed in server logs.
|
|
- `OPENAI_API_KEY`: enables AI upload moderation. Without it, uploads are queued for admin review.
|
|
- `OPENAI_MODERATION_MODEL`: moderation vision model, default `gpt-4o-mini`
|
|
- `TRUST_PROXY`: set to `true` when running behind a trusted reverse proxy so upload limits use `X-Forwarded-For`
|
|
|
|
Uploads accept only square PNG and JPEG images. The server rejects files over 5 MB, images over `6000x6000`, and images over 20 million pixels. Accepted uploads are decoded, metadata-stripped, resized down to `1600x1600` if needed, and stored as WebP.
|
|
Upload caps are 5 per hour per IP, 10 per day per IP, and 100 globally per day. AI-approved uploads publish immediately; ambiguous uploads are queued for the secret admin review page; likely illegal uploads are rejected immediately.
|
|
|
|
Files are stored under sharded date/hash paths:
|
|
|
|
```text
|
|
data/
|
|
index/memes.jsonl
|
|
memes/YYYY/MM/DD/aa/bb/<sha256>.<ext>
|
|
meta/YYYY/MM/DD/aa/bb/<sha256>.json
|
|
```
|
|
|
|
## Discovery Metadata
|
|
|
|
The app serves crawler and answer-engine metadata without adding visible page copy:
|
|
|
|
- `/robots.txt`
|
|
- `/sitemap.xml` with approved meme image entries
|
|
- `/feed.json`
|
|
- `/llms.txt`
|
|
- `/site.webmanifest`
|
|
- Open Graph, Twitter card, canonical, and JSON-LD metadata on `/`
|
|
|
|
Set `SITE_URL` in production so canonical URLs use the public domain instead of an internal proxy hostname.
|
|
|
|
## Docker
|
|
|
|
```sh
|
|
docker build -t meme-protocol .
|
|
docker run --rm -p 8080:8080 -v meme-protocol-data:/data meme-protocol
|
|
```
|
|
|
|
For production, copy `.env.example` to `.env`, set real secrets, then run:
|
|
|
|
```sh
|
|
docker compose up -d --build
|
|
```
|
|
|
|
The included compose file binds the app to `127.0.0.1:18080` on the host so a reverse proxy can publish it without exposing the Node container directly.
|
|
|
|
### Production Build Note
|
|
|
|
On the production host used for this project, npm registry downloads from inside Docker timed out unless `registry.npmjs.org` was pinned to a known-good IPv4 address during build. The proven build command is:
|
|
|
|
```sh
|
|
sudo docker build \
|
|
--network=host \
|
|
--add-host registry.npmjs.org:104.16.1.34 \
|
|
-t meme-protocol:latest .
|
|
```
|
|
|
|
Then start with the already-built image:
|
|
|
|
```sh
|
|
sudo docker compose up -d --no-build
|
|
```
|
|
|
|
If the registry IP ever stops working, resolve and test another IPv4 address for `registry.npmjs.org`, then replace `104.16.1.34` in the build command.
|
|
|
|
When using a host-mounted data directory, the container writes as UID/GID `10001:10001`:
|
|
|
|
```sh
|
|
sudo mkdir -p ./data
|
|
sudo chown -R 10001:10001 ./data
|
|
sudo chmod -R u+rwX,g+rwX,o-rwx ./data
|
|
```
|