Justinchellisvia treechat·6mo
❤️ 0 Likes · ⚡ 0 Tips
{
  "txid": "3507e885496872d4f7c54c45e3cfc6854cd71cd9a8513739eb49b41a8c9d716a",
  "block_height": 0,
  "time": null,
  "app": "treechat",
  "type": "post",
  "map_content": "Create start.py in teranode directory",
  "media_type": "text/markdown",
  "filename": "|",
  "author": "14aqJ2hMtENYJVCJaekcrqi12fiZJzoWGK",
  "display_name": "Justinchellis",
  "channel": null,
  "parent_txid": null,
  "ref_txid": null,
  "tags": null,
  "reply_count": 1,
  "like_count": 0,
  "timestamp": "2025-09-26T08:31:57.000Z",
  "media_url": null,
  "aip_verified": true,
  "has_access": true,
  "attachments": [],
  "ui_name": "Justinchellis",
  "ui_display_name": "Justinchellis",
  "ui_handle": "Justinchellis",
  "ui_display_raw": "Justinchellis",
  "ui_signer": "14aqJ2hMtENYJVCJaekcrqi12fiZJzoWGK",
  "ref_ui_name": "unknown",
  "ref_ui_signer": "unknown"
}
Signed by14aqJ2hMtENYJVCJaekcrqi12fiZJzoWGKAIP!

Replies (1)

