标签 开源工具 下的文章

简介

AutoCFT(Automate Cloudflare Tunnel)是一个简化 Cloudflare Tunnels 配置和运维的开源工具,适合 Self-hosted 的 Docker 环境,主要提供配置自动化更新,避免频繁登录 Zero Trust Dashboard 进行更新;通过 Docker 或 Docker compose 部署,便于管理;采用 Go 实现,轻量化结构;中英文文档完整。

项目地址:GitHub - cloudfogtech/autocft: A tool for Docker Compose to automatically update access endpoints to Cloudflare Tunnel.

官方文档:https://autocft.cloudfogtech.ltd/

部署与使用

要使用 AutoCFT,基本上默认已经有 Docker 环境了,下面的教程将直接基于 Docker 环境开始。

AutoCFT 部署

services: autocft:  cloudfogtech/autocft:latest container_name: autocft restart: always environment: - AUTOCFT_CF_API_TOKEN=<AUTOCFT_CF_API_TOKEN> - AUTOCFT_CF_ACCOUNT_ID=<AUTOCFT_CF_ACCOUNT_ID> - AUTOCFT_CF_TUNNEL_ID=<AUTOCFT_CF_TUNNEL_ID> volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - /root/data/autocft:/app/data networks: - test_net networks: test_net: external: true 

必填参数获取指南:https://autocft.cloudfogtech.ltd/zh/cloudflare.html

Cloudflared 部署

宿主机部署

如果 Cloudflared 在宿主机部署,则需要为每个应用增加端口映射。auto.service 设置为从宿主机访问的地址。

容器部署

services: cloudflared:  cloudflare/cloudflared:2025.11.1 container_name: cloudflared restart: always command: tunnel run environment: - TUNNEL_TOKEN=<TUNNEL_TOKEN> networks: - test_net networks: test_net: external: true 

容器部署的情况下,所有容器应该处于同一网络,因此不需要端口映射到宿主机,直接通过 Docker 网络访问。

示例应用

下面将部署示例应用展示应用如何和 AutoCFT 结合使用

普通应用

services: nginx:  nginx container_name: nginx restart: always # 宿主机部署Cloudflared需要开启端口映射 # ports: #   - "80:80" volumes: - /etc/ssl/certs:/etc/ssl/certs:ro labels: - autocft.enabled=true - autocft.hostname=nginx.example.com - autocft.service=http://nginx:80 # 宿主机访问地址为本地方位映射的端口 # - autocft.service=http://localhost:80 networks: - test_net networks: test_net: external: true 

HTTPS 应用

services: nginx:  nginx container_name: nginx restart: always # 宿主机部署Cloudflared需要开启端口映射 # ports: #   - "443:443" volumes: - /etc/ssl/certs:/etc/ssl/certs:ro labels: - autocft.enabled=true - autocft.hostname=nginx.example.com - autocft.service=https://nginx:443 # 宿主机访问地址为本地方位映射的端口 # - autocft.service=https://localhost:443 - autocft.origin.origin-server-name=nginx.example.com # 如需忽略TLS证书验证,请设置下面的值 - autocft.origin.no-tls-verify=true networks: - test_net networks: test_net: external: true 

应用部署后,默认配置的情况下大约 10s 后,配置将同步到 Tunnel,就可以访问了。

问题与反馈

两种方式供君挑选

  • 跟帖反馈
  • 通过 Github Issue 反馈


这算是我首次成体系的一个开源项目,在 2025 年末给大家一个更便于使用的工具,也给我自己做一个 2025 年的总结。

感谢始皇和 linux.do 平台的各位佬提供的各种教程、公益资源和这个交流的平台,缘分让我们聚在一起。

真诚友善团结专业 ,共建你我引以为荣之社区。

预祝 2026 的各位佬事业步步高升,生活顺心如意~linux.do 做大做强,再创辉煌~

新年快乐


📌 转载信息
原作者:
catfishlty
转载时间:
2025/12/31 17:14:05

又看到站内大佬的一款,太棒了!~

佬们,还有别的推荐吗?

给个赞呗~


📌 转载信息
原作者:
you_z
转载时间:
2025/12/30 10:23:43

