CF _worker搭建 WS01 Note，可设置私人日记或公开分享变量设置：1、帐号：USERNAME，默认：9527a2、密码：PASSWORD，默认：9527abc3、KV空间邦定：WS01_NOTE_KV// WS01 Note - Cloudflare Workers + KV 日记本应用 // 作者: WS01 // 功能: 基于Cloudflare Workers和KV数据库的简单日记本 export default { async fetch(request, env, ctx) { const url = new URL(request.url); const path = url.pathname; // 处理CORS const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization', }; if (request.method === 'OPTIONS') { return new Response(null, { status: 200, headers: corsHeaders }); } try { // 路由处理 if (path === '/' || path === '/login') { return new Response(getLoginPage(), { headers: { 'Content-Type': 'text/html; charset=utf-8', ...corsHeaders } }); } if (path === '/diary') { return new Response(getDiaryPage(), { headers: { 'Content-Type': 'text/html; charset=utf-8', ...corsHeaders } }); } // 查看具体日记页面 if (path.startsWith('/diary/') && path.split('/').length === 3) { const diaryId = path.split('/')[2]; return new Response(getDiaryDetailPage(diaryId), { headers: { 'Content-Type': 'text/html; charset=utf-8', ...corsHeaders } }); } // 分享日记目录页面路由 if (path === '/share') { return new Response(getShareIndexPage(), { headers: { 'Content-Type': 'text/html; charset=utf-8', ...corsHeaders } }); } // 分享页面路由 if (path.startsWith('/share/') && path.split('/').length === 3) { const shareId = path.split('/')[2]; return new Response(getSharePage(shareId), { headers: { 'Content-Type': 'text/html; charset=utf-8', ...corsHeaders } }); } if (path === '/api/auth') { return handleAuth(request, env, corsHeaders); } if (path === '/api/diary') { return handleDiaryAPI(request, env, corsHeaders); } // 添加分享相关API路由 if (path === '/api/diary/share' && (request.method === 'POST' || request.method === 'PUT')) { return handleShareDiary(request, env, corsHeaders); } // 获取分享日记API路由 if (path.startsWith('/api/share/') && path.split('/').length === 4) { const shareId = path.split('/')[3]; return handleGetSharedDiary(shareId, env, corsHeaders); } // 获取所有分享日记API路由 if (path === '/api/shares' && request.method === 'GET') { return handleGetAllShares(request, env, corsHeaders); } // 获取用户分享日记列表API路由 if (path === '/api/diary/shares' && request.method === 'GET') { return handleGetUserShares(request, env, corsHeaders); } // 删除分享日记API路由 if (path === '/api/diary/share' && request.method === 'DELETE') { return handleDeleteShare(request, env, corsHeaders); } // 添加备份相关API路由 if (path === '/api/diary/backup' && request.method === 'POST') { return handleCreateBackup(request, env, corsHeaders); } if (path === '/api/diary/backups' && request.method === 'GET') { return handleGetBackups(request, env, corsHeaders); } if (path === '/api/diary/restore' && request.method === 'POST') { return handleRestoreBackup(request, env, corsHeaders); } if (path === '/api/diary/backup' && request.method === 'DELETE') { return handleDeleteBackup(request, env, corsHeaders); } // 404处理 return new Response('页面未找到', { status: 404, headers: { 'Content-Type': 'text/plain; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('Worker错误:', error); return new Response('服务器内部错误', { status: 500, headers: { 'Content-Type': 'text/plain; charset=utf-8', ...corsHeaders } }); } } }; // 登录页面 function getLoginPage() { return ` WS01 Note - 登录 WS01 Note 您的私人日记本 用户名 密码 登录 `; } // 日记页面 function getDiaryPage() { return ` WS01 Note - 我的日记 WS01 Note 📂 🚪 写日记 12px 14px 16px 18px 20px 22px 💾 🔗 0 / 100000 × 我的日记 还没有日记，开始记录吧！ 分享日记 刷新分享列表 暂无分享日记 数据备份与恢复 创建备份 刷新备份列表 暂无备份 `; } // 日记详情页面 function getDiaryDetailPage(diaryId) { return ` WS01 Note - 日记详情 WS01 Note 12px 14px 16px 18px 20px 22px ← 返回 加载中... 内容已复制到剪贴板 `; } // 分享日记目录页面 function getShareIndexPage() { return ` WS01 Note - 分享目录 WS01 Note - 分享目录 分享文章目录 这里展示所有公开分享的文章，点击标题可查看完整内容。复制的分享内容可能有改变。 加载中... 上一页 下一页 跳转到 页 跳转 载入天数... <span | 本页总访问量 次 | 登录 `; } // 分享页面 function getSharePage(shareId) { return ` WS01 Note - 分享内容 WS01 Note - 分享内容 ← 返回分享目录 📢 这是一篇分享的文章，任何人都可以查看 加载中... 内容已复制到剪贴板 载入天数... <span | 本页总访问量 次 | 登录 `; } // 处理认证API async function handleAuth(request, env, corsHeaders) { if (request.method !== 'POST') { return new Response('方法不允许', { status: 405, headers: { 'Content-Type': 'text/plain; charset=utf-8', ...corsHeaders } }); } try { const { username, password } = await request.json(); // 验证用户名和密码（从环境变量获取） const validUsername = env.USERNAME || '9527a'; const validPassword = env.PASSWORD || '9527abc'; if (username === validUsername && password === validPassword) { // 生成简单的token（实际应用中应该使用更安全的方法） const token = btoa(username + ':' + Date.now()); return new Response(JSON.stringify({ success: true, token: token, message: '登录成功' }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } else { return new Response(JSON.stringify({ success: false, message: '用户名或密码错误' }), { status: 401, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } catch (error) { return new Response(JSON.stringify({ success: false, message: '请求格式错误' }), { status: 400, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 处理日记API async function handleDiaryAPI(request, env, corsHeaders) { // 验证token const authHeader = request.headers.get('Authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return new Response(JSON.stringify({ success: false, message: '未授权' }), { status: 401, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } const token = authHeader.substring(7); try { if (request.method === 'GET') { // 获取日记列表 const diaries = await getDiaries(env); return new Response(JSON.stringify({ success: true, diaries: diaries }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } if (request.method === 'POST') { // 保存新日记 const { title, content, editShareId } = await request.json(); const diary = { id: Date.now().toString(), title: title, content: content, date: new Date().toISOString() }; await saveDiary(env, diary); // 如果是从分享日记编辑而来，需要从分享日记中删除 if (editShareId) { await env.WS01_NOTE_KV.delete(`shared_${editShareId}`); } return new Response(JSON.stringify({ success: true, message: '日记保存成功' }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } // 添加: 处理更新日记 (PUT) if (request.method === 'PUT') { const { id, title, content } = await request.json(); // 获取现有日记 const diaries = await getDiaries(env); const diaryIndex = diaries.findIndex(d => d.id === id); if (diaryIndex === -1) { return new Response(JSON.stringify({ success: false, message: '日记不存在' }), { status: 404, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } // 更新日记内容 diaries[diaryIndex] = { ...diaries[diaryIndex], title, content, date: new Date().toISOString() // 更新时间 }; await env.WS01_NOTE_KV.put('diaries', JSON.stringify(diaries)); return new Response(JSON.stringify({ success: true, message: '日记更新成功' }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } // 添加: 处理删除日记 (DELETE) if (request.method === 'DELETE') { const { id } = await request.json(); const diaries = await getDiaries(env); const filteredDiaries = diaries.filter(d => d.id !== id); if (filteredDiaries.length === diaries.length) { return new Response(JSON.stringify({ success: false, message: '日记不存在' }), { status: 404, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } await env.WS01_NOTE_KV.put('diaries', JSON.stringify(filteredDiaries)); return new Response(JSON.stringify({ success: true, message: '日记删除成功' }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } return new Response('方法不允许', { status: 405, headers: { 'Content-Type': 'text/plain; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('日记API错误:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 获取日记列表 async function getDiaries(env) { try { const diariesJson = await env.WS01_NOTE_KV.get('diaries'); if (diariesJson) { const diaries = JSON.parse(diariesJson); return diaries.sort((a, b) => new Date(b.date) - new Date(a.date)); } return []; } catch (error) { console.error('获取日记失败:', error); return []; } } // 保存日记 async function saveDiary(env, diary) { try { const diaries = await getDiaries(env); diaries.unshift(diary); // 添加到开头 // 限制最多保存100篇日记 if (diaries.length > 100) { diaries.splice(100); } await env.WS01_NOTE_KV.put('diaries', JSON.stringify(diaries)); } catch (error) { console.error('保存日记失败:', error); throw error; } } // 处理创建备份 async function handleCreateBackup(request, env, corsHeaders) { // 验证token const authHeader = request.headers.get('Authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return new Response(JSON.stringify({ success: false, message: '未授权' }), { status: 401, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } try { // 获取当前所有日记 const diaries = await getDiaries(env); // 获取所有分享日记 const listResult = await env.WS01_NOTE_KV.list({ prefix: 'shared_' }); const shares = []; for (const key of listResult.keys) { try { const sharedDiaryJson = await env.WS01_NOTE_KV.get(key.name); if (sharedDiaryJson) { const sharedDiary = JSON.parse(sharedDiaryJson); shares.push(sharedDiary); } } catch (e) { console.error('读取分享日记失败:', e); } } // 创建备份数据 const backup = { id: Date.now().toString(), timestamp: new Date().toISOString(), data: diaries, shares: shares, count: diaries.length, shareCount: shares.length }; // 获取现有的备份列表 let backups = []; try { const backupsJson = await env.WS01_NOTE_KV.get('backups'); if (backupsJson) { backups = JSON.parse(backupsJson); } } catch (e) { console.error('读取备份列表失败:', e); } // 添加新备份到列表开头 backups.unshift(backup); // 限制最多5个备份 if (backups.length > 5) { backups = backups.slice(0, 5); } // 保存备份列表 await env.WS01_NOTE_KV.put('backups', JSON.stringify(backups)); return new Response(JSON.stringify({ success: true, message: '备份创建成功', backupId: backup.id }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('创建备份失败:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 处理获取备份列表 async function handleGetBackups(request, env, corsHeaders) { // 验证token const authHeader = request.headers.get('Authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return new Response(JSON.stringify({ success: false, message: '未授权' }), { status: 401, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } try { // 获取备份列表 let backups = []; try { const backupsJson = await env.WS01_NOTE_KV.get('backups'); if (backupsJson) { backups = JSON.parse(backupsJson); } } catch (e) { console.error('读取备份列表失败:', e); } // 只返回必要的信息 const backupInfo = backups.map(backup => ({ id: backup.id, timestamp: backup.timestamp, count: backup.count, shareCount: backup.shareCount || 0 })); return new Response(JSON.stringify({ success: true, backups: backupInfo }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('获取备份列表失败:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 处理恢复备份 async function handleRestoreBackup(request, env, corsHeaders) { // 验证token const authHeader = request.headers.get('Authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return new Response(JSON.stringify({ success: false, message: '未授权' }), { status: 401, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } try { const { backupId } = await request.json(); // 获取备份列表 let backups = []; try { const backupsJson = await env.WS01_NOTE_KV.get('backups'); if (backupsJson) { backups = JSON.parse(backupsJson); } } catch (e) { console.error('读取备份列表失败:', e); } // 查找指定的备份 const backup = backups.find(b => b.id === backupId); if (!backup) { return new Response(JSON.stringify({ success: false, message: '备份不存在' }), { status: 404, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } // 恢复个人日记数据 await env.WS01_NOTE_KV.put('diaries', JSON.stringify(backup.data)); // 恢复分享日记数据 if (backup.shares && backup.shares.length > 0) { // 先删除所有现有的分享日记 const existingShares = await env.WS01_NOTE_KV.list({ prefix: 'shared_' }); for (const key of existingShares.keys) { await env.WS01_NOTE_KV.delete(key.name); } // 恢复备份中的分享日记 for (const share of backup.shares) { await env.WS01_NOTE_KV.put(`shared_${share.id}`, JSON.stringify(share)); } } return new Response(JSON.stringify({ success: true, message: '数据恢复成功' }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('恢复备份失败:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 处理分享日记 async function handleShareDiary(request, env, corsHeaders) { // 验证token const authHeader = request.headers.get('Authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return new Response(JSON.stringify({ success: false, message: '未授权' }), { status: 401, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } try { const { title, content, shareId, editDiaryId } = await request.json(); if (request.method === 'PUT' && shareId) { // 更新分享日记 const existingShareJson = await env.WS01_NOTE_KV.get(`shared_${shareId}`); if (!existingShareJson) { return new Response(JSON.stringify({ success: false, message: '分享日记不存在' }), { status: 404, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } const existingShare = JSON.parse(existingShareJson); const updatedShare = { ...existingShare, title: title, content: content, date: new Date().toISOString() // 更新修改时间 }; await env.WS01_NOTE_KV.put(`shared_${shareId}`, JSON.stringify(updatedShare)); return new Response(JSON.stringify({ success: true, message: '分享日记更新成功', shareId: shareId }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } else { // 创建新分享日记 const newShareId = Date.now().toString() + Math.random().toString(36).substr(2, 9); const sharedDiary = { id: newShareId, title: title, content: content, date: new Date().toISOString(), shared: true }; // 保存到KV存储 await env.WS01_NOTE_KV.put(`shared_${newShareId}`, JSON.stringify(sharedDiary)); // 如果是从我的日记编辑而来，需要从我的日记中删除 if (editDiaryId) { const diaries = await getDiaries(env); const filteredDiaries = diaries.filter(d => d.id !== editDiaryId); await env.WS01_NOTE_KV.put('diaries', JSON.stringify(filteredDiaries)); } return new Response(JSON.stringify({ success: true, message: '分享成功', shareId: newShareId }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } catch (error) { console.error('分享日记失败:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 处理获取分享日记 async function handleGetSharedDiary(shareId, env, corsHeaders) { try { // 从KV存储中获取分享日记 const sharedDiaryJson = await env.WS01_NOTE_KV.get(`shared_${shareId}`); if (!sharedDiaryJson) { return new Response(JSON.stringify({ success: false, message: '分享的日记不存在' }), { status: 404, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } const sharedDiary = JSON.parse(sharedDiaryJson); return new Response(JSON.stringify(sharedDiary), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('获取分享日记失败:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 处理获取所有分享日记（公开访问） async function handleGetAllShares(request, env, corsHeaders) { try { // 解析URL参数 const url = new URL(request.url); const page = parseInt(url.searchParams.get('page')) || 1; const limit = parseInt(url.searchParams.get('limit')) || 20; // 获取所有分享日记的键 const listResult = await env.WS01_NOTE_KV.list({ prefix: 'shared_' }); const shares = []; for (const key of listResult.keys) { try { const sharedDiaryJson = await env.WS01_NOTE_KV.get(key.name); if (sharedDiaryJson) { const sharedDiary = JSON.parse(sharedDiaryJson); shares.push({ id: sharedDiary.id, title: sharedDiary.title, content: sharedDiary.content, date: sharedDiary.date, shareUrl: `/share/${sharedDiary.id}` }); } } catch (e) { console.error('解析分享日记失败:', e); } } // 按日期排序（最新的在前） shares.sort((a, b) => new Date(b.date) - new Date(a.date)); // 计算分页 const totalCount = shares.length; const totalPages = Math.ceil(totalCount / limit); const startIndex = (page - 1) * limit; const endIndex = startIndex + limit; const paginatedShares = shares.slice(startIndex, endIndex); return new Response(JSON.stringify({ success: true, shares: paginatedShares, pagination: { currentPage: page, totalPages: totalPages, totalCount: totalCount, limit: limit, hasNext: page < totalPages, hasPrev: page > 1 } }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('获取所有分享日记失败:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 处理获取用户分享日记列表 async function handleGetUserShares(request, env, corsHeaders) { // 验证token const authHeader = request.headers.get('Authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return new Response(JSON.stringify({ success: false, message: '未授权' }), { status: 401, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } try { // 获取所有分享日记的键 const listResult = await env.WS01_NOTE_KV.list({ prefix: 'shared_' }); const shares = []; for (const key of listResult.keys) { try { const sharedDiaryJson = await env.WS01_NOTE_KV.get(key.name); if (sharedDiaryJson) { const sharedDiary = JSON.parse(sharedDiaryJson); shares.push({ id: sharedDiary.id, title: sharedDiary.title, content: sharedDiary.content, date: sharedDiary.date, shareUrl: `${request.url.split('/')[0]}//${request.headers.get('host')}/share/${sharedDiary.id}` }); } } catch (e) { console.error('解析分享日记失败:', e); } } // 按日期排序（最新的在前） shares.sort((a, b) => new Date(b.date) - new Date(a.date)); return new Response(JSON.stringify({ success: true, shares: shares }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('获取分享日记列表失败:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 处理删除分享日记 async function handleDeleteShare(request, env, corsHeaders) { // 验证token const authHeader = request.headers.get('Authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return new Response(JSON.stringify({ success: false, message: '未授权' }), { status: 401, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } try { const { shareId } = await request.json(); // 删除分享日记 await env.WS01_NOTE_KV.delete(`shared_${shareId}`); return new Response(JSON.stringify({ success: true, message: '分享日记删除成功' }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('删除分享日记失败:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } } // 处理删除备份 async function handleDeleteBackup(request, env, corsHeaders) { // 验证token const authHeader = request.headers.get('Authorization'); if (!authHeader || !authHeader.startsWith('Bearer ')) { return new Response(JSON.stringify({ success: false, message: '未授权' }), { status: 401, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } try { const { backupId } = await request.json(); // 获取备份列表 let backups = []; try { const backupsJson = await env.WS01_NOTE_KV.get('backups'); if (backupsJson) { backups = JSON.parse(backupsJson); } } catch (e) { console.error('读取备份列表失败:', e); } // 过滤掉要删除的备份 const filteredBackups = backups.filter(b => b.id !== backupId); if (filteredBackups.length === backups.length) { return new Response(JSON.stringify({ success: false, message: '备份不存在' }), { status: 404, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } // 保存更新后的备份列表 await env.WS01_NOTE_KV.put('backups', JSON.stringify(filteredBackups)); return new Response(JSON.stringify({ success: true, message: '备份删除成功' }), { headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } catch (error) { console.error('删除备份失败:', error); return new Response(JSON.stringify({ success: false, message: '服务器错误' }), { status: 500, headers: { 'Content-Type': 'application/json; charset=utf-8', ...corsHeaders } }); } }
★★★在Debian 11、12系统，用docker搭建Caddy + PHP动、静态网站
在Debian 11、12系统，用docker搭建Caddy + PHP动、静态网站文件夹 home/html/web/web1 一、常规安装，一个网站，不用数据库【推荐用第二或第三个方式 多站点 方式安装，便于以后在vps上搭建更多网站】，方法可行。 目录结构 （建议）/home/html/web/web1/├── docker-compose.yml├── Caddyfile└── public/ # 网站文件 (index.php, index.html等)1、更新系统sudo apt update && sudo apt upgrade -y2、安装 Docker 和 Docker Composesudo apt install -y docker.io docker-compose3、创建网站目录a、灵活安装sudo mkdir -p /home/html/web/web1 cd /home/html/web/web1或 b、简单安装【推荐】创建目录结构# 创建目录结构 sudo mkdir -p /home/html/web/web1/public创建 Caddyfile，解析好域名的，:80可替换域名example.com# 创建 Caddyfile cat << 'EOF' | sudo tee /home/html/web/web1/Caddyfile :80 { root * /srv php_fastcgi php:9000 file_server } EOF创建测试 PHP 文件# 创建测试 PHP 文件 cat << 'EOF' | sudo tee /home/html/web/web1/public/index.php <?php phpinfo(); EOF4、编写 docker-compose.ymlversion: "3.8" services: caddy: image: caddy:latest container_name: caddy_web1 restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile - ./public:/srv - caddy_data:/data - caddy_config:/config depends_on: - php php: image: php:8.2-fpm container_name: php_web1 restart: unless-stopped volumes: - ./public:/srv volumes: caddy_data: caddy_config:5、编写 Caddyfile，如果第三步中已安装好域名的，此步省略example.com { root * /srv php_fastcgi php:9000 file_server }6、放置测试文件，如果第三步中已放置测试文件，此步省略echo "<?php phpinfo(); ?>" > /home/html/web/web1/public/index.php7、启动服务cd /home/html/web/web1 sudo docker-compose up -d查看容器状态：sudo docker ps8、访问测试打开浏览器访问 http://服务器IP/如果绑定了域名（并改了 Caddyfile），访问 https://你的域名/二、多站点 Caddyfile 模板【不用数据库】，以三个web1、web2、web3为例，更多个网站类推，方法可行目录结构建议 /home/html/web/├── web1/│ ├── public/ # 网站1的文件│ ├── Caddyfile.web1 # 网站1配置├── web2/│ ├── public/ # 网站2的文件│ ├── Caddyfile.web2 # 网站2配置├── web3/│ ├── public/ # 网站3的文件│ ├── Caddyfile.web3 # 网站3配置└── docker-compose.yml # 统一的 compose└── Caddyfile # 主配置，引用各站点1、更新系统sudo apt update && sudo apt upgrade -y2、安装 Docker 和 Docker Composesudo apt install -y docker.io docker-compose3、创建三个目录结构并给予权限【 更多网站类似添加 】网站1sudo mkdir -p /home/html/web/web1/public sudo chmod -R 755 /home/html/web/web1/public 网站2sudo mkdir -p /home/html/web/web2/public sudo chmod -R 755 /home/html/web/web2/public网站3sudo mkdir -p /home/html/web/web3/public sudo chmod -R 755 /home/html/web/web3/public4、在文件夹 /home/html/web/ 创建统一的 docker-compose.yml【 更多网站类似添加 】version: "3.8" services: caddy: image: caddy:latest container_name: caddy_main restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile - ./web1/public:/srv/web1 - ./web2/public:/srv/web2 - ./web3/public:/srv/web3 - caddy_data:/data - caddy_config:/config depends_on: - php php: image: php:8.2-fpm container_name: php_main restart: unless-stopped volumes: - ./web1/public:/srv/web1 - ./web2/public:/srv/web2 - ./web3/public:/srv/web3 volumes: caddy_data: caddy_config:5、总控 Caddyfile【 更多网站类似添加 】# Web1 - 用域名 web1.example.com { root * /srv/web1 php_fastcgi php:9000 file_server } # Web2 - 用域名 web2.example.com { root * /srv/web2 php_fastcgi php:9000 file_server } # Web3 - 用 IP + 端口（测试用） :8080 { root * /srv/web3 php_fastcgi php:9000 file_server }6、启动cd /home/html/web sudo docker-compose up -d7、快速测试文件【 更多网站类似添加,个别vps可能添加不上，用上传方式 】echo "<?php echo 'Hello Web1!'; ?>" | sudo tee /home/html/web/web1/public/index.phpecho "<?php echo 'Hello Web2!'; ?>" | sudo tee /home/html/web/web2/public/index.phpecho "<?php echo 'Hello Web3!'; ?>" | sudo tee /home/html/web/web3/public/index.php三、如果 需要数据库 ，在方式二的基础上修改， docker-compose.yml 添加 db 项，web主目录下新建 php 文件夹，php 文件夹添加 Dockerfile 文件， 注意修改数据库相关密码 目录结构/home/html/web/├── web1/│ └── public/ # 放 Typecho├── web2/│ └── public/├── web3/│ └── public/├── docker-compose.yml├── php/│ └── Dockerfile└── Caddyfile1、统一的 docker-compose.yml 【 注意修改数据库相关密码 】version: "3.8" services: caddy: image: caddy:latest container_name: caddy_main restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile - ./web1/public:/srv/web1 - ./web2/public:/srv/web2 - ./web3/public:/srv/web3 - caddy_data:/data - caddy_config:/config depends_on: - php php: build: ./php image: php:8.2-fpm container_name: php_main restart: unless-stopped volumes: - ./web1/public:/srv/web1 - ./web2/public:/srv/web2 - ./web3/public:/srv/web3 depends_on: - db db: image: mariadb:10.7 container_name: mariadb_web1 restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: rootpass123 MYSQL_DATABASE: typecho MYSQL_USER: typecho MYSQL_PASSWORD: typechopass volumes: - db_data:/var/lib/mysql volumes: caddy_data: caddy_config: db_data:2、php/DockerfileFROM php:8.2-fpm # 安装 MySQL 扩展 RUN docker-php-ext-install pdo_mysql mysqli # 可选：安装 gd、zip 等（上传图片/压缩包可能用到） RUN apt-get update && apt-get install -y libjpeg-dev libpng-dev libzip-dev \ && docker-php-ext-install gd zip WORKDIR /srv 访问 http://web1.example.com，进入安装向导。数据库地址： db 数据库名： typecho 用户： typecho 密码： typechopass 2、安装 Typecho 命令，也可以下载修改好后上传cd /home/html/web/web1/public wget https://github.com/typecho/typecho/archive/refs/tags/v1.2.1.zip -O typecho-1.2.1.zip unzip typecho-1.2.1.zip mv typecho-1.2.1/* ./ rm -rf typecho-1.2.1 typecho-1.2.1.zip 3、启动cd /home/html/web sudo docker-compose up -d四、如果搭建网站中需要两个数据库【三个数据库或更多数据库类推】，例如，安装两个Typecho1、一个数据库服务，多个数据库docker-compose.yml（推荐方案）version: "3.8" services: caddy: image: caddy:latest container_name: caddy_main restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile - ./web1/public:/srv/web1 - ./web2/public:/srv/web2 - ./web3/public:/srv/web3 - caddy_data:/data - caddy_config:/config depends_on: - php php: image: php:8.2-fpm container_name: php_main restart: unless-stopped volumes: - ./web1/public:/srv/web1 - ./web2/public:/srv/web2 - ./web3/public:/srv/web3 depends_on: - db db: image: mariadb:10.6 container_name: mariadb_main restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: rootpass123 volumes: - db_data:/var/lib/mysql volumes: caddy_data: caddy_config: db_data:2、初始化两个数据库启动容器后进入 MariaDB：docker exec -it mariadb_main mysql -uroot -p输入 rootpass123 后，创建两个数据库和用户：CREATE DATABASE typecho1; CREATE DATABASE typecho2; CREATE USER 'typecho1'@'%' IDENTIFIED BY 'pass1'; CREATE USER 'typecho2'@'%' IDENTIFIED BY 'pass2'; GRANT ALL PRIVILEGES ON typecho1.* TO 'typecho1'@'%'; GRANT ALL PRIVILEGES ON typecho2.* TO 'typecho2'@'%'; FLUSH PRIVILEGES;然后【实际安装时用 Pdo驱动SQLite适配器安装， SQLite原生函数适配器安装】：web1 Typecho 安装时填：数据库名 typecho1，用户 typecho1，密码 pass1web2 Typecho 安装时填：数据库名 typecho2，用户 typecho2，密码 pass2五、最小化的 Typecho Dockerfile + docker-compose.yml 示例，放到 /home/html/web/web1/ 下就能直接跑【可以，本方法安装步骤 专门链接 】目录结构（推荐）/home/html/web/web1/├── docker-compose.yml├── Caddyfile├── php/│ └── Dockerfile└── data/ (安装后数据库文件会自动生成)1、docker-compose.ymlversion: '3.9' services: caddy: image: caddy:2-alpine container_name: caddy_web1 ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile - ./site:/srv - caddy_data:/data - caddy_config:/config depends_on: - php php: build: ./php container_name: php_web1 volumes: - ./site:/srv depends_on: - db db: image: mariadb:10.7 container_name: mariadb_web1 restart: always environment: MYSQL_ROOT_PASSWORD: rootpass MYSQL_DATABASE: typecho1 MYSQL_USER: typecho MYSQL_PASSWORD: typechopass volumes: - ./data:/var/lib/mysql volumes: caddy_data: caddy_config:2、Caddyfile文件:80 { root * /srv php_fastcgi php:9000 file_server }或# Web1 - 用域名 web1.example.com { root * /srv/web1 php_fastcgi php:9000 file_server }3、php/DockerfileFROM php:8.1-fpm RUN docker-php-ext-install pdo pdo_mysql mysqli WORKDIR /srv4、下载 Typecho，不是在web1文件夹，是在site文件夹cd /home/html/web/web1 mkdir site cd site wget https://github.com/typecho/typecho/releases/download/v1.2.1/typecho.zip unzip typecho.zip rm typecho.zip5、启动cd /home/html/web/web1 docker-compose up -d然后访问服务器 http://你的IP/或域名 就能进入 Typecho 安装页面【安装时uplads文件夹没权限，单独设置一下，config.inc.php文件也可能没创建权限，手动创建一个就行】。数据库信息填写：数据库地址：db数据库名：typecho1用户名：typecho密码：typechopass⚡ 如果你要再装第二个 Typecho（web2），只需要复制一份 /home/html/web/web1/ 为 /home/html/web/web2/，然后在 docker-compose.yml 里修改：容器名（caddy_web2、php_web2、mariadb_web2）数据库名（typecho2）端口映射（比如 8080:80）六、多个网站web1、web2 → 各自安装一个 Typecho（需要数据库）web3、web4 → 普通动态 PHP 网站（不需要数据库）全部共用同一个 Caddy 容器，不同的域名 / 目录对应不同的网站网站目录结构（在 /home/html/web/ 下）web1/ ← Typecho 站点 1web2/ ← Typecho 站点 2web3/ ← 动态 PHP 站点web4/ ← 动态 PHP 站点1、在 /home/html/ 下创建目录：mkdir -p /home/html/{web1,web2,web3,web4,db1_data,db2_data,caddy_data,caddy_config} 2、把 docker-compose.yml 和 Caddyfile 放在 /home/html/3、下载 Typecho 程序包到 web1/ 和 web2/ 目录，解压4、完整的 docker-compose.yml 示例services: caddy: image: caddy:2 container_name: caddy restart: always ports: - "80:80" - "443:443" volumes: - ./Caddyfile:/etc/caddy/Caddyfile - ./caddy_data:/data - ./caddy_config:/config - ./web1:/srv/web1 - ./web2:/srv/web2 - ./web3:/srv/web3 - ./web4:/srv/web4 depends_on: - php php: image: php:8.2-fpm container_name: php-fpm restart: always volumes: - ./web1:/srv/web1 - ./web2:/srv/web2 - ./web3:/srv/web3 - ./web4:/srv/web4 working_dir: /srv command: php-fpm # Typecho 数据库 1 db1: image: mariadb:10.6 container_name: db1 restart: always environment: MYSQL_ROOT_PASSWORD: rootpass MYSQL_DATABASE: typecho1 MYSQL_USER: user1 MYSQL_PASSWORD: pass1 volumes: - ./db1_data:/var/lib/mysql # Typecho 数据库 2 db2: image: mariadb:10.6 container_name: db2 restart: always environment: MYSQL_ROOT_PASSWORD: rootpass MYSQL_DATABASE: typecho2 MYSQL_USER: user2 MYSQL_PASSWORD: pass2 volumes: - ./db2_data:/var/lib/mysql 5、Caddyfile放在 /home/html/Caddyfile，不同网站对应不同文件夹：# web1 - Typecho 1 web1.example.com { root * /srv/web1 php_fastcgi php:9000 file_server } # web2 - Typecho 2 web2.example.com { root * /srv/web2 php_fastcgi php:9000 file_server } # web3 - 普通 PHP 动态站 web3.example.com { root * /srv/web3 php_fastcgi php:9000 file_server } # web4 - 普通 PHP 动态站 web4.example.com { root * /srv/web4 php_fastcgi php:9000 file_server } 6、启动服务：cd /home/html docker-compose up -d7、浏览器访问：http://web1.example.com → 使用数据库 db1，连接参数：数据库主机: db1用户名: user1密码: pass1数据库: typecho1http://web2.example.com → 使用数据库 db2，连接参数：数据库主机: db2用户名: user2密码: pass2数据库: typecho2
用Cloudflare轻松搭建Uptime-Flare监控服务！实时掌握网站状况！本文转载自：https://am.809098.xyz/am-uptime-flare/Uptime-Flare是一个监控工具，能够帮助你监控网站的可用性和性能。通过Cloudflare你可以轻松部署这个服务，免费监控你的网站，告别宕机烦恼！ 原项目地址： 其它项目地址 一、需要准备的前提资料创建Cloudflare TOKEN 用于github部署用创建链接：https://dash.cloudflare.com/profile/api-tokens点击创建令牌,选择 编辑 Cloudflare Workers 模板,然后将 帐户资源 设置为自己的账户。 区域资源 设置为 所有区域二、部署uptime-flare1、打开 原项目地址： 其它项目地址 点击项目首页的 use this template ,然后点击 create new repo 完成项目创建2、设置 SECRET点击 settings -> secrets and variables -> new repo secret② Name 的值是 CLOUDFLARE_API_TOKEN③ Secret 的值是在CF获得的 Token 值3、修改 uptime.config.ts 文件修改 PageConfig 配置里的Links数组,这是监控首页的站点显示信,不是监控的站点,如links: [ { link: 'https://am.809098.xyz', label: '个人博客' }, { link: 'https://809098.xyz', label: 'Blog', highlight: true }, ]修改 WorkConfig 配置,里面是要监控的站点monitors: [ { id: 'am.809098.xyz', name: '个人博客', method: 'GET', target: 'https://am.809098.xyz', tooltip: 'My production server monitor', statusPageLink: 'https://am.809098.xyz', timeout: 10000, }, ], 3、在github的actions看部署成功,就完成了部署
永久免费节点搭建！通过Cloudflare Worker部署免费的VLESS节点，4K高速，解锁Netflix、ChatGPT
永久免费节点搭建！通过Cloudflare Worker部署免费的VLESS节点，4K高速，解锁Netflix、ChatGPT本文转载自： 科技小露 本文是通过 zizifn 大佬的一个开源项目 edgetunnel ，使得我们可以免费的在 Cloudflare 上面通过部署 Worker ，来创建一个免费 VLESS 节点！一、准备工作注册 Cloudflare 账号并登录二、CloudFlare 部署免费节点原作者 GitHub 源项目地址 Worker 部署 VLESS1、来到 Cloudflare 首页，点击 Workers 和 Pages ，创建 Work ，自定义名称，然后部署！而后，编辑代码，清除原先代码，填入如下代码： Workers代码 2、在线生成一个 UUID ，用于替换下面代码中第七行的 UUID。（或是用 V2rayN 生成一个）3、因为原项目里面的代码“ proxy IP ”为空，关于 proxy IP ，是用于转发CF的一些流量，所以，若是存在套了CF的一些网站无法打开，请更换其中的其他网址，也就是第九行中的部分网址！ProxyIP 可以替换成域名或优选IPproxyip.us.fxxk.dedyn.io更多 proxyIP 列表：CM 维护proxyip.us.fxxk.dedyn.io IP落地区域: 美国 维护频率: 12小时/次 proxyip.sg.fxxk.dedyn.io IP落地区域: 新加坡 维护频率: 12小时/次 proxyip.jp.fxxk.dedyn.io IP落地区域: 日本 维护频率: 12小时/次 proxyip.hk.fxxk.dedyn.io IP落地区域: 香港 维护频率: 12小时/次 proxyip.aliyun.fxxk.dedyn.io IP落地区域: 阿里云 维护频率: 4小时/次 proxyip.oracle.fxxk.dedyn.io IP落地区域: 甲骨文 维护频率: 4小时/次 proxyip.digitalocean.fxxk.dedyn.io IP落地区域: 数码海 维护频率: 4小时/次白嫖哥维护workers.cloudflare.cyouMingyu 维护my-telegram-is-herocore.onecf.eu.org sg.ipdb.rr.nu nl.ipdb.rr.nu hk.ipdb.rr.nu jp.ipdb.rr.nu us.ipdb.rr.nu小一维护hk.cf.zhetengsha.eu.org sg.cf.zhetengsha.eu.org us.cf.zhetengsha.eu.org jp.cf.zhetengsha.eu.org代码修改完毕以后，点击右边的部署 – 保存并部署，然后点击左边的箭头，返回！ 4、访问创建的vlessjd，在网站后加上“/UUID” ，即可看到节点信息，当然，里面包含了 订阅链接！ 至此，节点部署完毕，复制 VLESS 的订阅链接，粘贴到 V2rayN 里面。其它使用和优选等自己找教程
部署MJJ自己的信息聚合站 —— rss-reader改版搭建～（内含详细步骤）
部署MJJ自己的信息聚合站 —— rss-reader改版搭建～（内含详细步骤）本文摘自： topang 原项目 rss-reader 在原项目（旧版）基础上改动：自定义标题和描述列表高度一点样式预览 一、搭建准备：小鸡一键安装好dockercurl -fsSL https://get.docker.com | sh && ln -s /usr/libexec/docker/cli-plugins/docker-compose /usr/local/bin二、开始：1、创建文件夹，如“rrx”mkdir rrx2、进入文件夹cd rrx/3、创建文件：config.json（“listHeight”控制列表高度，源多可以试试300）{ "values": [ "https://rss.nodeseek.com/", "https://hostloc.com/forum.php?mod=rss&fid=45&auth=389ec3vtQanmEuRoghE%2FpZPWnYCPmvwWgSa7RsfjbQ%2BJpA%2F6y6eHAx%2FKqtmPOg", "https://linux.do/latest.rss", "https://www.v2ex.com/feed/vps.xml", "https://rss.agag.us.kg/telegram/channel/@vps_track_share", "https://rss.agag.us.kg/telegram/channel/wawowiki", "https://rss.agag.us.kg/telegram/channel/XiangxiuNB", "https://plink.anyfeeder.com/jingjiribao", "https://plink.anyfeeder.com/guangmingribao", "https://plink.anyfeeder.com/people-daily", "https://plink.anyfeeder.com/newscn/whxw", "https://plink.anyfeeder.com/36kr" ], "refresh": 6, "autoUpdatePush": 7, "nightStartTime": "06:30:00", "nightEndTime": "19:30:00" }4、创建文件：docker-compose.yml【现已修改的源有：原版srcrs和其它修改源topang、huhengbo1992等，各有特点】a、srcrs原版源version: "3" services: server: image: srcrs/rss-reader:latest container_name: rss-reader restart: always ports: - "8880:8080" volumes: - "$PWD/config.json:/app/config.json"b、topang修改源version: "3" services: server: image: topang/rss-reader-mix:latest container_name: rss-reader-mix restart: always ports: - "8880:8080" volumes: - "$PWD/config.json:/app/config.json"c、huhengbo1992修改源version: "3" services: server: image: huhengbo1992/rss-reader:latest container_name: rss-reader restart: always ports: - "8880:8080" volumes: - "$PWD/config.json:/app/config.json"5、启动！docker-compose up -d访问8880端口（ip:8880）6、添加反代（Nginx配置文件添加在server{...}里面） location ^~ /{ proxy_pass http://127.0.0.1:8880; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header REMOTE-HOST $remote_addr; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_http_version 1.1; # proxy_hide_header Upgrade; add_header X-Cache $upstream_cache_status; #Set Nginx Cache set $static_fileHXsgUAWW 0; if ( $uri ~* "\.(gif|png|jpg|css|js|woff|woff2)$" ) { set $static_fileHXsgUAWW 1; expires 1m; } if ( $static_fileHXsgUAWW = 0 ) { add_header Cache-Control no-cache; } }其它，一些 RSS资源