Skip to content

UncleJS/bookmarkManager

Repository files navigation

Bookmark Manager

License: CC BY-SA 4.0 Bun Elysia MariaDB Podman Chrome Extension OpenAPI

A Chrome extension (MV3) that captures bookmarks and sends them to a local Bun/Elysia API backed by MariaDB.

Everything is implemented in JavaScript (no TypeScript) for the extension; the API uses TypeScript with Bun.


Table of Contents




Homepage

Edit Bookmark


Stack

Layer Technology
Chrome Extension Manifest V3, vanilla JS/HTML/CSS
API Bun + Elysia + TypeScript
ORM Drizzle
Database MariaDB 11
Infrastructure Rootless Podman + systemd (Quadlet)

↑ Table of Contents

Quick Start

1. Configure environment

cp api/.env.example api/.env
# Edit api/.env — set DB_PASSWORD, MARIADB_PASSWORD, MARIADB_ROOT_PASSWORD
nano api/.env

2. Install (build image + deploy Quadlet + start pod)

./scripts/install.sh

install.sh will:

  • Copy api/.env.example → api/.env if missing (then exit so you can set passwords)
  • Pull mariadb:11 and phpmyadmin:5
  • Build localhost/bookmark-api:latest
  • Copy quadlet/ unit files into ~/.config/containers/systemd/
  • Run systemctl --user daemon-reload and start the pod

3. Health check

curl http://localhost:11650/health
# → {"status":"ok"}
Service URL
API http://localhost:11650
Swagger UI http://localhost:11650/docs
OpenAPI JSON http://localhost:11650/openapi.json
Bookmark viewer http://localhost:11650/app
Category manager http://localhost:11650/categories
phpMyAdmin http://localhost:11651

4. Load the Chrome extension

  1. Open chrome://extensions
  2. Enable Developer mode
  3. Click Load unpacked → select the extension/ folder
  4. The extension defaults to http://localhost:11650 — change it in Options if needed

↑ Table of Contents

Scripts

All scripts live in scripts/ and are run from the repo root.

Script Description
./scripts/install.sh Full install: build image, deploy Quadlet files, start pod
./scripts/uninstall.sh Stop services, remove Quadlet files, optionally remove image + data
./scripts/rebuild.sh Rebuild API image and restart bookmark-api.service
./scripts/start.sh Start the pod (all services)
./scripts/stop.sh Stop the pod (all services)
./scripts/restart.sh [api|db|pma] Restart one service or the whole pod
./scripts/logs.sh [api|db|pma|all] Tail logs — defaults to api
./scripts/status.sh Show systemctl --user status for all services
./scripts/dev.sh Run API locally via bun run dev (no container)
./scripts/backup.sh Dump MariaDB to backups/bookmark_YYYY-MM-DD_HHMMSS.sql.gz

See scripts/README.md for full per-script documentation.


↑ Table of Contents

Repository Structure

bookmarkManager/
├── api/                    # Bun/Elysia API
│   ├── src/
│   │   ├── server.ts       # Elysia app + routes
│   │   ├── db/             # Drizzle schema, client, migrations
│   │   ├── ui/             # Served HTML pages (/app, /categories)
│   │   └── smoke/          # No-DB health smoke test
│   ├── Dockerfile
│   ├── drizzle.config.ts
│   ├── package.json
│   ├── .env                # Live credentials (gitignored)
│   └── .env.example        # Template — copy to .env
├── extension/              # Chrome MV3 extension (vanilla JS)
│   ├── manifest.json
│   ├── popup/
│   ├── background/
│   ├── options/
│   ├── lib/
│   └── assets/
├── quadlet/                # Canonical Quadlet unit files (version-controlled)
│   ├── bookmark.pod
│   ├── bookmark-api.container
│   ├── bookmark-db.container
│   └── bookmark-pma.container
├── scripts/                # Lifecycle scripts
│   ├── install.sh
│   ├── uninstall.sh
│   ├── rebuild.sh
│   ├── start.sh
│   ├── stop.sh
│   ├── restart.sh
│   ├── logs.sh
│   ├── status.sh
│   ├── dev.sh
│   └── backup.sh           # DB dump → backups/
├── backups/                # Dump files (gitignored)
├── PLAN.md                 # Full project reference
└── README.md               # This file

↑ Table of Contents

API Overview

Interactive docs always available at http://localhost:11650/docs.

Health & UI