还在为 Gemini 生成图片右下角的半透明水印烦恼吗?无论是商务演示、设计稿制作还是社交分享,那个小小的 Logo 总显得格格不入。

现在,这款开源、免费、极速Gemini Watermark Tool 来了!


核心优势

  • 数学级无损还原:不同于传统的 “涂抹” 或 AI 补全(Inpainting),本工具采用 ** 逆向 Alpha 混合(Reverse Alpha Blending)** 算法。它通过精确计算水印透明度,还原像素原始数值,不模糊、不扭曲,画质几乎零损失。
  • 全自动识别:智能检测图片尺寸,自动匹配 48px 或 96px 水印规格,无需手动框选。
  • 极致轻便
  • 批量处理:支持成百上千张图片秒级处理,极速高效。

使用方法

  1. 极简模式:直接将图片拖拽到工具图标上,瞬间完成。
  2. 在线版本:访问 re.easynote.cc,即开即用。
  3. 开发者模式:支持命令行操作,方便集成到自动化工作流中。

技术背景

本工具由 Allen Kuo (kwyshell) 开发并开源(MIT 协议)。它通过重建水印的 Alpha 通道图层,利用公式 original=(watermarkedα×255)/(1α) 完美逆转图像叠加过程。


立即体验,告别水印瑕疵!

在线版:https://re.easynote.cc

开源地址:GitHub/allenk/GeminiWatermarkTool

温馨提示:本工具仅供个人及学习使用,请尊重原作者的内容策略及法律法规。


📌 转载信息
原作者:
whyqq
转载时间:
2025/12/30 10:13:57

第一个版本链接:

同时也修复了很多水印部分的 BUG,也感谢部分同学的反馈,非常感谢

以下是压缩工具的界面截图:

依旧免费开源!!!觉得不错的记得给我点 Starred​。十分感谢

效果预览:

如果你有任何想法或者建议,可以告诉我


📌 转载信息
原作者:
dexiaoly
转载时间:
2025/12/29 12:33:53

检测模型是 AIGC_detector_zhv3 的 q8 量化版本,使用 WebGPU 进行部署,实测 13 代 i5 核显也能跑。

纯前端工具,除了下载模型的时候会发起网络请求,其他都是在本地运行的。

图一乐,仅供参考,该模型是基于 bert 的所以比基于大模型的检测工具要轻量很多。

效果如下图:


[bsgit user="Kritoooo"]Zenith[/bsgit]

📌 转载信息
转载时间:
2025/12/29 12:24:49

Claude Code 重度用户应该都有这个痛点 - 不知道配额还剩多少。

做了个菜单栏小工具,实时显示用量。

功能

  • 托盘图标显示百分比

  • 5 小时会话 + 7 天周限制

  • 多种主题可选

支持平台

  • macOS(Intel / Apple Silicon)

  • Windows

下载

MIT 开源,欢迎使用。


📌 转载信息
原作者:
StarLighter
转载时间:
2025/12/28 10:42:48

基本上可以激活任意版本的finalshell 高级版、专业版
任意版本finalshell 离线激活码生成工具(高级版、专业版)

