看片神器!完全免费开源,支持Vercel、Cloudflare Pages、Docker等一键部署!

https://github.com/yuebinliu/MoonTV
Cloudflare 部署

Cloudflare Pages 的环境变量尽量设置为密钥而非文本

普通部署(localstorage)

  1. Fork 本仓库到你的 GitHub 账户。
  2. 登陆 Cloudflare,点击 计算(Workers)-> Workers 和 Pages,点击创建
  3. 选择 Pages,导入现有的 Git 存储库,选择 Fork 后的仓库
  4. 构建命令填写 pnpm install –frozen-lockfile && pnpm run pages:build,预设框架为无,构建输出目录为 .vercel/output/static
  5. 保持默认设置完成首次部署。进入设置,将兼容性标志设置为 nodejs_compat,无需选择,直接粘贴
  6. 首次部署完成后进入设置,新增 PASSWORD 密钥(变量和机密下),而后重试部署。
  7. 如需自定义 config.json,请直接修改 Fork 后仓库中该文件。
  8. 每次 Push 到 main 分支将自动触发重新构建。

D1 支持

  1. 完成普通部署并成功访问
  2. 点击 存储和数据库 -> D1 SQL 数据库,创建一个新的数据库,名称随意
  3. 进入刚创建的数据库,点击左上角的 Explore Data,将D1 初始化 中的内容粘贴到 Query 窗口后点击 Run All,等待运行完成
  4. 返回你的 pages 项目,进入 设置 -> 绑定,添加绑定 D1 数据库,选择你刚创建的数据库,变量名称填 DB
  5. 设置环境变量 NEXT_PUBLIC_STORAGE_TYPE,值为 d1;设置 USERNAME 和 PASSWORD 作为站长账号
  6. 重试部署

Docker 部署

1. 直接运行(最简单,localstorage)

# 拉取预构建镜像
docker pull ghcr.io/senshinya/moontv:latest

# 运行容器
# -d: 后台运行  -p: 映射端口 3000 -> 3000
docker run -d --name moontv -p 3000:3000 --env PASSWORD=your_password ghcr.io/senshinya/moontv:latest

访问 http://服务器 IP:3000 即可。(需自行到服务器控制台放通 3000 端口)

Docker Compose 最佳实践

若你使用 docker compose 部署,以下是一些 compose 示例

local storage 版本

services:
  moontv:
    image: ghcr.io/senshinya/moontv:latest
    container_name: moontv
    restart: unless-stopped
    ports:
      - '3000:3000'
    environment:
      - PASSWORD=your_password
    # 如需自定义配置,可挂载文件
    # volumes:
    #   - ./config.json:/app/config.json:ro

Redis 版本(推荐,多账户数据隔离,跨设备同步)

services:
  moontv-core:
    image: ghcr.io/senshinya/moontv:latest
    container_name: moontv
    restart: unless-stopped
    ports:
      - '3000:3000'
    environment:
      - USERNAME=admin
      - PASSWORD=admin_password
      - NEXT_PUBLIC_STORAGE_TYPE=redis
      - REDIS_URL=redis://moontv-redis:6379
      - NEXT_PUBLIC_ENABLE_REGISTER=true
    networks:
      - moontv-network
    depends_on:
      - moontv-redis
    # 如需自定义配置,可挂载文件
    # volumes:
    #   - ./config.json:/app/config.json:ro
  moontv-redis:
    image: redis
    container_name: moontv-redis
    restart: unless-stopped
    networks:
      - moontv-network
    # 如需持久化
    # volumes:
    #   - ./data:/data
networks:
  moontv-network:
    driver: bridge

自动同步最近更改

建议在 fork 的仓库中启用本仓库自带的 GitHub Actions 自动同步功能(见 .github/workflows/sync.yml)。

如需手动同步主仓库更新,也可以使用 GitHub 官方的 Sync fork 功能。

Posted in Uncategorized | Leave a comment

通过cloudflare的zerotrust进行内网穿透保留IP直接访问能力

WordPress我通过cloudflare的zerotrust进行了内网穿透,绑定到了https://www.thanx.top,现在我想保留IP:端口的访问能力,怎么做?

修改 wp-config.php


// 在 wp-config.php 最顶部添加
$_SERVER['HTTPS'] = 'off'; // 全局强制禁用 HTTPS
define('FORCE_SSL_ADMIN', false);
define('FORCE_SSL_LOGIN', false);

// 然后添加你的域名/IP判断逻辑
$is_domain_request = (strpos($_SERVER['HTTP_HOST'], 'thanx.top') !== false);

