Add PyInstaller builds, AGPLv3 license, transport node selection, and rmap.world link
- Add pyinstaller.spec and GitHub/Forgejo CI workflows for cross-platform builds - Add AGPLv3 license - Move data storage to ~/.tinyweb/ - Add --version and --port CLI flags - Add transport node selection in /style (smart regeneration preserves Reticulum config) - Add discover more nodes link to rmap.world
This commit is contained in:
parent
696a32cef9
commit
57a79e5e8e
9 changed files with 924 additions and 20 deletions
109
app.py
109
app.py
|
|
@ -1,6 +1,8 @@
|
|||
import os
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
import argparse
|
||||
import RNS
|
||||
from http.server import HTTPServer
|
||||
|
||||
|
|
@ -13,18 +15,59 @@ ASPECTS = ["server"]
|
|||
IDENTITY_FILE = "tinyweb_identity"
|
||||
DEFAULT_TRANSPORT_HOST = "reticulum.derickphan.com"
|
||||
DEFAULT_TRANSPORT_PORT = 4242
|
||||
DATA_DIR = os.path.expanduser("~/.tinyweb")
|
||||
|
||||
|
||||
def get_transport_config():
|
||||
host = get_setting("transport_host", DEFAULT_TRANSPORT_HOST)
|
||||
port = get_setting("transport_port", str(DEFAULT_TRANSPORT_PORT))
|
||||
return host, int(port)
|
||||
|
||||
|
||||
def find_available_port(start=8080, max_attempts=20):
|
||||
"""Find an available port starting from start."""
|
||||
import socket
|
||||
for port in range(start, start + max_attempts):
|
||||
try:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.bind(("0.0.0.0", port))
|
||||
return port
|
||||
except OSError:
|
||||
continue
|
||||
return start
|
||||
|
||||
|
||||
def get_version():
|
||||
"""Get version from git tag or VERSION file."""
|
||||
try:
|
||||
import subprocess
|
||||
tag = subprocess.check_output(
|
||||
["git", "describe", "--tags", "--abbrev=0"],
|
||||
stderr=subprocess.DEVNULL,
|
||||
text=True
|
||||
).strip()
|
||||
if tag.startswith("v"):
|
||||
return tag[1:]
|
||||
return tag
|
||||
except Exception:
|
||||
version_file = os.path.join(os.path.dirname(__file__), "VERSION")
|
||||
if os.path.exists(version_file):
|
||||
with open(version_file) as f:
|
||||
return f.read().strip()
|
||||
return "0.0.0"
|
||||
|
||||
|
||||
def load_or_create_identity():
|
||||
if os.path.isfile(IDENTITY_FILE):
|
||||
# Ensure identity file is only readable by owner
|
||||
current = os.stat(IDENTITY_FILE).st_mode & 0o777
|
||||
os.makedirs(DATA_DIR, exist_ok=True)
|
||||
identity_path = os.path.join(DATA_DIR, IDENTITY_FILE)
|
||||
if os.path.isfile(identity_path):
|
||||
current = os.stat(identity_path).st_mode & 0o777
|
||||
if current != 0o600:
|
||||
os.chmod(IDENTITY_FILE, 0o600)
|
||||
return RNS.Identity.from_file(IDENTITY_FILE)
|
||||
os.chmod(identity_path, 0o600)
|
||||
return RNS.Identity.from_file(identity_path)
|
||||
identity = RNS.Identity()
|
||||
identity.to_file(IDENTITY_FILE)
|
||||
os.chmod(IDENTITY_FILE, 0o600)
|
||||
identity.to_file(identity_path)
|
||||
os.chmod(identity_path, 0o600)
|
||||
return identity
|
||||
|
||||
|
||||
|
|
@ -42,13 +85,35 @@ def start_gateway(reticulum):
|
|||
thread.start()
|
||||
|
||||
|
||||
def ensure_rns_config(config_dir):
|
||||
def _transport_settings_match(config_file, desired_host, desired_port):
|
||||
"""Check if existing config transport settings match desired values."""
|
||||
import configparser
|
||||
try:
|
||||
config = configparser.ConfigParser()
|
||||
config.read(config_file)
|
||||
if config.has_section("TCP Transport"):
|
||||
existing_host = config.get("TCP Transport", "target_host")
|
||||
existing_port = config.get("TCP Transport", "target_port")
|
||||
return existing_host == desired_host and existing_port == str(desired_port)
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def ensure_rns_config(config_dir, transport_host=None, transport_port=None):
|
||||
"""Generate a default Reticulum config with internet transport if none exists."""
|
||||
if config_dir is None:
|
||||
config_dir = os.path.expanduser("~/.reticulum")
|
||||
config_file = os.path.join(config_dir, "config")
|
||||
if transport_host is None:
|
||||
transport_host = get_setting("transport_host", DEFAULT_TRANSPORT_HOST)
|
||||
if transport_port is None:
|
||||
transport_port = int(get_setting("transport_port", str(DEFAULT_TRANSPORT_PORT)))
|
||||
|
||||
if os.path.exists(config_file):
|
||||
return
|
||||
if _transport_settings_match(config_file, transport_host, transport_port):
|
||||
return
|
||||
|
||||
os.makedirs(config_dir, exist_ok=True)
|
||||
with open(config_file, "w") as f:
|
||||
f.write(f"""[reticulum]
|
||||
|
|
@ -66,8 +131,8 @@ def ensure_rns_config(config_dir):
|
|||
[[TCP Transport]]
|
||||
type = TCPClientInterface
|
||||
enabled = yes
|
||||
target_host = {DEFAULT_TRANSPORT_HOST}
|
||||
target_port = {DEFAULT_TRANSPORT_PORT}
|
||||
target_host = {transport_host}
|
||||
target_port = {transport_port}
|
||||
""")
|
||||
print(f"Created Reticulum config at {config_file}")
|
||||
|
||||
|
|
@ -79,9 +144,8 @@ def _preload_embeddings():
|
|||
return
|
||||
try:
|
||||
from embeddings import _get_session, _get_reranker, build_index
|
||||
_get_session() # downloads model on first run, loads ONNX session
|
||||
build_index() # builds HNSW index from existing chunks
|
||||
# Preload cross-encoder unless user has explicitly disabled it
|
||||
_get_session()
|
||||
build_index()
|
||||
if get_setting("use_reranker", "1") == "1":
|
||||
_get_reranker()
|
||||
print("Semantic search ready (with reranker).")
|
||||
|
|
@ -92,10 +156,25 @@ def _preload_embeddings():
|
|||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(prog="tinyweb", description="Personal decentralized search engine")
|
||||
parser.add_argument("--version", "-v", action="store_true", help="Show version")
|
||||
parser.add_argument("--port", "-p", type=int, default=None, help="HTTP gateway port (default: 8080)")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.version:
|
||||
print(f"TinyWeb {get_version()}")
|
||||
return
|
||||
|
||||
port = args.port or 8080
|
||||
import gateway
|
||||
gateway.GATEWAY_PORT = find_available_port(port)
|
||||
|
||||
init_db()
|
||||
transport_host = get_setting("transport_host", DEFAULT_TRANSPORT_HOST)
|
||||
transport_port = int(get_setting("transport_port", str(DEFAULT_TRANSPORT_PORT)))
|
||||
threading.Thread(target=_preload_embeddings, daemon=True).start()
|
||||
config_dir = os.environ.get("RNS_CONFIG_DIR")
|
||||
ensure_rns_config(config_dir)
|
||||
ensure_rns_config(config_dir, transport_host, transport_port)
|
||||
reticulum = RNS.Reticulum(configdir=config_dir)
|
||||
identity = load_or_create_identity()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue