dsh2 / index.js
qwen2api's picture
Upload 2 files
f78edd7 verified
Raw
History Blame Contribute Delete
9.81 kB
const http = require('http');
const { exec } = require('child_process');
// 公网 IP
let publicIP = 'unknown';
// 上报状态
let lastReportTime = null;
let lastReportSuccess = false;
// 获取环境变量
const TARGET_IMAGE = process.env.TARGET_IMAGE || '';
const DOCKER_ACCOUNTS = process.env.DOCKER_ACCOUNTS || ''; // 格式: user1:pass1,user2:pass2
const REPORT_URL = process.env.REPORT_URL || '';
const INTERVAL_SECONDS = parseInt(process.env.INTERVAL_SECONDS) || 60; // 间隔时间(秒)
const MARK_DURATION_SECONDS = parseInt(process.env.MARK_DURATION_SECONDS) || 3600; // 标记持续时间(秒),默认1小时
// 解析 Docker 账号列表
const accounts = DOCKER_ACCOUNTS.split(',')
.map(acc => {
const [user, pass] = acc.trim().split(':');
return user && pass ? { user, pass } : null;
})
.filter(acc => acc);
// 账号状态管理
const accountMarkedTime = new Map(); // 记录账号被标记的时间
let currentAccountIndex = 0;
let loggedInAccount = null; // 当前已登录的账号
let anonymousMarkedTime = null; // 匿名用户被标记的时间
// 获取当前可用账号(跳过被标记的)
function getAvailableAccount() {
if (accounts.length === 0) return null;
const now = Date.now();
const markDuration = MARK_DURATION_SECONDS * 1000;
// 清理已过期的标记
for (const [user, markedTime] of accountMarkedTime) {
if (now - markedTime >= markDuration) {
accountMarkedTime.delete(user);
console.log(`账号 ${user} 标记已移除`);
}
}
// 查找可用账号
for (let i = 0; i < accounts.length; i++) {
const idx = (currentAccountIndex + i) % accounts.length;
const acc = accounts[idx];
if (!accountMarkedTime.has(acc.user)) {
currentAccountIndex = idx;
return acc;
}
}
// 所有账号都被标记
return null;
}
// 获取离最近标记移除的时间(毫秒)
function getTimeUntilNextUnmark() {
if (accountMarkedTime.size === 0) return 0;
const now = Date.now();
const markDuration = MARK_DURATION_SECONDS * 1000;
let minWait = Infinity;
for (const markedTime of accountMarkedTime.values()) {
const wait = markDuration - (now - markedTime);
if (wait < minWait && wait > 0) minWait = wait;
}
return minWait === Infinity ? 0 : minWait;
}
// 获取公网 IP
async function fetchPublicIP() {
return new Promise((resolve) => {
exec('curl -s --connect-timeout 5 https://api.ipify.org', (error, stdout, stderr) => {
if (error || !stdout.trim()) {
console.error('获取公网 IP 失败:', error?.message || 'empty response');
resolve('unknown');
} else {
resolve(stdout.trim());
}
});
});
}
// 定期更新公网 IP
async function startIPUpdate() {
publicIP = await fetchPublicIP();
console.log(`公网 IP: ${publicIP}`);
// 每 5 分钟更新一次
setInterval(async () => {
publicIP = await fetchPublicIP();
}, 5 * 60 * 1000);
}
// 上报 URL
async function reportToURL() {
if (!REPORT_URL) return;
return new Promise((resolve) => {
exec(`curl -s --connect-timeout 10 "${REPORT_URL}"`, (error, stdout, stderr) => {
lastReportTime = new Date().toISOString();
if (error) {
lastReportSuccess = false;
console.error(`[${lastReportTime}] 上报失败:`, error.message);
} else {
lastReportSuccess = true;
console.log(`[${lastReportTime}] 上报成功`);
}
resolve();
});
});
}
// 定时上报(每 6 小时)
async function startReportTask() {
if (!REPORT_URL) {
console.log('REPORT_URL 未配置,跳过上报');
return;
}
// 立即执行一次
await reportToURL();
// 每 6 小时执行一次
setInterval(async () => {
await reportToURL();
}, 6 * 60 * 60 * 1000);
}
// 解析镜像名称列表
const images = TARGET_IMAGE.split(',').map(img => img.trim()).filter(img => img);
// 当前镜像索引(循环使用)
let currentImageIndex = 0;
console.log(`配置: ${images.length} 个镜像, ${accounts.length} 个账号, 间隔 ${INTERVAL_SECONDS} 秒`);
// Docker 登录
async function dockerLogin(account) {
return new Promise((resolve) => {
exec(`echo "${account.pass}" | docker login -u "${account.user}" --password-stdin`, (error, stdout, stderr) => {
if (error) {
console.error(`账号 ${account.user} 登录失败:`, stderr.trim());
resolve(false);
} else {
console.log(`账号 ${account.user} 登录成功`);
resolve(true);
}
});
});
}
// 运行一次 docker manifest inspect(只检查一个镜像)
async function runManifestInspect() {
if (images.length === 0) {
console.log('没有配置 TARGET_IMAGE');
return;
}
// 获取当前要检查的镜像
const image = images[currentImageIndex];
// 获取可用账号
const account = getAvailableAccount();
if (accounts.length > 0 && !account) {
// 所有账号都被标记,等待
const waitTime = getTimeUntilNextUnmark();
console.log(`所有账号都被标记,等待 ${Math.round(waitTime / 1000)} 秒后重试`);
setTimeout(() => runManifestInspect(), waitTime);
return;
}
// 检查匿名用户是否被标记
if (accounts.length === 0 && anonymousMarkedTime) {
const now = Date.now();
const markDuration = MARK_DURATION_SECONDS * 1000;
if (now - anonymousMarkedTime < markDuration) {
// 匿名用户仍在标记期内,等待
const waitTime = markDuration - (now - anonymousMarkedTime);
console.log(`匿名用户已被标记,等待 ${Math.round(waitTime / 1000)} 秒后重试`);
setTimeout(() => runManifestInspect(), waitTime);
return;
} else {
// 标记已过期,清除
anonymousMarkedTime = null;
console.log('匿名用户标记已移除');
}
}
// 如果有账号,检查是否需要登录
if (account) {
// 如果当前账号已登录,跳过登录
if (loggedInAccount?.user !== account.user) {
const loginSuccess = await dockerLogin(account);
if (!loginSuccess) {
// 登录失败,标记账号,尝试下一个
accountMarkedTime.set(account.user, Date.now());
console.log(`账号 ${account.user} 登录失败,已标记,${MARK_DURATION_SECONDS} 秒后移除标记`);
loggedInAccount = null; // 重置登录状态
currentAccountIndex = (currentAccountIndex + 1) % accounts.length;
return;
}
loggedInAccount = account;
}
}
// 执行 manifest inspect
await new Promise((resolve) => {
exec(`docker manifest inspect ${image}`, (error, stdout, stderr) => {
const timestamp = new Date().toISOString();
if (error) {
const errorMsg = stderr.trim();
console.error(`[${timestamp}] 检查镜像 ${image} 失败:`, errorMsg);
// 检查是否是 rate limit 错误
if (errorMsg.includes('toomanyrequests') || errorMsg.includes('rate limit')) {
if (account) {
accountMarkedTime.set(account.user, Date.now());
console.log(`账号 ${account.user} 触发 rate limit,已标记,${MARK_DURATION_SECONDS} 秒后移除标记`);
currentAccountIndex = (currentAccountIndex + 1) % accounts.length;
loggedInAccount = null; // 重置登录状态
} else {
// 没有配置账号,标记匿名用户
anonymousMarkedTime = Date.now();
console.log(`匿名用户触发 rate limit,已标记,${MARK_DURATION_SECONDS} 秒后移除标记`);
}
}
} else {
console.log(`[${timestamp}] 检查镜像 ${image} 成功${account ? ` (账号: ${account.user})` : ' (匿名)'}`);
}
resolve();
});
});
// 更新索引,循环到下一个镜像
currentImageIndex = (currentImageIndex + 1) % images.length;
}
// 后台定时任务
async function startBackgroundTask() {
// 立即运行一次
await runManifestInspect();
// 使用递归 setTimeout 代替 setInterval,支持动态等待
function scheduleNext() {
setTimeout(async () => {
await runManifestInspect();
scheduleNext();
}, INTERVAL_SECONDS * 1000);
}
scheduleNext();
}
// HTTP 服务器
const server = http.createServer((req, res) => {
// 隐藏镜像名称:只显示前后一位字母
const hiddenImages = images.map(img => {
if (img.length <= 2) return img;
return img[0] + '...' + img[img.length - 1];
});
// 账号状态
const accountStatus = accounts.map(acc => ({
user: acc.user,
marked: accountMarkedTime.has(acc.user)
}));
// 匿名用户标记状态
let anonymousMarked = false;
let anonymousMarkedRemaining = 0;
if (anonymousMarkedTime) {
const now = Date.now();
const elapsed = now - anonymousMarkedTime;
const markDuration = MARK_DURATION_SECONDS * 1000;
if (elapsed >= markDuration) {
anonymousMarkedTime = null; // 标记已过期
} else {
anonymousMarked = true;
anonymousMarkedRemaining = Math.round((markDuration - elapsed) / 1000);
}
}
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
status: 'running',
publicIP: publicIP,
imageCount: images.length,
images: hiddenImages,
intervalSeconds: INTERVAL_SECONDS,
accounts: accountStatus,
currentAccount: accounts[currentAccountIndex]?.user || null,
anonymousMarked: anonymousMarked,
anonymousMarkedRemaining: anonymousMarkedRemaining,
lastReportTime: lastReportTime,
lastReportSuccess: lastReportSuccess
}));
});
// 启动
const PORT = 7860;
server.listen(PORT, () => {
console.log(`HTTP 服务器运行在端口 ${PORT}`);
startIPUpdate();
startReportTask();
startBackgroundTask();
});