if ($is_domain_request) {
    // 仅域名访问时启用 HTTPS
    $_SERVER['HTTPS'] = 'on';
    define('WP_HOME', 'https://www.thanx.top');
    define('WP_SITEURL', 'https://www.thanx.top');
    
    // 域名 HTTP 访问跳转到 HTTPS
    if (!isset($_SERVER['HTTPS'])) {
        header('Location: https://www.thanx.top' . $_SERVER['REQUEST_URI']);
        exit;
    }
} else {
    // IP访问强制保持 HTTP
    $_SERVER['HTTPS'] = 'off';
    define('WP_HOME', 'http://' . $_SERVER['HTTP_HOST']);
    define('WP_SITEURL', 'http://' . $_SERVER['HTTP_HOST']);
}
Posted in Uncategorized | Leave a comment

在最新WordPress blog中启用经典编辑器或者古登堡编辑器

1. 默认古登堡已启用

  • WordPress 5.0+版本:古登堡是默认编辑器,无需额外操作。直接创建或编辑文章/页面即可使用。

  • 如果看到的是经典编辑器(Classic Editor),可能是被插件或配置覆盖,需检查以下内容。


2. 检查是否被插件禁用

  • 进入 仪表盘 → 插件 → 已安装的插件

  • 如果安装了 Classic Editor 插件,它会强制禁用古登堡。你可以:

    • 停用并删除 Classic Editor插件(完全使用古登堡)。

    •  进入 设置 → 撰写,选择默认编辑器为“区块编辑器(古登堡)”。

Posted in Uncategorized | Leave a comment

月度工作量统计脚本

使用python3写了个脚本,用来统计月度工作量。本来只是在本地电脑运行的,想想还是把他移植到web端吧。说真的,要我一个个去搜索拉取,我真不敢说自己就能拉出来,而且一点差错都没有,且不说还要制表汇总,琐碎一地。好在,确实可以通过编写脚本来提高效率,不用一个个去拉取,啥也不说了,上代码。
web演示:https://www.thanx.top/re/index.php
直连地址,生产环境用:
http://106.15.4.153:8085/re/index.php
代码:



import pandas as pd
import argparse

def process_files(file1_path, file2_path, output_filename):
# 读取数据
df_jan = pd.read_excel(file1_path, sheet_name='导出数据')
df_feb = pd.read_excel(file2_path, sheet_name='导出数据')

# 定义项目名称映射规则
project_mapping = {
'无痛胃镜': '无胃',
'无痛肠镜': '无肠',
'EMR/APC': 'EMR',
'止血术': '止血',
'扩张术': '扩张',
'超声内镜': '超声',
'异物摄取': '异物',
'病例数': '总数'
}

# 定义项目顺序(使用映射后的名称)
project_order = [
'胃镜', '无胃', '肠镜', '无肠', '超声', 'EMR', 'ESD', 'ERCP',
'止血', '异物', '扩张', '其他'
]

# 定义统计函数
def count_stats(df):
stats = {
'胃镜': 0,
'无痛胃镜': 0,
'肠镜': 0,
'无痛肠镜': 0,
'超声内镜': 0,
'EMR/APC': 0,
'ESD': 0,
'ERCP': 0,
'止血术': 0,
'异物摄取': 0,
'扩张术': 0,
'其他': 0
}
for _, row in df.iterrows():
category = str(row['检查类别']).lower().strip()
diagnosis = str(row['镜下诊断']).lower().strip()

# 统计检查类别
if '十二指肠镜' in category or 'ercp' in category:
stats['ERCP'] += 1
elif '胃镜' in category and '无痛' not in category:
stats['胃镜'] += 1
elif '无痛胃镜' in category:
stats['无痛胃镜'] += 1
elif '肠镜' in category and '无痛' not in category:
stats['肠镜'] += 1
elif '无痛肠镜' in category:
stats['无痛肠镜'] += 1
elif '超声内镜' in category:
stats['超声内镜'] += 1
else:
stats['其他'] += 1

# 统计镜下诊断
if '扩张' in diagnosis:
stats['扩张术'] += 1
if 'esd' in diagnosis and 'esd术后' not in diagnosis:
stats['ESD'] += 1
if 'emr' in diagnosis or 'apc' in diagnosis:
stats['EMR/APC'] += 1
if '止血' in diagnosis:
stats['止血术'] += 1
if '异物' in diagnosis:
stats['异物摄取'] += 1

# 计算病例数
stats['病例数'] = (
stats['胃镜'] +
stats['无痛胃镜'] +
stats['肠镜'] +
stats['无痛肠镜'] +
stats['超声内镜'] +
stats['ERCP'] +
stats['其他']
)
return stats

# 获取上月和本月的统计数据
stats_jan = count_stats(df_jan)
stats_feb = count_stats(df_feb)

# 计算同比变化
def calculate_change(current, previous):
if previous == 0:
return 0
return round((current - previous) / previous * 100, 2)