Justinchellisvia treechat·6mo
Replying to #3507e885
❤️ 0 Likes · ⚡ 0 Tips
{
  "txid": "1d76b68e6ec3026ffc3110bfbdadc986eb3763bbcb0bd95b1b6588e7cad7a1c1",
  "block_height": 0,
  "time": null,
  "app": "treechat",
  "type": "reply",
  "map_content": "Save the script \r\nimport os\r\nimport sys\r\nimport datetime\r\nimport subprocess\r\nimport time\r\nimport signal\r\nimport shutil\r\nimport re\r\nfrom typing import Optional\r\nimport json\r\nfrom urllib.request import urlopen\r\nfrom urllib.error import URLError\r\nSCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))\r\nSETTINGS_FILE = os.path.join(SCRIPT_DIR, 'base', 'settings_local.conf')\r\nBACKUP_FILE = f\"{SETTINGS_FILE}.backup.{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}\"\r\nGREEN = '\\033[0;32m'\r\nRED = '\\033[0;31m'\r\nYELLOW = '\\033[1;33m'\r\nNC = '\\033[0m'\r\nUSE_NGROK = True\r\ndef echo_info(msg: str):\r\n    print(f\"{GREEN}[INFO]{NC} {msg}\")\r\ndef echo_error(msg: str):\r\n    print(f\"{RED}[ERROR]{NC} {msg}\")\r\ndef echo_warning(msg: str):\r\n    print(f\"{YELLOW}[WARNING]{NC} {msg}\")\r\ndef parse_args():\r\n    global USE_NGROK\r\n    for arg in sys.argv[1:]:\r\n        if arg == '--no-ngrok':\r\n            USE_NGROK = False\r\n        elif arg in ['--help', '-h']:\r\n            print(f\"Usage: {sys.argv[0]} [--no-ngrok]\")\r\n            print(\"\")\r\n            print(\"Options:\")\r\n            print(\"  --no-ngrok    Skip ngrok setup (for users with existing domain/proxy)\")\r\n            print(\"  --help, -h    Show this help message\")\r\n            sys.exit(0)\r\ndef check_prerequisites():\r\n    echo_info(\"Checking prerequisites...\")\r\n    \r\n    if USE_NGROK:\r\n        if shutil.which('ngrok') is None:\r\n            echo_error(\"ngrok is not installed. Please install ngrok and configure it with an auth token.\")\r\n            echo_error(\"Visit https://ngrok.com/download for installation instructions.\")\r\n            echo_error(\"Or use --no-ngrok if you have your own domain/proxy setup.\")\r\n            sys.exit(1)\r\n    \r\n    if shutil.which('docker') is None:\r\n        echo_error(\"Docker is not installed. Please install Docker.\")\r\n        sys.exit(1)\r\n    \r\n    docker_compose_cmd = shutil.which('docker-compose') or (shutil.which('docker') and 'docker compose')\r\n    if docker_compose_cmd is None:\r\n        echo_error(\"Docker Compose is not installed. Please install Docker Compose.\")\r\n        sys.exit(1)\r\n    \r\n    if not os.path.isfile(SETTINGS_FILE):\r\n        echo_error(f\"Settings file not found at: {SETTINGS_FILE}\")\r\n        sys.exit(1)\r\n    \r\n    echo_info(\"All prerequisites met.\")\r\ndef process_ngrok_url(input_str: str) -> tuple:\r\n    input_str = input_str.rstrip('/')\r\n    if input_str.startswith(('http://', 'https://')):\r\n        url = input_str\r\n        domain = url.split('://', 1)[1]\r\n    else:\r\n        domain = input_str\r\n        url = f\"https://{input_str}\"\r\n    domain = domain.split('/', 1)[0]\r\n    return url, domain\r\ndef prompt_for_inputs():\r\n    print()\r\n    echo_info(\"=== Teratestnet Configuration ===\")\r\n    print()\r\n    echo_info(\"Node Operation Mode Selection\")\r\n    print()\r\n    print(\"Please select how you want to run your Teranode:\")\r\n    print(\"  1. Full mode - Fully participate in the network (requires ngrok or public domain)\")\r\n    print(\"  2. Listen-only mode - Only receive blocks, no mining or transaction relay\")\r\n    print()\r\n    echo_info(\"Listen-only mode is ideal for monitoring the network without external access\")\r\n    print()\r\n    mode_choice = input(\"Select mode (1 for Full, 2 for Listen-only): \").strip()\r\n    if mode_choice == \"2\":\r\n        listen_mode = \"listen_only\"\r\n        echo_info(\"Listen-only mode selected - ngrok configuration not required\")\r\n        global USE_NGROK\r\n        USE_NGROK = False\r\n        ngrok_url = \"http://localhost\"\r\n        ngrok_domain = \"localhost\"\r\n    elif mode_choice == \"1\":\r\n        listen_mode = \"full\"\r\n        echo_info(\"Full mode selected - external access configuration required\")\r\n        if USE_NGROK:\r\n            url_input = input(\"Enter your ngrok domain (e.g., example.ngrok-free.app): \").strip()\r\n        else:\r\n            url_input = input(\"Enter your domain/URL (e.g., teranode.example.com or https://teranode.example.com): \").strip()\r\n        if not url_input:\r\n            echo_error(\"Domain/URL cannot be empty for full mode\")\r\n            sys.exit(1)\r\n        ngrok_url, ngrok_domain = process_ngrok_url(url_input)\r\n    else:\r\n        echo_error(\"Invalid selection. Please run the script again and select 1 or 2\")\r\n        sys.exit(1)\r\n    print()\r\n    echo_info(\"RPC Credentials Configuration\")\r\n    echo_info(\"You can either:\")\r\n    print(\"  1. Set RPC credentials now (automatic)\")\r\n    print(\"  2. Configure them manually in settings.conf later\")\r\n    print()\r\n    echo_info(\"Note: RPC credentials are required for remote access to your node\")\r\n    print()\r\n    set_rpc_creds = input(\"Would you like to set RPC credentials now? (y/n): \").strip().lower()\r\n    rpc_user = \"\"\r\n    rpc_pass = \"\"\r\n    if set_rpc_creds == 'y':\r\n        rpc_user = input(\"Enter RPC username: \").strip()\r\n        if not rpc_user:\r\n            echo_error(\"RPC username cannot be empty\")\r\n            sys.exit(1)\r\n        rpc_pass = input(\"Enter RPC password: \")\r\n        rpc_pass_confirm = input(\"Confirm RPC password: \")\r\n        if rpc_pass != rpc_pass_confirm:\r\n            echo_error(\"Passwords do not match\")\r\n            sys.exit(1)\r\n        if not rpc_pass:\r\n            echo_error(\"RPC password cannot be empty\")\r\n            sys.exit(1)\r\n    else:\r\n        echo_info(\"Skipping RPC credentials setup\")\r\n        echo_info(f\"You can add them manually to {SETTINGS_FILE}:\")\r\n        print(\"  rpc_user = your_username\")\r\n        print(\"  rpc_pass = your_password\")\r\n    client_name = input(\"Enter Human Readable Client Name (for web interface viewing only) (optional, press Enter to skip): \").strip()\r\n    if client_name and len(client_name) > 100:\r\n        echo_warning(f\"Client Name is quite long ({len(client_name)} characters). Consider using a shorter identifier.\")\r\n    mining_enabled = \"false\"\r\n    mining_address = \"\"\r\n    miner_id = \"\"\r\n    if listen_mode == \"full\":\r\n        print()\r\n        echo_info(\"Mining Configuration\")\r\n        echo_info(\"Note: Mining requires computational resources and will use CPU\")\r\n        print()\r\n        enable_mining = input(\"Would you like to enable CPU mining? (y/n): \").strip().lower()\r\n        if enable_mining == 'y':\r\n            mining_enabled = \"true\"\r\n            mining_address = input(\"Enter Bitcoin address for mining rewards: \").strip()\r\n            while not mining_address:\r\n                echo_error(\"Mining address cannot be empty when mining is enabled\")\r\n                mining_address = input(\"Enter Bitcoin address for mining rewards: \").strip()\r\n            miner_id = input(\"Enter Miner ID/Signature (e.g., /YourMinerID/) (optional, press Enter to skip): \").strip()\r\n            if not miner_id:\r\n                miner_id = \"/Teratestnet/\"\r\n                echo_info(f\"Using default miner ID: {miner_id}\")\r\n            echo_info(\"Mining will be enabled with:\")\r\n            print(f\"  - Mining address: {mining_address}\")\r\n            print(f\"  - Miner ID: {miner_id}\")\r\n            print(\"  - CPU threads: 2\")\r\n        else:\r\n            echo_info(\"Mining disabled\")\r\n    else:\r\n        echo_info(\"Mining is not available in listen-only mode\")\r\n    print()\r\n    echo_info(\"Configuration summary:\")\r\n    print(f\"  - Mode: {'Listen-only' if listen_mode == 'listen_only' else 'Full'}\")\r\n    if listen_mode == \"full\":\r\n        if USE_NGROK:\r\n            print(f\"  - Ngrok Domain: {ngrok_domain}\")\r\n        else:\r\n            print(f\"  - Domain: {ngrok_domain}\")\r\n        print(f\"  - Full URL: {ngrok_url}\")\r\n    if rpc_user:\r\n        print(f\"  - RPC Username: {rpc_user}\")\r\n        print(\"  - RPC Password: [hidden]\")\r\n    else:\r\n        print(\"  - RPC Credentials: To be configured manually\")\r\n    if mining_enabled == \"true\":\r\n        print(\"  - Mining: Enabled\")\r\n        print(f\"  - Mining Address: {mining_address}\")\r\n        print(f\"  - Miner ID: {miner_id}\")\r\n    else:\r\n        print(\"  - Mining: Disabled\")\r\n    if client_name:\r\n        print(f\"  - Client Name: {client_name}\")\r\n    print()\r\n    confirm = input(\"Is this correct? (y/n): \").strip().lower()\r\n    if confirm != 'y':\r\n        echo_info(\"Configuration cancelled.\")\r\n        sys.exit(0)\r\n    return {\r\n        'listen_mode': listen_mode,\r\n        'ngrok_url': ngrok_url if listen_mode == 'full' else None,\r\n        'ngrok_domain': ngrok_domain if listen_mode == 'full' else None,\r\n        'rpc_user': rpc_user,\r\n        'rpc_pass': rpc_pass,\r\n        'client_name': client_name,\r\n        'mining_enabled': mining_enabled,\r\n        'mining_address': mining_address,\r\n        'miner_id': miner_id\r\n    }\r\ndef backup_settings():\r\n    echo_info(\"Creating backup of settings.conf...\")\r\n    shutil.copy(SETTINGS_FILE, BACKUP_FILE)\r\n    echo_info(f\"Backup created at: {BACKUP_FILE}\")\r\ndef update_settings(config: dict):\r\n    echo_info(\"Updating settings.conf...\")\r\n    with open(SETTINGS_FILE, 'r') as f:\r\n        lines = f.readlines()\r\n    def update_or_add(key: str, value: str):\r\n        for i, line in enumerate(lines):\r\n            if line.strip().startswith(key):\r\n                lines[i] = f\"{key} = {value}\\n\"\r\n                return True\r\n        lines.append(f\"{key} = {value}\\n\")\r\n        return False\r\n    # Update listen_mode\r\n    updated = update_or_add('listen_mode', config['listen_mode'])\r\n    echo_info(f\"{'Updated' if updated else 'Added'} listen_mode to: {config['listen_mode']}\")\r\n    if config['listen_mode'] == 'full':\r\n        key = 'asset_httpPublicAddress.docker.m'\r\n        value = f\"{config['ngrok_url']}/api/v1\"\r\n        updated = update_or_add(key, value)\r\n        echo_info(f\"{'Updated' if updated else 'Added'} asset_httpPublicAddress\")\r\n    if config['rpc_user']:\r\n        updated = update_or_add('rpc_user', config['rpc_user'])\r\n        echo_info(f\"{'Updated' if updated else 'Added'} rpc_user\")\r\n        updated = update_or_add('rpc_pass', config['rpc_pass'])\r\n        echo_info(f\"{'Updated' if updated else 'Added'} rpc_pass\")\r\n    else:\r\n        echo_warning(\"RPC credentials not configured. Remember to add them manually to settings.conf\")\r\n    if config['miner_id']:\r\n        updated = update_or_add('coinbase_arbitrary_text', config['miner_id'])\r\n        echo_info(f\"{'Updated' if updated else 'Added'} coinbase_arbitrary_text (Miner ID)\")\r\n    if config['client_name']:\r\n        updated = update_or_add('clientName', config['client_name'])\r\n        echo_info(f\"{'Updated' if updated else 'Added'} clientName (Client Name)\")\r\n    with open(SETTINGS_FILE, 'w') as f:\r\n        f.writelines(lines)\r\n    echo_info(\"Settings updated successfully.\")\r\ndef start_docker_compose(config: dict):\r\n    echo_info(\"Starting Teratestnet with Docker Compose...\")\r\n    os.chdir(SCRIPT_DIR)\r\n    os.environ['MINING_ENABLED'] = config['mining_enabled']\r\n    os.environ['MINING_ADDRESS'] = config['mining_address']\r\n    os.environ['MINING_SIG'] = config['miner_id']\r\n    rpc_user = config['rpc_user'] or 'bitcoin'\r\n    rpc_pass = config['rpc_pass'] or 'bitcoin'\r\n    os.environ['RPC_USER'] = rpc_user\r\n    os.environ['RPC_PASS'] = rpc_pass\r\n    if shutil.which('docker compose'):\r\n        compose_base = ['docker', 'compose']\r\n    else:\r\n        compose_base = ['docker-compose']\r\n    if config['mining_enabled'] == 'true':\r\n        echo_info(\"Mining is enabled, starting with mining profile...\")\r\n        cmd = compose_base + ['--profile', 'mining', 'up', '-d']\r\n    else:\r\n        cmd = compose_base + ['up', '-d']\r\n    echo_info(f\"Running: {' '.join(cmd)}\")\r\n    try:\r\n        subprocess.run(cmd, check=True)\r\n        echo_info(\"Docker Compose started successfully.\")\r\n        if config['mining_enabled'] == 'true':\r\n            echo_info(\"CPU miner container will start mining shortly...\")\r\n    except subprocess.CalledProcessError:\r\n        echo_error(\"Failed to start Docker Compose.\")\r\n        sys.exit(1)\r\ndef start_ngrok(config: dict):\r\n    if not USE_NGROK:\r\n        if config['listen_mode'] == 'listen_only':\r\n            echo_info(\"Skipping ngrok setup (listen-only mode)\")\r\n        else:\r\n            echo_info(\"Skipping ngrok setup (--no-ngrok flag set)\")\r\n        return\r\n    echo_info(\"Checking for existing ngrok process...\")\r\n    # Cross-platform check using ngrok's local API\r\n    def is_ngrok_running_with_domain():\r\n        try:\r\n            with urlopen('http://localhost:4040/api/tunnels') as response:\r\n                data = json.loads(response.read().decode())\r\n                for tunnel in data.get('tunnels', []):\r\n                    public_url = tunnel.get('public_url', '')\r\n                    if config['ngrok_domain'] in public_url:\r\n                        return True\r\n                return False\r\n        except URLError:\r\n            return False\r\n    if is_ngrok_running_with_domain():\r\n        echo_info(f\"ngrok is already running with domain: {config['ngrok_domain']}\")\r\n        echo_info(\"Using existing ngrok tunnel\")\r\n        echo_info(\"You can check ngrok status at: http://localhost:4040\")\r\n        echo_info(f\"Public URL: {config['ngrok_url']}\")\r\n        return\r\n    else:\r\n        echo_info(\"No matching ngrok tunnel found (or ngrok not running).\")\r\n    print()\r\n    echo_warning(\"=========================================\")\r\n    echo_warning(\"NGROK SETUP REQUIRED\")\r\n    echo_warning(\"=========================================\")\r\n    print()\r\n    echo_info(\"Please open a new terminal window and run the following command:\")\r\n    print()\r\n    print(f\"{GREEN}    ngrok http --url={config['ngrok_domain']} 8090{NC}\")\r\n    print()\r\n    echo_info(f\"This will create a tunnel from {config['ngrok_url']} to your local Teranode asset service.\")\r\n    print()\r\n    echo_info(\"After starting ngrok, you can verify it's running at: http://localhost:4040\")\r\n    print()\r\n    input(\"Press Enter once ngrok is running in another terminal... \")\r\n    echo_info(\"Verifying ngrok connection...\")\r\n    max_attempts = 5\r\n    attempt = 1\r\n    wait_time = 2\r\n    while attempt <= max_attempts:\r\n        if is_ngrok_running_with_domain():\r\n            echo_info(\"ngrok verified successfully!\")\r\n            echo_info(f\"Tunnel established: {config['ngrok_url']} -> localhost:8090\")\r\n            echo_info(\"You can monitor ngrok at: http://localhost:4040\")\r\n            return\r\n        else:\r\n            echo_warning(\"ngrok tunnel not yet established or not matching domain...\")\r\n        if attempt == max_attempts:\r\n            echo_error(f\"Could not verify ngrok is running with domain: {config['ngrok_domain']}\")\r\n            echo_error(\"Please ensure you ran the command exactly as shown above\")\r\n            echo_error(\"To retry, stop this script and run it again\")\r\n            sys.exit(1)\r\n        echo_info(f\"Retrying verification (attempt {attempt}/{max_attempts})...\")\r\n        time.sleep(wait_time)\r\n        attempt += 1\r\ndef set_fsm_state_running():\r\n    echo_info(\"Setting FSM state to RUNNING...\")\r\n    \r\n    max_attempts = 30  # Increased for ~5 minutes total timeout\r\n    attempt = 1\r\n    wait_time = 10  # Increased wait time between checks\r\n    \r\n    echo_info(\"Waiting for blockchain service to be ready...\")\r\n    \r\n    while attempt <= max_attempts:\r\n        echo_info(f\"Checking blockchain container status (attempt {attempt}/{max_attempts})...\")\r\n        \r\n        try:\r\n            ps_output = subprocess.run(['docker', 'ps'], capture_output=True, text=True, check=True)\r\n            if 'blockchain' in ps_output.stdout:\r\n                try:\r\n                    # Capture output for better debugging\r\n                    check_output = subprocess.run(['docker', 'exec', 'blockchain', 'teranode-cli', 'getfsmstate'], capture_output=True, text=True, check=True)\r\n                    echo_info(f\"Blockchain container is ready (current FSM state: {check_output.stdout.strip()})\")\r\n                    break\r\n                except subprocess.CalledProcessError as e:\r\n                    echo_info(f\"Container is running but not yet responsive (error: {e.stderr.strip() or 'no output'})...\")\r\n            else:\r\n                echo_info(\"Blockchain container not yet running...\")\r\n        except subprocess.CalledProcessError as e:\r\n            echo_info(f\"Error checking docker ps: {e.stderr.strip()}...\")\r\n        \r\n        if attempt == max_attempts:\r\n            echo_error(f\"Blockchain container failed to become responsive after {max_attempts} attempts (~5 minutes)\")\r\n            echo_error(\"Cannot set FSM state to RUNNING automatically\")\r\n            echo_warning(\"Check 'docker logs blockchain' for errors. If no issues, run manually: docker exec -it blockchain teranode-cli setfsmstate --fsmstate RUNNING\")\r\n            sys.exit(1)\r\n        \r\n        echo_info(f\"Waiting {wait_time} seconds before retry...\")\r\n        time.sleep(wait_time)\r\n        attempt += 1\r\n    \r\n    echo_info(\"Executing: docker exec -it blockchain teranode-cli setfsmstate --fsmstate RUNNING\")\r\n    try:\r\n        subprocess.run(['docker', 'exec', '-it', 'blockchain', 'teranode-cli', 'setfsmstate', '--fsmstate', 'RUNNING'], check=True)\r\n        echo_info(\"Successfully transitioned FSM state to RUNNING\")\r\n        echo_info(\"Teranode is now operational and ready to process transactions\")\r\n    except subprocess.CalledProcessError:\r\n        echo_error(\"Failed to set FSM state to RUNNING\")\r\n        echo_warning(\"You may need to manually run: docker exec -it blockchain teranode-cli setfsmstate --fsmstate RUNNING\")\r\n        echo_warning(\"Continuing anyway...\")\r\ndef show_completion_message(config: dict):\r\n    print()\r\n    echo_info(\"=========================================\")\r\n    echo_info(\"Teratestnet started successfully!\")\r\n    echo_info(\"=========================================\")\r\n    print()\r\n    print(\"Node Status:\")\r\n    print(f\"  - Mode: {'Listen-only' if config['listen_mode'] == 'listen_only' else 'Full'}\")\r\n    print(\"  - FSM State: RUNNING (operational)\")\r\n    print(\"  - Docker Compose: Running in background\")\r\n    if USE_NGROK:\r\n        print(\"  - ngrok: Running in separate terminal (monitor at http://localhost:4040)\")\r\n    print()\r\n    if config['listen_mode'] == 'full':\r\n        print(\"Endpoints:\")\r\n        print(f\"  - RPC endpoint: {config['ngrok_url']}:9292\")\r\n        print(f\"  - Asset API: {config['ngrok_url']}/api/v1\")\r\n        print(f\"  - P2P advertise: {config['ngrok_domain']}\")\r\n    else:\r\n        print(\"Endpoints (local only - listen-only mode):\")\r\n        print(\"  - RPC endpoint: http://localhost:9292\")\r\n        print(\"  - Asset API: http://localhost:8090/api/v1\")\r\n        print(\"  - Note: External connections not available in listen-only mode\")\r\n    print()\r\n    print(\"Credentials:\")\r\n    if config['rpc_user']:\r\n        print(f\"  - RPC Username: {config['rpc_user']}\")\r\n        print(\"  - RPC Password: [saved in settings.conf]\")\r\n    else:\r\n        print(\"  - RPC Credentials: Not configured (add to settings.conf manually)\")\r\n    print()\r\n    if config['mining_enabled'] == 'true':\r\n        print(\"Mining Status:\")\r\n        print(\"  - CPU Miner: ENABLED\")\r\n        print(f\"  - Mining Address: {config['mining_address']}\")\r\n        print(f\"  - Miner ID: {config['miner_id']}\")\r\n        print(\"  - CPU Threads: 2\")\r\n        print(\"  - Container: cpuminer\")\r\n        print()\r\n        print(\"Monitor mining:\")\r\n        print(\"  - Logs: docker logs -f cpuminer\")\r\n        print(\"  - Stop mining: docker compose --profile mining down\")\r\n    else:\r\n        print(\"Mining Status: DISABLED\")\r\n    print()\r\n    print(\"To stop services:\")\r\n    print(\"  - Docker: docker compose down\")\r\n    if USE_NGROK:\r\n        print(\"  - ngrok: Stop the ngrok process in its terminal (Ctrl+C)\")\r\n    print()\r\n    print(f\"Settings backup: {BACKUP_FILE}\")\r\n    print()\r\n    if config['listen_mode'] == 'listen_only':\r\n        echo_info(\"Your Teranode is running in listen-only mode!\")\r\n        echo_info(\"The node will sync with the network but won't mine or relay transactions.\")\r\n    else:\r\n        echo_info(\"Your Teranode is now ready to process transactions!\")\r\ndef signal_handler(sig, frame):\r\n    echo_error(\"Script interrupted. Exiting...\")\r\n    sys.exit(1)\r\ndef main():\r\n    signal.signal(signal.SIGINT, signal_handler)\r\n    signal.signal(signal.SIGTERM, signal_handler)\r\n    print()\r\n    print(\"======================================\")\r\n    print(\"   Teratestnet Docker Helper Script\")\r\n    if not USE_NGROK:\r\n        print(\"      (Running without ngrok)\")\r\n    print(\"======================================\")\r\n    print()\r\n    \r\n    parse_args()\r\n    check_prerequisites()\r\n    config = prompt_for_inputs()\r\n    backup_settings()\r\n    update_settings(config)\r\n    start_docker_compose(config)\r\n    if config['listen_mode'] == 'full':\r\n        start_ngrok(config)\r\n    set_fsm_state_running()\r\n    show_completion_message(config)\r\nif name == \"__main__\":\r\n    main()",
  "media_type": "text/markdown",
  "filename": "|",
  "author": "14aqJ2hMtENYJVCJaekcrqi12fiZJzoWGK",
  "display_name": "Justinchellis",
  "channel": null,
  "parent_txid": "3507e885496872d4f7c54c45e3cfc6854cd71cd9a8513739eb49b41a8c9d716a",
  "ref_txid": null,
  "tags": null,
  "reply_count": 0,
  "like_count": 0,
  "timestamp": "2025-09-26T08:31:57.000Z",
  "media_url": null,
  "aip_verified": true,
  "attachments": [],
  "ui_name": "Justinchellis",
  "ui_display_name": "Justinchellis",
  "ui_handle": "Justinchellis",
  "ui_display_raw": "Justinchellis",
  "ui_signer": "14aqJ2hMtENYJVCJaekcrqi12fiZJzoWGK",
  "ref_ui_name": "unknown",
  "ref_ui_signer": "unknown"
}
Signed by14aqJ2hMtENYJVCJaekcrqi12fiZJzoWGKAIP!