有了Codex的加持,也渐渐得疯狂起来了,本身运维就是全干,对系统的各个点把控就比较精准

这次准备薅羊毛的是 wispbyte.com , 是欧洲的一个厂家

它可以免费建立一个server,准确的来说是一个容器,并且暴露一个端口出来

image-20260102172243875

那先Create Server,Server Name 和 Server Description就都写 god01 好了

image-20260102172435838

Server Type当然选Free Plan的,运行环境就选NodeJS

image-20260102172459520

选完就按最下面的创建即可。

建好了那就去到 server的manage管理界面,下面有个Startup:

image-20260102172759672

分析那句命令,意思很简单,如果配了git,就去拉代码,如果有存在npm的package.json,就先npm install安装,最后,运行index.js

下面是Docker Image,本来以为是可以随便引用别处的镜像,结果是不能,只能选固定的,那就选不太激进的 nodejs_22 ,保存好

然后就什么都不用动了

image-20260102173016033

然后去到Files选项,缺省路径是 /hoe/container/

image-20260102173221647

上面图是已经弄好的,如果是新服务器,是空无一物的

我们只需要准备5个文件和一个域名:

  • tls.crt 证书文件,用let’s encrypt申请

  • tls.key 密钥文件,用let’s encrypt申请

  • index.html 用来装饰的环保单页面,如果不加,就会显示hello world,太假了,可以让 gemini 给你生成一个,代码如下:

  • <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Green Network - Protect Earth</title>
    
        <style>
            * {
                margin: 0;
                padding: 0;
                box-sizing: border-box;
                font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            }
    
            :root {
                --primary: #2e7d32;
                --secondary: #4caf50;
                --accent: #8bc34a;
                --light: #e8f5e9;
                --dark: #1b5e20;
                --text: #333333;
                --white: #ffffff;
            }
    
            body {
                background-color: var(--light);
                color: var(--text);
                line-height: 1.7; /* Slightly increased line height for readability */
                font-size: 16px;
            }
    
            header {
                background: rgba(46, 125, 50, 0.95); /* Slightly transparent primary color */
                color: var(--white);
                padding: 1rem 0;
                position: sticky;
                top: 0;
                z-index: 100;
                box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); /* Enhanced shadow */
            }
    
            .container {
                width: 90%;
                max-width: 1200px;
                margin: 0 auto;
            }
    
            .nav-container {
                display: flex;
                justify-content: space-between;
                align-items: center;
            }
    
            .logo {
                display: flex;
                align-items: center;
                gap: 10px;
                font-size: 1.8rem;
                font-weight: 700;
            }
    
            .logo i {
                font-size: 2rem;
            }
    
            nav ul {
                display: flex;
                list-style: none;
            }
    
            nav ul li {
                margin-left: 2rem;
            }
    
            nav ul li a {
                color: var(--white);
                text-decoration: none;
                font-weight: 500;
                transition: all 0.3s ease;
                padding: 0.5rem 0;
                position: relative;
            }
    
            nav ul li a:hover {
                color: var(--accent);
            }
    
            nav ul li a::after {
                content: '';
                position: absolute;
                bottom: 0;
                left: 0;
                width: 0;
                height: 2px;
                background-color: var(--accent);
                transition: width 0.3s ease;
            }
    
            nav ul li a:hover::after {
                width: 100%;
            }
    
            .hero {
                background: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.4)), url('https://images.unsplash.com/photo-1542601906990-b4d3fb778b09?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2073&q=80');
                background-size: cover;
                background-position: center;
                height: 80vh;
                display: flex;
                align-items: center;
                text-align: center;
                color: var(--white);
            }
    
            .hero-content {
                max-width: 800px;
                margin: 0 auto;
            }
    
            .hero h1 {
                font-size: 3.5rem;
                margin-bottom: 1rem;
                text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
            }
    
            .hero p {
                font-size: 1.3rem;
                margin-bottom: 2rem;
            }
    
            .btn {
                display: inline-block;
                background-color: var(--accent);
                color: var(--white);
                padding: 0.9rem 2.2rem; /* Slightly larger padding */
                border-radius: 8px; /* More modern rounded corners */
                text-decoration: none;
                font-weight: 600;
                transition: all 0.3s ease;
                border: none;
                cursor: pointer;
                box-shadow: 0 6px 10px rgba(0, 0, 0, 0.1); /* Slightly enhanced shadow */
            }
    
            .btn:hover {
                background-color: var(--primary);
                transform: translateY(-5px); /* More pronounced lift effect */
                box-shadow: 0 8px 15px rgba(0, 0, 0, 0.2); /* Stronger hover shadow */
            }
    
            section {
                padding: 5rem 0;
            }
    
            .section-title {
                text-align: center;
                margin-bottom: 3rem;
            }
    
            .section-title h2 {
                font-size: 2.5rem;
                color: var(--primary);
                margin-bottom: 1rem;
                position: relative;
                display: inline-block;
            }
    
            .section-title h2::after {
                content: '';
                position: absolute;
                bottom: -10px;
                left: 50%;
                transform: translateX(-50%);
                width: 90px; /* Slightly wider underline */
                height: 5px; /* Thicker underline */
                background-color: var(--accent);
                border-radius: 3px;
            }
    
            .section-title p {
                color: var(--text);
                max-width: 700px;
                margin: 0 auto;
                font-size: 1.1rem;
            }
    
            .about-content {
                display: grid;
                grid-template-columns: 1fr 1fr;
                gap: 3rem;
                align-items: center;
            }
    
            .about-img {
                border-radius: 12px; /* More rounded */
                overflow: hidden;
                box-shadow: 0 12px 25px rgba(0, 0, 0, 0.15); /* Enhanced shadow */
            }
    
            .about-img img {
                width: 100%;
                height: auto;
                display: block;
                transition: transform 0.5s ease;
            }
    
            .about-img:hover img {
                transform: scale(1.08); /* More pronounced zoom */
            }
    
            .about-text h3 {
                font-size: 2rem;
                margin-bottom: 1.5rem;
                color: var(--dark);
            }
    
            .about-text p {
                margin-bottom: 1.5rem;
                font-size: 1.05rem;
            }
    
            .stats {
                display: grid;
                grid-template-columns: repeat(4, 1fr);
                gap: 2rem;
                margin-top: 4rem; /* More space */
            }
    
            .stat-item {
                text-align: center;
                padding: 2.5rem 1.5rem; /* More padding */
                background-color: var(--white);
                border-radius: 12px; /* More rounded */
                box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08); /* Enhanced shadow */
                transition: transform 0.3s ease, box-shadow 0.3s ease;
            }
    
            .stat-item:hover {
                transform: translateY(-12px); /* More pronounced lift */
                box-shadow: 0 15px 30px rgba(0, 0, 0, 0.15); /* Stronger hover shadow */
            }
    
            .stat-item h3 {
                font-size: 3rem; /* Larger numbers */
                color: var(--dark);
                margin-bottom: 0.8rem;
            }
    
            .stat-item p {
                color: var(--text);
                font-size: 1.1rem;
            }
    
            .initiatives {
                background-color: var(--white);
            }
    
            .initiatives-grid {
                display: grid;
                grid-template-columns: repeat(3, 1fr);
                gap: 2.5rem;
            }
    
            .initiative-card {
                background-color: var(--light);
                border-radius: 12px; /* More rounded */
                overflow: hidden;
                box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08); /* Enhanced shadow */
                transition: transform 0.3s ease, box-shadow 0.3s ease;
            }
    
            .initiative-card:hover {
                transform: translateY(-12px); /* More pronounced lift */
                box-shadow: 0 15px 30px rgba(0, 0, 0, 0.15); /* Stronger hover shadow */
            }
    
            .initiative-img {
                height: 220px; /* Slightly taller images */
                overflow: hidden;
            }
    
            .initiative-img img {
                width: 100%;
                height: 100%;
                object-fit: cover;
                transition: transform 0.5s ease;
            }
    
            .initiative-card:hover .initiative-img img {
                transform: scale(1.15); /* More pronounced zoom */
            }
    
            .initiative-content {
                padding: 2rem;
            }
    
            .initiative-content h3 {
                font-size: 1.6rem;
                margin-bottom: 1rem;
                color: var(--dark);
            }
    
            .initiative-content p {
                margin-bottom: 1.8rem;
                font-size: 1.05rem;
            }
    
            .cta {
                background: linear-gradient(to right, var(--primary), var(--secondary));
                color: var(--white);
                text-align: center;
                padding: 6rem 0; /* More padding */
            }
    
            .cta h2 {
                font-size: 3rem; /* Larger heading */
                margin-bottom: 1.8rem;
            }
    
            .cta p {
                font-size: 1.3rem;
                margin-bottom: 2.5rem;
                max-width: 700px;
                margin-left: auto;
                margin-right: auto;
            }
    
            .cta .btn {
                background-color: var(--white);
                color: var(--primary);
            }
    
            .cta .btn:hover {
                background-color: var(--accent);
                color: var(--white);
            }
    
            footer {
                background-color: var(--dark);
                color: var(--white);
                padding: 4rem 0 2rem; /* More padding */
            }
    
            .footer-content {
                display: grid;
                grid-template-columns: repeat(4, 1fr);
                gap: 2.5rem; /* More gap */
                margin-bottom: 2.5rem;
            }
    
            .footer-column h3 {
                font-size: 1.4rem;
                margin-bottom: 1.8rem;
                position: relative;
                padding-bottom: 12px;
            }
    
            .footer-column h3::after {
                content: '';
                position: absolute;
                bottom: 0;
                left: 0;
                width: 50px; /* Wider underline */
                height: 3px; /* Thicker underline */
                background-color: var(--accent);
            }
    
            .footer-column p {
                margin-bottom: 1.2rem;
            }
    
            .footer-links {
                list-style: none;
            }
    
            .footer-links li {
                margin-bottom: 1rem;
            }
    
            .footer-links li a {
                color: var(--light);
                text-decoration: none;
                transition: all 0.3s ease;
            }
    
            .footer-links li a:hover {
                color: var(--accent);
                padding-left: 8px; /* More pronounced slide effect */
            }
    
            .social-links {
                display: flex;
                gap: 1.2rem;
                margin-top: 1.5rem;
            }
    
            .social-links a {
                display: inline-flex;
                align-items: center;
                justify-content: center;
                width: 50px; /* Larger social buttons */
                height: 50px;
                background-color: rgba(255, 255, 255, 0.15); /* Slightly more visible background */
                border-radius: 8px; /* Square with rounded corners */
                color: var(--white);
                text-decoration: none;
                font-weight: 600;
                transition: all 0.3s ease;
                font-size: 0.9rem;
            }
    
            .social-links a:hover {
                background-color: var(--accent);
                transform: translateY(-7px); /* More pronounced lift */
            }
    
            .copyright {
                text-align: center;
                padding-top: 2.5rem;
                border-top: 1px solid rgba(255, 255, 255, 0.15);
            }
    
            .mobile-menu {
                display: none;
                font-size: 1.5rem;
                cursor: pointer;
                padding: 0.5rem 1rem;
                border: 1px solid rgba(255, 255, 255, 0.3);
                border-radius: 5px;
                transition: all 0.3s ease;
            }
    
            .mobile-menu:hover {
                background-color: rgba(255, 255, 255, 0.1);
            }
    
            @media (max-width: 992px) {
                .about-content {
                    grid-template-columns: 1fr;
                }
    
                .initiatives-grid {
                    grid-template-columns: repeat(2, 1fr);
                }
    
                .footer-content {
                    grid-template-columns: repeat(2, 1fr);
                }
    
                .stats {
                    grid-template-columns: repeat(2, 1fr);
                }
            }
    
            @media (max-width: 768px) {
                .hero h1 {
                    font-size: 2.8rem; /* Adjusted for better readability on smaller screens */
                }
    
                .hero p {
                    font-size: 1.2rem; /* Adjusted for better readability on smaller screens */
                }
    
                nav ul {
                    display: none;
                    position: absolute;
                    top: 100%;
                    left: 0;
                    width: 100%;
                    background-color: var(--primary);
                    flex-direction: column;
                    padding: 1rem 0;
                    text-align: center;
                    box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
                }
    
                nav ul.active {
                    display: flex;
                }
    
                nav ul li {
                    margin: 0;
                    padding: 0.8rem 0;
                }
    
                .mobile-menu {
                    display: block;
                    font-size: 1.2rem; /* Slightly smaller for mobile */
                    padding: 0.4rem 0.8rem;
                }
    
                .initiatives-grid {
                    grid-template-columns: 1fr;
                }
    
                .footer-content {
                    grid-template-columns: 1fr;
                }
    
                .stats {
                    grid-template-columns: 1fr;
                }
    
                .social-links a {
                    width: 45px; /* Slightly smaller social buttons */
                    height: 45px;
                    font-size: 0.85rem;
                }
            }
    
            /* Animation for stats counter */
            .counter {
                transition: all 0.5s ease;
            }
        </style>
    </head>
    <body>
        <!-- Header -->
        <header>
            <div class="container nav-container">
                <div class="logo">
                    <span>Green Network</span>
                </div>
                <nav>
                    <div class="mobile-menu">
                        Menu
                    </div>
                    <ul>
                        <li><a href="#home">Home</a></li>
                        <li><a href="#about">About</a></li>
                        <li><a href="#initiatives">Initiatives</a></li>
                        <li><a href="#get-involved">Get Involved</a></li>
                        <li><a href="#contact">Contact</a></li>
                    </ul>
                </nav>
            </div>
        </header>
    
        <!-- Hero Section -->
        <section class="hero" id="home">
            <div class="container hero-content">
                <h1>Protect Our Planet, Preserve Our Future</h1>
                <p>Join the global movement to create a sustainable world through conservation, education, and community action.</p>
                <a href="#get-involved" class="btn">Join Our Network</a>
            </div>
        </section>
    
        <!-- About Section -->
        <section id="about">
            <div class="container">
                <div class="section-title">
                    <h2>About Green Network</h2>
                    <p>We are a global community dedicated to environmental protection and sustainable development.</p>
                </div>
                <div class="about-content">
                    <div class="about-img">
                        <img src="https://images.unsplash.com/photo-1507591064344-4c6ce005b128?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2070&q=80" alt="Team planting trees">
                    </div>
                    <div class="about-text">
                        <h3>Our Mission</h3>
                        <p>Green Network is committed to creating a sustainable future by protecting natural ecosystems, promoting renewable energy, and empowering communities to take environmental action.</p>
                        <p>Founded in 2010, we've grown from a small grassroots organization to an international network with over 50,000 members across 120 countries.</p>
                        <p>Our approach combines scientific research, community engagement, and policy advocacy to address the most pressing environmental challenges of our time.</p>
                        <a href="#" class="btn">Learn More</a>
                    </div>
                </div>
                <div class="stats">
                    <div class="stat-item">
                        <h3 class="counter" data-target="2500000">0</h3>
                        <p>Trees Planted</p>
                    </div>
                    <div class="stat-item">
                        <h3 class="counter" data-target="50000">0</h3>
                        <p>Active Members</p>
                    </div>
                    <div class="stat-item">
                        <h3 class="counter" data-target="120">0</h3>
                        <p>Countries Reached</p>
                    </div>
                    <div class="stat-item">
                        <h3 class="counter" data-target="1500">0</h3>
                        <p>Cleanup Projects</p>
                    </div>
                </div>
            </div>
        </section>
    
        <!-- Initiatives Section -->
        <section class="initiatives" id="initiatives">
            <div class="container">
                <div class="section-title">
                    <h2>Our Initiatives</h2>
                    <p>Discover the key programs and projects we're implementing to protect our planet.</p>
                </div>
                <div class="initiatives-grid">
                    <div class="initiative-card">
                        <div class="initiative-img">
                            <img src="https://dialogue.earth/content/uploads/2021/04/lessons-from-the-rush-to-reforest-china-dialogue-2400x1599.jpg" alt="Reforestation">
                        </div>
                        <div class="initiative-content">
                            <h3>Global Reforestation</h3>
                            <p>Planting millions of trees worldwide to restore ecosystems, combat climate change, and protect biodiversity.</p>
                            <a href="#" class="btn">Learn More</a>
                        </div>
                    </div>
                    <div class="initiative-card">
                        <div class="initiative-img">
                            <img src="https://images.unsplash.com/photo-1621451537084-482c73073a0f?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=1974&q=80" alt="Ocean Conservation">
                        </div>
                        <div class="initiative-content">
                            <h3>Ocean Conservation</h3>
                            <p>Protecting marine ecosystems, reducing plastic pollution, and promoting sustainable fishing practices.</p>
                            <a href="#" class="btn">Learn More</a>
                        </div>
                    </div>
                    <div class="initiative-card">
                        <div class="initiative-img">
                            <img src="https://images.unsplash.com/photo-1508514177221-188b1cf16e9d?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2072&q=80" alt="Renewable Energy">
                        </div>
                        <div class="initiative-content">
                            <h3>Renewable Energy</h3>
                            <p>Promoting solar, wind, and other clean energy sources to reduce dependence on fossil fuels.</p>
                            <a href="#" class="btn">Learn More</a>
                        </div>
                    </div>
                </div>
            </div>
        </section>
    
        <!-- CTA Section -->
        <section class="cta" id="get-involved">
            <div class="container">
                <h2>Join Our Global Movement</h2>
                <p>Together, we can create a sustainable future for generations to come. Every action counts, no matter how small.</p>
                <a href="#" class="btn">Become a Member</a>
            </div>
        </section>
    
        <!-- Footer -->
        <footer id="contact">
            <div class="container">
                <div class="footer-content">
                    <div class="footer-column">
                        <h3>Green Network</h3>
                        <p>We are dedicated to protecting our planet through collaborative action, education, and sustainable solutions.</p>
                        <div class="social-links">
                            <a href="#">Facebook</a>
                            <a href="#">Twitter</a>
                            <a href="#">Instagram</a>
                            <a href="#">LinkedIn</a>
                        </div>
                    </div>
                    <div class="footer-column">
                        <h3>Quick Links</h3>
                        <ul class="footer-links">
                            <li><a href="#home">Home</a></li>
                            <li><a href="#about">About Us</a></li>
                            <li><a href="#initiatives">Initiatives</a></li>
                            <li><a href="#get-involved">Get Involved</a></li>
                            <li><a href="#contact">Contact</a></li>
                        </ul>
                    </div>
                    <div class="footer-column">
                        <h3>Our Programs</h3>
                        <ul class="footer-links">
                            <li><a href="#">Reforestation</a></li>
                            <li><a href="#">Ocean Cleanup</a></li>
                            <li><a href="#">Wildlife Protection</a></li>
                            <li><a href="#">Climate Education</a></li>
                            <li><a href="#">Sustainable Agriculture</a></li>
                        </ul>
                    </div>
                    <div class="footer-column">
                        <h3>Contact Us</h3>
                        <p>Copper Creek Drive, New York</p>
                        <p>+1 (312) 171-0771</p>
                        <p>support@greennetwork.org</p>
                    </div>
                </div>
                <div class="copyright">
                    <p>&copy; 2025 Green Network. All rights reserved.</p>
                </div>
            </div>
        </footer>
    
        <script>
            // Mobile Menu Toggle
            document.querySelector('.mobile-menu').addEventListener('click', function() {
                document.querySelector('nav ul').classList.toggle('active');
            });
    
            // Counter Animation
            function animateCounters() {
                const counters = document.querySelectorAll('.counter');
                const speed = 200; // The lower the slower
    
                counters.forEach(counter => {
                    const target = +counter.getAttribute('data-target');
                    const count = +counter.innerText;
    
                    const inc = target / speed;
    
                    if(count < target) {
                        counter.innerText = Math.ceil(count + inc);
                        setTimeout(animateCounters, 1);
                    } else {
                        counter.innerText = target;
                    }
                });
            }
    
            // Initialize counters when page loads
            window.addEventListener('load', animateCounters);
    
            // Smooth scrolling for navigation links
            document.querySelectorAll('a[href^="#"]').forEach(anchor => {
                anchor.addEventListener('click', function(e) {
                    e.preventDefault();
    
                    const targetId = this.getAttribute('href');
                    if(targetId === '#') return;
    
                    const targetElement = document.querySelector(targetId);
                    if(targetElement) {
                        window.scrollTo({
                            top: targetElement.offsetTop - 80,
                            behavior: 'smooth'
                        });
    
                        document.querySelector('nav ul').classList.remove('active');
                    }
                });
            });
        </script>
    </body>
    
    </html>
    
  • package.json 文件,index.js 运行时需要依赖的安装包文件,安装了2个包,axios和ws

  • {
      "name": "js01",
      "version": "0.0.1",
      "description": "Nodejs-server",
      "main": "index.js",
      "private": false,
      "scripts": {
        "start": "node index.js"
      },
      "dependencies": {
        "ws": "^8.14.2",
        "axios": "^1.12.2"
      },
      "engines": {
        "node": ">=14"
      }
    }
    
  • index.js 主文件,所有东西都在这里面

  • const http = require('http');
    const https = require('https');
    const fs = require('fs');
    const axios = require('axios');
    const net = require('net');
    const path = require('path');
    const crypto = require('crypto');
    const { Buffer } = require('buffer');
    const { WebSocket, createWebSocketStream } = require('ws');
    
    // 生成 UUID v4
    function generateUUID() {
      return crypto.randomUUID();
    }
    
    const UUID = process.env.UUID || generateUUID();
    const DOMAIN = process.env.DOMAIN || '1234.abc.com';       // 填写项目域名
    const WSPATH = process.env.WSPATH || UUID.slice(0, 8);     // 节点路径,默认获取uuid前8位
    const SUB_PATH = process.env.SUB_PATH || 'sub';            // 获取节点的订阅路径
    const NAME = process.env.NAME || '';                       // 节点名称
    const PORT = process.env.PORT || 7860;                     // http和ws服务端口
    const TLS_KEY_PATH = process.env.TLS_KEY_PATH || 'tls.key';
    const TLS_CERT_PATH = process.env.TLS_CERT_PATH || 'tls.crt'; 
    
    let ISP = '';
    const GetISP = async () => {
      try {
        const res = await axios.get('https://api.ip.sb/geoip');
        const data = res.data;
        ISP = `${data.country_code}-${data.isp}`.replace(/ /g, '_');
      } catch (e) {
        ISP = 'Unknown';
      }
    }
    GetISP();
    
    function normalizeSecret(value) {
      if (!value) return '';
      return value.includes('\\n') ? value.replace(/\\n/g, '\n') : value;
    }
    
    function readOptionalFile(filePath) {
      if (!filePath) return '';
      const resolvedPath = path.isAbsolute(filePath) ? filePath : path.join(__dirname, filePath);
      try {
        return fs.readFileSync(resolvedPath, 'utf8');
      } catch (error) {
        console.warn(`Failed to read TLS file ${resolvedPath}: ${error.message}`);
        return '';
      }
    }
    
    function getTLSOptions() {
      let key = normalizeSecret(process.env.TLS_KEY);
      let cert = normalizeSecret(process.env.TLS_CERT);
    
      if (!key) {
        key = readOptionalFile(TLS_KEY_PATH);
      }
      if (!cert) {
        cert = readOptionalFile(TLS_CERT_PATH);
      }
    
      if (key && cert) {
        return { key, cert };
      }
      if (key || cert) {
        console.warn('Both TLS key and certificate are required; falling back to HTTP.');
      }
      return null;
    }
    
    const requestHandler = (req, res) => {
      if (req.url === '/') {
        const filePath = path.join(__dirname, 'index.html');
        fs.readFile(filePath, 'utf8', (err, content) => {
          if (err) {
            res.writeHead(200, { 'Content-Type': 'text/html' });
            res.end('Hello world!');
            return;
          }
          res.writeHead(200, { 'Content-Type': 'text/html' });
          res.end(content);
        });
        return;
      } else if (req.url === `/${SUB_PATH}`) {
        const namePart = NAME ? `${NAME}-${ISP}` : ISP;
        const vlessURL = `vless://${UUID}@${DOMAIN}:${PORT}?encryption=none&security=tls&sni=${DOMAIN}&fp=chrome&type=ws&host=${DOMAIN}&path=%2F${WSPATH}#${namePart}`;
        const trojanURL = `trojan://${UUID}@${DOMAIN}:${PORT}?security=tls&sni=${DOMAIN}&fp=chrome&type=ws&host=${DOMAIN}&path=%2F${WSPATH}#${namePart}`;
        const subscription = vlessURL + '\n' + trojanURL;
        const base64Content = Buffer.from(subscription).toString('base64');
    
        res.writeHead(200, { 'Content-Type': 'text/plain' });
        res.end(base64Content + '\n');
      } else {
        res.writeHead(404, { 'Content-Type': 'text/plain' });
        res.end('Not Found\n');
      }
    };
    
    const tlsOptions = getTLSOptions();
    const httpServer = tlsOptions ? https.createServer(tlsOptions, requestHandler) : http.createServer(requestHandler);
    
    const wss = new WebSocket.Server({ server: httpServer });
    const uuid = UUID.replace(/-/g, "");
    const DNS_SERVERS = ['8.8.4.4', '1.1.1.1'];
    // Custom DNS
    function resolveHost(host) {
      return new Promise((resolve, reject) => {
        if (/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test(host)) {
          resolve(host);
          return;
        }
        let attempts = 0;
        function tryNextDNS() {
          if (attempts >= DNS_SERVERS.length) {
            reject(new Error(`Failed to resolve ${host} with all DNS servers`));
            return;
          }
          const dnsServer = DNS_SERVERS[attempts];
          attempts++;
          const dnsQuery = `https://dns.google/resolve?name=${encodeURIComponent(host)}&type=A`;
          axios.get(dnsQuery, {
            timeout: 5000,
            headers: {
              'Accept': 'application/dns-json'
            }
          })
          .then(response => {
            const data = response.data;
            if (data.Status === 0 && data.Answer && data.Answer.length > 0) {
              const ip = data.Answer.find(record => record.type === 1);
              if (ip) {
                resolve(ip.data);
                return;
              }
            }
            tryNextDNS();
          })
          .catch(error => {
            tryNextDNS();
          });
        }
    
        tryNextDNS();
      });
    }
    
    // VLE-SS处理
    function handleVlessConnection(ws, msg) {
      const [VERSION] = msg;
      const id = msg.slice(1, 17);
      if (!id.every((v, i) => v == parseInt(uuid.substr(i * 2, 2), 16))) return false;
    
      let i = msg.slice(17, 18).readUInt8() + 19;
      const port = msg.slice(i, i += 2).readUInt16BE(0);
      const ATYP = msg.slice(i, i += 1).readUInt8();
      const host = ATYP == 1 ? msg.slice(i, i += 4).join('.') :
        (ATYP == 2 ? new TextDecoder().decode(msg.slice(i + 1, i += 1 + msg.slice(i, i + 1).readUInt8())) :
        (ATYP == 3 ? msg.slice(i, i += 16).reduce((s, b, i, a) => (i % 2 ? s.concat(a.slice(i - 1, i + 1)) : s), []).map(b => b.readUInt16BE(0).toString(16)).join(':') : ''));
      ws.send(new Uint8Array([VERSION, 0]));
      const duplex = createWebSocketStream(ws);
      resolveHost(host)
        .then(resolvedIP => {
          net.connect({ host: resolvedIP, port }, function() {
            this.write(msg.slice(i));
            duplex.on('error', () => {}).pipe(this).on('error', () => {}).pipe(duplex);
          }).on('error', () => {});
        })
        .catch(error => {
          net.connect({ host, port }, function() {
            this.write(msg.slice(i));
            duplex.on('error', () => {}).pipe(this).on('error', () => {}).pipe(duplex);
          }).on('error', () => {});
        });
    
      return true;
    }
    
    // Tro-jan处理
    function handleTrojanConnection(ws, msg) {
      try {
        if (msg.length < 58) return false;
        const receivedPasswordHash = msg.slice(0, 56).toString();
        const possiblePasswords = [
          UUID,
        ];
    
        let matchedPassword = null;
        for (const pwd of possiblePasswords) {
          const hash = crypto.createHash('sha224').update(pwd).digest('hex');
          if (hash === receivedPasswordHash) {
            matchedPassword = pwd;
            break;
          }
        }
    
        if (!matchedPassword) return false;
        let offset = 56;
        if (msg[offset] === 0x0d && msg[offset + 1] === 0x0a) {
          offset += 2;
        }
    
        const cmd = msg[offset];
        if (cmd !== 0x01) return false;
        offset += 1;
        const atyp = msg[offset];
        offset += 1;
        let host, port;
        if (atyp === 0x01) {
          host = msg.slice(offset, offset + 4).join('.');
          offset += 4;
        } else if (atyp === 0x03) {
          const hostLen = msg[offset];
          offset += 1;
          host = msg.slice(offset, offset + hostLen).toString();
          offset += hostLen;
        } else if (atyp === 0x04) {
          host = msg.slice(offset, offset + 16).reduce((s, b, i, a) => 
            (i % 2 ? s.concat(a.slice(i - 1, i + 1)) : s), [])
            .map(b => b.readUInt16BE(0).toString(16)).join(':');
          offset += 16;
        } else {
          return false;
        }
    
        port = msg.readUInt16BE(offset);
        offset += 2;
    
        if (offset < msg.length && msg[offset] === 0x0d && msg[offset + 1] === 0x0a) {
          offset += 2;
        }
    
        const duplex = createWebSocketStream(ws);
    
        resolveHost(host)
          .then(resolvedIP => {
            net.connect({ host: resolvedIP, port }, function() {
              if (offset < msg.length) {
                this.write(msg.slice(offset));
              }
              duplex.on('error', () => {}).pipe(this).on('error', () => {}).pipe(duplex);
            }).on('error', () => {});
          })
          .catch(error => {
            net.connect({ host, port }, function() {
              if (offset < msg.length) {
                this.write(msg.slice(offset));
              }
              duplex.on('error', () => {}).pipe(this).on('error', () => {}).pipe(duplex);
            }).on('error', () => {});
          });
    
        return true;
      } catch (error) {
        return false;
      }
    }
    // Ws 连接处理
    wss.on('connection', (ws, req) => {
      const url = req.url || '';
      ws.once('message', msg => {
        if (msg.length > 17 && msg[0] === 0) {
          const id = msg.slice(1, 17);
          const isVless = id.every((v, i) => v == parseInt(uuid.substr(i * 2, 2), 16));
          if (isVless) {
            if (!handleVlessConnection(ws, msg)) {
              ws.close();
            }
            return;
          }
        }
    
        if (!handleTrojanConnection(ws, msg)) {
          ws.close();
        }
      }).on('error', () => {});
    });
    
    httpServer.listen(PORT, () => {
      const scheme = tlsOptions ? 'HTTPS/WSS' : 'HTTP/WS';
      console.log(`Server is running on ${scheme} port ${PORT}`);
    });
    