# 创建内镜中心工作量统计 DataFrame
center_data = []
for project in project_order:
original_project = next(
(key for key, value in project_mapping.items() if value == project),
project
)
center_data.append({
'项目': project,
'本月数量': stats_feb.get(original_project, 0),
'上月数量': stats_jan.get(original_project, 0),
'同比变化(%)': calculate_change(stats_feb.get(original_project, 0), stats_jan.get(original_project, 0))
})

center_df = pd.DataFrame(center_data)

# 增加汇总行
summary_row = pd.DataFrame({
'项目': ['汇总'],
'本月数量': [
stats_feb['胃镜'] + stats_feb['无痛胃镜'] + stats_feb['肠镜'] +
stats_feb['无痛肠镜'] + stats_feb['超声内镜'] + stats_feb['ERCP'] + stats_feb['其他']
],
'上月数量': [
stats_jan['胃镜'] + stats_jan['无痛胃镜'] + stats_jan['肠镜'] +
stats_jan['无痛肠镜'] + stats_jan['超声内镜'] + stats_jan['ERCP'] + stats_jan['其他']
],
'同比变化(%)': [calculate_change(
stats_feb['胃镜'] + stats_feb['无痛胃镜'] + stats_feb['肠镜'] +
stats_feb['无痛肠镜'] + stats_feb['超声内镜'] + stats_feb['ERCP'] + stats_feb['其他'],
stats_jan['胃镜'] + stats_jan['无痛胃镜'] + stats_jan['肠镜'] +
stats_jan['无痛肠镜'] + stats_jan['超声内镜'] + stats_jan['ERCP'] + stats_jan['其他']
)],
'备注': ['']
})
center_df = pd.concat([center_df, summary_row], ignore_index=True)

# 统计医生工作量
def count_doctor_stats(df):
doctor_stats = {}
for _, row in df.iterrows():
doctor = row['报告医师']
category = str(row['检查类别']).lower().strip()
diagnosis = str(row['镜下诊断']).lower().strip()

if doctor not in doctor_stats:
doctor_stats[doctor] = {
'胃镜': 0,
'无痛胃镜': 0,
'肠镜': 0,
'无痛肠镜': 0,
'超声内镜': 0,
'ERCP': 0,
'EMR/APC': 0,
'ESD': 0,
'止血术': 0,
'扩张术': 0,
'异物摄取': 0,
'其他': 0,
'病例数': 0
}

if '十二指肠镜' in category or 'ercp' in category:
doctor_stats[doctor]['ERCP'] += 1
elif '胃镜' in category and '无痛' not in category:
doctor_stats[doctor]['胃镜'] += 1
elif '无痛胃镜' in category:
doctor_stats[doctor]['无痛胃镜'] += 1
elif '肠镜' in category and '无痛' not in category:
doctor_stats[doctor]['肠镜'] += 1
elif '无痛肠镜' in category:
doctor_stats[doctor]['无痛肠镜'] += 1
elif '超声内镜' in category:
doctor_stats[doctor]['超声内镜'] += 1
else:
doctor_stats[doctor]['其他'] += 1

if '扩张' in diagnosis:
doctor_stats[doctor]['扩张术'] += 1
if 'esd' in diagnosis and 'esd术后' not in diagnosis:
doctor_stats[doctor]['ESD'] += 1
if 'emr' in diagnosis or 'apc' in diagnosis:
doctor_stats[doctor]['EMR/APC'] += 1
if '止血' in diagnosis:
doctor_stats[doctor]['止血术'] += 1
if '异物' in diagnosis:
doctor_stats[doctor]['异物摄取'] += 1

# 计算病例数
doctor_stats[doctor]['病例数'] = (
doctor_stats[doctor]['胃镜'] +
doctor_stats[doctor]['无痛胃镜'] +
doctor_stats[doctor]['肠镜'] +
doctor_stats[doctor]['无痛肠镜'] +
doctor_stats[doctor]['超声内镜'] +
doctor_stats[doctor]['其他'] +
doctor_stats[doctor]['ERCP']
)
return doctor_stats

# 获取上月和本月的医生统计数据
doctor_stats_jan = count_doctor_stats(df_jan)
doctor_stats_feb = count_doctor_stats(df_feb)

# 创建医生工作量统计 DataFrame
doctor_data = []
for doctor, stats in doctor_stats_feb.items():
doctor_data.append({
'医师': doctor,
**{project_mapping.get(k, k): v for k, v in stats.items()}
})

doctor_df = pd.DataFrame(doctor_data)

# 增加汇总行
summary_row = pd.DataFrame({
'医师': ['汇总'],
**{project_mapping.get(k, k): [doctor_df[project_mapping.get(k, k)].sum()] for k in project_order},
'总数': [doctor_df['总数'].sum()]
})
doctor_df = pd.concat([doctor_df, summary_row], ignore_index=True)

