Skip to content

Latest commit

 

History

325 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mempaper - Bitcoin Meme Block Clock

mempaper

A Bitcoin block clock for E-Ink displays.
Real-time network data, miner stats, and curated memes from the Einundzwanzig community in one framed screen.

What is this?  •  Technical Stuff  •  Gallery  •  Getting Started  •  Configuration  •  Maintenance  •  Architecture  •  Documentation  •  Support



WHAT IS THIS?

mempaper is a Bitcoin block clock on E-Ink - but with style! Instead of boring blockchain monitoring, it shows the best Bitcoin memes from the Einundzwanzig community alongside real-time network data, miner stats, and wallet balances.

It connects to your local (or public) mempool instance to visualize the Bitcoin network status, mine data from your Bitaxe, and display Bitcoin memes on a beautiful e-Paper display mounted in a picture frame.




EINUNDZWANZIG MEMES - THE SOURCE OF FUN

The memes on this display come from einundzwanzig-memes.space - a community project collecting the best Bitcoin memes from the German-speaking Bitcoin scene, and a big shoutout and thank you to them for building and maintaining this awesome project. The broader Einundzwanzig community is a major inspiration.

Disclaimer: This mempaper app is an independent project and has no connection or affiliation with the Einundzwanzig association.




TECHNICAL STUFF

