wrolpi/docker
lrnselfreliance 86d4bbab98
Some checks are pending
CodeQL / Analyze (push) Waiting to run
Controller Tests / controller-tests (push) Waiting to run
Jest Tests / jest-tests (push) Waiting to run
Bookmarks: a nav menu of user links, stored in bookmarks.yaml (#729)
* bookmarks: user bookmarks in the navigation bar, stored in bookmarks.yaml

A "Bookmarks" tab between Calculators and Flasher opens on hover or click and
lists the user's bookmarks, with directories as flyout submenus (Menu.Sub).
The mobile hamburger shows directories as labelled groups instead.  The tab
always shows, with an "Edit bookmarks" item, so the feature can be found
before any bookmark exists.

Bookmarks live in a new config-only bookmarks.yaml: a nested tree where a
node with `children` is a directory and a node with `url` is a bookmark.
Every node carries an integer id so the API can rename, move and delete
without relying on names.  A URL may be a path on this WROLPi, a port on this
host (`:8096/` or `http://:8096/`, resolved against whatever hostname the
browser used, so it works over LAN IP, mDNS and the hotspot alike), or an
absolute URL.  Each bookmark chooses whether to open in a new tab.

The editor at /more/bookmarks follows the playlist items table: numbered rows,
up/down/edit/delete, and inline "Add a bookmark" / "Add a directory" forms
with a Location picker.  Deleting a directory asks first, since its contents
go with it.  The menu and the editor are in the component gallery.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* bookmarks: mark More current when the editor is folded inside it; rem indents in the mobile menu

Found on the QA Pi at a width where Bookmarks overflows into More: the More
button carried no mark on /more/bookmarks, because the bookmarks link has no
`to` for isLinkActive to match.  isLinkActive now knows the bookmarks link owns
its editor page, and the Bookmarks tab uses the same rule.

The mobile menu's directory labels sat a few pixels left of the labels around
them: the indent was in em, and a Menu.Label is set smaller than a Menu.Item.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* bookmarks: parse URLs instead of matching a prefix; validate the whole tree on every write

The URL contract was checked by prefix on both sides, and a prefix is not a
form: `//evil.com` starts like a path and `:8096@evil.com` starts like a port,
and a browser takes both off this host.  A path is now a single leading slash
that resolves to this origin, a port form is an optional scheme, `:`, digits
and then only a path, query or fragment (no `@`, no backslash, no control
characters), and anything else must parse as http(s).  The same rules live in
wrolpi/bookmarks.py and BookmarksContext.js, with the same bypass strings in
both test suites.

The config's dataclass validator only saw that `bookmarks` was a list, so a
hand-edited file or the generic config API could store a `javascript:` URL
that the bookmarks API would refuse, and the nav rendered it.  The config now
validates the tree itself on every write and import: URL forms, exactly one of
url/children, unique integer ids, boolean new_tab, and no cycles (a YAML
anchor can build one).  An invalid file is not imported and, since it was never
successfully imported, is never overwritten.

On the page, a stored URL that does not resolve is rendered as text, never as
an href.  A refused add keeps what was typed instead of clearing the form, and
an edit whose move is refused still refreshes the nav (the rename was
committed) and keeps the modal open.  An IPv6 host is bracketed before a port.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

* help: document Bookmarks

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-26 13:11:36 -06:00
..
api Debian 13 (trixie) rebase: docker, debian-live-config, and pi-gen builds. 2026-07-13 15:43:08 -06:00
app Debian 13 (trixie) rebase: docker, debian-live-config, and pi-gen builds. 2026-07-13 15:43:08 -06:00
archive archive: raise archive service request limit to 500MB (#692) 2026-09-01 20:09:35 -06:00
controller Debian 13 (trixie) rebase: docker, debian-live-config, and pi-gen builds. 2026-07-13 15:43:08 -06:00
help Bookmarks: a nav menu of user links, stored in bookmarks.yaml (#729) 2026-09-26 13:11:36 -06:00
samba Debian 13 (trixie) rebase: docker, debian-live-config, and pi-gen builds. 2026-07-13 15:43:08 -06:00
web Speed Test calculator: ping, download, and upload against the API (#708) 2026-09-13 18:47:18 -06:00
zim zim: ship a default fallback Zim and honor the configured Zim directory 2026-07-20 19:09:57 -06:00
README.md Postgres -> SQLite hard cutover: the database moves into the media directory. 2026-07-12 14:09:37 -06:00

WROLPi Docker

Development and testing environment using Docker containers.

Quick Start

git clone https://github.com/lrnselfreliance/wrolpi.git
cd wrolpi
git submodule update --init
docker-compose build --parallel
docker volume create --name=openstreetmap-data
docker volume create --name=openstreetmap-rendered-tiles
docker-compose run --rm api db upgrade
docker-compose up

Browse to https://localhost:8443

Services

Service Port(s) Description
web 8443 (HTTPS), 8084-8086 Caddy reverse proxy
api 8081 Sanic API server
app (via web) React frontend
controller 8080 System management (FastAPI)
archive 8083 SingleFile/Readability
zim (via web:8085) Kiwix
help (via web:8086) MkDocs documentation
map (via web:8084) OpenStreetMap tile server

TLS Certificates

WROLPi uses a two-tier PKI for HTTPS. A root CA is generated once and persists in the media directory. A leaf certificate is regenerated on every container start to pick up hostname and IP changes.

How it works

  1. On first start, the web container generates a root CA at /opt/media/config/ssl/.
  2. A leaf cert signed by the root CA is created at /etc/ssl/caddy/ inside the container.
  3. Caddy serves HTTPS on ports 443, 8084, 8085, and 8086 using the leaf cert.
  4. The Controller serves on port 80 with a certificate trust banner when needed.

Trusting the certificate

  1. Browse to http://<host-ip> to see the Controller. If the HTTPS certificate is not yet trusted, a banner will appear with the download link and platform-specific trust instructions.
  2. Download and install the root CA on your device.
  3. All HTTPS ports will work without browser warnings.

The root CA only needs to be trusted once. Leaf certs can be regenerated (hostname change, container rebuild) without needing to re-trust.

Name Constraints

The root CA is restricted to local/private networks only via X.509 Name Constraints. Even if the CA key were compromised, it cannot sign certificates for public domains.

Permitted:

  • DNS: *.local, localhost
  • IPv4: 127.0.0.0/8, 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
  • IPv6: ::1, fc00::/7 (unique local), fe80::/10 (link-local)

EXTRA_SANS

The Docker container cannot discover the host's real LAN IP. Set EXTRA_SANS in a .env file so the leaf certificate includes your host's IP and hostname:

# .env
EXTRA_SANS=IP:10.0.0.5,DNS:myhost.local

Each entry must have a type prefix (IP: or DNS:). Multiple entries are comma-separated. After changing EXTRA_SANS, restart the web container:

docker compose up -d --build web

Regenerating the root CA

If you need to regenerate the root CA (e.g. after updating the certificate generation script), delete the old CA files and rebuild:

rm test/config/ssl/ca.crt test/config/ssl/ca.key
docker compose up -d --build web

All devices will need to re-download and re-trust the new CA from http://<host-ip>/ca.crt.

Volumes

The test/ directory is mounted to /opt/media inside containers, serving as the media directory for development. Certificate files, configs, and downloaded content are stored here.

Two external volumes are required for the map service:

docker volume create --name=openstreetmap-data
docker volume create --name=openstreetmap-rendered-tiles

Environment Variables

Variable Default Description
MEDIA_DIRECTORY /opt/media Media directory inside containers
EXTRA_SANS (empty) Additional SANs for the TLS leaf certificate
WEB_HOST 0.0.0.0 Host bind address for the web service
WEB_HTTPS_PORT 8443 HTTPS port
REACT_APP_API 0.0.0.0:8081 API bind address
UID 1000 User ID for api/app containers
GID 1000 Group ID for api/app containers

Upgrading

See UPGRADE.md for upgrade instructions.