# 统计护士工作量
def count_nurse_stats(df):
nurse_stats = {}
for _, row in df.iterrows():
nurse = row['助手']
category = str(row['检查类别']).lower().strip()
diagnosis = str(row['镜下诊断']).lower().strip()

if nurse not in nurse_stats:
nurse_stats[nurse] = {
'胃镜': 0,
'无痛胃镜': 0,
'肠镜': 0,
'无痛肠镜': 0,
'超声内镜': 0,
'ERCP': 0,
'EMR/APC': 0,
'ESD': 0,
'止血术': 0,
'扩张术': 0,
'异物摄取': 0,
'其他': 0,
'病例数': 0
}

if '十二指肠镜' in category or 'ercp' in category:
nurse_stats[nurse]['ERCP'] += 1
elif '胃镜' in category and '无痛' not in category:
nurse_stats[nurse]['胃镜'] += 1
elif '无痛胃镜' in category:
nurse_stats[nurse]['无痛胃镜'] += 1
elif '肠镜' in category and '无痛' not in category:
nurse_stats[nurse]['肠镜'] += 1
elif '无痛肠镜' in category:
nurse_stats[nurse]['无痛肠镜'] += 1
elif '超声内镜' in category:
nurse_stats[nurse]['超声内镜'] += 1
else:
nurse_stats[nurse]['其他'] += 1

if '扩张' in diagnosis:
nurse_stats[nurse]['扩张术'] += 1
if 'esd' in diagnosis and 'esd术后' not in diagnosis:
nurse_stats[nurse]['ESD'] += 1
if 'emr' in diagnosis or 'apc' in diagnosis:
nurse_stats[nurse]['EMR/APC'] += 1
if '止血' in diagnosis:
nurse_stats[nurse]['止血术'] += 1
if '异物' in diagnosis:
nurse_stats[nurse]['异物摄取'] += 1

# 计算病例数
nurse_stats[nurse]['病例数'] = (
nurse_stats[nurse]['胃镜'] +
nurse_stats[nurse]['无痛胃镜'] +
nurse_stats[nurse]['肠镜'] +
nurse_stats[nurse]['无痛肠镜'] +
nurse_stats[nurse]['超声内镜'] +
nurse_stats[nurse]['其他'] +
nurse_stats[nurse]['ERCP']
)
return nurse_stats

# 获取上月和本月的护士统计数据
nurse_stats_jan = count_nurse_stats(df_jan)
nurse_stats_feb = count_nurse_stats(df_feb)

# 创建护士工作量统计 DataFrame
nurse_data = []
for nurse, stats in nurse_stats_feb.items():
nurse_data.append({
'护士': nurse,
**{project_mapping.get(k, k): v for k, v in stats.items()}
})

nurse_df = pd.DataFrame(nurse_data)

# 增加汇总行
summary_row = pd.DataFrame({
'护士': ['汇总'],
**{project_mapping.get(k, k): [nurse_df[project_mapping.get(k, k)].sum()] for k in project_order},
'总数': [nurse_df['总数'].sum()]
})
nurse_df = pd.concat([nurse_df, summary_row], ignore_index=True)

# 保存更新后的Excel文件
with pd.ExcelWriter(output_filename) as writer:
center_df.to_excel(writer, sheet_name='内镜中心工作量统计', index=False)
doctor_df.to_excel(writer, sheet_name='医生工作量统计', index=False)
nurse_df.to_excel(writer, sheet_name='护士工作量统计', index=False)

if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Process Excel files.')
parser.add_argument('file1', help='Path to the first Excel file')
parser.add_argument('file2', help='Path to the second Excel file')
parser.add_argument('output', help='Path to save the output Excel file')

args = parser.parse_args()

process_files(args.file1, args.file2, args.output)
Posted in Resourse | Leave a comment

PVE安装OpenWrt做主路由后局域网DHCP分配不了地址的修复

通过悟空的日常安装的istoreos设置成主路由后无论如何设置,都会发现DHCP分配不了IPV4地址,无论怎么设置,最后发现:
root@iStoreOS:~# uci show dhcp.lan
dhcp.lan=dhcp
dhcp.lan.interface='lan'
dhcp.lan.start='100'
dhcp.lan.leasetime='12h'
dhcp.lan.dhcpv4='disabled'
dhcp.lan.force='1'
dhcp.lan.limit='116'

uci show 输出可以看出,DHCPv4 被显式禁用了dhcp.lan.dhcpv4='disabled'),这是导致局域网设备无法获取 IP 地址的直接原因。以下是完整的修复步骤:
#启用 DHCPv4 服务
uci set dhcp.lan.dhcpv4='server' # 启用DHCPv4服务
uci commit dhcp
/etc/init.d/dnsmasq restart

Posted in Resourse | Leave a comment

使用monica写了一个自动保存deepseek聊天记录的脚本

