首页
关于我
Search
1
魔改comfy2go支持Https、wss以及Basic Auth
17 阅读
2
Navidrome 自建家庭音乐服务
11 阅读
3
Go实现密钥协商
9 阅读
4
雷池社区版通信机制优化
8 阅读
5
CF反代Docker镜像源,国内拉取Docker镜像
8 阅读
默认分类
Go
safeline
cloudflare
docker
HomeLab
登录
Search
标签搜索
safeline
密钥协商
typecho
comfyUI
go
闻道
累计撰写
7
篇文章
累计收到
1
条评论
首页
栏目
默认分类
Go
safeline
cloudflare
docker
HomeLab
页面
关于我
搜索到
7
篇与
的结果
2024-12-23
Rust 轻量监控系统指标工具
介绍写了一个简单的系统监控练手,这是一个轻量级的终端应用,可以实时显示系统的各项指标。执行会在 terminal 绘制 CPU、内存、网络 的使用情况。在 terminal 按下 q 会退出程序。使用 Rust 语言开发,确保了高性能和低资源占用开发和测试环境:macOS(M系列芯片)支持跨平台:可在 macOS、Linux 和 Windows 上运行项目完全开源,欢迎贡献和建议主要feature监控CPU使用监控内存使用监控网络实时上传下载截图2024-12-23T03:35:02.pnggithub releasehttps://github.com/dundunHa/sys-overseer/releasesRust 的练手项目,欢迎各位提出宝贵意见和建议,帮助改进和提升。
2024年12月23日
7 阅读
0 评论
0 点赞
2024-12-17
魔改comfy2go支持Https、wss以及Basic Auth
comfy2go这是一个go 语言的 调用comfyUI的库。今天搞comfyUI相关需求发现go相关的库比较少,暂且选用了comfy2go。comfy2go的整体逻辑是对comfyUI的API调用进行了一层封装,这样业务代码写起来可以不用关心comfyUI的API格式。魔改支持Https wss basic认证但是comfy2go 仅支持http以及ws。不支持对https和wss的调用,也不支持nginx basic认证。所以稍微魔改了一下。魔改后的版本创建客户端时允许传入options参数来决定是否启用https、wss以及Nginx basic认证。需要的佬可以点击观察一下:https://github.com/dundunHa/comfy2go
2024年12月17日
17 阅读
0 评论
0 点赞
2024-08-16
typecho sqlite安装忘记密码解决方案
背景用typecho写博客,换了电脑突然忘记了密码(实际上是上次刚配置好,bitwarden没有保存下来)。这种情况下可以直接进入到容器里找到对应的sqlite数据库文件重置一下密码。操作首先进入到容器,然后找到sqlite的数据库文件,如果不是以sqlite部署的可以直接通过数据库GUI工具直接链接到对应的数据库服务,操作起来更简单安装sqlite工具,容器内需要执行以下命令进行安装,至于是用apk、yum还是apt和容器的基础镜像有关apk add --no-cache sqlite安装完成后可以通过如下命令打开DB文件,sqlite3 数据库文件名,我这里的数据库文件名是66a85d5477807.db,请换成你的数据库文件名sqlite3 66a85d5477807.db执行命令查看所有表,不出意外的话可以看见一个名为 "typecho_users" 的表,用户信息就记录在这里 .table更新密码,默认情况下管理员的用户应该是1,如果不是你常用的用户,可以再查询一下想更改用户密码的UID是什么。这里将password 更新为 "e10adc3949ba59abbe56e057f20f883e",对应的明文密码是 "123456"UPDATE 'typecho_users' SET 'password' = 'e10adc3949ba59abbe56e057f20f883e' WHERE uid=1;更改完成后执行命令退出保存,然后可以打开自己的typecho后台登录,密码输入 "123456"。可以发现能够正常登录了,然后再去个人中心修改一下密码。
2024年08月16日
8 阅读
0 评论
0 点赞
2024-08-02
Navidrome 自建家庭音乐服务
客户端的选择这里直接选用了国人开发的 音流 作为客户端,优势是各平台都支持,包括 MAC、IOS、安卓、安卓 TV,支持通过接口调用获取歌词和封面。同时可以发动钞能力,支持文件夹浏览播放、多线路配置。服务端的选择本篇只讨论免费的服务端,jellyfin 和 navidrome。都有尝试搭建过,不过我的 jellyfin 主要用来观影,还是专门搭建了 navidrome 用作音乐服务端。优势是 GO 开发,简单轻量。这里贴一个各个服务端的对比图。服务端搭建示例这里给出一个 docker compose 文件示例,我的家庭网络是存储和计算在两台机器上,所以挂载目录上使用的 sshfs 插件来进行一个远程目录的挂载。在使用sshfs之前你可能需要搜索一下docker如何安装sshfs插件。使用方式可以参考下面示例的结尾注释部分,在volumes声明sshfs远程目录后,可以在 service.navidrome.volumes 直接使用sshfs挂载时声明的名称,也可以参考下面示例中 service.navidrome.volumes 注释部分音乐目录和navidrome应用目录分开在两个磁盘上,以下配置文件仅做参考,实际使用可能需要网络和存储情况修改一下mount的配置。version: "3.9" services: navidrome: image: deluan/navidrome:latest container_name: navidrome # user: 1000:1000 ports: - 4533:4533 volumes: - /app/navidrome/data:/data - /data/music:/music # - data:/data # - music:/music environment: #程序默认语言 - ND_DEFAULTLANGUAGE=zh-Hans #启用GRAVATAR头像 - ND_ENABLEGRAVATAR=true #关闭转码,如需要转码请设置为true - ND_ENABLETRANSCODINGCONFIG=false #SPOTIFY相关 # - ND_SPOTIFY_ID= # - ND_SPOTIFY_SECRET= #LAST.FM相关 # - ND_LASTFM_LANGUAGE=zh # - ND_LASTFM_APIKEY= # - ND_LASTFM_SECRET= #PROXY相关 # - HTTP_PROXY=http://proxy:port # - HTTPS_PROXY=http://192.168.1.2:7890 restart: always #volumes: # data: # driver: vieux/sshfs # driver_opts: # sshcmd:
[email protected]
:/share/CACHEDEV3_DATA/media/software/navidrome/data # password: xxxx # music: # driver: vieux/sshfs # driver_opts: # sshcmd:
[email protected]
:/share/CACHEDEV4_DATA/private/media/music # password: xxx # allow_other: ""其他可能存在的问题如果挂载的仅仅是本地目录不会有太多的问题,使用 sshfs 挂载 NAS类的远程目录比较容易遇见权限问题,这种情况下可以参考示例文件的 volumes.*.driver_opts.allow_other 配置。
2024年08月02日
11 阅读
0 评论
0 点赞
2024-08-01
CF反代Docker镜像源,国内拉取Docker镜像
背景从某个时间段起国内禁止了对docker镜像源的拉取,各大云厂商提供的加速器也陆陆续续都停止了服务.这里给出一个通过CF(CloudFlare)反代 DockerHub服务,实现国内docker pull拉取.环境准备一个CloudFlare账号一个在CloudFlare托管的域名实际操作在 CF 上创建一个新的worker,创建完成后点击编辑代码,然后将下面的代码粘贴上去.这里需要特别注意的是 workers_url 这个变量.需要改成你自定义的域名,假设你的主域名是 "example.com",那么可以将代理镜像的地址也就是 workers_url 的值设为 "https://docker.example.com"// _worker.js // Docker镜像仓库主机地址 let hub_host = 'registry-1.docker.io' // Docker认证服务器地址 const auth_url = 'https://auth.docker.io' // 注意!!! 这里替换为你的工作域名地址,创建完worker后在当前worker的设置->触发器->自定义域将这个 let workers_url = 'https://docker.example.com' let 屏蔽爬虫UA = ['netcraft']; // 根据主机名选择对应的上游地址 function routeByHosts(host) { // 定义路由表 const routes = { // 生产环境 "quay": "quay.io", "gcr": "gcr.io", "k8s-gcr": "k8s.gcr.io", "k8s": "registry.k8s.io", "ghcr": "ghcr.io", "cloudsmith": "docker.cloudsmith.io", "nvcr": "nvcr.io", // 测试环境 "test": "registry-1.docker.io", }; if (host in routes) return [ routes[host], false ]; else return [ hub_host, true ]; } /** @type {RequestInit} */ const PREFLIGHT_INIT = { // 预检请求配置 headers: new Headers({ 'access-control-allow-origin': '*', // 允许所有来源 'access-control-allow-methods': 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS', // 允许的HTTP方法 'access-control-max-age': '1728000', // 预检请求的缓存时间 }), } /** * 构造响应 * @param {any} body 响应体 * @param {number} status 响应状态码 * @param {Object<string, string>} headers 响应头 */ function makeRes(body, status = 200, headers = {}) { headers['access-control-allow-origin'] = '*' // 允许所有来源 return new Response(body, { status, headers }) // 返回新构造的响应 } /** * 构造新的URL对象 * @param {string} urlStr URL字符串 */ function newUrl(urlStr) { try { return new URL(urlStr) // 尝试构造新的URL对象 } catch (err) { return null // 构造失败返回null } } function isUUID(uuid) { // 定义一个正则表达式来匹配 UUID 格式 const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; // 使用正则表达式测试 UUID 字符串 return uuidRegex.test(uuid); } async function nginx() { const text = ` <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> <style> body { width: 35em; margin: 0 auto; font-family: Tahoma, Verdana, Arial, sans-serif; } </style> </head> <body> <h1>Welcome to nginx!</h1> <p>If you see this page, the nginx web server is successfully installed and working. Further configuration is required.</p> <p>For online documentation and support please refer to <a href="http://nginx.org/">nginx.org</a>.<br/> Commercial support is available at <a href="http://nginx.com/">nginx.com</a>.</p> <p><em>Thank you for using nginx.</em></p> </body> </html> ` return text ; } export default { async fetch(request, env, ctx) { const getReqHeader = (key) => request.headers.get(key); // 获取请求头 let url = new URL(request.url); // 解析请求URL const userAgentHeader = request.headers.get('User-Agent'); const userAgent = userAgentHeader ? userAgentHeader.toLowerCase() : "null"; if (env.UA) 屏蔽爬虫UA = 屏蔽爬虫UA.concat(await ADD(env.UA)); workers_url = `https://${url.hostname}`; const pathname = url.pathname; const hostname = url.searchParams.get('hubhost') || url.hostname; const hostTop = hostname.split('.')[0];// 获取主机名的第一部分 const checkHost = routeByHosts(hostTop); hub_host = checkHost[0]; // 获取上游地址 const fakePage = checkHost[1]; console.log(`域名头部: ${hostTop}\n反代地址: ${hub_host}\n伪装首页: ${fakePage}`); const isUuid = isUUID(pathname.split('/')[1].split('/')[0]); if (屏蔽爬虫UA.some(fxxk => userAgent.includes(fxxk)) && 屏蔽爬虫UA.length > 0){ //首页改成一个nginx伪装页 return new Response(await nginx(), { headers: { 'Content-Type': 'text/html; charset=UTF-8', }, }); } const conditions = [ isUuid, pathname.includes('/_'), pathname.includes('/r'), pathname.includes('/v2/user'), pathname.includes('/v2/orgs'), pathname.includes('/v2/_catalog'), pathname.includes('/v2/categories'), pathname.includes('/v2/feature-flags'), pathname.includes('search'), pathname.includes('source'), pathname === '/', pathname === '/favicon.ico', pathname === '/auth/profile', ]; if (conditions.some(condition => condition) && (fakePage === true || hostTop == 'docker')) { if (env.URL302){ return Response.redirect(env.URL302, 302); } else if (env.URL){ if (env.URL.toLowerCase() == 'nginx'){ //首页改成一个nginx伪装页 return new Response(await nginx(), { headers: { 'Content-Type': 'text/html; charset=UTF-8', }, }); } else return fetch(new Request(env.URL, request)); } const newUrl = new URL("https://registry.hub.docker.com" + pathname + url.search); // 复制原始请求的标头 const headers = new Headers(request.headers); // 确保 Host 头部被替换为 hub.docker.com headers.set('Host', 'registry.hub.docker.com'); const newRequest = new Request(newUrl, { method: request.method, headers: headers, body: request.method !== 'GET' && request.method !== 'HEAD' ? await request.blob() : null, redirect: 'follow' }); return fetch(newRequest); } // 修改包含 %2F 和 %3A 的请求 if (!/%2F/.test(url.search) && /%3A/.test(url.toString())) { let modifiedUrl = url.toString().replace(/%3A(?=.*?&)/, '%3Alibrary%2F'); url = new URL(modifiedUrl); console.log(`handle_url: ${url}`) } // 处理token请求 if (url.pathname.includes('/token')) { let token_parameter = { headers: { 'Host': 'auth.docker.io', 'User-Agent': getReqHeader("User-Agent"), 'Accept': getReqHeader("Accept"), 'Accept-Language': getReqHeader("Accept-Language"), 'Accept-Encoding': getReqHeader("Accept-Encoding"), 'Connection': 'keep-alive', 'Cache-Control': 'max-age=0' } }; let token_url = auth_url + url.pathname + url.search return fetch(new Request(token_url, request), token_parameter) } // 修改 /v2/ 请求路径 if (/^\/v2\/[^/]+\/[^/]+\/[^/]+$/.test(url.pathname) && !/^\/v2\/library/.test(url.pathname)) { url.pathname = url.pathname.replace(/\/v2\//, '/v2/library/'); console.log(`modified_url: ${url.pathname}`) } // 更改请求的主机名 url.hostname = hub_host; // 构造请求参数 let parameter = { headers: { 'Host': hub_host, 'User-Agent': getReqHeader("User-Agent"), 'Accept': getReqHeader("Accept"), 'Accept-Language': getReqHeader("Accept-Language"), 'Accept-Encoding': getReqHeader("Accept-Encoding"), 'Connection': 'keep-alive', 'Cache-Control': 'max-age=0' }, cacheTtl: 3600 // 缓存时间 }; // 添加Authorization头 if (request.headers.has("Authorization")) { parameter.headers.Authorization = getReqHeader("Authorization"); } // 发起请求并处理响应 let original_response = await fetch(new Request(url, request), parameter) let original_response_clone = original_response.clone(); let original_text = original_response_clone.body; let response_headers = original_response.headers; let new_response_headers = new Headers(response_headers); let status = original_response.status; // 修改 Www-Authenticate 头 if (new_response_headers.get("Www-Authenticate")) { let auth = new_response_headers.get("Www-Authenticate"); let re = new RegExp(auth_url, 'g'); new_response_headers.set("Www-Authenticate", response_headers.get("Www-Authenticate").replace(re, workers_url)); } // 处理重定向 if (new_response_headers.get("Location")) { return httpHandler(request, new_response_headers.get("Location")) } // 返回修改后的响应 let response = new Response(original_text, { status, headers: new_response_headers }) return response; } }; /** * 处理HTTP请求 * @param {Request} req 请求对象 * @param {string} pathname 请求路径 */ function httpHandler(req, pathname) { const reqHdrRaw = req.headers // 处理预检请求 if (req.method === 'OPTIONS' && reqHdrRaw.has('access-control-request-headers') ) { return new Response(null, PREFLIGHT_INIT) } let rawLen = '' const reqHdrNew = new Headers(reqHdrRaw) const refer = reqHdrNew.get('referer') let urlStr = pathname const urlObj = newUrl(urlStr) /** @type {RequestInit} */ const reqInit = { method: req.method, headers: reqHdrNew, redirect: 'follow', body: req.body } return proxy(urlObj, reqInit, rawLen) } /** * 代理请求 * @param {URL} urlObj URL对象 * @param {RequestInit} reqInit 请求初始化对象 * @param {string} rawLen 原始长度 */ async function proxy(urlObj, reqInit, rawLen) { const res = await fetch(urlObj.href, reqInit) const resHdrOld = res.headers const resHdrNew = new Headers(resHdrOld) // 验证长度 if (rawLen) { const newLen = resHdrOld.get('content-length') || '' const badLen = (rawLen !== newLen) if (badLen) { return makeRes(res.body, 400, { '--error': `bad len: ${newLen}, except: ${rawLen}`, 'access-control-expose-headers': '--error', }) } } const status = res.status resHdrNew.set('access-control-expose-headers', '*') resHdrNew.set('access-control-allow-origin', '*') resHdrNew.set('Cache-Control', 'max-age=1500') // 删除不必要的头 resHdrNew.delete('content-security-policy') resHdrNew.delete('content-security-policy-report-only') resHdrNew.delete('clear-site-data') return new Response(res.body, { status, headers: resHdrNew }) } async function ADD(envadd) { var addtext = envadd.replace(/[ |"'\r\n]+/g, ',').replace(/,+/g, ','); // 将空格、双引号、单引号和换行符替换为逗号 //console.log(addtext); if (addtext.charAt(0) == ',') addtext = addtext.slice(1); if (addtext.charAt(addtext.length -1) == ',') addtext = addtext.slice(0, addtext.length - 1); const add = addtext.split(','); //console.log(add); return add ; }设置 worker 的自定义域完成上述的保存操作后,在当前 worker 页面点击设置->触发器->右边页面的自定义域->添加自定义域,将上面的设定好的反代子域名(workers_url)填写进去.然后点击保存,打开浏览器可以直接访问上面的反代地址,可以打开和 Docker镜像源一样的页面,这个过程可能需要稍等几分钟.之后我们可以尝试拉取一个镜像,这里直接通过 仓库地址+镜像名的方式进行拉取,也可以通过修改拉取仓库的方式,具体的配置过程可以搜索一下.docker pull docker.example.com/library/redis此时国内可正常拉取镜像
2024年08月01日
8 阅读
0 评论
0 点赞
1
2