作者: lhstudio

  • 如何拥有一个免费节点?

    ^-^

    零成本方法

    参考零度解说https://www.freedidi.com/23618.html

    https://www.dnshe.com

    https://dash.cloudflare.com

    vpn软件 https://flclash.cc https://clashverge.net/

    自己租服务器

    基本如果自己使用的话,租一个2核2g的香港或者美国的服务器就可以了,像国内的腾讯云阿里云,或者说国外的都可以。如果国内的话,大概100元以内可以下来。

    安装时系统使用Ubuntu,打开远程界面

    先进入管理员sudo -i

    然后执行安装指令
    bash <(wget -qO- https://raw.githubusercontent.com/fscarmen/sing-box/main/sing-box.sh)

    语言可以直接选择中文,执行中那个安装选1,全部自动安装,然后接下来就一路默认即可。

    安装完之后,他就会给你对应的节点地址。如果之后还要再查看节点地址的话,也是先进入管理员sudo -i,再使用指令sb即可查看。

    sudo rsb

    原生ip https://dash.cliproxy.com

    要换购买的代理信息,选 2;要暂时撤销住宅出口,选 3;以后想重新启用,选 4。选 3 会保留节点资料,方便随时切回。每次修改的备份都在 /etc/sing-box/rsb-backups/

    sudo tee /usr/local/sbin/rsb >/dev/null <<'PY'
    #!/usr/bin/env python3
    import getpass
    import ipaddress
    import json
    import os
    import shutil
    import stat
    import subprocess
    import tempfile
    from datetime import datetime
    from pathlib import Path
    
    BASE = Path("/etc/sing-box")
    OUT = BASE / "conf/01_outbounds.json"
    ROUTE = BASE / "conf/03_route.json"
    BIN = BASE / "sing-box"
    BACKUPS = BASE / "rsb-backups"
    
    def read():
        out = json.loads(OUT.read_text())
        route = json.loads(ROUTE.read_text())
        nodes = [x for x in out["outbounds"] if x.get("tag") == "residential"]
        if len(nodes) != 1:
            raise RuntimeError("找不到唯一的 residential 节点,请先不要修改。")
        return out, route, nodes[0]
    
    def openai_rule(rule):
        return (
            "api.openai.com" in rule.get("domain", [])
            or "geosite-openai" in rule.get("rule_set", [])
        ) and "outbound" in rule
    
    def write_atomic(path, data):
        fd, name = tempfile.mkstemp(dir=path.parent, prefix=".rsb-")
        try:
            os.fchmod(fd, stat.S_IMODE(path.stat().st_mode))
            with os.fdopen(fd, "w") as file:
                json.dump(data, file, ensure_ascii=False, indent=2)
                file.write("\n")
            os.replace(name, path)
        finally:
            if os.path.exists(name):
                os.unlink(name)
    
    def save(out, route):
        BACKUPS.mkdir(mode=0o700, exist_ok=True)
        os.chmod(BACKUPS, 0o700)
        folder = BACKUPS / datetime.now().strftime("%Y%m%d-%H%M%S-%f")
        folder.mkdir(mode=0o700)
        shutil.copy2(OUT, folder / OUT.name)
        shutil.copy2(ROUTE, folder / ROUTE.name)
        try:
            write_atomic(OUT, out)
            write_atomic(ROUTE, route)
            subprocess.run([str(BIN), "check", "-C", str(BASE / "conf")], check=True)
            subprocess.run(["systemctl", "restart", "sing-box"], check=True)
            subprocess.run(
                ["systemctl", "is-active", "--quiet", "sing-box"], check=True
            )
        except Exception:
            shutil.copy2(folder / OUT.name, OUT)
            shutil.copy2(folder / ROUTE.name, ROUTE)
            subprocess.run(["systemctl", "restart", "sing-box"], check=False)
            print("操作失败,已从备份恢复配置:", folder)
            raise
        print("保存成功。备份位置:", folder)
    
    def test_proxy(node):
        host = str(node["server"])
        port = int(node["server_port"])
        user = str(node["username"])
        password = str(node["password"])
        config = 'proxy-user = ' + json.dumps(user + ":" + password) + "\n"
        result = subprocess.run(
            ["curl", "-4", "-fsS", "--connect-timeout", "8",
             "--max-time", "20", "--socks5-hostname", f"{host}:{port}",
             "--config", "-", "https://api.ipify.org"],
            input=config, text=True, capture_output=True
        )
        try:
            address = ipaddress.ip_address(result.stdout.strip())
        except ValueError:
            address = None
        if result.returncode != 0 or address is None:
            print("代理检测失败;未保存新参数。")
            if result.stderr:
                print(result.stderr.strip())
            return False
        print("代理检测成功,出口 IP:", address)
        return True
    
    def main():
        if os.geteuid() != 0:
            raise RuntimeError("请使用 sudo rsb 运行。")
        if not BIN.is_file():
            raise RuntimeError("找不到 /etc/sing-box/sing-box,未修改配置。")
    
        while True:
            print("\n住宅代理管理")
            print("1. 查看当前配置(不显示密码)")
            print("2. 修改 IP、端口、用户名或密码")
            print("3. 改回服务器直连(保留住宅节点信息)")
            print("4. 启用住宅出口")
            print("5. 检测住宅代理出口 IP")
            print("0. 退出")
            choice = input("请选择:").strip()
    
            if choice == "0":
                return
            if choice not in ("1", "2", "3", "4", "5"):
                print("请输入菜单中的数字。")
                continue
    
            out, route, node = read()
            current = route.get("route", {}).get("final", "direct")
    
            if choice == "1":
                print("当前出口:", current)
                print("住宅代理地址:", node["server"])
                print("端口:", node["server_port"])
                print("用户名:", node.get("username", ""))
                print("密码:已隐藏")
                print("提示:原有 UDP 直连规则仍按服务器配置执行。")
    
            elif choice == "2":
                print("直接回车表示保留原值;密码输入时不会显示。")
                host = input(f'IP或域名 [{node["server"]}]:').strip()
                port = input(f'端口 [{node["server_port"]}]:').strip()
                user = input(f'用户名 [{node["username"]}]:').strip()
                password = getpass.getpass("新密码(回车保留原密码):")
                new = dict(node)
                if host:
                    new["server"] = host
                if port:
                    if not port.isdigit() or not 1 <= int(port) <= 65535:
                        print("端口必须是 1~65535;未修改。")
                        continue
                    new["server_port"] = int(port)
                if user:
                    new["username"] = user
                if password:
                    new["password"] = password
                if test_proxy(new):
                    node.update(new)
                    save(out, route)
                    print("当前出口模式仍为:", current)
    
            elif choice == "3":
                route["route"]["final"] = "direct"
                for rule in route["route"].get("rules", []):
                    if openai_rule(rule) and rule.get("outbound") == "residential":
                        rule["outbound"] = "direct"
                save(out, route)
                print("已切换为服务器直连。")
    
            elif choice == "4":
                route["route"]["final"] = "residential"
                for rule in route["route"].get("rules", []):
                    if openai_rule(rule) and rule.get("outbound") == "direct":
                        rule["outbound"] = "residential"
                save(out, route)
                print("已切换为住宅出口。")
    
            elif choice == "5":
                test_proxy(node)
    
    try:
        main()
    except (OSError, ValueError, KeyError, RuntimeError,
            subprocess.CalledProcessError) as exc:
        print("错误:", exc)
        raise SystemExit(1)
    PY
    sudo chmod 700 /usr/local/sbin/rsb