Method Path Description
GET / Redirect to /app
GET /health Returns { status: "ok" }
GET /docs Swagger UI
GET /openapi.json OpenAPI spec
GET /app Bookmark viewer UI
GET /categories Category management UI
GET /flag-counts Count of active bookmarks per flag
GET /backup Download a gzipped MariaDB dump (requires ?token=)

↑ Table of Contents

Bookmarks

Method Path Description
GET /bookmarks List/filter bookmarks (?limit=&offset=&classificationId=&tagId=&flag=&sortBy=&archived=)
POST /bookmarks Create bookmark
PATCH /bookmarks/:id Edit title, description, flags, tags, classifications
PATCH /bookmarks/:id/archive Soft-delete (sets archivedAt)
PATCH /bookmarks/:id/restore Restore archived bookmark

↑ Table of Contents

Tags

Method Path Description
GET /tags List/search tags (?query=&limit=&offset=&sort=)
POST /tags Create tag
PATCH /tags/:id/archive Archive tag
PATCH /tags/:id/restore Restore archived tag

↑ Table of Contents

Classifications

Method Path Description
GET /classifications All active classifications, nested by group
POST /classifications Create classification (optionally with new group)
PATCH /classifications/:id Rename classification
PATCH /classifications/:id/reorder Set display order
PATCH /classifications/:id/archive Archive classification
PATCH /classifications/:id/restore Restore archived classification

↑ Table of Contents

Classification Groups

Method Path Description
GET /classifications/groups List groups with nested classifications (management view)
POST /classifications/groups Create group
PATCH /classifications/groups/:id Rename group
PATCH /classifications/groups/:id/reorder Set display order
PATCH /classifications/groups/:id/archive Archive group
PATCH /classifications/groups/:id/restore Restore archived group

Data lifecycle: nothing is hard-deleted. All "delete" actions set archivedAt and can be reversed with the corresponding /restore endpoint.

Duplicate URL detection: POST /bookmarks returns 409 with existing bookmark metadata. Pass allowDuplicate: true to save anyway after explicit user confirmation.


↑ Table of Contents

Chrome Extension

  • Click the action icon for the popup (full save with form)
  • Right-click a page for context menus: Quick Save / Full Save

See extension/README.md for full extension documentation. See api/README.md for full API documentation.


↑ Table of Contents

Backup

Two backup methods are available: a shell script (recommended for cron/automation) and an in-browser API endpoint.

Shell script

./scripts/backup.sh
  • Runs mariadb-dump inside the bookmark-db container via podman exec
  • Pipes output through gzip -9
  • Saves to backups/bookmark_YYYY-MM-DD_HHMMSS.sql.gz (directory is gitignored)

See scripts/README.md for full options and a restore command.

API endpoint

GET http://localhost:11650/backup?token=<BACKUP_TOKEN>
  • Returns a bookmark_YYYY-MM-DD_HHMMSS.sql.gz download
  • Requires BACKUP_TOKEN to be set in api/.env (a strong random value — the default change_me_please is refused with 503)
  • Returns 401 on token mismatch, 503 if token is unconfigured
  • The ⬇ Backup button in the http://localhost:11650/app topbar calls this endpoint — it will prompt for your token

Restore

gunzip -c backups/bookmark_YYYY-MM-DD_HHMMSS.sql.gz \
  | podman exec -i bookmark-db mariadb -u bookmarks -p bookmarks

↑ Table of Contents

Troubleshooting

Symptom Fix
Port 11650 or 11651 busy Edit quadlet/bookmark.pod, change PublishPort, re-run ./scripts/install.sh
API can't reach DB Check api/.envDB_PASSWORD must match MARIADB_PASSWORD; DB_HOST must be 127.0.0.1
Extension can't reach API Verify base URL in Options; check host_permissions in extension/manifest.json
Services not starting at boot Run loginctl enable-linger $USER to enable linger for your user

↑ Table of Contents

License

This project is licensed under the Creative Commons Attribution-ShareAlike 4.0 International (CC BY-SA 4.0).

https://creativecommons.org/licenses/by-sa/4.0/

↑ Table of Contents


© 2026 Jaco Steyn — Licensed under CC BY-SA 4.0

About

A Chrome extension (MV3) that captures bookmarks and sends them to a local Bun/Elysia API backed by MariaDB. Everything is implemented in JavaScript (no TypeScript) for the extension; the API uses TypeScript with Bun.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors