注册 Serv00

注册没什么好说的, gmail+干净一点的ip基本上都可以成功, 这里不多做展开.
收不到邮件信息的估计是邮箱问题. 比如:https://mail.proton.me

准备工作

进入 Additional services 选项卡中找到 Run your own applications 项目,
如下图所示, 必须要设置成如图所示的 Enabled.

如果不开启这一项, 自己的用户目录下的所有文件都无法添加可执行权限.

部署记录

Alist

创建端口

每个账户只能创建3个端口.
其中一个用来反代本地搭建 Alist 的端口, 这里我用 26666 端口.

创建网站

Serv00 账号创建好之后默认就有一个网站, 一般是USERNAME.serv00.net, 可以随意删除.

下图我写的是自己的域名.

其中 26666 这个端口是 Alist 服务的端口.

网站添加 SSL 证书

站点创建完成后, 点击右边的 SSL 👉 WWW websites 👉 Manage.

点击 Add certificate, 确认好要创建证书的域名, 最后点击 Add 完成创建证书.

安装 Alist

Tip

Alist 官方在 24年8月17日, 增加了 beta 版本, 支持 FreeBSD 系统下能够运行的 Alist 可执行文件🎉

Serv00 本身提供的网站托管在~/domains路径下, 所以我建议把 Alist 也部署到这个路径下的子目录.

一键创建目录并下载 Alist, 增加执行权限, 复制到终端粘贴使用.

mkdir -p ~/domains/alist && cd ~/domains/alist && curl -L -o alist.tar.gz https://github.com/AlistGo/alist/releases/download/beta/alist-freebsd-amd64.tar.gz && tar -xzf alist.tar.gz && chomd +x alist

第一次启动 Alist 生成配置文件

文件下载好后需要先启动一次 Alist, 让它生成配置文件, 此次一定会报错, 请不用在意~

./alist server

创建 Alist 所需数据库

回到 Panel 面板, 找到 MySQL 选项卡, 使用 Add database 功能新建一个数据库:

Database name 和 Username 字段为了方便好记就写 Alist 就行了.

密码要求含有大写字母, 小写字母和数字三种字符, 且长度必须超过6个字符.

修改配置文件

进入 Panel 面板, 找到 File manager 选项卡, 会如下图的进入文件管理器.

定位这个config.json文件, 双击编辑它:

我主要修改了CDN database scheme三个部分,

CDN

可以在 Alist 的官方文档找到, 请选择你本地网络连接速度最快的一个.

database type 因为我们创建的数据库类型是 mysql, 就写mysql.
host 例如我的 serv00 是12, 就写mysql12.serv00.com, 自己看着写.
port mysql 端口, 默认3306.
user 填写创建的数据库用户名
passdowd 填写创建的数据密码
name 填写创建的数据表名称
scheme address 0.0.0.0
http_port 填写创建的端口

改完之后, 点击 save 保存.

再次启动 Alist

回到 SSH 窗口中进行操作.

./alist server

运行正常, 显示的管理员账号的密码一定要记住. 接着使用 Ctrl+c 停止运行.

自定义域名绑定

我这边使用 us.kg 的免费域名进行访问 Alist.

因为 serv00 的域名基本上都会被墙, 没办法只能用Cloudflare减速器跨墙了, CDN 回源加速不会弄.

我们进入 https://dash.cloudflare.com

点击添加域, 再输入自己的域名, 选择最底下的 free 计划一路创建.

然后复制 Cloudflare 给的 dns 名称服务器.

然后转到你的域名提供服务商, 添加 dns 记录.

如果没问题就可以通过自定义域名访问了.

Alist 保活

因为 Serv00 会不定时杀进程😅, 所以诞生此方案.

我在 Alist 目录下创建了runAlist.sh脚本, 内容如下:

screen -ls | awk 'NR>=2&&NR<=20{print $1}' | awk '{print "screen -S "$1" -X quit"}' | sh
echo "Attempting to start screen session 'alist'"
screen -dmS alist bash -c 'cd ~/domains/alist && ./alist server'
#screen -ls
echo "$(date '+%Y-%m-%d %H:%M:%S')" > ~/domains/alist/logfile.txt

转到 panel 面板, 创建 Cron Jobs 定时任务.

我们需要创建一个每小时执行的任务进行保活, 如下图:

