Files
Link-Log/README.md
T
2026-08-25 08:12:39 +02:00

12 KiB

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 with traefik reverse proxy hooks
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:

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. The visible LinkLog brand text uses the Google Foundry Asset font when available, with local fallbacks in the Firefox extension.

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.

  1. Start the backend locally.
  2. Open Firefox and visit about:debugging#/runtime/this-firefox.
  3. Select Load Temporary Add-on.
  4. Choose webextension/manifest.json.
  5. Open the LinkLog extension options and enter:
    • Backend URL: http://localhost:8000
    • Username: alice
    • Password: secret123
  6. Save the settings and login.
  7. Open a webpage, select the LinkLog toolbar button, review the title and URL, add a comment, and submit it.

When the extension settings page has a valid session, it shows <username> logged in at <backend URL> and a Sign out button instead of the login form. Signing out revokes the token and returns the form.

Temporary extensions are removed when Firefox restarts. Reload the extension from about:debugging after changing its files.

Docker

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 (stripped from logged URLs) built-in list
TRAEFIK_HOST hostname routed by Traefik localhost
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:

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.social or https://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 .venv and rerun python -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 in backend/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 run docker compose down -v for 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.