这样就齐活了,index.js 需要修改的地方

const DOMAIN = process.env.DOMAIN || '1234.abc.com';       // 填写域名
const SUB_PATH = process.env.SUB_PATH || 'sub';            // 获取节点的订阅路径
const PORT = process.env.PORT || 7860;   
  • 域名我们要填写自己的域名
  • 路径要换掉,缺省是sub,也就是订阅地址是 https://xxx.aaa.bbb/sub , 最好换成自己的地址
  • 端口也选这个服务器给你开的端口,我的是10407

image-20260102174138408

然后改好了,别急着贴进去

去到 https://obfuscator.io/legacy-playground

贴进去代码,混淆一下,弄成谁也认不得的模样,然后Copy,再贴进去

image-20260102175758163

最后运行

image-20260102180500437

看到Online就好了

那域名解析到这个IP,打开https看看:

image-20260102180633205

那再打开 https://www.aaa.bbb.com/sub , 如果改了sub,那就是别的路径,显示一堆base64加密的字符串

image-20260102180739307

从v2rayN导入,有两个代理,一个是vless,一个是trojan,就可以用了

关键是这么做的原理,就是用index.js完整实现了vless和trojan的功能,加了证书,并且做了混淆和伪装

这样很自然,比在容器里方singbox安全,不容易被扫描到

当然,这个网站的速度不太行,所以只是一个玩具,没有套大善人Cloudflare的CDN

自己用的是 huggingface.co + CF worker,速度还是可以的,下一篇写出来。

代码放在:https://github.com/zhangrr/js-server

大家自行取用吧