计划任务执行的是我的runAlist.sh脚本.

不出意外的话, 隔一段时间进入 Alist 网页需要重新登陆账号, 因为定时脚本会先杀原来的 Alist 进程再重启.

Serv00+CT8 保活(可TG通知)

引用自 Linux.do

先决条件

首先上代码

Worker 代码:

JavaScript Code
addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request))
})

addEventListener('scheduled', event => {
  event.waitUntil(handleScheduled(event.scheduledTime))
})

async function handleRequest(request) {
  return new Response('Worker is running')
}

async function handleScheduled(scheduledTime) {
  const accounts = JSON.parse(ACCOUNTS_JSON)
  const results = await loginAccounts(accounts)
  await sendSummary(results)
}

async function loginAccounts(accounts) {
  const results = []
  for (const account of accounts) {
    const result = await loginAccount(account)
    results.push({ ...account, ...result })
    await delay(Math.floor(Math.random() * 8000) + 1000)
  }
  return results
}

function generateRandomUserAgent() {
  const browsers = ['Chrome', 'Firefox', 'Safari', 'Edge', 'Opera'];
  const browser = browsers[Math.floor(Math.random() * browsers.length)];
  const version = Math.floor(Math.random() * 100) + 1;
  const os = ['Windows NT 10.0', 'Macintosh', 'X11'];
  const selectedOS = os[Math.floor(Math.random() * os.length)];
  const osVersion = selectedOS === 'X11' ? 'Linux x86_64' : selectedOS === 'Macintosh' ? 'Intel Mac OS X 10_15_7' : 'Win64; x64';

  return `Mozilla/5.0 (${selectedOS}; ${osVersion}) AppleWebKit/537.36 (KHTML, like Gecko) ${browser}/${version}.0.0.0 Safari/537.36`;
}

async function loginAccount(account) {
  const { username, password, panelnum, type } = account
  let url = type === 'ct8' 
    ? 'https://panel.ct8.pl/login/?next=/' 
    : `https://panel${panelnum}.serv00.com/login/?next=/`

  const userAgent = generateRandomUserAgent();

  try {
    const response = await fetch(url, {
      method: 'GET',
      headers: {
        'User-Agent': userAgent,
      },
    })

    const pageContent = await response.text()
    const csrfMatch = pageContent.match(/name="csrfmiddlewaretoken" value="([^"]*)"/)
    const csrfToken = csrfMatch ? csrfMatch[1] : null

    if (!csrfToken) {
      throw new Error('CSRF token not found')
    }

    const initialCookies = response.headers.get('set-cookie') || ''

    const formData = new URLSearchParams({
      'username': username,
      'password': password,
      'csrfmiddlewaretoken': csrfToken,
      'next': '/'
    })

    const loginResponse = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Referer': url,
        'User-Agent': userAgent,
        'Cookie': initialCookies,
      },
      body: formData.toString(),
      redirect: 'manual'
    })

    console.log(`Login response status: ${loginResponse.status}`)
    console.log(`Login response headers: ${JSON.stringify(Object.fromEntries(loginResponse.headers))}`)

    const loginResponseBody = await loginResponse.text()
    console.log(`Login response body: ${loginResponseBody.substring(0, 200)}...`)

    if (loginResponse.status === 302 && loginResponse.headers.get('location') === '/') {
      const loginCookies = loginResponse.headers.get('set-cookie') || ''
      const allCookies = combineCookies(initialCookies, loginCookies)

      const dashboardResponse = await fetch(url.replace('/login/', '/'), {
        headers: {
          'Cookie': allCookies,
          'User-Agent': userAgent,
        }
      })
      const dashboardContent = await dashboardResponse.text()
      console.log(`Dashboard content: ${dashboardContent.substring(0, 200)}...`)

      if (dashboardContent.includes('href="/logout/"') || dashboardContent.includes('href="/wyloguj/"')) {
        const nowUtc = formatToISO(new Date())
        const nowBeijing = formatToISO(new Date(Date.now() + 8 * 60 * 60 * 1000))
        const message = `账号 ${username} (${type}) 于北京时间 ${nowBeijing}(UTC时间 ${nowUtc})登录成功!`
        console.log(message)
        await sendTelegramMessage(message)
        return { success: true, message }
      } else {
        const message = `账号 ${username} (${type}) 登录后未找到登出链接,可能登录失败。`
        console.error(message)
        await sendTelegramMessage(message)
        return { success: false, message }
      }
    } else if (loginResponseBody.includes('Nieprawidłowy login lub hasło')) {
      const message = `账号 ${username} (${type}) 登录失败: 用户名或密码错误。`
      console.error(message)
      await sendTelegramMessage(message)
      return { success: false, message }
    } else {
      const message = `账号 ${username} (${type}) 登录失败,未知原因。请检查账号和密码是否正确。`
      console.error(message)
      await sendTelegramMessage(message)
      return { success: false, message }
    }
  } catch (error) {
    const message = `账号 ${username} (${type}) 登录时出现错误: ${error.message}`
    console.error(message)
    await sendTelegramMessage(message)
    return { success: false, message }
  }
}