DISPLAY
7.3" Waveshare E-Ink (7-color)
13.3" Waveshare E-Ink Spectra 6
RESOLUTION
800x480 px (7.3")
1600x1200 px (13.3")
HARDWARE
Raspberry Pi Zero 1 WH / 2 WH
SOFTWARE
Python backend (Flask + Jinja2, Flask-SocketIO, Gunicorn/gevent, Pillow/numpy)
Web frontend (Vanilla JS + Socket.IO)
POWER USAGE
~1 Watt
DATA SOURCE
mempool.space API — public or
self-hosted mempool on your own node

Features

  • Real-time Data - BTC price, halving countdown, network hashrate, difficulty, fees, and remaining supply via mempool.space or self-hosted mempool instance
  • Hardware Support - Ready for Raspberry Pi (Zero/3/4/5) and Waveshare e-Paper displays (7.3" 7-color, 13.3" 6-color)
  • Web Dashboard - Responsive interface for configuration, monitoring, and live block notifications
  • Miner Integration - Monitor Bitaxe miner stats, aggregate hashrate, best difficulty, and found blocks
  • Wallet Monitoring - Track on-chain balances for addresses, XPUBs, and ZPUBs with automatic address derivation
  • Block Reward Monitoring - Track mining pool payouts and solo mining rewards for specific addresses
  • Lightning Donations - Display incoming Lightning tips via LNbits webhook relay
  • Meme Rotation - Curated Bitcoin memes from the Einundzwanzig community, with custom upload support
  • OPSec Mode - One-click toggle to show a random cover image on the e-ink display instead of Bitcoin data
  • Privacy Controls - Public mempool warnings, wallet cache wipe, User-Agent stripping
  • Security - Argon2id password hashing, rate limiting, encrypted configuration, and basic auth for mempool
  • Auto Updates - Scheduled software and system updates from the web UI
  • Multi-language - English, German, Spanish, French, and Italian
  • WiFi Onboarding - Hotspot-based setup flow for shipped devices, no SSH required



GALLERY

Hardware Setup

Waveshare 7.3" e-Paper (7-color)

7.3 inch Display Dark Mode

7.3" E-Paper Display - Dark Mode

7.3 inch Display Light Mode

7.3" E-Paper Display - Light Mode

Waveshare 13.3" e-Paper (6-color)

13.3 inch Display Dark Mode

13.3" E-Paper Display - Dark Mode

13.3 inch Display Light Mode

13.3" E-Paper Display - Light Mode

Web Interface

Login Screen

Secure login with Argon2id encryption

Dashboard Dark Mode

Dashboard - Dark Mode

Dashboard Light Mode

Dashboard - Light Mode

Settings Page

Configuration Settings

System Logs (Click to expand)

System Logs
Real-time system logs showing WebSocket connections and block updates

SSH Login Banner

SSH Login Banner
Live system stats on every SSH login — block height, mempool status, memes count, service uptime, and display info.




GETTING STARTED

Three ways in — pick the one that describes you:

You are… Go to
Setting up a mempaper someone gave you First-Time Setup — power on, join the hotspot, connect your Wi-Fi. No SSH, no terminal.
Building one yourself from parts Build Your Own — shopping list, one-line installer, display wiring
Running a mempaper already Maintenance — updates, SSH access, admin accounts

Handing a device to someone else? See Preparing a Device for Someone Else.



First-Time Setup (Delivered Device)

This is the flow for a device that arrives with no Wi-Fi configured — one prepared for shipping, or a freshly flashed card. It walks you through Wi-Fi and creating your admin account entirely from your phone; no SSH or technical knowledge is required.

If you just ran the installer yourself, you do not need this. Your Pi is already on Wi-Fi and running — go straight to Configuration.

Step 1 -- Delivery State (E-Ink)

The device ships with the delivery-state image on the e-ink display.

Delivery state e-ink screen

Step 2 -- Setup Hotspot (E-Ink)

On first boot the device detects that no Wi-Fi is configured and starts a WPA2-protected setup hotspot. This takes between 90 seconds and 2 minutes 21 seconds — the Pi has to boot, initialise the Wi-Fi radio, and switch to AP mode. Once ready, the e-ink display refreshes and shows the network name, its password and a QR code.

  • SSID: setup-xxxxxxxxxx, random for each setup session. It carries no brand, so a network scan does not advertise what the device is.
  • Security: WPA2-PSK with a random 24-character password, generated per session and shown only on the panel. Everything the setup page then carries — your home Wi-Fi password, the admin account you create — is encrypted over the air.
  • Scan the first QR code with your phone: it carries the network and its password, so the phone joins without anything being typed. The password is printed underneath for cameras that will not scan.
  • There is no separate portal password. Being on the network is what grants access to the setup page.

Tip: Wait for the display to change from the delivery-state image to the hotspot screen before trying to connect. If nothing has changed after 2 minutes, the hotspot failed to start — power-cycle the device and try again.

Hotspot onboarding e-ink screen

Step 3 -- Wi-Fi Setup Page

Once connected to the hotspot, open http://10.42.0.1:5000/setup — the QR code on the right of the e-ink screen goes to the same address, and captive-portal detection usually opens it by itself. The page asks you to:

  1. Select a language (English, German, Spanish, French, Italian)
  2. Choose your home Wi-Fi from the scanned list, or enter a hidden SSID
  3. Enter the Wi-Fi password
  4. Create an admin account — username and password for the dashboard

WiFi setup web page

Step 4 -- Connected (E-Ink)

Once the device joins your home Wi-Fi, the display shows a success screen telling you how to reach the dashboard from your network.

WiFi connected e-ink screen

After 60 seconds the display switches to normal operation and renders its first dashboard image. From here, see Configuration.

Resetting a Device

Forgotten admin password, or you want to start fresh? There are three ways.

Option A -- Factory Reset in the settings page

When you can still log in, this is the complete one: Settings > General > Advanced > Factory Reset, at the bottom. A confirmation lists exactly what goes, and the device then erases everything, returns the panel to the delivery screen, and powers itself off.

  • All saved Wi-Fi networks
  • All admin accounts and their passwords
  • Wallet addresses, Bitaxe miners, donation history and every cache
  • Every setting, back to its default -- theming, mempool host, schedules
  • Network-bound encryption (Tang): the sealed data is removed before the encryption is turned off, so nothing sensitive is written back in the clear
  • Your uploaded OPSec cover photos, and the meme tags you edited
  • The SSH keys the device manages

The installed software stays, and so does the meme library -- it is shipped content rather than yours, and re-downloading a few thousand files over Tor is not something a reset should commit you to silently.

It powers off rather than rebooting, so the panel keeps the delivery image. Reconnect the power when you want it back: finding no Wi-Fi, it starts the setup hotspot and shows the network and password to join.

Allow around two to three minutes before pulling the plug -- the delivery image has to be rendered and written to the panel first, and interrupting an e-ink refresh is the one thing the display must not suffer. The dialog counts that time down and tells you when it is safe.

Option B -- Reset button on the setup page

If the device is already in hotspot/setup mode (for example its stored Wi-Fi is unavailable), the setup page has a Reset Device button at the bottom. It clears:

  • All admin accounts
  • Wallet addresses and monitoring data
  • Bitaxe miner configuration
  • Donation history and webhook URLs
  • Mempool authentication

The device stays in setup mode so you can reconfigure Wi-Fi and create a new admin account.

Option C -- Power-cycle factory reset

For a full reset including saved Wi-Fi profiles, power-cycle the device three times:

  1. Power on and wait for the e-ink display to refresh — on a Pi Zero this can take up to about 3 minutes 30 seconds. Only then power off.
  2. Repeat twice more. On the third boot the reset triggers automatically; nothing else to press.

The device recognises 3 boot timestamps inside a 15-minute window and then clears all user data, deletes every saved Wi-Fi profile, renders the delivery-state image, and restarts the setup hotspot.

Important: Wait for the e-ink refresh each time before cutting power. That refresh is the device's own confirmation that it finished booting, recorded the timestamp, and flushed writes to the SD card — pulling power earlier risks corrupting the filesystem. Three cycles of ~3:30 still fit comfortably inside the 15-minute window.



Build Your Own

Shopping List

Everything needed to build a mempaper from scratch.

Note: Prices are approximate and vary by region and vendor. The Raspberry Pi Zero 2 W is recommended over the original Zero W for better performance.

Shared components (~63 EUR) — the same whichever display you choose:

Component Description Price Link
Raspberry Pi Zero 1 WH / 2 WH Main controller (512MB RAM, WiFi/BT) ~21 EUR Zero 1 WH | Zero 2 WH
MicroSD Card 32GB or larger, Class 10 recommended ~13 EUR SanDisk Extreme 64GB
USB-C Power Supply 5V/2.5A minimum ~10 EUR
USB-C to Micro-USB Adapter 2-pack adapter for power routing ~4 EUR Amazon
90 deg USB-C Panel Mount Cable Right-angle extension for clean cable routing ~15 EUR Amazon

Cable Routing: The USB-C to Micro-USB adapter and 90 deg panel mount cable allow you to cleanly route power from the Raspberry Pi to the back of the picture frame for a professional finish.

Option A -- Waveshare 7.3" e-Paper (7-color) -- total ~215 EUR

Component Description Price Link
Waveshare 7.3" e-Paper (F) 7-color e-ink display (800x480) ~88 EUR Waveshare | Amazon
Photo Frame 18x24cm frame for display mounting ~47 EUR allesrahmen.de
Passepartout (Mat Board) 180x240mm outer, 94x158mm opening ~17 EUR wandstyle.com

Passepartout Dimensions: The 7.3" display has a visible area of 160x96mm. The passepartout opening is 158x94mm (2mm smaller on each side) to hold the display securely in place.

Option B -- Waveshare 13.3" e-Paper (6-color) -- total ~518 EUR

Component Description Price Link
Waveshare 13.3" e-Paper (E) 6-color Spectra 6 e-ink display (1200x1600) ~362 EUR Waveshare | Amazon
Photo Frame 28x35cm frame for display mounting ~76 EUR allesrahmen.de
Passepartout (Mat Board) 280x350mm outer, 200x268mm opening ~17 EUR wandstyle.com
Assembly Photos (Click to expand)

Components
All components ready for assembly

Wiring
Raspberry Pi Zero W connected to e-Paper display via SPI Control interface

Mounting
Mounting display in photo frame

Complete Setup
Photo frame back with USB-C power connector

Installation

Flash Raspberry Pi OS Lite 32-bit (Trixie / Debian 13), connect the Pi to your Wi-Fi, then paste this single command:

sudo apt install -y git \
&& git clone https://github.com/satcat21/btc-mempaper.git \
&& cd btc-mempaper && bash install.sh

Run it as your normal user (e.g. pi) — not as root; the script uses sudo where it needs to. It asks every configuration question upfront (display type, admin account, Tor, optional security features), then installs without further interruption.

Allow 10–20 minutes on a Pi Zero 1 WH. On ARMv6 the installer compiles gevent and Pillow from source, because no prebuilt wheels exist for that CPU.

When the installer finishes, the device goes straight into normal operation — it is already on your Wi-Fi, so there is no onboarding step. Open http://<pi-ip>:5000 and log in with the admin account you just created.

What the installer does (click to expand)
  • Creates the mempaper service account
  • Installs all system and Python packages
  • Creates a swap file where the device has none, so a source build cannot be killed for want of memory
  • Rebuilds gevent and Pillow from source on ARMv6 (Pi Zero 1 WH) where the piwheels build is incompatible
  • Copies the example config (skipped if config/config.json already exists)
  • Configures the e-ink display (interactive prompt)
  • Generates and installs the mempaper.service systemd unit
  • Sets up Wi-Fi hotspot permissions
  • Disables UFW and nftables — their default chains drop the DHCP broadcasts the setup hotspot depends on
  • Optionally configures fail2ban
  • Starts the service

Supported OS: Raspberry Pi OS Lite 32-bit, Trixie (Debian 13).

Debian 12 (Bookworm) is not supported and not tested. It will still install — you just get less of a guarantee.

apt-requirements.txt pins exact package versions, so that a device installs the combination each release was tested against; several are the revisions carrying Debian's security fixes (…+deb13u1 and similar). Those version strings exist only in the Trixie archive, and apt matches a pinned version exactly. So the file names the suite its pins belong to, and a device running anything else ignores them and installs the same packages unpinned — you get whatever versions that archive currently offers, floating, with nothing held. The installer says so when it happens.

What you lose on Bookworm is the tested combination, not the ability to install. Nothing needs editing by hand. Two caveats worth knowing:

  • tools/python_version expects Python 3.13 on Trixie against Bookworm's 3.11. Bookworm's own entry (bookworm=11) is satisfied by what it ships, so no interpreter rebuild is triggered — but the project is exercised on 3.13.
  • requirements.txt is not codename-bound; those pins come from PyPI and install the same on either. But one may require a newer Python than 3.11, and piwheels builds per Python minor — a package with a prebuilt armhf wheel for 3.13 may have none for 3.11 and will compile from source instead (slow on a Pi Zero, not fatal). A wheel can also be named for a platform it cannot run on: piwheels builds on ARMv8 hardware and publishes the result under linux_armv6l names. python tools/check_wheels.py --platform linux_armv6l --python cp313 --arm-level 6 --suggest reads what is inside each wheel and exits non-zero if anything would be built from source.

To pin a combination for Bookworm yourself, run it, confirm it works, then promote what the device recorded — see docs/MAINTENANCE_GUIDE.md.

Verify it came up:

sudo systemctl status mempaper.service
sudo journalctl -u mempaper.service -f
PC / Windows — development only, no e-ink display
git clone https://github.com/satcat21/btc-mempaper.git
cd btc-mempaper
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt

Display wiring and SPI are handled by the installer. It enables SPI, downloads the Waveshare driver for the model you picked, and disables Wi-Fi power saving on the Pi Zero W. Changing display later is not possible from the web UI — the Settings page shows the configured model read-only. Re-run install.sh, or run the display tool directly:

cd /home/mempaper/btc-mempaper
sudo -u mempaper .venv/bin/python tools/configure_display.py

Prefer to do it by hand, or need to understand what the script touches? Manual Installation lists every command install.sh runs.

Preparing a Device for Someone Else

Only needed if you are handing a mempaper to somebody who should set it up themselves. This resets the device and leaves it in the delivery state:

cd /home/mempaper/btc-mempaper
sudo -u mempaper .venv/bin/python tools/delivery_state.py

What this does:

  • renders a clean delivery image on e-ink
  • leaves startup behavior in integrated mode (mempaper.service only)
  • clears setup-mode state so the next boot starts clean

What the recipient then experiences is the First-Time Setup flow above — worth reading once, since it is what you are handing over:

  • mempaper.service starts on boot and first attempts a normal Wi-Fi connection
  • Finding none after the startup grace period, it brings up the setup-xxxxxxxxxx hotspot
  • The recipient connects, opens http://10.42.0.1:5000, and enters their Wi-Fi credentials
  • On success the hotspot shuts down and normal operation resumes automatically


CONFIGURATION

Navigate to Settings in the web interface at http://<pi-ip>:5000.

  • Mempool Connection -- Default is mempool.space. Change IP/Port to use a local node or self-hosted mempool instance.
  • Display -- Toggle "E-Ink Display Connected" to ON.
  • Bitaxe -- Add miner IPs to monitor hashrate.

For advanced manual configuration, edit config/config.json.

Protecting Wallet Data Against Device Theft

Wallet addresses and xpubs live in a separate 600 file, not encrypted. Any key the device could derive by itself would also be reconstructible by whoever holds the device, so encrypting under one would imply a protection it could not deliver. Physical access to the device means access to this data.

If a stolen device must not give up your addresses and balances, run a Tang server on your LAN — a node, a NAS, or a small Proxmox LXC. mempaper then seals its wallet data with a random 256-bit key held off the device, so carried off your network it cannot be decrypted at all. Tang needs very little: 3.7 MiB RAM idle. Its keypair is P-521 — roughly 256 bits of security, held on that host and never sent to the Pi.

Self-hosted mempool with Tang-sealed storage

If the Tang host is unreachable, mempaper still starts and runs — the wallet and donation blocks are disabled until it returns, then restore themselves automatically.

Enable it before entering your xpubs. Deleted data lingers in freed flash blocks, so sealing later cannot reach addresses already written to the card in clear text.

Setup, docker-compose.yml, LXC sizing and the limits of this approach: Self-Hosting Guide → Tang.

Block Height Color — Is Now a Good Moment?

The block height number is colored by the current fee. Colored against a fixed table, that stops telling you anything: through 2024–2025 fees sat between 0 and 4 sat/vB almost permanently, which is one shade of green from one end of the year to the other.

So the color is anchored to the median fee of the last 30 days instead. The question it answers is not "what is the fee" — the number below already says that — but "is this cheap for right now?", which is the one that decides whether you consolidate UTXOs today or wait.

Which fee gets measured is up to you: Fee Used for the Block Height (fee_parameter), in the Theming section right above the color group, picks the priority tier, and the same tier is both printed under the height and compared against the median. Choose the one you actually transact at. Because the baseline is what blocks really cost, a fast fee sits above it more often and reads warm, while a minimum fee sits below it and reads cool — which is exactly the answer each question deserves.

Fee color scale

Both bars above are the same scale. Only the sat/vB underneath them changes, and that is the whole idea: 3 sat/vB is a bad deal when normal is 2, and a bargain when normal is 20. The same number, opposite advice.

You see It means
Blue Far below normal — as cheap as this network gets. Move coins
Green Comfortably below normal
Your base color Within 5% of the median. Ordinary
Yellow → amber Somewhat above normal
Orange Expensive for the times
Red Several times the going rate. Wait unless it is urgent

The digits carry a vertical gradient, and both ends are fee readings: the previous block at the top, the current one at the bottom. So the number shows not just where fees are but which way they just moved.

What you see What just happened
Both ends cool It was cheap and it still is
Both ends warm It spiked and has stayed there
Cool over warm Fees have just jumped
Warm over cool Fees have just crashed
Two tones of your color Both blocks were ordinary. Nothing to report

Against a 30-day median of 20 sat/vB, on a dark theme:

Move Top (last block) Bottom (this block)
8 → 8 — still cheap #0089E0 blue #72BEED light blue
8 → 40 — spiked #0089E0 blue #FAA376 light orange
40 → 8 — crashed #F75907 orange #72BEED light blue
40 → 40 — still dear #F75907 orange #FAA376 light orange
20 → 20 — ordinary #C8C8D2 your color #E0E0E6 lighter tone of it

An end whose block sat within 5% of the median has nothing to say, so it carries your color instead of a hue that would mean nothing. Two ordinary blocks in a row therefore render the digits as two tones of your color — which is why the default is a neutral grey (#3C3C46 on light, #C8C8D2 on dark) rather than something that competes with the fee hues around it.

Pick it per theme under General → Advanced → Block Height Color & Scale. That panel previews all four cases above in both themes, and its fee colors are computed by the renderer itself, so the preview cannot drift from the panel.

Tone follows the theme, so the bottom — where the fee label sits — is always the readable end:

Theme Top (last block) Bottom (this block)
Dark full value lightened
Light lightened deepened

The ramp spans the digits themselves, cap top to baseline, so both ends are drawn at full strength rather than fading out into the descender space.

Three scales are available from the same panel — neutral-centred (default), a continuous blue-to-red rainbow, or the original fixed thresholds with your own editable limits. On e-ink the same colors are used, and the panel dithers them onto the six or seven inks it has — that blend is what gives the gradient its intermediate and lighter tones. Details and tuning: Config Reference → Block height color scale.

Info Blocks

The dashboard image is composed of a meme and a set of optional info blocks displayed alongside it. Each block can be independently enabled or disabled in Settings. If more blocks are enabled than fit the available space, a random subset is shown each refresh.

Block Config key What it shows
BTC Price show_btc_price_block Current price in fiat and Moscow Time (sats/fiat)
Countdown show_countdown_block Remaining BTC supply and percentage mined
Halving show_halving_block Estimated next halving date and blocks remaining
Network show_network_block Global hashrate and mining difficulty
Bitaxe show_bitaxe_block Aggregate hashrate and found blocks or best difficulty
Wallet Balances show_wallet_balances_block On-chain balances for addresses / XPUBs / ZPUBs
Lightning Donation show_donation_block Latest Lightning donation via LNbits webhook

All blocks are on by default except Bitaxe, Wallet Balances, and Donation, which require additional setup.

See Configuration Reference for every setting in detail.




MAINTENANCE

Everything for running a mempaper day to day, whether you built it, were given it, or look after several.

Service control — over SSH:

sudo systemctl status mempaper.service       # is it running?
sudo journalctl -u mempaper.service -f       # live logs
sudo systemctl restart mempaper.service      # restart after manual config edits

For OS-level upkeep — safe apt upgrades and Python version changes — see the Maintenance Guide.

SSH Admin Access

Each admin generates an SSH key pair once on their own machine, then adds the public key through the web UI (Settings → General → Advanced → SSH Access). It gets installed for both the mempaper account (scoped sudo) and pi (full sudo).

Full procedure, including disabling password login: Security Guide → SSH keys and password login.

Admin Users

Multiple admin users are supported. Users are stored as Argon2id hashes in config/config.json under the admin_users key.

cd /home/mempaper/btc-mempaper
sudo -u mempaper .venv/bin/python tools/setup_user.py                # create or update
sudo -u mempaper .venv/bin/python tools/setup_user.py --list         # list users
sudo -u mempaper .venv/bin/python tools/setup_user.py --delete alice # delete a user

Use the venv interpreter and the mempaper user, as above. A bare python misses the dependencies, and running as pi writes a root-owned config the service cannot read.

The script refuses to delete the last remaining user to prevent lockout.

The script can be run while the service is running -- the application picks up the config change automatically. For password resets it is safer to stop the service first: sudo systemctl stop mempaper.

Existing installations are migrated automatically on first startup: the single admin_username / admin_password_hash fields in the config are moved into the admin_users dict -- no manual action required.

Software Update

mempaper can be updated directly from the web UI. Navigate to Settings > Updates to see the current version and available releases.

Software Update Section
Software Update section in Settings

Web UI Update (Recommended)

  1. Open the Software Updates section in Settings
  2. Select the desired release from the dropdown (latest is pre-selected and highlighted in orange)
  3. Click Update and confirm
  4. The app will fetch the release, install dependencies, and restart the service
  5. If the e-ink display is currently refreshing, the restart waits until the display is idle
  6. The page refreshes automatically once the service is back online

Automatic Updates

Enable scheduled updates to keep mempaper up to date without manual intervention:

Setting Config Key Description
Automatic Updates auto_update_enabled Enable/disable scheduled updates
Update Time auto_update_time Time of day to check for updates (HH:MM, default: 03:00)
Update Days auto_update_days Days of the week to run updates (default: Mon, Wed, Fri)

When enabled, mempaper checks for new releases at the configured time and day, installs the update, and restarts the service automatically.

Manual Update via SSH

The app lives in the service account's home directory and every file is owned by mempaper, so run the update steps as that user — otherwise pip writes root-owned files into the virtualenv and the service fails to start afterwards.

ssh pi@<pi-ip>
cd /home/mempaper/btc-mempaper

sudo -u mempaper git fetch --tags
sudo -u mempaper git checkout <tag>          # e.g. git checkout v2.1.0
sudo -u mempaper .venv/bin/pip install -r requirements.txt --quiet

# Re-minify only if you opted into minification at install time.
# This mirrors what the web updater does: it re-minifies when dist/ has content.
[ -n "$(ls -A static/js/dist 2>/dev/null)" ] \
  && sudo -u mempaper .venv/bin/python tools/minify.py

sudo systemctl restart mempaper.service

Skipping the minify step on a device that does use minified assets leaves static/js/dist/ holding the previous release's JavaScript — the UI then runs stale code against a new backend.

Transferring Memes via SCP

The installer adds the pi user to the mempaper group and sets static/memes/ to group-writable (chmod 2775), so you can copy memes directly from another machine without switching users:

# Copy a local memes folder to the Pi
scp -r ~/memes/* pi@<pi-ip>:/home/mempaper/btc-mempaper/static/memes/

Note: You must log out and back in (or run newgrp mempaper) on the Pi after installation for the group membership to take effect for any already-running SSH session.

Meme Library Health

A meme is reachable by holiday and tag matching only when the image is in static/memes/ and a record for it exists in index.jsonl or _state_memes.jsonl carrying tags or descriptive text. A meme without one still appears in the ordinary rotation, but is never chosen for a holiday or a keyword — and nothing on the device says so. The audit reports the state of every file in the directory:

cd /home/mempaper/btc-mempaper
sudo -u mempaper .venv/bin/python tools/meme_index_audit.py
sudo -u mempaper .venv/bin/python tools/meme_index_audit.py --list   # every filename, not a sample
sudo -u mempaper .venv/bin/python tools/meme_index_audit.py --csv    # to pipe somewhere

Three cases are counted apart, because each has a different answer:

Reported as What it means What to do
from the API, no record A uuid-named file whose metadata never got written --repair fetches the records back from einundzwanzig-memes.space and appends them
added by hand, no record Uploaded or copied in, so nothing exists upstream to fetch Tag it under Meme Management, or leave it in the ordinary rotation
record, nothing to match on Indexed, but carries neither tags nor text Tag it under Meme Management

It also finds one meme sitting in the directory as several files — the .webp the API served plus a .jpg or .png converted from it. The renderer draws whatever is in the directory, so a meme with two files comes up twice as often as every other one.

To find the untagged ones from the web UI instead, tick Only memes without tags above the gallery in Meme Management.

The audit is read-only unless --repair is given, which appends to index.jsonl and touches nothing else.

Private Repositories

If your git remote points to a private repository (e.g. self-hosted GitLab), the updater falls back to local git tags — updates still work, but release notes won't be shown in the web UI.

To enable full release notes, create a .env file with an API token:

cp .env.example .env
nano .env
# GitHub: Personal Access Token with "repo" scope
# GitLab: Personal Access Token with "read_api" scope
GIT_API_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxx

Permissions

The web UI update requires a sudoers entry for passwordless service restart. install.sh sets this up for you; to install it manually:

echo "$(whoami) ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart mempaper.service" | sudo tee /etc/sudoers.d/mempaper-update
sudo chmod 0440 /etc/sudoers.d/mempaper-update


DOCUMENTATION

Project Structure

The codebase is organised into four layers — adapters bring data in, one orchestrator schedules, one renderer produces both images, two sinks deliver them. See the component map and directory layout in the Architecture doc.




LICENSE

This project is 100% Open Source under GPL-3.0 license. Clone it, build your own mempaper, or contribute to the project!




SUPPORT THE PROJECT

If you find this project useful and want to support its development, you can send a Lightning tip:

Lightning Donation QR Code
khakioctopus15@primal.net

Every sat helps keep the project maintained and adds new features!

mempaper · GPL-3.0 · satcat21          Built with love for the Bitcoin community