# 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.
For full transparency: The author used vibe coding to create this software.
## Project Layout
```text
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.
The backend version is `0.1.0` and is exposed through the FastAPI/OpenAPI metadata. It can be overridden with `LINKLOG_VERSION`.
LinkLog is licensed under the GNU General Public License, version 3 or any later version. See [LICENSE](LICENSE).
## Local Installation
From the repository root:
```sh
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:
```powershell
.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:
```sh
. .venv/bin/activate
PYTHONPATH=. uvicorn backend.app.main:app --reload --host 127.0.0.1 --port 8000
```
Open these URLs:
- Public feed:
- User feed:
- Profile settings:
- Labels:
- About:
- Admin page:
- Web login:
- Health check:
- OpenAPI documentation:
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:
```sh
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.
Create an installable Firefox XPI bundle with:
```sh
make xpi
```
This creates `XPI/unsigned/LinkLog-0.1.0.xpi` from the `webextension/` package and excludes macOS metadata and minified artifacts. The version is read from `webextension/manifest.json`. The `XPI/signed/` directory is reserved for signed release bundles.
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
```sh
. .venv/bin/activate
PYTHONPATH=. pytest backend/tests -q
```
To ensure warnings are clean as well:
```sh
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 ` logged in at ` 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:
```sh
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:
```sh
docker compose up --build
```
The services are available at:
- LinkLog through Traefik:
- Direct application port:
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:
```sh
docker compose down
```
Stop the stack and delete the named database volume:
```sh
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 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:
```sh
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:
```sh
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:
```text
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:
```sh
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.