From e387d056e84ee30c683072d22c50a08029a74f41 Mon Sep 17 00:00:00 2001 From: Sangye Ince-Johannsen <sij@sij.law> Date: Fri, 28 Mar 2025 15:56:25 +0000 Subject: [PATCH] Update changes --- .gitignore | 5 +- ...rnames.txt => banned_usernames.txt_example | 0 canary.py | 332 ++++++++++++++++++ canary.txt | 34 ++ cleanup.py | 133 +++++++ conduwuit_logs.txt | 74 ++++ refresh_token.sh | 84 +++-- registration.py | 16 + relaunch_without_refresh.sh | 61 ++++ update_conduwuit.sh | 19 + 10 files changed, 719 insertions(+), 39 deletions(-) rename example-banned_usernames.txt => banned_usernames.txt_example (100%) create mode 100644 canary.py create mode 100644 canary.txt create mode 100644 cleanup.py create mode 100644 conduwuit_logs.txt create mode 100755 relaunch_without_refresh.sh create mode 100644 update_conduwuit.sh diff --git a/.gitignore b/.gitignore index 48cbcbb..6060618 100644 --- a/.gitignore +++ b/.gitignore @@ -29,12 +29,15 @@ Thumbs.db .classpath # Project-specific sensitive files +*.txt .registration_token config.yaml registrations.json banned_emails.txt banned_ips.txt -refresh_token.shbanned_usernames.txt +refresh_token.sh +banned_usernames.txt +testbench/ # Backup directories backup/ diff --git a/example-banned_usernames.txt b/banned_usernames.txt_example similarity index 100% rename from example-banned_usernames.txt rename to banned_usernames.txt_example diff --git a/canary.py b/canary.py new file mode 100644 index 0000000..db744f5 --- /dev/null +++ b/canary.py @@ -0,0 +1,332 @@ +#!/usr/bin/env python3 + +import yaml +import requests +import feedparser +import datetime +import subprocess +import json +import os +import sys +import asyncio +from time import sleep +from pathlib import Path + +# File paths +CONFIG_FILE = "config.yaml" +OUTPUT_FILE = "canary.txt" +TEMP_MESSAGE_FILE = "temp_canary_message.txt" + +# Possible attestations +ATTESTATIONS = [ + "We have not received any National Security Letters.", + "We have not received any court orders under the Foreign Intelligence Surveillance Act.", + "We have not received any gag orders that prevent us from stating we have received legal process.", + "We have not been required to modify our systems to facilitate surveillance.", + "We have not been subject to any searches or seizures of our servers." +] + +def load_config(): + """Load configuration from YAML file.""" + try: + if not os.path.exists(CONFIG_FILE): + print(f"Error: Configuration file '{CONFIG_FILE}' not found.") + print("Please create a configuration file with the following structure:") + print(""" +gpg: + key_id: YOUR_GPG_KEY_ID +matrix: + enabled: true + homeserver: https://we2.ee + username: @canary:we2.ee + password: YOUR_PASSWORD + room_id: !l7XTTF6tudReoEJEvr:we2.ee + """) + sys.exit(1) + + with open(CONFIG_FILE, 'r') as file: + config = yaml.safe_load(file) + + # Check for required fields + required_fields = [ + ('gpg', 'key_id') + ] + + for section, field in required_fields: + if section not in config or field not in config[section]: + print(f"Error: Required configuration field '{section}.{field}' is missing.") + sys.exit(1) + + return config + except Exception as e: + print(f"Error loading configuration: {e}") + sys.exit(1) + +def get_current_date(): + """Return the current date in YYYY-MM-DD format.""" + return datetime.datetime.now().strftime("%Y-%m-%d") + +def get_nist_time(): + """Get the current time from NIST time server.""" + try: + response = requests.get("https://timeapi.io/api/Time/current/zone?timeZone=UTC", timeout=10) + if response.status_code == 200: + time_data = response.json() + return f"{time_data['dateTime']} UTC" + else: + print(f"Error fetching NIST time: HTTP {response.status_code}") + return None + except Exception as e: + print(f"Error fetching NIST time: {e}") + return None + +def get_democracy_now_headline(): + """Get the latest headline from Democracy Now! RSS feed.""" + try: + feed = feedparser.parse("https://www.democracynow.org/democracynow.rss") + if feed.entries and len(feed.entries) > 0: + return feed.entries[0].title + else: + print("No entries found in Democracy Now! RSS feed") + return None + except Exception as e: + print(f"Error fetching Democracy Now! headline: {e}") + return None + +def get_bitcoin_latest_block(): + """Get the latest Bitcoin block hash and number.""" + try: + response = requests.get("https://blockchain.info/latestblock", timeout=10) + if response.status_code == 200: + data = response.json() + # Get block details + block_response = requests.get(f"https://blockchain.info/rawblock/{data['hash']}", timeout=10) + if block_response.status_code == 200: + block_data = block_response.json() + return { + "height": data["height"], + "hash": data["hash"], + "time": datetime.datetime.fromtimestamp(block_data["time"]).strftime("%Y-%m-%d %H:%M:%S UTC") + } + print(f"Error fetching Bitcoin block: HTTP {response.status_code}") + return None + except Exception as e: + print(f"Error fetching Bitcoin block data: {e}") + return None + +def collect_attestations(): + """Prompt user for each attestation.""" + selected_attestations = [] + + print("\nPlease confirm each attestation separately:") + for i, attestation in enumerate(ATTESTATIONS, 1): + while True: + response = input(f"Confirm attestation {i}: '{attestation}' (y/n): ").lower() + if response in ['y', 'n']: + break + print("Please answer 'y' or 'n'.") + + if response == 'y': + selected_attestations.append(attestation) + + return selected_attestations + +def create_warrant_canary_message(config): + """Create the warrant canary message with attestations and verification elements.""" + current_date = get_current_date() + nist_time = get_nist_time() + democracy_now_headline = get_democracy_now_headline() + bitcoin_block = get_bitcoin_latest_block() + + # Check if all required elements are available + if not all([nist_time, democracy_now_headline, bitcoin_block]): + missing = [] + if not nist_time: missing.append("NIST time") + if not democracy_now_headline: missing.append("Democracy Now! headline") + if not bitcoin_block: missing.append("Bitcoin block data") + print(f"Error: Could not fetch: {', '.join(missing)}") + return None + + # Collect attestations from user + attestations = collect_attestations() + if not attestations: + print("Warning: No attestations were confirmed.") + proceed = input("Do you want to proceed without any attestations? (y/n): ").lower() + if proceed != 'y': + print("Operation cancelled") + return None + + # Create the message + message = f"""We2.ee Warrant Canary +Date: {current_date} + +""" + + # Add attestations + for i, attestation in enumerate(attestations, 1): + message += f"{i}. {attestation}\n" + + message += f""" +Proofs: +NIST time: {nist_time} +Democracy Now! headline: "{democracy_now_headline}" +Bitcoin block #{bitcoin_block['height']} hash: {bitcoin_block['hash']} +Bitcoin block time: {bitcoin_block['time']} + +""" + return message + +def sign_with_gpg(message, gpg_key_id): + """Sign the warrant canary message with GPG.""" + try: + # Write message to temporary file + with open(TEMP_MESSAGE_FILE, "w") as f: + f.write(message) + + # Sign the message with GPG + cmd = ["gpg", "--clearsign", "--default-key", gpg_key_id, TEMP_MESSAGE_FILE] + subprocess.run(cmd, check=True) + + # Read the signed message + with open(f"{TEMP_MESSAGE_FILE}.asc", "r") as f: + signed_message = f.read() + + # Clean up temporary files + os.remove(TEMP_MESSAGE_FILE) + os.remove(f"{TEMP_MESSAGE_FILE}.asc") + + return signed_message + except subprocess.CalledProcessError as e: + print(f"GPG signing error: {e}") + return None + except Exception as e: + print(f"Error during GPG signing: {e}") + return None + +def save_warrant_canary(signed_message): + """Save the signed warrant canary to a file.""" + try: + with open(OUTPUT_FILE, "w") as f: + f.write(signed_message) + print(f"Warrant canary saved to {OUTPUT_FILE}") + return True + except Exception as e: + print(f"Error saving warrant canary: {e}") + return False + +async def post_to_matrix(config, signed_message): + """Post the signed warrant canary to Matrix room using nio library.""" + if not config.get('matrix', {}).get('enabled', False): + print("Matrix posting is disabled in config") + return False + + try: + from nio import AsyncClient, LoginResponse + + # Get Matrix config + homeserver = config['matrix']['homeserver'] + username = config['matrix']['username'] + password = config['matrix']['password'] + room_id = config['matrix']['room_id'] + + # Extract username without domain for login + user_id = username + if username.startswith('@'): + user_id = username[1:] # Remove @ prefix + if ':' in user_id: + user_id = user_id.split(':')[0] # Remove server part + + # Create client + client = AsyncClient(homeserver, username) + + # Login + print(f"Logging in as {username} on {homeserver}...") + response = await client.login(password) + + if isinstance(response, LoginResponse): + print("Login successful") + else: + print(f"Matrix login failed: {response}") + await client.close() + return False + + # Format message for Matrix + print(f"Posting canary to room {room_id}...") + try: + # Use HTML formatting for the message + content = { + "msgtype": "m.text", + "body": signed_message, # Plain text version + "format": "org.matrix.custom.html", + "formatted_body": f"<pre>{signed_message}</pre>" # HTML version with preformatted text + } + + response = await client.room_send( + room_id=room_id, + message_type="m.room.message", + content=content + ) + + print("Successfully posted warrant canary to Matrix room") + except Exception as e: + print(f"Error sending message: {e}") + await client.close() + return False + + # Logout and close + await client.logout() + await client.close() + + return True + except ImportError: + print("Error: matrix-nio library not installed. Install with: pip install matrix-nio") + return False + except Exception as e: + print(f"Error posting to Matrix: {e}") + return False + +def main(): + print("Generating We2.ee warrant canary...") + + # Load configuration + config = load_config() + gpg_key_id = config['gpg']['key_id'] + + # Create message + message = create_warrant_canary_message(config) + if not message: + print("Failed to create warrant canary message") + sys.exit(1) + + # Display the message + print("\nWarrant Canary Message Preview:") + print("-" * 50) + print(message) + print("-" * 50) + + # Confirm with user + user_input = input("\nDo you want to sign this message with GPG? (y/n): ") + if user_input.lower() != 'y': + print("Operation cancelled") + sys.exit(0) + + # Sign and save + signed_message = sign_with_gpg(message, gpg_key_id) + if not signed_message: + print("Failed to sign warrant canary message") + sys.exit(1) + + if save_warrant_canary(signed_message): + print("Warrant canary generated successfully!") + else: + print("Failed to save warrant canary") + sys.exit(1) + + # Post to Matrix if enabled + if config.get('matrix', {}).get('enabled', False): + post_to_matrix_input = input("\nDo you want to post the warrant canary to Matrix? (y/n): ") + if post_to_matrix_input.lower() == 'y': + asyncio.run(post_to_matrix(config, signed_message)) + +if __name__ == "__main__": + main() diff --git a/canary.txt b/canary.txt new file mode 100644 index 0000000..bc6b80f --- /dev/null +++ b/canary.txt @@ -0,0 +1,34 @@ +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA512 + +We2.ee Warrant Canary +Date: 2025-03-27 + +1. We have not received any National Security Letters. +2. We have not received any court orders under the Foreign Intelligence Surveillance Act. +3. We have not received any gag orders that prevent us from stating we have received legal process. +4. We have not been required to modify our systems to facilitate surveillance. +5. We have not been subject to any searches or seizures of our servers. + +Proofs: +NIST time: 2025-03-27T00:32:57.229589 UTC +Democracy Now! headline: "1,400+ Arrested in Turkey as Erdoğan Jails Istanbul Mayor & Intensifies Authoritarian Crackdown" +Bitcoin block #889596 hash: 000000000000000000018c38ea9043fd8710fa40d1cf90d5e541d050cd22b89d +Bitcoin block time: 2025-03-26 23:49:42 UTC + +-----BEGIN PGP SIGNATURE----- + +iQIzBAEBCgAdFiEEMjqKLEezdiJLNhO3U1smWu2+W0QFAmfknNcACgkQU1smWu2+ +W0RrlxAAinoE3ZsIKAEpt/qzKygQyUx06VozLL82wzLPQrICia+jOkzo6UHuYGmY +to4sj4SIOBaEyrdIhLvPG7Q6QRnrbn7NVasawRD484KsiO1+caPrnROFKJWyW/II +UNlAnmOCxGttu14SlKYPpgp/a6LnLQtciNTHEsj6A0i/JgP1kAPRjqOiM0UCXTKf +2MnNgwHHdjJt3f7AVJewzw5EPsW9ouh7VcIiIu9kZeuGotf0Gux5R8iTg9j2Cpum +FrsHhdfwgyFFasTtp+sTnsWvmtw86OpIYuqPpopkIe70e3w4m/+C7ybejqNiNlWh +1HCcFSyP17B6d516BCAKDJlrmCEKEQVz9MkTrqjpEKpZrVzo6Rl9bxQgN0QrohjV +buUQO9Zyu6Xl7BZSD4qPqGgGeTzRt8pi4BTWtrMMs+JKTel4TimzPONqLh8exYBa +Go5uDsbOAwnzbK/0VF9KIYqHc2t9pP5IgtUF3HGVZ0IputxTeDCF3uYJMiwO52cK +XWaSvSlXB+Nc6OIjHHxG35hflk4ch8ZSEchp8OmXIYiy0zC640YwnnAnosg1WCOA +UAeEvTO+QGyN7uP4rzGn9rtZgyoj5WT9GYGaiHFxrToCo9o3npOOQBAumcXLvP+B +6Wkd0RKajppKCVEtEKH0/aH57YGC9V5XdZ9o0aa1yDLpWXw7Ag8= +=5ZtT +-----END PGP SIGNATURE----- diff --git a/cleanup.py b/cleanup.py new file mode 100644 index 0000000..33e3c0f --- /dev/null +++ b/cleanup.py @@ -0,0 +1,133 @@ +import os +import json +import yaml +import httpx +import logging +from typing import List, Dict +from datetime import datetime, timedelta + +# Setup logging +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s' +) +logger = logging.getLogger(__name__) + +# Load paths and config +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +CONFIG_PATH = os.path.join(BASE_DIR, "config.yaml") +REGISTRATIONS_PATH = os.path.join(BASE_DIR, "registrations.json") + +def load_config() -> dict: + """Load configuration from yaml file.""" + with open(CONFIG_PATH, "r") as f: + return yaml.safe_load(f) + +def load_registrations() -> List[Dict]: + """Load current registrations from JSON file.""" + try: + with open(REGISTRATIONS_PATH, "r") as f: + return json.load(f) + except (FileNotFoundError, json.JSONDecodeError): + return [] + +def save_registrations(registrations: List[Dict]): + """Save updated registrations back to JSON file.""" + with open(REGISTRATIONS_PATH, "w") as f: + json.dump(registrations, f, indent=2) + +async def check_username_exists(username: str, homeserver: str) -> bool: + """ + Check if a username exists on the Matrix server. + Returns True if the username exists, False otherwise. + """ + url = f"https://{homeserver}/_matrix/client/v3/register/available?username={username}" + async with httpx.AsyncClient() as client: + try: + response = await client.get(url, timeout=5) + if response.status_code == 200: + # 200 OK with available=true means username exists + return response.json().get("available", False) + elif response.status_code == 400: + # 400 Bad Request means username is not available + return False + except httpx.RequestError as ex: + logger.error(f"Error checking username {username}: {ex}") + return False + return False + +async def cleanup_registrations(min_age_hours: int = 24): + """ + Clean up registrations by removing entries for usernames that don't exist on the server, + but only if they're older than min_age_hours. + + Never removes entries for existing Matrix users regardless of age. + """ + config = load_config() + registrations = load_registrations() + + if not registrations: + logger.info("No registrations found to clean up") + return + + logger.info(f"Starting cleanup of {len(registrations)} registrations") + logger.info(f"Will only remove non-existent users registered more than {min_age_hours} hours ago") + + # Track which entries to keep + entries_to_keep = [] + removed_count = 0 + too_new_count = 0 + exists_count = 0 + + current_time = datetime.utcnow() + + for entry in registrations: + username = entry["requested_name"] + reg_date = datetime.fromisoformat(entry["datetime"]) + age = current_time - reg_date + + # First check if the user exists on Matrix + exists = await check_username_exists(username, config["homeserver"]) + + if exists: + # Always keep entries for existing Matrix users + entries_to_keep.append(entry) + exists_count += 1 + logger.info(f"Keeping registration for existing user: {username}") + continue + + # For non-existent users, check if they're old enough to remove + if age < timedelta(hours=min_age_hours): + # Keep young entries even if user doesn't exist yet + entries_to_keep.append(entry) + too_new_count += 1 + logger.info(f"Keeping recent registration: {username} (age: {age.total_seconds()/3600:.1f} hours)") + else: + # Remove old entries where user doesn't exist + logger.info(f"Removing old registration: {username} (age: {age.total_seconds()/3600:.1f} hours)") + removed_count += 1 + + # Save updated registrations + save_registrations(entries_to_keep) + + logger.info(f"Cleanup complete:") + logger.info(f"- Kept {exists_count} entries for existing Matrix users") + logger.info(f"- Kept {too_new_count} entries younger than {min_age_hours} hours") + logger.info(f"- Removed {removed_count} old entries for non-existent users") + logger.info(f"- Total remaining entries: {len(entries_to_keep)}") + +if __name__ == "__main__": + import asyncio + import argparse + + parser = argparse.ArgumentParser(description="Clean up Matrix registration entries") + parser.add_argument( + "--min-age-hours", + type=int, + default=24, + help="Minimum age in hours before removing non-existent users (default: 24)" + ) + + args = parser.parse_args() + + asyncio.run(cleanup_registrations(args.min_age_hours)) diff --git a/conduwuit_logs.txt b/conduwuit_logs.txt new file mode 100644 index 0000000..2efa955 --- /dev/null +++ b/conduwuit_logs.txt @@ -0,0 +1,74 @@ +[2m2025-03-24T04:11:24.174302Z[0m [33m WARN[0m [2mconduwuit_core::config::check[0m[2m:[0m Config parameter "log_level" is unknown to conduwuit, ignoring. +[2m2025-03-24T04:11:24.174516Z[0m [32m INFO[0m [2mconduwuit::server[0m[2m:[0m 0.5.0 (b6e9dc3) [3mserver_name[0m[2m=[0mwe2.ee [3mdatabase_path[0m[2m=[0m"/var/lib/conduwuit/conduwuit.db" [3mlog_levels[0m[2m=[0minfo +[2m2025-03-24T04:11:24.999106Z[0m [32m INFO[0m [1mmain[0m[2m:[0m[1mstart[0m[2m:[0m[1mopen[0m[2m:[0m [2mconduwuit_database::engine::open[0m[2m:[0m Opened database. [3mcolumns[0m[2m=[0m87 [3msequence[0m[2m=[0m1216675526 [3mtime[0m[2m=[0m788.193091ms +[2m2025-03-24T04:11:25.232053Z[0m [32m INFO[0m [1mmain[0m[2m:[0m[1mstart[0m[2m:[0m [2mconduwuit_service::migrations[0m[2m:[0m Loaded RocksDB database with schema version 17 +[2m2025-03-24T04:11:25.625176Z[0m [32m INFO[0m [2mconduwuit_router::serve::plain[0m[2m:[0m Listening on [0.0.0.0:8008] + [2m2025-03-24T04:11:25.690807Z[0m [31mERROR[0m [1;31mconduwuit_service::sending::sender[0m[31m: [31mMissing pusher, [1;31muser_id[0m[31m: "@sij:we2.ee", [1;31mpushkey[0m[31m: "SlcxERw6XkNKx6Vu2GR4F4U1cvf98nyBM/Q6MIZ9TJg="[0m + [2;3mat[0m src/service/sending/sender.rs:761 [2;3mon[0m conduwuit:worker ThreadId(3) + [2;3min[0m conduwuit_service::sending::sender::[1mpush[0m [2;3mwith[0m [3muser_id[0m[2m=[0m"@sij:we2.ee" [3mpushkey[0m[2m=[0m"SlcxERw6XkNKx6Vu2GR4F4U1cvf98nyBM/Q6MIZ9TJg=" [3mevents[0m[2m=[0m1 + + [2m2025-03-24T04:11:25.696119Z[0m [31mERROR[0m [1;31mconduwuit_service::sending::sender[0m[31m: [31mMissing pusher, [1;31muser_id[0m[31m: "@tomasz:we2.ee", [1;31mpushkey[0m[31m: "https://ntfy.schildi.chat/up66QHbupxHL1u?up=1"[0m + [2;3mat[0m src/service/sending/sender.rs:761 [2;3mon[0m conduwuit:worker ThreadId(7) + [2;3min[0m conduwuit_service::sending::sender::[1mpush[0m [2;3mwith[0m [3muser_id[0m[2m=[0m"@tomasz:we2.ee" [3mpushkey[0m[2m=[0m"https://ntfy.schildi.chat/up66QHbupxHL1u?up=1" [3mevents[0m[2m=[0m1 + + [2m2025-03-24T04:11:25.724057Z[0m [31mERROR[0m [1;31mconduwuit_service::sending::sender[0m[31m: [31mMissing pusher, [1;31muser_id[0m[31m: "@tomasz:we2.ee", [1;31mpushkey[0m[31m: "https://ntfy.schildi.chat/upd06nnOhELBFx?up=1"[0m + [2;3mat[0m src/service/sending/sender.rs:761 [2;3mon[0m conduwuit:worker ThreadId(14) + [2;3min[0m conduwuit_service::sending::sender::[1mpush[0m [2;3mwith[0m [3muser_id[0m[2m=[0m"@tomasz:we2.ee" [3mpushkey[0m[2m=[0m"https://ntfy.schildi.chat/upd06nnOhELBFx?up=1" [3mevents[0m[2m=[0m1 + + [2m2025-03-24T04:11:25.806002Z[0m [31mERROR[0m [1;31mconduwuit_service::sending::sender[0m[31m: [31mMissing pusher, [1;31muser_id[0m[31m: "@tomasz:we2.ee", [1;31mpushkey[0m[31m: "https://ntfy.schildi.chat/upclfCDJ0va5d9?up=1"[0m + [2;3mat[0m src/service/sending/sender.rs:761 [2;3mon[0m conduwuit:worker ThreadId(7) + [2;3min[0m conduwuit_service::sending::sender::[1mpush[0m [2;3mwith[0m [3muser_id[0m[2m=[0m"@tomasz:we2.ee" [3mpushkey[0m[2m=[0m"https://ntfy.schildi.chat/upclfCDJ0va5d9?up=1" [3mevents[0m[2m=[0m1 + + [2m2025-03-24T04:11:25.835727Z[0m [31mERROR[0m [1;31mconduwuit_service::sending::sender[0m[31m: [31mMissing pusher, [1;31muser_id[0m[31m: "@sij:we2.ee", [1;31mpushkey[0m[31m: "M8ltVIFwxX/M1kKNuCGB7tQWAYppEJO6dkdY0JHJEvg="[0m + [2;3mat[0m src/service/sending/sender.rs:761 [2;3mon[0m conduwuit:worker ThreadId(14) + [2;3min[0m conduwuit_service::sending::sender::[1mpush[0m [2;3mwith[0m [3muser_id[0m[2m=[0m"@sij:we2.ee" [3mpushkey[0m[2m=[0m"M8ltVIFwxX/M1kKNuCGB7tQWAYppEJO6dkdY0JHJEvg=" [3mevents[0m[2m=[0m1 + + [2m2025-03-24T04:11:25.910755Z[0m [31mERROR[0m [1;31mconduwuit_service::sending::sender[0m[31m: [31mMissing pusher, [1;31muser_id[0m[31m: "@tomasz:we2.ee", [1;31mpushkey[0m[31m: "https://ntfy.schildi.chat/upkzMxi8WiQ5U5?up=1"[0m + [2;3mat[0m src/service/sending/sender.rs:761 [2;3mon[0m conduwuit:worker ThreadId(14) + [2;3min[0m conduwuit_service::sending::sender::[1mpush[0m [2;3mwith[0m [3muser_id[0m[2m=[0m"@tomasz:we2.ee" [3mpushkey[0m[2m=[0m"https://ntfy.schildi.chat/upkzMxi8WiQ5U5?up=1" [3mevents[0m[2m=[0m1 + + [2m2025-03-24T04:11:25.912156Z[0m [31mERROR[0m [1;31mconduwuit_service::sending::sender[0m[31m: [31mMissing pusher, [1;31muser_id[0m[31m: "@sij:we2.ee", [1;31mpushkey[0m[31m: "ly2ipat4bxsv/fQ0TRswYlU9zD5yhVb9mnrQ2NZ5e1A="[0m + [2;3mat[0m src/service/sending/sender.rs:761 [2;3mon[0m conduwuit:worker ThreadId(14) + [2;3min[0m conduwuit_service::sending::sender::[1mpush[0m [2;3mwith[0m [3muser_id[0m[2m=[0m"@sij:we2.ee" [3mpushkey[0m[2m=[0m"ly2ipat4bxsv/fQ0TRswYlU9zD5yhVb9mnrQ2NZ5e1A=" [3mevents[0m[2m=[0m1 + + [2m2025-03-24T04:11:25.933229Z[0m [31mERROR[0m [1;31mconduwuit_service::sending::sender[0m[31m: [31mMissing pusher, [1;31muser_id[0m[31m: "@tomasz:we2.ee", [1;31mpushkey[0m[31m: "https://ntfy.schildi.chat/upfxUUwV0ecpTR?up=1"[0m + [2;3mat[0m src/service/sending/sender.rs:761 [2;3mon[0m conduwuit:worker ThreadId(7) + [2;3min[0m conduwuit_service::sending::sender::[1mpush[0m [2;3mwith[0m [3muser_id[0m[2m=[0m"@tomasz:we2.ee" [3mpushkey[0m[2m=[0m"https://ntfy.schildi.chat/upfxUUwV0ecpTR?up=1" [3mevents[0m[2m=[0m1 + + [2m2025-03-24T04:11:26.118584Z[0m [31mERROR[0m [1;31mconduwuit_service::sending::sender[0m[31m: [31mMissing pusher, [1;31muser_id[0m[31m: "@tomasz:we2.ee", [1;31mpushkey[0m[31m: "https://ntfy.schildi.chat/upxH2vJWhUrLsD?up=1"[0m + [2;3mat[0m src/service/sending/sender.rs:761 [2;3mon[0m conduwuit:worker ThreadId(14) + [2;3min[0m conduwuit_service::sending::sender::[1mpush[0m [2;3mwith[0m [3muser_id[0m[2m=[0m"@tomasz:we2.ee" [3mpushkey[0m[2m=[0m"https://ntfy.schildi.chat/upxH2vJWhUrLsD?up=1" [3mevents[0m[2m=[0m1 + + [2m2025-03-24T04:11:26.206792Z[0m [31mERROR[0m [1;31mconduwuit_service::sending::sender[0m[31m: [31mMissing pusher, [1;31muser_id[0m[31m: "@tomasz:we2.ee", [1;31mpushkey[0m[31m: "https://ntfy.sh/up4KrJgOMfmGxD?up=1"[0m + [2;3mat[0m src/service/sending/sender.rs:761 [2;3mon[0m conduwuit:worker ThreadId(7) + [2;3min[0m conduwuit_service::sending::sender::[1mpush[0m [2;3mwith[0m [3muser_id[0m[2m=[0m"@tomasz:we2.ee" [3mpushkey[0m[2m=[0m"https://ntfy.sh/up4KrJgOMfmGxD?up=1" [3mevents[0m[2m=[0m1 + + [2m2025-03-24T04:11:26.488520Z[0m [31mERROR[0m [1;31mconduwuit_service::sending::sender[0m[31m: [31mMissing pusher, [1;31muser_id[0m[31m: "@tomasz:we2.ee", [1;31mpushkey[0m[31m: "https://ntfy.schildi.chat/up2nJSKdtIoisj?up=1"[0m + [2;3mat[0m src/service/sending/sender.rs:761 [2;3mon[0m conduwuit:worker ThreadId(14) + [2;3min[0m conduwuit_service::sending::sender::[1mpush[0m [2;3mwith[0m [3muser_id[0m[2m=[0m"@tomasz:we2.ee" [3mpushkey[0m[2m=[0m"https://ntfy.schildi.chat/up2nJSKdtIoisj?up=1" [3mevents[0m[2m=[0m1 + + [2m2025-03-24T04:11:26.528252Z[0m [31mERROR[0m [1;31mconduwuit_service::sending::sender[0m[31m: [31mMissing pusher, [1;31muser_id[0m[31m: "@sij:we2.ee", [1;31mpushkey[0m[31m: "TjhzIT+QrIPqAAdaxzmfxtraX3xlYN9CRx3T8snGJsY="[0m + [2;3mat[0m src/service/sending/sender.rs:761 [2;3mon[0m conduwuit:worker ThreadId(14) + [2;3min[0m conduwuit_service::sending::sender::[1mpush[0m [2;3mwith[0m [3muser_id[0m[2m=[0m"@sij:we2.ee" [3mpushkey[0m[2m=[0m"TjhzIT+QrIPqAAdaxzmfxtraX3xlYN9CRx3T8snGJsY=" [3mevents[0m[2m=[0m1 + + [2m2025-03-24T04:11:26.898903Z[0m [31mERROR[0m [1;31mconduwuit_service::sending::sender[0m[31m: [31mMissing pusher, [1;31muser_id[0m[31m: "@sij:we2.ee", [1;31mpushkey[0m[31m: "kVAJHyqTY6nD3fXuMOz5uFQy11KF6nNw9jzZT9vH8lc="[0m + [2;3mat[0m src/service/sending/sender.rs:761 [2;3mon[0m conduwuit:worker ThreadId(2) + [2;3min[0m conduwuit_service::sending::sender::[1mpush[0m [2;3mwith[0m [3muser_id[0m[2m=[0m"@sij:we2.ee" [3mpushkey[0m[2m=[0m"kVAJHyqTY6nD3fXuMOz5uFQy11KF6nNw9jzZT9vH8lc=" [3mevents[0m[2m=[0m1 + + [2m2025-03-24T04:11:26.905588Z[0m [31mERROR[0m [1;31mconduwuit_service::sending::sender[0m[31m: [31mMissing pusher, [1;31muser_id[0m[31m: "@oddlid:we2.ee", [1;31mpushkey[0m[31m: "oUT1KtxnLMQxuG7otnPxZlY28ZFP7dyC0B3LQlQJm70="[0m + [2;3mat[0m src/service/sending/sender.rs:761 [2;3mon[0m conduwuit:worker ThreadId(7) + [2;3min[0m conduwuit_service::sending::sender::[1mpush[0m [2;3mwith[0m [3muser_id[0m[2m=[0m"@oddlid:we2.ee" [3mpushkey[0m[2m=[0m"oUT1KtxnLMQxuG7otnPxZlY28ZFP7dyC0B3LQlQJm70=" [3mevents[0m[2m=[0m1 + + [2m2025-03-24T04:11:27.008417Z[0m [31mERROR[0m [1;31mconduwuit_service::sending::sender[0m[31m: [31mMissing pusher, [1;31muser_id[0m[31m: "@sij:we2.ee", [1;31mpushkey[0m[31m: "yBBcAp+lJHry3qIkOnjtuXQ+fvXsmOqIJT3s+723Wf0="[0m + [2;3mat[0m src/service/sending/sender.rs:761 [2;3mon[0m conduwuit:worker ThreadId(7) + [2;3min[0m conduwuit_service::sending::sender::[1mpush[0m [2;3mwith[0m [3muser_id[0m[2m=[0m"@sij:we2.ee" [3mpushkey[0m[2m=[0m"yBBcAp+lJHry3qIkOnjtuXQ+fvXsmOqIJT3s+723Wf0=" [3mevents[0m[2m=[0m1 + + [2m2025-03-24T04:11:27.111117Z[0m [31mERROR[0m [1;31mconduwuit_service::sending::sender[0m[31m: [31mMissing pusher, [1;31muser_id[0m[31m: "@coldsideofyourpillow:we2.ee", [1;31mpushkey[0m[31m: "https://updates.push.services.mozilla.com/wpush/v1/gAAAAABngkOarCdQHaOOcbWvEq2NRTaqanvveX2x7tORFgnhjomYiTMOjaPw9zTrQVe8ConiPBHKvNAMd96-jLQURG5Q83zk1d3lT2mPW6ek6MatC7-f3ipoxJC-8hwiXLtARI5WfNl-"[0m + [2;3mat[0m src/service/sending/sender.rs:761 [2;3mon[0m conduwuit:worker ThreadId(2) + [2;3min[0m conduwuit_service::sending::sender::[1mpush[0m [2;3mwith[0m [3muser_id[0m[2m=[0m"@coldsideofyourpillow:we2.ee" [3mpushkey[0m[2m=[0m"https://updates.push.services.mozilla.com/wpush/v1/gAAAAABngkOarCdQHaOOcbWvEq2NRTaqanvveX2x7tORFgnhjomYiTMOjaPw9zTrQVe8ConiPBHKvNAMd96-jLQURG5Q83zk1d3lT2mPW6ek6MatC7-f3ipoxJC-8hwiXLtARI5WfNl-" [3mevents[0m[2m=[0m1 + + [2m2025-03-24T04:11:27.141576Z[0m [31mERROR[0m [1;31mconduwuit_service::sending::sender[0m[31m: [31mMissing pusher, [1;31muser_id[0m[31m: "@tomasz:we2.ee", [1;31mpushkey[0m[31m: "https://ntfy.schildi.chat/upqkpYOQT8t3jd?up=1"[0m + [2;3mat[0m src/service/sending/sender.rs:761 [2;3mon[0m conduwuit:worker ThreadId(14) + [2;3min[0m conduwuit_service::sending::sender::[1mpush[0m [2;3mwith[0m [3muser_id[0m[2m=[0m"@tomasz:we2.ee" [3mpushkey[0m[2m=[0m"https://ntfy.schildi.chat/upqkpYOQT8t3jd?up=1" [3mevents[0m[2m=[0m1 + +[2m2025-03-24T04:11:36.014821Z[0m [33m WARN[0m [2mhickory_proto::udp::udp_client_stream[0m[2m:[0m expected message id: 35283 got: 31381, dropped diff --git a/refresh_token.sh b/refresh_token.sh index 8ff8b49..82df071 100755 --- a/refresh_token.sh +++ b/refresh_token.sh @@ -6,29 +6,21 @@ TOKEN_FILE="$BASE_PATH/.registration_token" LOG_FILE="$BASE_PATH/token_refresh.log" BACKUP_PATH="/home/sij/conduwuit_backup" -# Server configuration +# Server/domain info SERVER_DOMAIN="we2.ee" -HOST_PORT=8448 -CONTAINER_PORT=6167 CONTAINER_NAME="conduwuit" -CONTAINER_IMAGE="ghcr.io/girlbossceo/conduwuit:v0.5.0-rc3-b6e9dc3d98704c56027219d3775336910a0136c6" - -# Performance tuning -DB_READ_CACHE_MB=16384 # 16GB for read cache -DB_WRITE_BUFFER_MB=2048 # 2GB write buffer -CACHE_MODIFIER=4.0 # 4x default LRU caches -DB_POOL_WORKERS=128 # Optimized for NVMe -STREAM_WIDTH_SCALE=2.0 # Concurrent operations scaling -STREAM_AMPLIFICATION=4096 # Batch size for operations -MAX_REQUEST_SIZE=104857600 # 100MB uploads -BLURHASH_MAX_SIZE=134217728 # 128MB for blurhash processing +CONTAINER_IMAGE="conduwuit:custom" +ADDRESS='["0.0.0.0", "::"]' +PORT=8008 # Auto-join room configuration -AUTO_JOIN_ROOMS="[\"#pub:$SERVER_DOMAIN\",\"#home:$SERVER_DOMAIN\"]" +AUTO_JOIN_ROOMS='["#server:we2.ee"]' -# Function to log with timestamp +# Function to log with timestamp to both file and terminal log() { - echo "$(date --iso-8601=seconds) $1" >> "$LOG_FILE" + local message="$(date --iso-8601=seconds) $1" + echo "$message" >> "$LOG_FILE" # Write to log file + echo "$message" # Print to terminal } # Generate new token (6 random hex characters) @@ -43,13 +35,13 @@ fi log "Generated new registration token" -# Recreate conduwuit container -docker stop $CONTAINER_NAME -docker rm $CONTAINER_NAME +# Stop and remove existing container +docker stop "$CONTAINER_NAME" 2>/dev/null +docker rm "$CONTAINER_NAME" 2>/dev/null +# Launch new container docker run -d \ - -p 0.0.0.0:${HOST_PORT}:${CONTAINER_PORT} \ - -v db:/var/lib/conduwuit/ \ + -v "db:/var/lib/conduwuit/" \ -v "${TOKEN_FILE}:/.registration_token:ro" \ -v "${BACKUP_PATH}:/backup" \ -e CONDUWUIT_SERVER_NAME="$SERVER_DOMAIN" \ @@ -57,28 +49,44 @@ docker run -d \ -e CONDUWUIT_DATABASE_BACKUP_PATH="/backup" \ -e CONDUWUIT_ALLOW_REGISTRATION=true \ -e CONDUWUIT_REGISTRATION_TOKEN_FILE="/.registration_token" \ - -e CONDUWUIT_PORT=$CONTAINER_PORT \ - -e CONDUWUIT_ADDRESS="0.0.0.0" \ + -e CONDUWUIT_ADDRESS="$ADDRESS" \ + -e CONDUWUIT_PORT="$PORT" \ -e CONDUWUIT_NEW_USER_DISPLAYNAME_SUFFIX="" \ + -e CONDUWUIT_AUTO_JOIN_ROOMS="$AUTO_JOIN_ROOMS" \ + -e CONDUWUIT_FORGET_FORCED_UPON_LEAVE=true \ + -e CONDUWUIT_DB_CACHE_CAPACITY_MB=1024 \ + -e CONDUWUIT_DB_WRITE_BUFFER_CAPACITY_MB=256 \ + -e CONDUWUIT_DB_POOL_WORKERS=64 \ + -e CONDUWUIT_DB_POOL_WORKERS_LIMIT=128 \ + -e CONDUWUIT_STREAM_AMPLIFICATION=8192 \ + -e CONDUWUIT_MAX_REQUEST_SIZE=33554432 \ + -e CONDUWUIT_CACHE_CAPACITY_MODIFIER=1.5 \ + -e CONDUWUIT_ALLOW_FEDERATION=true \ -e CONDUWUIT_ALLOW_PUBLIC_ROOM_DIRECTORY_OVER_FEDERATION=true \ -e CONDUWUIT_ALLOW_PUBLIC_ROOM_DIRECTORY_WITHOUT_AUTH=true \ - -e CONDUWUIT_ALLOW_FEDERATION=true \ - -e CONDUWUIT_AUTO_JOIN_ROOMS="$AUTO_JOIN_ROOMS" \ - -e CONDUWUIT_DB_CACHE_CAPACITY_MB=$DB_READ_CACHE_MB \ - -e CONDUWUIT_DB_WRITE_BUFFER_CAPACITY_MB=$DB_WRITE_BUFFER_MB \ - -e CONDUWUIT_CACHE_CAPACITY_MODIFIER=$CACHE_MODIFIER \ - -e CONDUWUIT_DB_POOL_WORKERS=$DB_POOL_WORKERS \ - -e CONDUWUIT_STREAM_WIDTH_SCALE=$STREAM_WIDTH_SCALE \ - -e CONDUWUIT_STREAM_AMPLIFICATION=$STREAM_AMPLIFICATION \ - -e CONDUWUIT_MAX_REQUEST_SIZE=$MAX_REQUEST_SIZE \ - -e CONDUWUIT_BLURHASH_MAX_RAW_SIZE=$BLURHASH_MAX_SIZE \ - --name $CONTAINER_NAME \ + -e CONDUWUIT_WELL_KNOWN_CONN_TIMEOUT=30 \ + -e CONDUWUIT_FEDERATION_TIMEOUT=600 \ + -e CONDUWUIT_FEDERATION_IDLE_TIMEOUT=60 \ + -e CONDUWUIT_SENDER_TIMEOUT=600 \ + -e CONDUWUIT_SENDER_IDLE_TIMEOUT=360 \ + -e CONDUWUIT_SENDER_SHUTDOWN_TIMEOUT=30 \ + -e CONDUWUIT_DNS_CACHE_ENTRIES=1000 \ + -e CONDUWUIT_DNS_MIN_TTL=300 \ + -e CONDUWUIT_DNS_MIN_TTL_NXDOMAIN=600 \ + -e CONDUWUIT_DNS_TCP_FALLBACK=true \ + -e CONDUWUIT_IP_LOOKUP_STRATEGY=3 \ + -e RUST_LOG="conduwuit=trace,reqwest=trace,hickory_proto=trace" \ + --network host \ + --name "$CONTAINER_NAME" \ --restart unless-stopped \ - $CONTAINER_IMAGE - + "$CONTAINER_IMAGE" if [ $? -ne 0 ]; then log "ERROR: Failed to create new conduwuit container" exit 1 fi -log "Successfully recreated conduwuit container with new token" +log "Successfully recreated container \"$CONTAINER_NAME\" with image \"$CONTAINER_IMAGE\" and these parameters:" +log " - domain: $SERVER_DOMAIN" +log " - address: $ADDRESS" +log " - port: $PORT" +log " - auto-join rooms: $AUTO_JOIN_ROOMS" diff --git a/registration.py b/registration.py index 8f5323a..dcb0e93 100644 --- a/registration.py +++ b/registration.py @@ -368,6 +368,8 @@ async def register( raise HTTPException(status_code=500, detail="Registration token file not found.") time_until_reset = get_time_until_reset_str(now) + + # Plain text email body email_body = config["email_body"].format( homeserver=config["homeserver"], registration_token=token, @@ -375,9 +377,23 @@ async def register( utc_time=now.strftime("%H:%M:%S"), time_until_reset=time_until_reset ) + + # HTML email body + email_body_html = config.get("email_body_html", "").format( + homeserver=config["homeserver"], + registration_token=token, + requested_username=requested_username, + utc_time=now.strftime("%H:%M:%S"), + time_until_reset=time_until_reset + ) msg = EmailMessage() msg.set_content(email_body) + + # Add HTML version if configured + if email_body_html: + msg.add_alternative(email_body_html, subtype='html') + msg["Subject"] = config["email_subject"].format(homeserver=config["homeserver"]) msg["From"] = config["smtp"]["username"] msg["To"] = email diff --git a/relaunch_without_refresh.sh b/relaunch_without_refresh.sh new file mode 100755 index 0000000..7ef9016 --- /dev/null +++ b/relaunch_without_refresh.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +# File paths +BASE_PATH="/home/sij/hand_of_morpheus" +TOKEN_FILE="$BASE_PATH/.registration_token" +BACKUP_PATH="/home/sij/conduwuit_backup" + +# Server/domain info +SERVER_DOMAIN="we2.ee" +HOST="127.0.0.1" +HOST_PORT=8448 +CONTAINER_PORT=6167 +CONTAINER_NAME="conduwuit" +CONTAINER_IMAGE="ghcr.io/girlbossceo/conduwuit:v0.5.0-rc3-b6e9dc3d98704c56027219d3775336910a0136c6" + +# Keep max request size +MAX_REQUEST_SIZE=33554432 # 32MB + +# Auto-join room configuration +AUTO_JOIN_ROOMS="[\"#pub:we2.ee\",\"#home:we2.ee\"]" +TRUSTED_SERVERS="[\"matrix.org\",\"envs.net\",\"tchncs.de\"]" +BANNED_SERVERS="[\"tzchat.org\"]" +NO_MEDIA_FROM="[\"bark.lgbt\",\"cutefunny.art\",\"tzchat.org\",\"nitro.chat\",\"lolispace.moe\",\"lolisho.chat\",\"midov.pl\"]" + +# Recreate Conduwuit container +docker stop "$CONTAINER_NAME" +docker rm "$CONTAINER_NAME" + +docker run -d \ + -p "${HOST}:${HOST_PORT}:${CONTAINER_PORT}" \ + -v "db:/var/lib/conduwuit/" \ + -v "${TOKEN_FILE}:/.registration_token:ro" \ + -v "${BACKUP_PATH}:/backup" \ + -e CONDUWUIT_SERVER_NAME="$SERVER_DOMAIN" \ + -e CONDUWUIT_DATABASE_PATH="/var/lib/conduwuit/conduwuit.db" \ + -e CONDUWUIT_DATABASE_BACKUP_PATH="/backup" \ + -e CONDUWUIT_ALLOW_REGISTRATION=true \ + -e CONDUWUIT_REGISTRATION_TOKEN_FILE="/.registration_token" \ + -e CONDUWUIT_PORT=$CONTAINER_PORT \ + -e CONDUWUIT_ADDRESS="0.0.0.0" \ + -e CONDUWUIT_NEW_USER_DISPLAYNAME_SUFFIX="" \ + -e CONDUWUIT_ALLOW_PUBLIC_ROOM_DIRECTORY_OVER_FEDERATION=true \ + -e CONDUWUIT_ALLOW_PUBLIC_ROOM_DIRECTORY_WITHOUT_AUTH=false \ + -e CONDUWUIT_ALLOW_FEDERATION=true \ + -e CONDUWUIT_AUTO_JOIN_ROOMS="$AUTO_JOIN_ROOMS" \ + -e CONDUWUIT_MAX_REQUEST_SIZE=$MAX_REQUEST_SIZE \ + -e CONDUWUIT_LOG=debug \ + -e CONDUWUIT_LOG_SPAN_EVENTS=all \ + -e CONDUWUIT_LOG_COLORS=true \ + -e CONDUWUIT_TRUSTED_SERVERS=$TRUSTED_SERVERS \ + -e CONDUWUIT_PRUNE_MISSING_MEDIA=true \ + -e CONDUWUIT_ALLOW_LEGACY_MEDIA=false \ + -e CONDUWUIT_IP_RANGE_DENYLIST="[]" \ + -e CONDUWUIT_AUTO_DEACTIVATE_BANNED_ROOM_ATTEMPTS=true \ + -e CONDUWUIT_PREVENT_MEDIA_DOWNLOADS_FROM=$NO_MEDIA_FROM \ + -e CONDUWUIT_IP_LOOKUP_STRATEGY="1" \ + -e CONDUWUIT_QUERY_OVER_TCP_ONLY=true \ + -e CONDUWUIT_QUERY_ALL_NAMESERVERS=false \ + --name "$CONTAINER_NAME" \ + --restart unless-stopped \ + "$CONTAINER_IMAGE" diff --git a/update_conduwuit.sh b/update_conduwuit.sh new file mode 100644 index 0000000..c0ac7e2 --- /dev/null +++ b/update_conduwuit.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# Navigate to the repository directory +cd "$HOME/conduwuit" || exit + +# Pull the latest changes +git pull + +# Build the Docker image using Nix +nix build -L --extra-experimental-features "nix-command flakes" .#oci-image-x86_64-linux-musl-all-features + +# Extract the image tarball path from the build result +IMAGE_TAR_PATH=$(nix path-info -r .#oci-image-x86_64-linux-musl-all-features)/image.tar.gz + +# Load the image into Docker and tag it +docker load < "$IMAGE_TAR_PATH" | awk '/Loaded image:/ { print $3 }' | xargs -I {} docker tag {} conduwuit:custom + +# Confirm tagging +echo "Docker image tagged as conduwuit:custom"