""" Mieru protocol manager — native mita server package (systemd). Installs pinned release from https://github.com/enfein/mieru Server binary: mita (systemd service), client: mieru. Share links: mierus://user:pass@host?port=...&protocol=TCP&profile=default """ from __future__ import annotations import json import logging import re import secrets import shlex import string from urllib.parse import quote logger = logging.getLogger(__name__) MIERU_RELEASE = '3.28.0' GITHUB_RELEASE = f'https://github.com/enfein/mieru/releases/download/v{MIERU_RELEASE}' def _q(value): return shlex.quote(str(value)) def _rand_token(length=16): alphabet = string.ascii_lowercase + string.digits return ''.join(secrets.choice(alphabet) for _ in range(length)) def _sanitize_username(name): base = re.sub(r'[^a-zA-Z0-9_-]', '', (name or 'user').strip())[:24] or 'user' return f'{base}_{_rand_token(4)}' class MieruManager: PROTOCOL = 'mieru' SERVICE_NAME = 'mita' BASE_DIR = '/opt/amnezia/mieru' DEFAULT_PORT = 2999 def __init__(self, ssh, protocol='mieru'): self.ssh = ssh self.protocol = protocol or self.PROTOCOL self.base_dir = self.BASE_DIR self.clients_path = f'{self.base_dir}/clients.json' self.meta_path = f'{self.base_dir}/metadata.json' self.config_path = f'{self.base_dir}/server_config.json' # ===================== STATUS ===================== def check_docker_installed(self): return True def _mita_installed(self): out, _, code = self.ssh.run_command('command -v mita 2>/dev/null') return code == 0 and bool(out.strip()) def check_protocol_installed(self, protocol_type=None): return self._mita_installed() and self._panel_installed() def _panel_installed(self): out, _, code = self.ssh.run_sudo_command(f"test -f {_q(self.meta_path)} && echo yes") return code == 0 and 'yes' in out def _proxy_running(self): out, _, code = self.ssh.run_sudo_command('mita status 2>/dev/null') if code != 0: return False return 'RUNNING' in (out or '').upper() def check_container_running(self, protocol_type=None): return self._proxy_running() def get_logs(self, protocol_type=None, tail=200): tail = max(20, min(int(tail or 200), 2000)) out, err, code = self.ssh.run_sudo_command( f"journalctl -u {self.SERVICE_NAME} -n {tail} --no-pager 2>&1", timeout=30, ) text = (out or err or '').strip() if not text: status, _, _ = self.ssh.run_sudo_command('mita status 2>&1') text = (status or '').strip() if not text: text = f'(no logs: exit {code})' return text def get_container_diagnostics(self, protocol_type=None): running = self._proxy_running() out, _, _ = self.ssh.run_sudo_command( f"systemctl is-active {self.SERVICE_NAME} 2>/dev/null" ) daemon_active = (out or '').strip() == 'active' diag = { 'status': 'running' if running else ('idle' if daemon_active else 'stopped'), 'running': running, 'error_summary': '', 'recent_logs': self.get_logs(protocol_type, tail=40), } if not self._mita_installed(): diag['status'] = 'missing' diag['error_summary'] = 'mita package not installed' elif not running and daemon_active: status_out, _, _ = self.ssh.run_sudo_command('mita status 2>&1') if 'IDLE' in (status_out or '').upper(): diag['error_summary'] = 'Proxy stopped (mita status IDLE)' elif status_out.strip(): diag['error_summary'] = status_out.strip().splitlines()[-1][:160] elif not daemon_active: diag['error_summary'] = f'{self.SERVICE_NAME} systemd service is not active' return diag def get_server_status(self, protocol_type=None): protocol_type = protocol_type or self.protocol exists = self.check_protocol_installed(protocol_type) running = self.check_container_running(protocol_type) if exists else False meta = self._read_metadata() if exists else {} clients = self._read_clients() if exists else [] port = int(meta.get('port') or self.DEFAULT_PORT) return { 'container_exists': exists, 'container_running': running, 'port': port, 'release': meta.get('release') or MIERU_RELEASE, 'clients_count': len(clients), 'protocol': protocol_type, 'base_protocol': self.PROTOCOL, 'instance': 1, 'container_name': self.SERVICE_NAME, } # ===================== IO HELPERS ===================== def _read_file(self, path): out, _, code = self.ssh.run_sudo_command(f"cat {_q(path)} 2>/dev/null") return out if code == 0 else '' def _write_file(self, path, content): import base64 b64 = base64.b64encode((content or '').encode('utf-8')).decode('ascii') script = ( f"mkdir -p $(dirname {_q(path)}) && " f"echo {_q(b64)} | base64 -d > {_q(path)}" ) out, err, code = self.ssh.run_sudo_command(f"sh -c {_q(script)}", timeout=30) if code != 0: raise RuntimeError(f'Failed to write {path}: {err or out}') def _read_metadata(self): raw = self._read_file(self.meta_path).strip() if not raw: return {} try: data = json.loads(raw) return data if isinstance(data, dict) else {} except Exception: return {} def _write_metadata(self, meta): self._write_file(self.meta_path, json.dumps(meta, indent=2)) def _read_clients(self): raw = self._read_file(self.clients_path).strip() if not raw: return [] try: data = json.loads(raw) return data if isinstance(data, list) else [] except Exception: return [] def _write_clients(self, clients): self._write_file(self.clients_path, json.dumps(clients, indent=2)) def _build_server_config(self, port, clients): users = [] for c in clients: if c.get('enabled', True): users.append({ 'name': c.get('username') or c.get('name') or c.get('id'), 'password': c.get('password') or '', }) return { 'portBindings': [{'port': int(port), 'protocol': 'TCP'}], 'users': users, 'loggingLevel': 'INFO', 'mtu': 1400, } def _apply_config(self, config, reload_only=False): self._write_file(self.config_path, json.dumps(config, indent=2)) out, err, code = self.ssh.run_sudo_command( f"mita apply config {_q(self.config_path)} 2>&1", timeout=60, ) if code != 0: raise RuntimeError((err or out or 'mita apply config failed').strip()) if reload_only: self.ssh.run_sudo_command('mita reload 2>/dev/null || true', timeout=30) else: self.ssh.run_sudo_command('mita stop 2>/dev/null || true', timeout=30) out2, err2, code2 = self.ssh.run_sudo_command('mita start 2>&1', timeout=60) if code2 != 0: raise RuntimeError((err2 or out2 or 'mita start failed').strip()) def _sync_server(self, reload_only=True): meta = self._read_metadata() port = int(meta.get('port') or self.DEFAULT_PORT) clients = self._read_clients() config = self._build_server_config(port, clients) self._apply_config(config, reload_only=reload_only) def _open_firewall_port(self, port): script = f""" PORT={int(port)} if command -v ufw >/dev/null 2>&1 && ufw status 2>/dev/null | grep -qi active; then ufw allow "$PORT"/tcp || true fi if command -v firewall-cmd >/dev/null 2>&1; then firewall-cmd --permanent --add-port="$PORT"/tcp 2>/dev/null || true firewall-cmd --reload 2>/dev/null || true fi """ self.ssh.run_sudo_script(script, timeout=60) def _package_url(self): arch_out, _, _ = self.ssh.run_command('uname -m') arch = (arch_out or '').strip().lower() _, _, deb_code = self.ssh.run_command('command -v dpkg 2>/dev/null') _, _, rpm_code = self.ssh.run_command('command -v rpm 2>/dev/null') use_deb = deb_code == 0 or rpm_code != 0 if use_deb: if arch in ('aarch64', 'arm64'): return f'{GITHUB_RELEASE}/mita_{MIERU_RELEASE}_arm64.deb', 'deb' return f'{GITHUB_RELEASE}/mita_{MIERU_RELEASE}_amd64.deb', 'deb' if arch in ('aarch64', 'arm64'): return f'{GITHUB_RELEASE}/mita-{MIERU_RELEASE}-1.aarch64.rpm', 'rpm' return f'{GITHUB_RELEASE}/mita-{MIERU_RELEASE}-1.x86_64.rpm', 'rpm' def _install_package(self, log): url, pkg_type = self._package_url() tmp = f'/tmp/mita_{MIERU_RELEASE}' if pkg_type == 'deb': tmp += '.deb' install_cmd = f"dpkg -i {_q(tmp)} || apt-get install -f -y" else: tmp += '.rpm' install_cmd = f"rpm -Uvh --force {_q(tmp)}" out, err, code = self.ssh.run_sudo_command( f"curl -fL {_q(url)} -o {_q(tmp)} 2>&1", timeout=300, ) if code != 0: raise RuntimeError(f'Failed to download mita package: {err or out}') log.append(f'Downloaded mita v{MIERU_RELEASE}') out, err, code = self.ssh.run_sudo_command(install_cmd, timeout=180) if code != 0: raise RuntimeError(f'Failed to install mita package: {err or out}') log.append('Installed mita package') self.ssh.run_sudo_command( f"systemctl enable --now {self.SERVICE_NAME} 2>/dev/null || true", timeout=30, ) def _build_share_uri(self, host, port, username, password, name=''): user = quote(username or '', safe='') pw = quote(password or '', safe='') params = f"port={int(port)}&protocol=TCP&profile=default" link = f"mierus://{user}:{pw}@{host}?{params}" if name: link += f"#{quote(name, safe='')}" return link def _build_client_json(self, host, port, username, password): return json.dumps({ 'profiles': [{ 'profileName': 'default', 'user': {'name': username, 'password': password}, 'servers': [{ 'ipAddress': host, 'domainName': '', 'portBindings': [{'port': int(port), 'protocol': 'TCP'}], }], 'mtu': 1400, 'multiplexing': {'level': 'MULTIPLEXING_LOW'}, 'handshakeMode': 'HANDSHAKE_STANDARD', }], 'activeProfile': 'default', 'rpcPort': 8964, 'socks5Port': 1080, 'loggingLevel': 'INFO', 'socks5ListenLAN': False, }, indent=2) # ===================== INSTALL / REMOVE ===================== def install_protocol(self, protocol_type=None, port=None): protocol_type = protocol_type or self.protocol port = int(port or self.DEFAULT_PORT) if port < 1025 or port > 65535: return {'status': 'error', 'message': 'Port must be between 1025 and 65535'} log = [] if not self._mita_installed(): self._install_package(log) else: log.append(f'mita already installed, configuring panel (v{MIERU_RELEASE})') self.ssh.run_sudo_command(f"mkdir -p {_q(self.base_dir)}") meta = {'port': port, 'release': MIERU_RELEASE} self._write_metadata(meta) self._write_clients([]) log.append(f'Prepared {self.base_dir}') try: config = self._build_server_config(port, []) self._apply_config(config, reload_only=False) except Exception as e: return {'status': 'error', 'message': str(e), 'log': log} self._open_firewall_port(port) log.append(f'Started mita proxy on TCP {port}') return { 'status': 'success', 'message': f'Mieru v{MIERU_RELEASE} installed', 'log': log, 'port': str(port), 'release': MIERU_RELEASE, } def remove_container(self, protocol_type=None): self.ssh.run_sudo_command('mita stop 2>/dev/null || true', timeout=30) self.ssh.run_sudo_command(f"rm -rf {_q(self.base_dir)}") return True def start_service(self): out, err, code = self.ssh.run_sudo_command('mita start 2>&1', timeout=60) if code != 0: raise RuntimeError((err or out or 'mita start failed').strip()) def stop_service(self): self.ssh.run_sudo_command('mita stop 2>/dev/null || true', timeout=30) def get_server_config(self, protocol_type=None): out, _, code = self.ssh.run_sudo_command('mita describe config 2>/dev/null') if code == 0 and (out or '').strip(): return out return self._read_file(self.config_path) def save_server_config(self, protocol_type=None, config_text=''): self._write_file(self.config_path, config_text or '') out, err, code = self.ssh.run_sudo_command( f"mita apply config {_q(self.config_path)} 2>&1", timeout=60, ) if code != 0: raise RuntimeError((err or out or 'mita apply config failed').strip()) self.ssh.run_sudo_command('mita stop 2>/dev/null || true', timeout=30) self.ssh.run_sudo_command('mita start 2>&1', timeout=60) return True # ===================== CLIENTS ===================== def get_clients(self, protocol_type=None): clients = self._read_clients() result = [] for c in clients: cname = c.get('name') or c.get('id') result.append({ 'clientId': c.get('id'), 'client_id': c.get('id'), 'id': c.get('id'), 'name': cname, 'email': cname, 'enabled': c.get('enabled', True), 'userData': {'clientName': cname, 'enabled': c.get('enabled', True)}, }) return result def add_client(self, protocol_type, name, host, port=None): meta = self._read_metadata() port = int(port or meta.get('port') or self.DEFAULT_PORT) client_id = secrets.token_hex(8) username = _sanitize_username(name) password = _rand_token(20) clients = self._read_clients() clients.append({ 'id': client_id, 'name': name or username, 'username': username, 'password': password, 'enabled': True, }) self._write_clients(clients) self._sync_server(reload_only=True) config = self._build_share_uri(host, port, username, password, name or username) json_config = self._build_client_json(host, port, username, password) return { 'clientId': client_id, 'client_id': client_id, 'id': client_id, 'name': name or username, 'config': config, 'json_config': json_config, } def get_client_config(self, protocol_type, client_id, host, port=None): meta = self._read_metadata() port = int(port or meta.get('port') or self.DEFAULT_PORT) clients = self._read_clients() client = next((c for c in clients if c.get('id') == client_id), None) if not client: return '' if not client.get('enabled', True): return '' username = client.get('username') or client.get('name') or client_id password = client.get('password') or '' name = client.get('name') or username return self._build_share_uri(host, port, username, password, name) def remove_client(self, protocol_type, client_id): clients = [c for c in self._read_clients() if c.get('id') != client_id] self._write_clients(clients) self._sync_server(reload_only=True) return True def toggle_client(self, protocol_type, client_id, enabled): clients = self._read_clients() for c in clients: if c.get('id') == client_id: c['enabled'] = bool(enabled) self._write_clients(clients) self._sync_server(reload_only=True) return True