开源项目仅供学习使用,请大家支持正版。
源代码如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <meta name="description" content="FinalShell 离线激活码生成器 安全便捷地生成FinalShell各个版本的离线激活码,无需联网验证。本工具仅用于学习和测试目的,请支持正版软件" />

    <title>FinalShell 离线激活码生成器</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            color: #00ffff;
            font-family: 'Courier New', monospace;
            background: linear-gradient(135deg, #0c0c0c 0%, #1a1a2e 25%, #16213e 50%, #0f3460 75%, #533483 100%);
            min-height: 100vh;
            overflow-x: hidden;
            position: relative;
        }

        /* 动态背景粒子 */
        .particles {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            pointer-events: none;
            z-index: 1;
        }

        .particle {
            position: absolute;
            width: 2px;
            height: 2px;
            background: #00ffff;
            border-radius: 50%;
            animation: float 8s infinite linear;
            box-shadow: 0 0 10px #00ffff;
        }

        @keyframes float {
            0% {
                transform: translateY(100vh) rotate(0deg);
                opacity: 0;
            }

            10% {
                opacity: 1;
            }

            90% {
                opacity: 1;
            }

            100% {
                transform: translateY(-100vh) rotate(360deg);
                opacity: 0;
            }
        }

        /* 网格背景 */
        .grid-bg {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            /*background-image: linear-gradient(rgba(0, 255, 255, 0.1) 1px, transparent 1px),*/
            /*linear-gradient(90deg, rgba(0, 255, 255, 0.1) 1px, transparent 1px);*/
            background: repeating-linear-gradient(
                    0deg,
                    rgba(0, 255, 255, 0.05) 0px,
                    rgba(0, 255, 255, 0.05) 1px,
                    transparent 1px,
                    transparent 20px
            ),
            repeating-linear-gradient(
                    90deg,
                    rgba(0, 255, 255, 0.05) 0px,
                    rgba(0, 255, 255, 0.05) 1px,
                    transparent 1px,
                    transparent 20px
            );
            animation: grid-move 2s linear infinite;
            background-size: 100px 100px;
            pointer-events: none;
            z-index: 0;
        }

        @keyframes grid-move {
            0% {
                background-position: 0 0,
                0 0;
            }
            100% {
                background-position: 40px 40px,
                40px 40px;
            }
        }

        /* 主体框架 */
        .ai-hud-component {
            background-color: transparent;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            min-width: 100%;
            position: relative;
            overflow: hidden;
        }

        /* 框架 */
        .ai-hud-frame {
            border-radius: 10px;
            display: flex;
            flex-direction: column;
            align-items: stretch;
            position: relative;
            padding: 1.5rem;
            width: 30em;
            background: rgba(0, 255, 255, 0.05);
            border: 1px solid rgba(0, 255, 255, 0.2);
            box-shadow: 0 0 30px rgba(0, 255, 255, 0.1);
            z-index: 2;
            animation: frame-pulse 5s infinite ease-in-out;
        }

        /* 输入框容器 */
        .ai-hud-input-wrapper {
            margin-bottom: 10px;
            position: relative;
            display: flex;
            align-items: center;
            z-index: 3;
        }

        /* 输入框 */
        .ai-hud-input {
            border-radius: 5px;
            flex: 1;
            background: transparent;
            border: 1px solid rgba(0, 255, 255, 0.2);
            outline: none;
            color: #00ffff;
            font-family: "Orbitron", sans-serif;
            font-size: 1rem;
            letter-spacing: 0.1em;
            padding: 0.5rem 0.75rem;
            caret-color: #00ffff;
            transition: border 0.3s,
            box-shadow 0.3s;
        }

        /* 输入框: 聚焦 */
        .ai-hud-input:hover {
            border: 1px solid rgba(0, 255, 255, 0.5);
            box-shadow: 0 0 12px rgba(0, 255, 255, 0.3);
        }

        /* 输入框: 占位符 */
        .ai-hud-input::placeholder {
            color: rgba(0, 255, 255, 0.4);
        }

        /* 优化后的 autofill 样式 */
        input:-webkit-autofill,
        input:-webkit-autofill:hover,
        input:-webkit-autofill:focus,
        input:-webkit-autofill:active {
            -webkit-background-clip: text;
            -webkit-text-fill-color: #00ffff;
            background-color: transparent !important;
        }

        input:-internal-autofill-selected {
            background-color: transparent;
            color: #00ffff;
        }

        /* 清除按钮 */
        .ai-hud-btn-clear {
            position: absolute;
            right: 110px; /* 调整位置以避开计算按钮 */
            background: transparent;
            border: none;
            color: rgba(0, 255, 255, 0.4);
            font-size: 1.2rem;
            cursor: pointer;
            width: 20px;
            height: 20px;
            display: none; /* 默认隐藏 */
            z-index: 4;
        }

        .ai-hud-btn-clear:hover {
            color: #00ffff;
        }

        /* 按钮 */
        .ai-hud-btn {
            border-radius: 5px;
            color: rgba(0, 255, 255, 0.4);
            background: rgba(0, 255, 255, 0.05);
            border: 1px solid rgba(0, 255, 255, 0.4);
            padding: 0.6rem 1rem;
            cursor: pointer;
            margin-left: 0.75rem;
            transition: all 0.4s ease;
            box-shadow: 0 0 8px rgba(0, 255, 255, 0.3);
        }

        /* 按钮: 聚焦 */
        .ai-hud-btn:hover {
            box-shadow: 0 0 10px rgba(0, 255, 255, 0.5), 0 0 20px rgba(0, 255, 255, 0.3);
            background: rgba(0, 255, 255, 0.08);
        }

        /* 按钮: 点击 */
        .ai-hud-btn:active {
            box-shadow: 0 0 10px rgba(0, 255, 255, 0.8), 0 0 20px rgba(0, 255, 255, 0.5);
            background: rgba(0, 255, 255, 0.08);
        }

        /* 复制按钮 */
        .ai-hud-btn-copy {
            color: rgb(93 178 26);
            background: transparent;
            border: none;
            padding: 0 5px;
            cursor: pointer;
        }

        /* 标题 */
        .ai-hud-title {
            text-align: center;
            font-size: 1.5rem;
            padding-bottom: 20px;
        }

        /* 内容 */
        .ai-hud-content {
            border: 1px solid rgba(0, 255, 255, 0.4);
            margin-top: 10px;
            padding: 10px 20px;
            border-radius: 5px;
        }

        /* 加载动画 */
        .loading {
            text-align: center;
            padding: 20px;
        }

        .spinner {
            border: 2px solid rgba(0, 255, 255, 0.3);
            border-top: 2px solid #00ffff;
            border-radius: 50%;
            width: 20px;
            height: 20px;
            animation: spin 1s linear infinite;
            margin: 0 auto;
        }

        @keyframes spin {
            0% {
                transform: rotate(0deg);
            }
            100% {
                transform: rotate(360deg);
            }
        }
    </style>