根据您提供的调试信息,您在页面中找到了大量的 div 元素,但没有找到包含聊天消息的特定元素。这表明可能需要更具体的选择器来提取聊天内容。以下是一些步骤来帮助您解决这个问题:

步骤 4: 逐步调试

如果您仍然无法找到消息元素,可以尝试逐步调试:

  1. 选择所有 div 元素:
    const allDivs = document.querySelectorAll('div');
    allDivs.forEach(div => {
       console.log(div); // 输出每个 div 的内容
    });
  2. 通过特定属性过滤:
    如果某些 div 有特定的属性(如 data-* 属性),您可以使用这些属性来过滤:

    const userMessages = document.querySelectorAll('div[data-role="message"]'); // 根据实际情况调整

    F12打开浏览器调试窗口,输入1中代码,得到了一些html代码,经过分析得到div标签的标识:
    获取到的标签

聊天的主题标题:d8ed659a
我的提问:fbb737a4
ai的思考过程:e1675d8b
AI的解答过程:ds-markdown ds-markdown--block

问Monica,经过几次调整,最终得到如下结果:

// ==UserScript==
// @name         DeepSeek Chat Content Saver
// @match        https://chat.deepseek.com/*
// @version      1.6
// @description  Save chat content as Markdown with auto-save option
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    const CONFIG = {
        BUTTON_ID: 'deepseek-save-btn',
        USER_PREFIX: "你",
        AI_PREFIX: "AI",
        AUTO_SAVE_ENABLED: true, // 配置项:是否启用自动保存
        AUTO_SAVE_INTERVAL: 30000 // 自动保存间隔(毫秒),例如30秒
    };

    function generateMarkdown(messages) {
        let md = `# 聊天记录\n\n`;
        messages.forEach(msg => {
            md += `**${msg.role === 'user' ? CONFIG.USER_PREFIX : CONFIG.AI_PREFIX}**:\n`;
            md += `${msg.text}\n\n---\n\n`;
        });
        md += `> 保存时间: ${new Date().toLocaleString()}\n`;
        return md;
    }

    function downloadFile(content, filename) {
        const blob = new Blob([content], { type: 'text/markdown;charset=utf-8' });
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = filename;
        document.body.appendChild(a); // 需要将链接添加到DOM中
        a.click();
        document.body.removeChild(a); // 下载后移除链接
        setTimeout(() => URL.revokeObjectURL(url), 100);
    }

    function extractMessages() {
        const messages = [];

        // 提取聊天主题标题
        const titleElement = document.querySelector('.d8ed659a');
        let title = titleElement ? titleElement.innerText : '聊天记录';

        // 提取用户提问、AI思考和AI回答,按顺序排列
        const userQuestions = document.querySelectorAll('.fbb737a4');
        const aiThoughts = document.querySelectorAll('.e1675d8b');
        const aiAnswers = document.querySelectorAll('.ds-markdown.ds-markdown--block');

        for (let i = 0; i < userQuestions.length; i++) {
            messages.push({
                role: 'user',
                text: userQuestions[i].innerText
            });
            if (i < aiThoughts.length) {
                messages.push({
                    role: 'ai',
                    text: aiThoughts[i].innerText
                });
            }
            if (i < aiAnswers.length) {
                messages.push({
                    role: 'ai',
                    text: aiAnswers[i].innerText
                });
            }
        }

        console.log('提取的消息:', messages); // 调试输出
        return { title, messages };
    }

    function saveChatRecord() {
        const { title, messages } = extractMessages();
        if (messages.length === 0) throw new Error("未找到对话内容");

        const content = generateMarkdown(messages);
        //const timestamp = new Date().toISOString().replace(/[-:.]/g, "_"); // 格式化时间戳
        //const filename = `${title}_${timestamp}.md`; // 文件名包含时间戳
        const filename = `${title}.md`; // 文件名不包含时间戳

        // 下载文件
        downloadFile(content, filename);
        //alert('保存成功!');
    }

    function startAutoSave() {
        if (CONFIG.AUTO_SAVE_ENABLED) {
            setInterval(() => {
                try {
                    saveChatRecord();
                } catch (error) {
                    console.error('自动保存失败:', error);
                }
            }, CONFIG.AUTO_SAVE_INTERVAL);
        }
    }

    function createSaveButton() {
        const button = document.createElement('button');
        button.id = CONFIG.BUTTON_ID;
        button.innerText = '保存对话';
        button.style.position = 'fixed';
        button.style.bottom = '20px';
        button.style.right = '20px';
        button.style.padding = '15px 30px';
        button.style.fontSize = '16px';
        button.style.backgroundColor = '#4CAF50';
        button.style.color = 'white';
        button.style.border = 'none';
        button.style.borderRadius = '5px';
        button.style.cursor = 'pointer';
        button.style.zIndex = '1000';
        button.style.boxShadow = '0 2px 5px rgba(0, 0, 0, 0.3)';
        document.body.appendChild(button);
        return button;
    }

    function setupButtonHandler(btn) {
        btn.addEventListener('click', saveChatRecord);
    }

    function initialize() {
        const btn = createSaveButton();
        setupButtonHandler(btn);
        startAutoSave(); // 启动自动保存功能
    }

    // 启动
    if (document.readyState === 'complete') initialize();
    else window.addEventListener('load', initialize);
})();

