⚠ Custodial relay (treechat.io)
This page shows content from treechat.io. Posts here are published by a shared relay key, not signed by each user individually. The display names are self-reported and cannot be cryptographically verified.

Justinchellis

14aqJ2hMtENYJVCJaekcrqi12fiZJzoWGK

0 Following0 Followers

Activity (15)

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": 0,
  "like_count": 0,
  "timestamp": "2025-09-26T08:31:57.000Z",
  "media_url": null,
  "aip_verified": true,
  "has_access": true,
  "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!
Justinchellisvia treechat·6mo
Replying to #27b6399d
❤️ 0 Likes · ⚡ 0 Tips
{
  "txid": "a1efb97afe2e988dbc052e7f127e6595dc3e3e72b04a975b7c41bc88d18eaae8",
  "block_height": 0,
  "time": null,
  "app": "treechat",
  "type": "reply",
  "map_content": "Docker-Compose. Yml. File\r\n\r\n\r\n\r\nx-teranode-settings:\r\n\u00a0 &teranode-settings\r\n\u00a0 environment:\r\n\u00a0 \u00a0 network: \"teratestnet\"\r\n\u00a0 \u00a0 p2p_bootstrapAddresses: \"/dns4/teranode-bootstrap-stage.bsvb.tech/tcp/9901/p2p/12D3KooWJ6kQHAR65xkA34NABsNVAJyVxPWh8JUSo1vtZsTyw4GD\"\r\n\u00a0 \u00a0 # logLevel: \"DEBUG\"\r\n\u00a0 \u00a0 # enable this when using your laptop or a server without a public ip\r\n\u00a0 \u00a0 # someOtherSetting: \"value\"\r\n\r\n\r\nnetworks:\r\n\u00a0 teranode-network:\r\n\u00a0 \u00a0 name: my-teranode-network\r\n\u00a0 \u00a0 # if your docker setup supports IPv6, you can enable it here\r\n\u00a0 \u00a0 # enable_ipv6: true\r\n\r\n\r\nvolumes:\r\n\u00a0 nginx-cache:\r\n\u00a0 postgres-data:\r\n\r\n\r\nservices:\r\n\u00a0 # Initialize directories with correct ownership\r\n\u00a0 # Note: Runs as root to create directories and set ownership before other services start\r\n\u00a0 data-init:\r\n\u00a0 \u00a0 image: alpine:latest\r\n\u00a0 \u00a0 volumes:\r\n\u00a0 \u00a0 \u00a0 - ${DATA_PATH}:/data\r\n\u00a0 \u00a0 \u00a0 - ./scripts/init-data.sh:/init-data.sh:ro\r\n\u00a0 \u00a0 environment:\r\n\u00a0 \u00a0 \u00a0 - USER_ID=${USER_ID}\r\n\u00a0 \u00a0 \u00a0 - GROUP_ID=${GROUP_ID}\r\n\u00a0 \u00a0 command: sh /init-data.sh\r\n\u00a0 \u00a0 user: \"0:0\" \u00a0# Required for chown operations\r\n\r\n\r\n\u00a0 # Teranode micro-services\r\n\u00a0 blockchain:\r\n\u00a0 \u00a0 extends:\r\n\u00a0 \u00a0 \u00a0 file: ./base/docker-teranode.yml\r\n\u00a0 \u00a0 \u00a0 service: blockchain\r\n\u00a0 \u00a0 <<: *teranode-settings\r\n\u00a0 \u00a0 depends_on:\r\n\u00a0 \u00a0 \u00a0 - data-init\r\n\r\n\r\n\u00a0 asset:\r\n\u00a0 \u00a0 extends:\r\n\u00a0 \u00a0 \u00a0 file: ./base/docker-teranode.yml\r\n\u00a0 \u00a0 \u00a0 service: asset\r\n\u00a0 \u00a0 <<: *teranode-settings\r\n\r\n\r\n\u00a0 asset-cache:\r\n\u00a0 \u00a0 extends:\r\n\u00a0 \u00a0 \u00a0 file: ./base/docker-services.yml\r\n\u00a0 \u00a0 \u00a0 service: asset-cache\r\n\r\n\r\n\u00a0 rpc:\r\n\u00a0 \u00a0 extends:\r\n\u00a0 \u00a0 \u00a0 file: ./base/docker-teranode.yml\r\n\u00a0 \u00a0 \u00a0 service: rpc\r\n\u00a0 \u00a0 <<: *teranode-settings\r\n\r\n\r\n\u00a0 subtreevalidation:\r\n\u00a0 \u00a0 extends:\r\n\u00a0 \u00a0 \u00a0 file: ./base/docker-teranode.yml\r\n\u00a0 \u00a0 \u00a0 service: subtreevalidation\r\n\u00a0 \u00a0 <<: *teranode-settings\r\n\r\n\r\n\u00a0 blockvalidation:\r\n\u00a0 \u00a0 extends:\r\n\u00a0 \u00a0 \u00a0 file: ./base/docker-teranode.yml\r\n\u00a0 \u00a0 \u00a0 service: blockvalidation\r\n\u00a0 \u00a0 <<: *teranode-settings\r\n\r\n\r\n\u00a0 blockassembly:\r\n\u00a0 \u00a0 extends:\r\n\u00a0 \u00a0 \u00a0 file: ./base/docker-teranode.yml\r\n\u00a0 \u00a0 \u00a0 service: blockassembly\r\n\u00a0 \u00a0 <<: *teranode-settings\r\n\r\n\r\n\u00a0 peer:\r\n\u00a0 \u00a0 extends:\r\n\u00a0 \u00a0 \u00a0 file: ./base/docker-teranode.yml\r\n\u00a0 \u00a0 \u00a0 service: peer\r\n\u00a0 \u00a0 <<: *teranode-settings\r\n\r\n\r\n\u00a0 # uses lots of disk space\r\n\u00a0 # \u00a0blockpersister:\r\n\u00a0 # \u00a0 \u00a0extends:\r\n\u00a0 # \u00a0 \u00a0 \u00a0file: ../base/docker-teranode.yml\r\n\u00a0 # \u00a0 \u00a0 \u00a0service: blockpersister\r\n\u00a0 # \u00a0 \u00a0<<: *teranode-settings\r\n\r\n\r\n\u00a0 # shared services\r\n\u00a0 postgres:\r\n\u00a0 \u00a0 extends:\r\n\u00a0 \u00a0 \u00a0 file: ./base/docker-services.yml\r\n\u00a0 \u00a0 \u00a0 service: postgres\r\n\u00a0 \u00a0 volumes:\r\n\u00a0 \u00a0 \u00a0 - postgres-data:/var/lib/postgresql/data\r\n\r\n\r\n\u00a0 kafka-shared:\r\n\u00a0 \u00a0 extends:\r\n\u00a0 \u00a0 \u00a0 file: ./base/docker-services.yml\r\n\u00a0 \u00a0 \u00a0 service: kafka-shared\r\n\r\n\r\n# \u00a0kafka-console-shared:\r\n# \u00a0 \u00a0extends:\r\n# \u00a0 \u00a0 \u00a0file: ./base/docker-services.yml\r\n# \u00a0 \u00a0 \u00a0service: kafka-console-shared\r\n\r\n\r\n\u00a0 aerospike:\r\n\u00a0 \u00a0 extends:\r\n\u00a0 \u00a0 \u00a0 file: ./base/docker-services.yml\r\n\u00a0 \u00a0 \u00a0 # Community Edition\r\n\u00a0 \u00a0 \u00a0 service: aerospike\r\n\u00a0 \u00a0 \u00a0 # Enterprise Edition, evaluation mode, single node\r\n\u00a0 \u00a0 \u00a0 # service: aerospike-ee\r\n\r\n\r\n\u00a0 aerospike-exporter:\r\n\u00a0 \u00a0 extends:\r\n\u00a0 \u00a0 \u00a0 file: ./base/docker-services.yml\r\n\u00a0 \u00a0 \u00a0 service: aerospike-exporter\r\n\r\n\r\n\u00a0 prometheus:\r\n\u00a0 \u00a0 extends:\r\n\u00a0 \u00a0 \u00a0 file: ./base/docker-services.yml\r\n\u00a0 \u00a0 \u00a0 service: prometheus\r\n\r\n\r\n\u00a0 grafana:\r\n\u00a0 \u00a0 extends:\r\n\u00a0 \u00a0 \u00a0 file: ./base/docker-services.yml\r\n\u00a0 \u00a0 \u00a0 service: grafana\r\n\r\n\r\n\u00a0 # CPU Miner (optional - configured via startup script)\r\n\u00a0 cpuminer:\r\n\u00a0 \u00a0 image: ghcr.io/bitcoin-sv/cpuminer:latest\r\n\u00a0 \u00a0 container_name: cpuminer\r\n\u00a0 \u00a0 networks:\r\n\u00a0 \u00a0 \u00a0 - teranode-network\r\n\u00a0 \u00a0 environment:\r\n\u00a0 \u00a0 \u00a0 MINING_ENABLED: \"${MINING_ENABLED:-false}\"\r\n\u00a0 \u00a0 \u00a0 MINING_ADDRESS: \"${MINING_ADDRESS:-}\"\r\n\u00a0 \u00a0 \u00a0 MINING_SIG: \"${MINING_SIG:-}\"\r\n\u00a0 \u00a0 \u00a0 RPC_USER: \"${RPC_USER:-bitcoin}\"\r\n\u00a0 \u00a0 \u00a0 RPC_PASS: \"${RPC_PASS:-bitcoin}\"\r\n\u00a0 \u00a0 entrypoint: [\"/bin/sh\", \"-c\"]\r\n\u00a0 \u00a0 command: >\r\n\u00a0 \u00a0 \u00a0 \"\r\n\u00a0 \u00a0 \u00a0 if [ \\\"$$MINING_ENABLED\\\" = \\\"true\\\" ] && [ -n \\\"$$MINING_ADDRESS\\\" ]; then\r\n\u00a0 \u00a0 \u00a0 \u00a0 echo 'Starting CPU miner...';\r\n\u00a0 \u00a0 \u00a0 \u00a0 echo 'Mining address: '$$MINING_ADDRESS;\r\n\u00a0 \u00a0 \u00a0 \u00a0 echo 'Miner ID: '$$MINING_SIG;\r\n\u00a0 \u00a0 \u00a0 \u00a0 exec ./minerd --url=http://rpc:9292 --userpass=$$RPC_USER:$$RPC_PASS --coinbase-addr=$$MINING_ADDRESS --coinbase-sig=\\\"$$MINING_SIG\\\" --threads=2;\r\n\u00a0 \u00a0 \u00a0 else\r\n\u00a0 \u00a0 \u00a0 \u00a0 echo 'Mining disabled or not configured. Container will exit.';\r\n\u00a0 \u00a0 \u00a0 \u00a0 exit 0;\r\n\u00a0 \u00a0 \u00a0 fi\r\n\u00a0 \u00a0 \u00a0 \"\r\n\u00a0 \u00a0 profiles:\r\n\u00a0 \u00a0 \u00a0 - mining\r\n\u00a0 \u00a0 restart: unless-stopped\r\n\u00a0 \u00a0 depends_on:\r\n\u00a0 \u00a0 \u00a0 rpc:\r\n\u00a0 \u00a0 \u00a0 \u00a0 condition: service_healthy",
  "media_type": "text/markdown",
  "filename": "|",
  "author": "14aqJ2hMtENYJVCJaekcrqi12fiZJzoWGK",
  "display_name": "Justinchellis",
  "channel": null,
  "parent_txid": "27b6399d79e105f17d9d28b33e9fab0c18f9c9a1a3d15547e25fe6ed1de1a1b4",
  "ref_txid": null,
  "tags": null,
  "reply_count": 0,
  "like_count": 0,
  "timestamp": "2025-09-26T08:31:57.000Z",
  "media_url": null,
  "aip_verified": true,
  "has_access": true,
  "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!
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 ",
  "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,
  "has_access": true,
  "content_size": 20647,
  "content_truncated": true,
  "map_content_size": 20647,
  "map_content_truncated": true,
  "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!
Justinchellisvia treechat·6mo
❤️ 0 Likes · ⚡ 0 Tips
{
  "txid": "6e11a21cb4a6d649d9b4229dcd6714229d66302250d57a3623a1b8564f2caab8",
  "block_height": 0,
  "time": null,
  "app": "treechat",
  "type": "post",
  "map_content": "Edit docker.compose.yml file.",
  "media_type": "text/markdown",
  "filename": "|",
  "author": "14aqJ2hMtENYJVCJaekcrqi12fiZJzoWGK",
  "display_name": "Justinchellis",
  "channel": null,
  "parent_txid": null,
  "ref_txid": null,
  "tags": null,
  "reply_count": 0,
  "like_count": 0,
  "timestamp": "2025-09-26T08:31:57.000Z",
  "media_url": null,
  "aip_verified": true,
  "has_access": true,
  "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!
Justinchellisvia treechat·6mo
❤️ 6 Likes · ⚡ 0 Tips
{
  "txid": "c7d507af323ab4758ea445fecf311226af33fa16d097d1aa89235595b1836701",
  "block_height": 0,
  "time": null,
  "app": "treechat",
  "type": "post",
  "map_content": "Terranode running on Windows",
  "media_type": "text/markdown",
  "filename": "|",
  "author": "14aqJ2hMtENYJVCJaekcrqi12fiZJzoWGK",
  "display_name": "Justinchellis",
  "channel": null,
  "parent_txid": null,
  "ref_txid": null,
  "tags": null,
  "reply_count": 0,
  "like_count": 6,
  "timestamp": "2025-09-26T08:31:57.000Z",
  "media_url": null,
  "aip_verified": true,
  "has_access": true,
  "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!
Justinchellisvia treechat·6mo
❤️ 0 Likes · ⚡ 0 Tips
{
  "txid": "27b6399d79e105f17d9d28b33e9fab0c18f9c9a1a3d15547e25fe6ed1de1a1b4",
  "block_height": 0,
  "time": null,
  "app": "treechat",
  "type": "post",
  "map_content": "Teranode test network windows",
  "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,
  "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!
Justinchellisvia treechat·6mo
❤️ 5 Likes · ⚡ 0 Tips
{
  "txid": "208f85f202e842cb8ea844078f9e81c1ed0c19fb678466cdb3d8175f64c9f3e5",
  "block_height": 0,
  "time": null,
  "app": "treechat",
  "type": "post",
  "map_content": "Teranode test network windows",
  "media_type": "text/markdown",
  "filename": "|",
  "author": "14aqJ2hMtENYJVCJaekcrqi12fiZJzoWGK",
  "display_name": "Justinchellis",
  "channel": null,
  "parent_txid": null,
  "ref_txid": null,
  "tags": null,
  "reply_count": 0,
  "like_count": 5,
  "timestamp": "2025-09-26T08:31:57.000Z",
  "media_url": null,
  "aip_verified": true,
  "has_access": true,
  "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!
Justinchellisvia treechat·6mo
❤️ 0 Likes · ⚡ 0 Tips
{
  "txid": "92f8af83518d1c855ccad52a8b710af4523139e2c0f39c0b37d139c204cecdb6",
  "block_height": 0,
  "time": null,
  "app": "treechat",
  "type": "post",
  "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 ",
  "media_type": "text/markdown",
  "filename": "|",
  "author": "14aqJ2hMtENYJVCJaekcrqi12fiZJzoWGK",
  "display_name": "Justinchellis",
  "channel": null,
  "parent_txid": null,
  "ref_txid": null,
  "tags": null,
  "reply_count": 0,
  "like_count": 0,
  "timestamp": "2025-09-26T08:31:57.000Z",
  "media_url": null,
  "aip_verified": true,
  "has_access": true,
  "content_size": 20647,
  "content_truncated": true,
  "map_content_size": 20647,
  "map_content_truncated": true,
  "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!
Justinchellisvia treechat·6mo
Replying to #e638a015
❤️ 0 Likes · ⚡ 0 Tips
{
  "txid": "4c421cee9c5642ba4f13c73b1aa20b5c9448ce71bb595c01b9bf331427535e81",
  "block_height": 0,
  "time": null,
  "app": "treechat",
  "type": "reply",
  "map_content": "Create start.py in teranode directory",
  "media_type": "text/markdown",
  "filename": "|",
  "author": "14aqJ2hMtENYJVCJaekcrqi12fiZJzoWGK",
  "display_name": "Justinchellis",
  "channel": null,
  "parent_txid": "e638a01526c3e9e5a0b5a658624ada5e05fe34e4243cb5a035d1b0a5d98d68d8",
  "ref_txid": null,
  "tags": null,
  "reply_count": 0,
  "like_count": 0,
  "timestamp": "2025-09-26T08:31:57.000Z",
  "media_url": null,
  "aip_verified": true,
  "has_access": true,
  "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!
Justinchellisvia treechat·6mo
❤️ 0 Likes · ⚡ 0 Tips
{
  "txid": "e638a01526c3e9e5a0b5a658624ada5e05fe34e4243cb5a035d1b0a5d98d68d8",
  "block_height": 0,
  "time": null,
  "app": "treechat",
  "type": "post",
  "map_content": "Terranode running on Windows",
  "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,
  "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!
Justinchellisvia treechat·7mo
Replying to #2bf4edc4
❤️ 0 Likes · ⚡ 0 Tips
{
  "txid": "a7191b19db45b67a4af475a854fde41dbf001f8f945b7f4931e7fd8be88e6def",
  "block_height": 0,
  "time": null,
  "app": "treechat",
  "type": "reply",
  "map_content": "!edit_nb  scrambled my text and jumble it around..",
  "media_type": "text/markdown",
  "filename": "|",
  "author": "14aqJ2hMtENYJVCJaekcrqi12fiZJzoWGK",
  "display_name": "Justinchellis",
  "channel": null,
  "parent_txid": "2bf4edc4952956de5263be9e1ad0b7a549cb5fbb3593e75e9b2d8d793984a36d",
  "ref_txid": null,
  "tags": null,
  "reply_count": 0,
  "like_count": 0,
  "timestamp": "2025-09-22T01:48:18.000Z",
  "media_url": null,
  "aip_verified": true,
  "has_access": true,
  "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!
Justinchellisvia treechat·7mo
❤️ 0 Likes · ⚡ 0 Tips
{
  "txid": "2bf4edc4952956de5263be9e1ad0b7a549cb5fbb3593e75e9b2d8d793984a36d",
  "block_height": 0,
  "time": null,
  "app": "treechat",
  "type": "post",
  "map_content": "Censorship coming to social media in the west.. Treechat should have a word jumble feature built into the application for every post to be marked senitive to protect its users? By tipping the post it unjumbles the post.",
  "media_type": "text/markdown",
  "filename": "|",
  "author": "14aqJ2hMtENYJVCJaekcrqi12fiZJzoWGK",
  "display_name": "Justinchellis",
  "channel": null,
  "parent_txid": null,
  "ref_txid": null,
  "tags": null,
  "reply_count": 0,
  "like_count": 0,
  "timestamp": "2025-09-22T01:40:59.000Z",
  "media_url": null,
  "aip_verified": true,
  "has_access": true,
  "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!
Justinchellisvia treechat·7mo
Replying to #cadaccfb
❤️ 6 Likes · ⚡ 0 Tips
{
  "txid": "205325fcc09bdec121b663689f8682a923c4ae16c29a0175f4af0037c6ff1ab6",
  "block_height": 0,
  "time": null,
  "app": "treechat",
  "type": "reply",
  "map_content": "Need to be posting on treechat.ai more",
  "media_type": "text/markdown",
  "filename": "|",
  "author": "14aqJ2hMtENYJVCJaekcrqi12fiZJzoWGK",
  "display_name": "Justinchellis",
  "channel": null,
  "parent_txid": "cadaccfb872177f2609cdacce5f472185d6dc485681e170ca28e7ab38cdfb43c",
  "ref_txid": null,
  "tags": null,
  "reply_count": 2,
  "like_count": 6,
  "timestamp": "2025-09-19T00:52:16.000Z",
  "media_url": null,
  "aip_verified": true,
  "has_access": true,
  "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!
Justinchellisvia treechat·7mo
Replying to #9254c282
❤️ 1 Likes · ⚡ 0 Tips
{
  "txid": "7e838ed07313265b164f52e3c89af82f923899559af3f34c43db0e6dbd97c976",
  "block_height": 0,
  "time": null,
  "app": "treechat",
  "type": "reply",
  "map_content": "I am from North Queensland also near Townsville.",
  "media_type": "text/markdown",
  "filename": "|",
  "author": "14aqJ2hMtENYJVCJaekcrqi12fiZJzoWGK",
  "display_name": "Justinchellis",
  "channel": null,
  "parent_txid": "9254c28250ef9e6fdc01085755c6eef31268b51ce9594f973b8e21dfdb1bc5bb",
  "ref_txid": null,
  "tags": null,
  "reply_count": 0,
  "like_count": 1,
  "timestamp": "2025-09-18T23:46:57.000Z",
  "media_url": null,
  "aip_verified": true,
  "has_access": true,
  "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!
Justinchellisvia treechat·7mo
❤️ 0 Likes · ⚡ 0 Tips
{
  "txid": "3734f1b2b23ce4b9ef24d0d12572d4c064ebc7593533080b52803182a83b63f7",
  "block_height": 0,
  "time": null,
  "app": "treechat",
  "type": "post",
  "map_content": "W chart could form and have a breakout, Bollinger bands closing and macd indicates it going green and over 1 percent dominance over btc today. People also saying it more profitable to mine on bsv the btc by 17 percent today..",
  "media_type": "text/markdown",
  "filename": "|",
  "author": "14aqJ2hMtENYJVCJaekcrqi12fiZJzoWGK",
  "display_name": "Justinchellis",
  "channel": null,
  "parent_txid": null,
  "ref_txid": null,
  "tags": null,
  "reply_count": 0,
  "like_count": 0,
  "timestamp": "2025-09-03T10:29:15.000Z",
  "media_url": null,
  "aip_verified": true,
  "has_access": true,
  "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!