</head>

<body>
<!-- 网格背景 -->
<div class="grid-bg"></div>
<!-- 粒子背景 -->
<div class="particles" id="particles"></div>
<div class="ai-hud-component">
    <div class="ai-hud-frame">
        <div class="ai-hud-title">
            FinalShell 离线激活码生成器
            <div style="font-size: 12px;margin-top: 10px">
                注意:本工具仅用于学习和测试目的,请支持正版软件
            </div>
        </div>
        <div class="ai-hud-input-wrapper">
            <input type="text" id="machineIdInput" class="ai-hud-input" placeholder="请输入机器码..."/>
            <button class="ai-hud-btn-clear" id="clearBtn" onclick="clearInput()" title="清空">×</button>
            <button class="ai-hud-btn" id="generateBtn" onclick="generateActivationCode()">开始计算</button>
        </div>
        <div id="result">
        </div>
    </div>
    <div style="position: absolute;bottom: 10px">
        © 2025 FinalShell激活码生成器 | 安全可靠 | 离线使用
    </div>
</div>
<script>
    // 清空输入框函数
    function clearInput() {
        const input = document.getElementById('machineIdInput');
        input.value = '';
        // 隐藏清除按钮
        document.getElementById('clearBtn').style.display = 'none';
        // 聚焦到输入框
        input.focus();
    }

    // 监听输入框变化,控制清除按钮显示/隐藏
    document.getElementById('machineIdInput').addEventListener('input', function () {
        const clearBtn = document.getElementById('clearBtn');
        if (this.value.trim() !== '') {
            clearBtn.style.display = 'block';
        } else {
            clearBtn.style.display = 'none';
        }
    });

    // 生成激活码主函数
    function generateActivationCode() {
        const machineId = document.getElementById('machineIdInput').value.trim();

        // 输入验证
        if (!machineId) {
            showNotification('请输入机器码');
            return;
        }

        // 显示加载状态
        showLoading();

        // 发送请求到API
        fetch(`https://t.543.ink/v1/api/activat?machineId=${encodeURIComponent(machineId)}`)
            .then(response => {
                // 检查响应状态
                if (!response.ok) {
                    throw new Error(`HTTP error! status: ${response.status}`);
                }
                return response.json();
            })
            .then(data => {
                // 检查API返回是否成功
                if (data && data.code === 200) {
                    // 处理返回的数据并在页面上显示结果
                    displayResult(data.data);
                } else {
                    // 处理API错误
                    throw new Error(data.message || 'API返回错误');
                }
            })
            .catch(error => {
                console.error('Error:', error);
                showError('请求失败: ' + error.message);
            });
    }

    // 展示结果函数
    function displayResult(data) {
        const resultDiv = document.getElementById('result');

        // 清空之前的结果
        resultDiv.innerHTML = '';

        // 检查数据是否存在且为数组
        if (!data || !Array.isArray(data) || data.length === 0) {
            resultDiv.innerHTML = '<div class="ai-hud-content">未找到相关数据</div>';
            return;
        }

        // 遍历数据并生成结果HTML
        data.forEach(item => {
            const version = item.version || 'N/A';
            const advanced = item.advanced || 'N/A';
            const professional = item.professional || 'N/A';

            const contentDiv = document.createElement('div');
            contentDiv.className = 'ai-hud-content';
            contentDiv.innerHTML = `
                <div>版本号: ${version}</div>
                <div>
                    <span>高级版: ${advanced}</span>
                    <button class="ai-hud-btn-copy" onclick="copyToClipboard('${advanced}')">复制</button>
                </div>
                <div>
                    <span>专业版: ${professional}</span>
                    <button class="ai-hud-btn-copy" onclick="copyToClipboard('${professional}')">复制</button>
                </div>
            `;

            resultDiv.appendChild(contentDiv);
        });
    }

    // 复制到剪贴板功能
    function copyToClipboard(text) {
        navigator.clipboard.writeText(text).then(() => {
            // 使用更友好的提示方式
            showNotification('已复制到剪贴板');
        }).catch(err => {
            console.error('复制失败:', err);
            showNotification('复制失败,请手动复制');
        });
    }

    // 显示加载状态
    function showLoading() {
        const resultDiv = document.getElementById('result');
        resultDiv.innerHTML = `
            <div class="loading">
                <div class="spinner"></div>
                <p>正在计算中...</p>
            </div>
        `;
    }

    // 显示错误信息
    function showError(message) {
        const resultDiv = document.getElementById('result');
        resultDiv.innerHTML = `<div class="ai-hud-content" style="color: #ff4d4d;">错误: ${message}</div>`;

        // 重新启用按钮
        const btn = document.getElementById('generateBtn');
        btn.disabled = false;
        btn.textContent = '开始计算';
    }

    // 显示通知消息
    function showNotification(message) {
        // 检查是否已存在通知容器,如果没有则创建
        let notificationContainer = document.getElementById('notification-container');
        if (!notificationContainer) {
            notificationContainer = document.createElement('div');
            notificationContainer.id = 'notification-container';
            notificationContainer.style.position = 'fixed';
            notificationContainer.style.top = '5%';
            notificationContainer.style.left = '50%';
            notificationContainer.style.transform = 'translateX(-50%)';
            notificationContainer.style.zIndex = '1000';
            notificationContainer.style.display = 'flex';
            notificationContainer.style.flexDirection = 'column';
            notificationContainer.style.alignItems = 'center';
            notificationContainer.style.gap = '10px';
            document.body.appendChild(notificationContainer);
        }

        // 创建通知元素(保留原始样式)
        const notification = document.createElement('div');
        notification.textContent = message;
        notification.style.border = '1px solid rgba(0, 255, 255, 0.4)';
        notification.style.color = '#00ffff';
        notification.style.padding = '5px 30px';
        notification.style.borderRadius = '5px';
        notification.style.fontFamily = 'sans-serif';
        notification.style.fontSize = '16px';
        notification.style.textAlign = 'center';
        notification.style.minWidth = '200px';
        notification.style.backgroundColor = 'rgba(0, 0, 0, 0.2)';
        notification.style.opacity = '0';
        notification.style.transition = 'opacity 0.3s ease-in-out';

        // 添加到容器中
        notificationContainer.appendChild(notification);

        // 触发动画显示
        setTimeout(() => {
            notification.style.opacity = '1';
        }, 10);

        // 3秒后自动移除
        setTimeout(() => {
            notification.style.opacity = '0';
            setTimeout(() => {
                if (notification.parentNode) {
                    notification.parentNode.removeChild(notification);
                }
                // 如果容器为空,则移除容器
                if (notificationContainer.children.length === 0) {
                    if (notificationContainer.parentNode) {
                        notificationContainer.parentNode.removeChild(notificationContainer);
                    }
                }
            }, 300);
        }, 3000);
    }

    // 创建粒子效果
    function createParticles() {
        const particlesContainer = document.getElementById('particles');
        const particleCount = 50;
        for (let i = 0; i < particleCount; i++) {
            const particle = document.createElement('div');
            particle.className = 'particle';
            particle.style.left = Math.random() * 100 + '%';
            particle.style.animationDelay = Math.random() * 8 + 's';
            particle.style.animationDuration = (Math.random() * 3 + 5) + 's';

            // 随机颜色
            const colors = ['#00ffff', '#ff00ff', '#ffff00', '#00ff00'];
            const randomColor = colors[Math.floor(Math.random() * colors.length)];
            particle.style.background = randomColor;
            particle.style.boxShadow = `0 0 10px ${randomColor}`;
            particlesContainer.appendChild(particle);
        }
    }

    // 初始化粒子
    createParticles();

    // 定期添加新粒子
    setInterval(() => {
        if (document.querySelectorAll('.particle').length < 100) {
            const particle = document.createElement('div');
            particle.className = 'particle';
            particle.style.left = Math.random() * 100 + '%';
            particle.style.animationDuration = (Math.random() * 3 + 5) + 's';

            const colors = ['#00ffff', '#ff00ff', '#ffff00', '#00ff00'];
            const randomColor = colors[Math.floor(Math.random() * colors.length)];
            particle.style.background = randomColor;
            particle.style.boxShadow = `0 0 10px ${randomColor}`;
            document.getElementById('particles').appendChild(particle);

            // 粒子动画结束后移除
            setTimeout(() => {
                if (particle.parentNode) {
                    particle.parentNode.removeChild(particle);
                }
            }, 8000);
        }
    }, 200);