脚本下载:DeepSeek Chat Content Saver-1.6.user.js

Posted in Resourse | Leave a comment

简易文件浏览5分钟快速解决方案

完全理解您的感受!配置这类服务确实会遇到很多"坑",尤其是WSL环境下权限和路径问题更复杂。不过别担心,我给您准备了一个极简替代方案,用最直接的方式实现文件浏览功能,无需FastCGI和Nginx复杂配置:


5分钟快速解决方案

🚀 使用Python内置HTTP服务器 + 纯前端实现

  1. 创建一个新目录

    mkdir ~/simple-file-browser && cd ~/simple-file-browser
  2. 新建server.py (用Python内置服务器替代Nginx)

    from http.server import SimpleHTTPRequestHandler
    from socketserver import TCPServer
    import os
    
    class CustomHandler(SimpleHTTPRequestHandler):
       def do_GET(self):
           if self.path == '/list':
               self.send_json(os.listdir('protected_files'))
           else:
               super().do_GET()
    
       def send_json(self, data):
           self.send_response(200)
           self.send_header('Content-Type', 'application/json')
           self.end_headers()
           self.wfile.write(str(data).encode())
    
    os.chdir('protected_files')  # 设置文件根目录
    TCPServer(('0.0.0.0', 8000), CustomHandler).serve_forever()
  3. 新建index.html (直接读取本地文件列表)

    <!DOCTYPE html>
    <html>
    <body>
       <h1>简易文件浏览器</h1>
       <div id="files"></div>
       <script>
           fetch('/list')
               .then(r => r.json())
               .then(files => {
                   document.getElementById('files').innerHTML = files.map(f => 
                       `<div><a href="/${f}" download>${f}</a></div>`
                   ).join('')
               })
       </script>
    </body>
    </html>
  4. 启动服务

    python3 server.py

    现在访问 http://localhost:8000 即可看到文件列表并下载!


🌟 这个方案的优势:

  1. 完全绕过FastCGI/Nginx:用Python原生HTTP服务器处理请求
  2. 零配置:所有代码在一个目录内,不涉及系统权限
  3. 即时生效:修改后刷新浏览器即可看到变化
  4. 便携性强:整个文件夹可以复制到任何地方运行

如果还是遇到问题,您可以选择:

  1. 使用现成工具:比如FileBrowser (一行命令安装)
    docker run -d -p 8080:80 -v /path/to/files:/srv filebrowser/filebrowser
  2. 改用SFTP:用WinSCP/FTP客户端直接访问WSL文件

有时候最简单的方案就是最好的方案。当技术让人沮丧时,退一步换个思路往往能更快解决问题。希望这个方案能帮您快速实现需求!如果还有其他困惑,我很乐意继续帮您简化流程。

是的,直接运行 python3 /var/www/simple-file-browser/server.py 需要手动启动服务,但可以通过以下方法实现自动启动和后台运行:


1. 使用 nohup 保持后台运行

nohup python3 /var/www/simple-file-browser/server.py > /var/log/filebrowser.log 2>&1 &
  • 作用:关闭终端后服务仍运行
  • 查看日志tail -f /var/log/filebrowser.log
  • 停止服务:先 ps aux | grep server.py 找到PID,再 kill [PID]

2. 配置系统服务(开机自启)

(1)创建服务单元文件

sudo nano /etc/systemd/system/filebrowser.service

写入以下内容:

[Unit]
Description=Simple File Browser
After=network.target

[Service]
User=your_username  # 改为你的实际用户名
WorkingDirectory=/var/www/simple-file-browser
ExecStart=/usr/bin/python3 /var/www/simple-file-browser/server.py
Restart=always

[Install]
WantedBy=multi-user.target

(2)启用并启动服务

sudo systemctl daemon-reload
sudo systemctl start filebrowser
sudo systemctl enable filebrowser  # 开机自启
  • 查看状态sudo systemctl status filebrowser
  • 停止服务sudo systemctl stop filebrowser

3. 使用 screentmux 会话管理

sudo apt install screen -y  # 如果未安装
screen -S filebrowser
python3 /var/www/simple-file-browser/server.py
# 按 Ctrl+A 然后按 D 退出会话(服务继续运行)
  • 重新连接会话screen -r filebrowser

4. 更简单的替代方案

如果不想处理服务管理,可以直接使用现成工具:

