#!/usr/bin/env bash set -euo pipefail cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." [[ $# == 2 ]] || { echo "Usage: $0 VM_LAN_IP_OR_DNS_NAME WORKSTATION_IP" >&2; exit 1; } python3 - "$1" "$2" <<'PY' from pathlib import Path import ipaddress, re, socket, sys host = sys.argv[1] if not re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9.-]*', host) or host in ('0.0.0.0', 'localhost', '127.0.0.1'): sys.exit('Use the VM LAN IPv4 address or a DNS name reachable from both the VM and workstation, without http:// or a port.') bind = ipaddress.IPv4Address(socket.gethostbyname(host)) workstation = ipaddress.IPv4Address(sys.argv[2]) private_networks = [ipaddress.ip_network(x) for x in ['10.0.0.0/8','172.16.0.0/12','192.168.0.0/16']] if not any(bind in n for n in private_networks) or not any(workstation in n for n in private_networks): sys.exit('This helper requires private LAN IPv4 addresses for the VM and workstation.') p = Path('.env') old = p.read_text() if p.exists() else Path('.env.example').read_text() updates = {'BIND_ADDRESS': str(bind), 'WORKSTATION_IP': str(workstation), 'KAFKA_ADVERTISED_HOST': host} lines = [line for line in old.splitlines() if line.split('=', 1)[0].strip() not in updates] lines.extend(f'{key}={value}' for key, value in updates.items()) p.write_text('\n'.join(lines) + '\n') p.chmod(0o600) print(f'Configured direct access. Dashboards: http://{host}:5601 ; OpenSearch: http://{host}:9200') print(f'Kafka advertises {host}:9092 to external clients; Docker clients continue to use kafka:29092.') print(f'LAN binding: {bind}; allowed workstation: {workstation}. Loopback access is retained.') print('Install the source restriction first: sudo ./scripts/install-access-rules.sh') print('Then apply with ./scripts/start.sh ; optional services: ./scripts/enable-metric-analytics.sh') PY