</script>
<script>
!function(p){"use strict";!function(t){var s=window,e=document,i=p,c="".concat("https:"===e.location.protocol?"https://":"http://","sdk.51.la/js-sdk-pro.min.js"),n=e.createElement("script"),r=e.getElementsByTagName("script")[0];n.type="text/javascript",n.setAttribute("charset","UTF-8"),n.async=!0,n.src=c,n.id="LA_COLLECT",i.d=n;var o=function(){s.LA.ids.push(i)};s.LA?s.LA.ids&&o():(s.LA=p,s.LA.ids=[],o()),r.parentNode.insertBefore(n,r)}()}({id:"3OGKOla4LiTErC1d",ck:"3OGKOla4LiTErC1d"});
</script>
<script id="LA-DATA-WIDGET" crossorigin="anonymous" charset="UTF-8" src="https://v6-widget.51.la/v6/3OGKOla4LiTErC1d/quote.js?theme=#00FFFF,#333333,#00FFFF,#00FFFF,#FFFFFF,#00FFFF,12&f=12&display=0,0,0,1,0,1,1,1"></script>
</body>
</html>

在线地址:https://finalshell.543.ink/

之前用 watchtower 发现必须要:ladder:才能检查镜像,对于国内机器太不友好了。于是我自己搞了个轻量版的,配好 Docker 的镜像加速源就能直接用了。