# 安装 FileBrowser(Go语言编写的工具)
curl -fsSL https://raw.githubusercontent.com/filebrowser/get/master/get.sh | bash
filebrowser -d /var/www/simple-file-browser/filebrowser.db -r /var/www/simple-file-browser/protected_files
  • 自动后台运行,支持开机自启
  • 提供Web界面,功能更完善

总结建议

方法 难度 持久性 适用场景
nohup 简单 终端关闭后仍运行 快速测试
systemd 中等 开机自启 生产环境
screen 简单 会话保持 临时调试
现成工具 最简单 配置即用 长期使用

推荐选择

  • 如果是临时测试,用 nohupscreen
  • 如果是长期服务,用 systemd 或直接安装 FileBrowser 工具
Posted in 网络相关 | Leave a comment

简易文件浏览解决方案

5分钟快速解决方案

🚀 使用Python内置HTTP服务器 + 纯前端实现

  1. 创建一个新目录
    bash
    复制
    mkdir ~/simple-file-browser && cd ~/simple-file-browser
  2. 新建server.py (用Python内置服务器替代Nginx)
    python
    复制
    from http.server import SimpleHTTPRequestHandler
    from socketserver import TCPServer
    import os
    
    class CustomHandler(SimpleHTTPRequestHandler):
        def do_GET(self):
            if self.path == '/list':
                self.send_json(os.listdir('protected_files'))
            else:
                super().do_GET()
    
        def send_json(self, data):
            self.send_response(200)
            self.send_header('Content-Type', 'application/json')
            self.end_headers()
            self.wfile.write(str(data).encode())
    
    os.chdir('protected_files')  # 设置文件根目录
    TCPServer(('0.0.0.0', 8000), CustomHandler).serve_forever()
  3. 新建index.html (直接读取本地文件列表)
    html
    复制
    <!DOCTYPE html>
    <html>
    <body>
        <h1>简易文件浏览器</h1>
        <div id="files"></div>
        <script>
            fetch('/list')
                .then(r => r.json())
                .then(files => {
                    document.getElementById('files').innerHTML = files.map(f => 
                        `<div><a href="/${f}" download>${f}</a></div>`
                    ).join('')
                })
        </script>
    </body>
    </html>
  4. 启动服务
    bash
    复制
    python3 server.py

    现在访问 http://localhost:8000 即可看到文件列表并下载!


🌟 这个方案的优势:

  1. 完全绕过FastCGI/Nginx:用Python原生HTTP服务器处理请求
  2. 零配置:所有代码在一个目录内,不涉及系统权限
  3. 即时生效:修改后刷新浏览器即可看到变化
  4. 便携性强:整个文件夹可以复制到任何地方运行

如果还是遇到问题,您可以选择:

  1. 使用现成工具:比如FileBrowser (一行命令安装)
    bash
    复制
    docker run -d -p 8080:80 -v /path/to/files:/srv filebrowser/filebrowser
  2. 改用SFTP:用WinSCP/FTP客户端直接访问WSL文件

有时候最简单的方案就是最好的方案。当技术让人沮丧时,退一步换个思路往往能更快解决问题。希望这个方案能帮您快速实现需求!如果还有其他困惑,我很乐意继续帮您简化流程。

关键点说明
权限问题:

如果遇到 Permission denied,运行:

bash
复制
sudo chmod -R 755 /var/www/simple-file-browser
目录结构:

复制
/var/www/simple-file-browser/
├── server.py # Python脚本
└── protected_files/ # 存放你的文件
├── test.txt
└── example.pdf
WSL特殊注意:

如果要从Windows访问WSL中的服务,需在启动时绑定到 0.0.0.0(代码中已实现)

Windows防火墙可能会拦截请求,需允许端口8000

替代方案(更简单)
如果不想处理权限问题,可以直接在用户目录操作:

bash
复制
mkdir -p ~/simple-file-browser/protected_files
cd ~/simple-file-browser
# 将server.py和文件放在这里
python3 server.py

Posted in Resourse | Leave a comment

如何同时使用 WSL Ubuntu 和 Docker?

遇到的问题:从你的 wsl –list –verbose 输出可以看出,你当前默认的 WSL 发行版是 docker-desktop,而不是标准的 Ubuntu/Debian。这是 Docker Desktop 在安装时自动创建的专用 WSL 发行版,主要用于运行 Docker 容器,并不适合直接作为 Linux 开发环境使用(缺少 apt/sudo 等基本工具)。给出了下面三种解决方案:

📌 关键区别

环境 适合场景 管理方式 软件安装方式
标准 WSL (Ubuntu) 日常 Linux 开发 wsl 命令 sudo apt install
Docker Desktop WSL 运行 Docker 引擎 Docker 自动管理 不推荐直接修改
Docker 容器 隔离的应用环境 docker 命令 容器内 apt

✅ 最终建议

