Direct boto3 calls against the R2 S3 endpoint fail with SignatureDoesNotMatch for this account, while presigned URLs served through the R2 custom domain are the path the app already uses. Switch migrate_image_storage download step to urlopen on the presigned URL.
URL Manager
A URL management tool that helps you organize and access your links efficiently.
Features
- Create and manage short links
- Template links with dynamic parameters
- Bookmark pages with automatic title and summary extraction
- Screenshot capture for bookmarked pages
- Advanced search capabilities
- REST API access
- In-process background task scheduling (APScheduler)
- Network security scanner (NetScan)
Prerequisites
- Python 3.12+
- uv — fast Python package manager
- just — command runner (
brew install just) - Docker + Docker Compose (for containerised setup)
Quick Start (Local)
# 1. Install dependencies and apply migrations
just install
# 2. Install Playwright browsers (for screenshot capture)
just install-browsers
# 3. Run the full stack (Django + Tailwind watcher)
just dev-all
The app will be available at http://localhost:8000
Run
just(no arguments) to see all available recipes.
Local Development Commands
| Recipe | Description |
|---|---|
just dev |
Run Django server + Tailwind together |
just tailwind |
Run Tailwind CSS watcher (standalone) |
just migrate |
Apply pending migrations |
just makemigrations |
Generate new migrations |
just shell |
Open Django shell |
just dbshell |
Open raw DB shell |
just superuser |
Create a superuser |
just build-css |
Build minified Tailwind CSS |
just collectstatic |
Collect static files |
just test |
Run tests |
just compilemessages |
Compile i18n translation files |
just makemessages |
Extract translatable strings (zh_Hans) |
Docker Commands
| Recipe | Description |
|---|---|
just docker-build |
Build images |
just docker-start |
Start all services (detached) |
just docker-stop |
Stop all services |
just docker-restart |
Restart services |
just docker-logs |
Tail logs (all services) |
just docker-logs web |
Tail logs for a specific service |
just docker-shell |
Shell into the web container |
just docker-migrate |
Run migrations inside Docker |
just docker-static |
Collect static files inside Docker |
just docker-rebuild web |
Rebuild and restart a specific service |
Docker Services
web— Django server on port 8000 (APScheduler runs in-process)node— Tailwind CSS compiler
Usage
Managing Links
- Create a new link at
/create/ - Access a link at
http://localhost:8000/your-alias - For template links use
{param, default=value}syntax in the URL
Managing Pages (Bookmarks)
- Add a bookmark at
/ui/pages/new/— title and summary are auto-extracted - Screenshots are captured automatically in the background
- View all bookmarks at
/ui/pages/
X (Twitter) Bookmarks
The app includes an offline archive of X.com bookmarks. Export your bookmarks from X, then import them for full-text search, filtering, and browsing — no further API calls needed.
1. Export bookmarks from X
Use x-bookmarks-exporter to download your X bookmarks as JSON files:
# Install
git clone https://github.com/nickcam/x-bookmarks-exporter.git
cd x-bookmarks-exporter
npm install
# Configure — copy .env.example to .env and fill in your X credentials
cp .env.example .env
# Export (produces individual <tweet_id>.json files in data/bookmarks/)
npm run export
The exporter produces either:
- A directory of
<tweet_id>.jsonfiles (one per bookmark), or - A
bookmarks_index.jsonfile containing all bookmarks in a{"bookmarks": [...]}wrapper.
Both formats are supported.
2. Import into URL Manager
Via management command:
# Import a directory of JSON files
just import-bookmarks data/bookmarks/
# Import an index file
just import-bookmarks data/bookmarks_index.json
# Import a single file
just import-bookmarks data/single_bookmark.json
# Wipe existing bookmarks first, then re-import
just import-bookmarks data/bookmarks/ --clear
The command reports created, updated, unchanged, and skipped counts. Re-importing the same data is safe — unchanged items are detected via content hash and skipped.
Via REST API:
# Bulk import (export-format payload)
curl -X POST http://localhost:8000/api/bookmarks/import/ \
-H "Content-Type: application/json" \
-d '{"bookmarks": [{...}, {...}]}'
# Bulk upsert (flat array, either format)
curl -X POST http://localhost:8000/api/bookmarks/bulk/ \
-H "Content-Type: application/json" \
-d '[{"tweet_id": "123", "text": "hello"}, ...]'
3. Browse imported bookmarks
- Web UI: visit
/ui/bookmarks/— filter by media type, search by text/author, view full details - API:
GET /api/bookmarks/with filters like?q=search&has_video=true&author=screen_name - API docs:
/ui/api-docs/(Bookmarks section)
Dedup behaviour
Each bookmark is keyed by tweet_id. On re-import:
- New tweet_id → created
- Existing tweet_id, content changed → updated (engagement counts, media, etc. refreshed)
- Existing tweet_id, content identical →
unchanged(onlylast_imported_atbumped; no save/signal overhead)
Network Scanner (NetScan)
Available at /ui/netscan/. Create a scan profile to automatically monitor your home network for security issues (router exposure, DNS, TLS certs, camera access).
API
- List pages:
GET /api/pages/ - Full API docs:
/ui/api-docs/
Architecture
core/ — Django project config, APScheduler setup
links/ — Main app: links, pages, tags, posts, collections
netscan/ — Network security scanner
templates/ — Base HTML templates
Background tasks (screenshot capture, page metadata extraction, network scans) run via APScheduler in-process — no separate worker or Redis needed.
Dependencies
Managed with uv via pyproject.toml.
# Add a new package
uv add package-name
# Sync environment from lockfile
uv sync
Favicon
Generated using the Zen Tokyo Zoo font (https://fonts.gstatic.com/s/zentokyozoo/).
Image Resizing (Cloudflare)
- Bind a custom domain
- Enable image resizing: https://developers.cloudflare.com/images/transform-images/