[bsgit user="naomi233"]watchducker[/bsgit]
一个用 Go 语言编写的 Docker 容器镜像更新检查和自动更新工具

  • 智能检查: 自动检测容器使用的镜像是否有新版本可用
  • 标签驱动: 通过 watchducker.update=true 标签自动管理需要更新的容器
  • 定时执行: 支持使用 cron 表达式进行定时检查
  • 自动更新: 检测到更新后可自动重启容器使用新镜像
  • 灵活控制: 提供只检查不重启的选项
  • 实时反馈: 检查过程中提供实时进度和结果输出
  • Docker 原生: 完全基于 Docker API,无需额外依赖
  • 无需代理: 复用现有 Docker 配置,无需额外配置认证和代理、加速镜像源

示例

# 检查指定容器
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock naomi233/watchducker:latest watchducker nginx redis mysql
# 检查所有带有更新标签的容器
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock naomi233/watchducker:latest watchducker --label
# 只更新镜像,不重启容器
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock naomi233/watchducker:latest watchducker --no-restart nginx redis
# 使用标签模式,同时防止自动重启
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock naomi233/watchducker:latest watchducker --label --no-restart
# 每天凌晨2点检查所有标签容器
docker run --name watchducker -v /var/run/docker.sock:/var/run/docker.sock naomi233/watchducker:latest watchducker --cron "0 2 * * *" --label
# 每30分钟检查指定容器
docker run --name watchducker -v /var/run/docker.sock:/var/run/docker.sock naomi233/watchducker:latest watchducker --cron "*/30 * * * *" nginx redis
# 每天执行,只检查不重启
docker run --name watchducker -v /var/run/docker.sock:/var/run/docker.sock naomi233/watchducker:latest watchducker --cron "@daily" --no-restart nginx