function combineCookies(cookies1, cookies2) {
  const cookieMap = new Map()
  
  const parseCookies = (cookieString) => {
    cookieString.split(',').forEach(cookie => {
      const [fullCookie] = cookie.trim().split(';')
      const [name, value] = fullCookie.split('=')
      if (name && value) {
        cookieMap.set(name.trim(), value.trim())
      }
    })
  }

  parseCookies(cookies1)
  parseCookies(cookies2)

  return Array.from(cookieMap.entries()).map(([name, value]) => `${name}=${value}`).join('; ')
}

async function sendSummary(results) {
  const successfulLogins = results.filter(r => r.success)
  const failedLogins = results.filter(r => !r.success)

  let summaryMessage = '登录结果统计: \n'
  summaryMessage += `成功登录的账号: ${successfulLogins.length}\n`
  summaryMessage += `登录失败的账号: ${failedLogins.length}\n`

  if (failedLogins.length > 0) {
    summaryMessage += '\n登录失败的账号列表: \n'
    failedLogins.forEach(({ username, type, message }) => {
      summaryMessage += `- ${username} (${type}): ${message}\n`
    })
  }

  console.log(summaryMessage)
  await sendTelegramMessage(summaryMessage)
}

async function sendTelegramMessage(message) {
  const telegramConfig = JSON.parse(TELEGRAM_JSON)
  const { telegramBotToken, telegramBotUserId } = telegramConfig
  const url = `https://api.telegram.org/bot${telegramBotToken}/sendMessage`
  
  try {
    await fetch(url, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        chat_id: telegramBotUserId,
        text: message
      })
    })
  } catch (error) {
    console.error('Error sending Telegram message:', error)
  }
}

function formatToISO(date) {
  return date.toISOString().replace('T', ' ').replace('Z', '').replace(/\.\d{3}Z/, '')
}

function delay(ms) {
  return new Promise(resolve => setTimeout(resolve, ms))
}

变量:

ACCOUNTS_JSON

Json
[  
  { "username": "serv00user1", "password": "serv00password1", "panelnum": "0", "type": "serv00" },
  { "username": "serv00user2", "password": "serv00password2", "panelnum": "4", "type": "serv00" },
  { "username": "serv00user3", "password": "serv00password3", "panelnum": "7", "type": "serv00" },
  { "username": "ct8user1", "password": "ct8password1", "type": "ct8" },
  { "username": "ct8user2", "password": "ct8password2", "type": "ct8" }
]

TELEGRAM_JSON

Json
{
  "telegramBotToken": "YOUR_BOT_TOKEN",
  "telegramBotUserId": "YOUR_USER_ID"
}

创建 Workers

进入 Cloudflare 面板, 创建 Workers

名字随意, 建议写Serv00Keep方便好记, 然后右下角点部署.

部署完成后点击编辑代码.

粘贴 JavaScript 代码之后再点击部署.

添加机密

返回到Serv00Keep的设置, 找到变量和机密

按照Json格式编辑好自己的 serv00 账号和密码, 创建ACCOUNTS_JSON变量.
按照Json格式编辑好自己的 telegram bot token, 创建TELEGRAM_JSON变量.

添加触发事件

手动部署

机密和触发事件填写完成之后, 我们手动部署一次.

手动执行验证效果

如下图, 进入编辑代码 → 设定时间 → 触发计划的事件, 即可手动触发.

同时可以看到正常运行没问题, 接下来就是定时执行不用再管它了.

文章引用

Saika's Blog | 知乎

🥰转载请注明,谢谢!🥰