LinkLog
LinkLog is a Firefox extension and Python web service for saving links with a title, comment, timestamp, and tracking parameters removed. The service stores links in SQLite and can publish them through plugins, including Mastodon.
Project Layout
backend/ FastAPI application, services, database, and tests
frontend/ Jinja templates and browser-side assets
webextension/ Firefox Manifest V3 extension
Logo.svg Source logo artwork used by the web and extension interfaces
Dockerfile Backend container image
docker-compose.yml App plus Traefik for local proxying
REQUIREMENTS.md Product requirements
VIBE/ Conversation and prompt logs
Requirements
For local development:
- macOS, Linux, or Windows with Python 3.11+
- Firefox for installing the extension
- Docker Desktop and Docker Compose v2 for the container workflow
The backend currently uses FastAPI, uvicorn, SQLite, and Pydantic. httpx2 is included for the Starlette-compatible test client.
Jinja2 is included for server-rendered HTML templates.
Local Installation
From the repository root:
python3.11 -m venv .venv
. .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -r backend/requirements.txt
On Windows PowerShell, activate the environment with:
.venv\Scripts\Activate.ps1
The SQLite database is created automatically at backend/data/linklog.db when the application starts or when the auth router is imported. The starter accounts are:
| Username | Password | Role |
|---|---|---|
alice |
secret123 |
administrator |
bob |
secret123 |
standard user |
These credentials are for development only. Change the authentication and seeding design before deploying publicly.
The database schema is versioned with SQLite PRAGMA user_version. Application startup applies all pending migrations in order, so updating the application does not require deleting an existing database. New schema changes should be added as a new numbered migration in backend/app/database.py; existing migration entries must remain unchanged.
Run The Backend
Activate the virtual environment, then run uvicorn from the repository root:
. .venv/bin/activate
PYTHONPATH=. uvicorn backend.app.main:app --reload --host 127.0.0.1 --port 8000
Open these URLs:
- Public feed: http://localhost:8000/
- User feed: http://localhost:8000/alice
- Profile settings: http://localhost:8000/profile
- Labels: http://localhost:8000/labels
- Admin page: http://localhost:8000/admin
- Web login: http://localhost:8000/login
- Health check: http://localhost:8000/health
- OpenAPI documentation: http://localhost:8000/docs
The browser extension defaults to http://localhost:8000.
The supplied Logo.svg is bundled as frontend/static/logo.svg for web pages and webextension/logo.svg for the Firefox popup and settings page.
Regenerate Logo Assets
LinkLog.svg is the source logo. Install ImageMagick, then regenerate the web logo, extension logo, and Firefox toolbar icons with:
make logos
The generated files are frontend/static/logo.svg, webextension/logo.svg, and webextension/icon-16.png, icon-32.png, icon-48.png, and icon-96.png. Override the ImageMagick executable with make MAGICK=magick logos when needed.
Appending a username to the root URL, such as /alice, opens that user's public feed and profile information.
Configuration APIs require a bearer token returned by the login endpoint. User configuration uses the identity in that token. Plugin administration additionally requires an administrator account; the development alice account is seeded as an administrator, while bob is a standard user.
Users can change their password from the profile page. The current password is required, new passwords must contain at least 8 characters, and the endpoint is PUT /api/user/password.
On the profile page, the authenticated username is displayed as read-only. Users can upload a PNG, JPEG, GIF, or WebP avatar up to 2 MB; uploaded files are stored in the persistent data volume and served by the application. Bio and email fields remain empty until the user provides values. Mastodon settings default to the mastodon.social instance and the From my #LinkLog: post prefix.
Run Tests
. .venv/bin/activate
PYTHONPATH=. pytest backend/tests -q
To ensure warnings are clean as well:
PYTHONPATH=. PYTHONWARNINGS=error pytest backend/tests -q
Install The Firefox Extension For Development
The extension is an unpacked Firefox extension. No build step is required.
The toolbar uses square PNG icons generated from the bundled dark-background logo; logo.svg remains available for the popup and settings branding.
The manifest includes stable Firefox extension metadata and references the packaged PNG icons for toolbar and add-on installation.
- Start the backend locally.
- Open Firefox and visit
about:debugging#/runtime/this-firefox. - Select Load Temporary Add-on.
- Choose
webextension/manifest.json. - Open the LinkLog extension options and enter:
- Backend URL:
http://localhost:8000 - Username:
alice - Password:
secret123
- Backend URL:
- Save the settings and login.
- Open a webpage, select the LinkLog toolbar button, review the title and URL, add a comment, and submit it.
Temporary extensions are removed when Firefox restarts. Reload the extension from about:debugging after changing its files.
Docker And Traefik
Create the Docker environment file before starting the stack:
cp .env.example .env
Edit .env and replace LINKLOG_SECRET_KEY with a long random value. Docker Compose automatically reads .env from the repository root. The committed .env.example contains safe defaults and placeholders; the real .env is ignored by Git.
The main configurable values are:
| Variable | Purpose | Default |
|---|---|---|
LINKLOG_SECRET_KEY |
token signing/security secret | required in Docker |
LINKLOG_DATABASE_PATH |
SQLite file path inside the container | /app/backend/data/linklog.db |
LINKLOG_TOKEN_EXPIRY_DAYS |
access-token lifetime | 30 |
LINKLOG_TRACKING_PARAMS |
comma-separated tracking parameters | built-in list |
TRAEFIK_HOST |
hostname routed by Traefik | localhost |
TRAEFIK_HTTP_PORT |
host port for the application proxy | 80 |
TRAEFIK_DASHBOARD_PORT |
host port for the dashboard | 8080 |
TRAEFIK_DASHBOARD_BIND_ADDRESS |
host address for the dashboard | 127.0.0.1 |
TRAEFIK_API_INSECURE |
enable the local dashboard API | true |
APP_PORT |
direct host port for FastAPI | 8000 |
The full set of supported variables is listed in .env.example. Application variables are passed into the container by Compose; Docker and Traefik variables are used by Compose itself.
Build and start the application and local Traefik proxy:
docker compose up --build
The services are available at:
- LinkLog through Traefik: http://example.com/
- Direct application port: http://localhost:8000/
The Compose configuration routes the hostname localhost through Traefik. The SQLite database is stored in the named Docker volume linklog_data, mounted at /app/backend/data.
The application runs as a non-root user and reports container health through /health; Traefik waits for the application health check before starting.
Stop the stack without deleting its database:
docker compose down
Stop the stack and delete the named database volume:
docker compose down -v
For a real deployment, replace the localhost router rule, configure TLS, protect the Traefik dashboard, avoid exposing the direct application port, and provide production secrets and authentication. The included Compose file is a local/prototype deployment scaffold, not a production security configuration.
Mastodon Configuration
After logging in, open http://localhost:8000/profile and save the Mastodon settings:
- Instance: hostname or URL such as
mastodon.socialorhttps://mastodon.social - Access token: a Mastodon API token with permission to create statuses
- Post prefix: text placed immediately before the link; defaults to
From my #LinkLog:
Enable the plugin from the admin API or the admin page. New links are saved first and then posted to the configured instance at /api/v1/statuses. A Mastodon network failure does not undo the saved link.
Useful API Calls
Login:
curl -X POST http://localhost:8000/api/auth/login \\
-H 'Content-Type: application/json' \\
-d '{"username":"alice","password":"secret123"}'
Submit a link using the returned access token:
curl -X POST http://localhost:8000/api/links \\
-H 'Content-Type: application/json' \\
-H 'Authorization: Bearer YOUR_ACCESS_TOKEN' \\
-d '{"title":"Example","url":"https://example.com","comment":"Worth reading"}'
The public feed is available at:
GET http://localhost:8000/api/public/feed
When a request includes a valid bearer token, entries owned by that authenticated user include edit permission and show an inline Edit action in the feed. The update endpoint is PUT /api/links/{link_id} and rejects edits from other users.
Feed items also expose is_owner; it is true only for entries owned by the authenticated user and false for anonymous viewers or other users.
Links support zero to ten tags. Tags are trimmed, deduplicated case-insensitively, and retain their original casing for display. The Firefox capture popup shows existing server tags as checkboxes and accepts new comma-separated tags. The home page displays tags and provides a case-insensitive tag filter; editing a link replaces its complete tag set.
The inline link editor also allows multiple existing tags to be selected and new tags to be entered. New tag values receive a leading # automatically, and the interface prevents saving more than ten tags.
The installation seeds these available tags: #Internet, #Cybersecurity, #Fediverse, #Food, #Photography, #Music, and #AI.
Users can create, edit, and delete their own labels from /labels. Administrators can delete any label from /admin; system-seeded labels have no user owner.
Admin plugin requests must include the administrator's token:
curl http://localhost:8000/api/admin/plugins \\
-H 'Authorization: Bearer YOUR_ADMIN_ACCESS_TOKEN'
Administrators can manage user privileges from the admin page. The Administrator checkbox sends PUT /api/admin/users/{user_id} with {"is_admin": true} or {"is_admin": false}. The API rejects any change that would leave the system without an administrator.
Troubleshooting
ModuleNotFoundError: activate.venvand rerunpython -m pip install -r backend/requirements.txt.- Jinja2 import error in Docker: rebuild the image with
docker compose up --build; the runtime dependency is declared inbackend/requirements.txt. - Extension cannot login: confirm the backend is running, use the exact backend origin without a trailing API path, and check the browser console for blocked requests.
- Port 8000 is occupied: run uvicorn with another port and update the extension backend URL, for example
--port 8001. - Port 80 or 8080 is occupied: change the host-side ports in
docker-compose.yml. - Stale development data: stop the backend and remove
backend/data/linklog.db, or rundocker compose down -vfor the container volume. - Schema migration issue: inspect the database version with
sqlite3 backend/data/linklog.db 'PRAGMA user_version;'and restart the application to apply pending migrations.