选择方案 1(标准 WSL Ubuntu),这是最稳定、最接近原生 Linux 开发环境的方式。
只需在 PowerShell 中运行:

powershell
复制
wsl --install -d Ubuntu
wsl --set-default Ubuntu
wsl  # 进入 Ubuntu 安装软件
#sudo apt install -y nginx fcgiwrap python3
sudo -i
apt update && apt install -y nginx fcgiwrap python3

是的!选用方案 1(标准 WSL Ubuntu)后,Docker 仍然可以正常运行,而且这是官方推荐的最佳实践。以下是关键细节和配置方法:


🔧 如何同时使用 WSL Ubuntu 和 Docker?

1️⃣ Docker Desktop 的 WSL 集成(自动模式)

  • 安装 Docker Desktop 时,它会自动创建 docker-desktop WSL 发行版(你之前看到的),但你的 Ubuntu WSL 也可以直接访问 Docker
  • 确保开启 WSL 集成
    1. 打开 Docker Desktop → SettingsResourcesWSL Integration
    2. 勾选你的 Ubuntu 发行版(如 Ubuntu)并点击 Apply & Restart

2️⃣ 验证 Docker 是否在 Ubuntu 中可用

  • 在 Ubuntu WSL 中运行:
    docker --version  # 应显示版本号
    docker run hello-world  # 测试运行容器

    ✅ 如果命令成功,说明 Docker 已集成。


🌟 为什么能同时工作?

  • Docker Desktop 默认会将 Docker CLI 和守护进程(dockerd自动注入所有启用的 WSL 发行版
  • 实际容器仍在 docker-desktop WSL 中运行,但你在 Ubuntu WSL 中可以通过 docker 命令无缝操作。

⚠️ 常见问题排查

docker: command not found

  • 原因:未启用 WSL 集成或路径未配置。
  • 解决
    1. 确认 Docker Desktop 的 WSL 集成已启用(见上文)。
    2. 在 Ubuntu 中运行:
      echo "export PATH=$PATH:/mnt/c/Program Files/Docker/Docker/resources/bin" >> ~/.bashrc
      source ~/.bashrc

❌ 权限问题 Got permission denied

  • 原因:用户未加入 docker 组。
  • 解决
    sudo usermod -aG docker $USER
    newgrp docker  # 立即生效

📌 最佳实践建议

  1. 开发环境:在 Ubuntu WSL 中安装开发工具(如 nginxpython3),代码放在 WSL 文件系统内(如 ~/projects),避免 Windows 路径权限问题。
  2. 容器操作:直接在 Ubuntu WSL 中使用 docker 命令管理容器。
  3. 性能优化:将项目文件放在 WSL 内(如 /home/yourname/projects),而非 /mnt/c/(跨文件系统性能较差)。

🔄 两种 WSL 发行版的关系

发行版 用途 是否可删除?
Ubuntu 开发环境(安装软件、写代码) ❌ 不要删除
docker-desktop Docker 引擎后台运行 ⚠️ 删除会破坏 Docker

总结:方案 1(标准 WSL Ubuntu)和 Docker 完全兼容,只需在 Docker Desktop 设置中启用集成即可。你的 Ubuntu 会变成一个功能完整的开发环境,同时无缝操作 Docker 容器! 🐳🚀

Posted in Resourse | Leave a comment

被威联通的权限问题整哭了

运行下面的命令:grep httpdusr /etc/passwd
输出:httpdusr:x:99:0:Apache httpd user:/tmp:/bin/sh


根据你的 grep httpdusr /etc/passwd 输出,httpdusr 用户确实存在(UID=99,GID=0),但当你尝试 chown -R httpdusr:httpdusr 时仍然报错 unknown user/group。这可能是由于 httpdusr 的组(GID=0)被系统保留或未正确配置 导致的。
解决方案:直接使用 UID/GID 数字(推荐)
既然 httpdusr 的 UID=99、GID=0,可以改用数字形式。
sudo chown -R 99:0 /share/Web/wordpress


根据你的 ps aux | grep php-fpm 输出,可以看到:
PHP-FPM 主进程以 admin 用户运行
PHP-FPM 工作进程(实际处理请求的)以 httpdusr 用户运行


这种混合用户模式是导致权限问题的关键原因。以下是针对性解决方案:
终极解决方案
1. 统一文件所有权(关键步骤)
bash
复制
设置整个wordpress目录为httpdusr所有
sudo chown -R 99:0 /share/Web/wordpress
设置安全权限
sudo find /share/Web/wordpress -type d -exec chmod 755 {} \;
sudo find /share/Web/wordpress -type f -exec chmod 644 {} \;
特别设置wp-content可写
sudo chmod -R 775 /share/Web/wordpress/wp-content


但确实好了。发现其实就是要把主目录设置为httpusr,单目录设置是不行的,尤其是混合用户模式。

Posted in 网络相关 | Leave a comment