diff --git a/README.md b/README.md index e74c69b..bd6d74a 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ A personal, decentralized search engine built on the [Reticulum](https://reticul - **Custom templates** — Full HTML/CSS/JS template editor to personalize your instance - **Import/export** — JSON-based backup and restore - **Mesh-native** — Works over Reticulum without the internet; encrypted and decentralized by default +- **Forum plugin** — Optional link-sharing discussion board over the mesh (see Forum section below) ## Performance & Scale @@ -107,6 +108,7 @@ Your data is stored in `~/.tinyweb/`: |------|-------------| | `index.db` | SQLite database with your indexed pages | | `tinyweb_identity` | Your Reticulum identity (keep safe!) | +| `forum.db` | Forum plugin database (only if forum is enabled) | | `models/` | Downloaded AI models for semantic search | | `index.hnsw` | Semantic search index | @@ -118,6 +120,7 @@ Back up the whole `~/.tinyweb/` directory periodically. The two files that matte - **`tinyweb_identity`** is your permanent mesh identity. If you lose it, your destination hash changes and every subscriber has to re-subscribe to the new one. Keep it somewhere you trust; the file is `0600` by default. - **`index.db`** is your full reading history — every page, note, tag, and synced remote page. Losing it loses everything you've curated. +- **`forum.db`** (if the forum plugin is enabled) — all threads, posts, upvotes, and moderation settings. Losing it loses your forum data. `models/` and `index.hnsw` are re-derivable (the model will re-download, and the HNSW index rebuilds from the database on next startup with semantic search enabled) so they don't need to be backed up. @@ -176,6 +179,27 @@ This connects over Reticulum and serves the remote instance at `http://localhost 3. **Subscribe** — Add a friend's destination hash on `/subscriptions` to sync their shared index 4. **Customize** — Edit your site name, HTML template, and sharing settings on `/style` +## Forum plugin + +TinyWeb ships with an optional [tinyweb-forum](https://git.derickphan.com/lichenblankie/tinyweb-forum) plugin — a decentralized link-sharing discussion board that runs in-process alongside TinyWeb. + +### Install + +```bash +pip install tinyweb-forum +``` + +Enable it on the `/style` page under "Forum". A "Forum" link will appear in the navigation bar. + +### How it works + +- Threads and posts are stored in `~/.tinyweb/forum.db` (separate from your search index) +- Instances sync forum content over Reticulum every 5 minutes +- Authors are identified by a short pseudonymous identity hash (no accounts, no sign-up) +- Moderation is local: block authors, mute threads, keyword filters, and gossip block lists with peers + +For full feature docs, see the [tinyweb-forum README](https://git.derickphan.com/lichenblankie/tinyweb-forum). + ## Project structure ``` diff --git a/app.py b/app.py index b1c4fe6..3830c85 100644 --- a/app.py +++ b/app.py @@ -8,6 +8,8 @@ from http.server import HTTPServer from db import init_db, get_setting, set_setting from handlers import dispatch_request +import handlers as handlers_mod +import templates as templates_mod import gateway from gateway import GatewayState, GatewayHandler @@ -31,6 +33,7 @@ def find_available_port(start=8080, max_attempts=20, host="127.0.0.1"): for port in range(start, start + max_attempts): try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind((host, port)) return port except OSError: @@ -97,6 +100,7 @@ def rns_request_handler(path, data, request_id, link_id, remote_identity, reques def start_gateway(reticulum, bind_host="127.0.0.1"): GatewayState.reticulum = reticulum GatewayState.local_dispatch = dispatch_request + HTTPServer.allow_reuse_address = True server = HTTPServer((bind_host, gateway.GATEWAY_PORT), GatewayHandler) thread = threading.Thread(target=server.serve_forever, daemon=True) thread.start() @@ -271,6 +275,22 @@ def main(): allow=RNS.Destination.ALLOW_ALL, ) + # Initialize forum plugin if available + forum = None + try: + from tinyweb_forum import ForumPlugin + from db import get_site_name + forum = ForumPlugin(DATA_DIR, identity, reticulum, site_name=get_site_name()) + if get_setting("forum_enabled", "0") == "1": + forum.enable() + templates_mod.FORUM_ENABLED = True + handlers_mod.forum_plugin = forum + print(f"Forum plugin: {'enabled' if forum.is_enabled() else 'available (enable in settings)'}") + except ImportError: + print("Forum plugin not installed (pip install tinyweb[forum])") + except Exception as e: + print(f"Forum plugin error: {e}") + # Brief delay to ensure all interfaces (especially TCP) are fully ready time.sleep(2) destination.announce() diff --git a/handlers.py b/handlers.py index e47520b..a3bbb4b 100644 --- a/handlers.py +++ b/handlers.py @@ -6,9 +6,11 @@ from datetime import datetime from urllib.parse import unquote from db import get_db, return_db, get_setting, set_setting, get_site_name, index_url, clean_url +import templates as templates_mod from templates import esc, wrap_page, DEFAULT_TEMPLATE from rns_client import fetch_remote_sites +forum_plugin = None _request_local = threading.local() @@ -360,7 +362,7 @@ def handle_search(query): ) -def handle_add_form(msg="", action_type="index"): +def handle_add_form(msg="", action_type="index", prefill_url=""): if action_type == "subscribe": return _respond( f"
{msg}
" f'back' ) + url_value = f'value="{esc(prefill_url)}" ' if prefill_url else "" return _respond( f"Add a site to your index
" f'