Docker compose yaml 示例

services:
  watchducker:
    image: naomi233/watchducker
    container_name: watchducker
    network_mode: bridge
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - TZ=Asia/Shanghai
      - WATCHDUCKER_LOG_LEVEL=DEBUG
      - WATCHDUCKER_CRON=0 2 * * *
      - WATCHDUCKER_LABEL=true

本次开源了一个我自用的工具 IPA-Harbor, 基于 ipatool ,用来下载 ipa ,避免重复的抓包下载 ipa 等操作,使用 Web 面板访问,支持 App 搜索、历史版本下载,支持 Docker 部署。

Docker 仓库地址 https://hub.docker.com/r/uuphy/ipa-harbor

Github 源码地址 https://github.com/ij369/ipa-harbor

GitHub 文档里有更详细的说明,感兴趣的可以点个小星星,有问题提 issue.

我以前每次想下一个旧版 ipa 都要抓包,然后 AirDrop 给 iPhone , 后面逛帖子时发现 ipatool ,后面拿电脑抠命令,是在是厌烦了,可读的版本号也没有,所以有了想法写这个。

另外,有一个 ipatool.ts 的项目,也非常好,不过我不想维护 ipatool 核心的部分,直接去 ipatool 项目的发版页下载最新的二进制文件,拷贝到我这个项目的 bin 目录即可,正所谓大树下好乘凉,感谢 ipatool 的贡献者,同时省去大家时间。

目前我 ipatool 自用到现在已经有一年时间,两个地区的 ID (美区和日区)都没被封过,非常建议使用的话拿独立的 Apple ID 独立的容器运行,看了源码且如果对 ipatool 项目信任的话,再使用主力 Apple ID 。

没有花钱购买应用的 ID ,这样能避免损失,具体可以去 App Store 进行切换登录,其实折腾这个的不一定只有一个 ID 吧。

整个项目拿 Cursor 断断续续写的, 前期几乎是 Vibe Coding ,后续人手改,所以后端实现以及界面啥的都有点糙,不想投入大精力在这方面,主打安全,能用,后续慢慢打磨。 因为我的文件夹辗转腾挪,我导出过提示词,看了下很多都包含敏感内容,脱敏工作量有点大,就 git 忽略了,后续我如果有空再阅读完整理下放出来。

侧载功能我按照好几个帖子试了下,好像是不可用, 前端已经暂时隐藏了该功能, 看看有没有大佬熟悉这这块帮忙看下能不能实现。

我目前一直挂在外网在用,方便手机领免费应用啥的,以下截图的域名我已经做了更换。 截图里的内容仅供参考,仅作为功能演示:

开源一个可以 Docker 容器部署的 ipa 下载工具,用于下载历史版本的 iOS 应用
开源一个可以 Docker 容器部署的 ipa 下载工具,用于下载历史版本的 iOS 应用1