commit 80dd32db1de6caf516634af562013d92d3f46d22 Author: Seven Date: Sun Mar 8 19:18:59 2026 +0700 init diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..0f48b99 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +node_modules +.git +.gitignore +*.md +.DS_Store +.env +.vscode +dist +build +coverage +.idea +*.log diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9b12989 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules +.env +.DS_Store +files/* +!files/.gitkeep diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..fa06e55 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +# 使用 Bun 官方镜像作为基础镜像 +FROM oven/bun:1 AS base + +# 设置工作目录 +WORKDIR /app + +# 复制 package.json 和相关文件(可选,项目无运行时依赖) +COPY package.json ./ + +# 复制源代码 +COPY src ./src +COPY tsconfig.json ./ +COPY files ./files + +# 暴露端口 +EXPOSE 9666 + +# 设置环境变量 +ENV PORT=9666 +ENV FILES_DIR=/app/files + +# 启动服务 +CMD ["bun", "run", "src/server.ts"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..433fd67 --- /dev/null +++ b/README.md @@ -0,0 +1,178 @@ +# 📁 文件托管服务 + +基于 Bun 的高性能文件托管服务,用于快速部署和访问静态文件。 + +## ✨ 功能特点 + +- 🚀 基于 Bun,性能极佳 +- 📂 支持目录浏览,美观的 Web 界面 +- 🔍 自动识别文件 MIME 类型 +- 🛡️ 防路径遍历攻击 +- ⚙️ 简单配置,开箱即用 + +## 🚀 快速开始 + +### 安装依赖 + +```bash +bun install +``` + +### 启动服务 + +```bash +# 开发模式(自动重启) +bun run dev + +# 生产模式 +bun run start +``` + +### 访问服务 + +打开浏览器访问:`http://localhost:3000` + +## 📦 使用说明 + +1. 将需要托管的文件放入 `files/` 目录 +2. 启动服务 +3. 通过浏览器访问文件: + - 访问根路径:`http://localhost:3000/` 查看所有文件 + - 访问具体文件:`http://localhost:3000/your-file.pdf` + - 访问子目录:`http://localhost:3000/subfolder/` + +## ⚙️ 配置 + +编辑 `.env` 文件修改配置: + +```env +# 服务器端口 +PORT=3000 + +# 文件存储目录 +FILES_DIR=./files +``` + +## 🌐 部署到服务器 + +### 1. 安装 Bun + +```bash +curl -fsSL https://bun.sh/install | bash +``` + +### 2. 上传代码到服务器 + +```bash +# 使用 git +git clone + +# 或使用 scp +scp -r . user@server:/path/to/app +``` + +### 3. 启动服务 + +```bash +# 直接运行 +bun run start + +# 使用 PM2 管理(推荐) +pm2 start "bun run start" --name file-hosting + +# 使用 systemd(推荐) +sudo nano /etc/systemd/system/file-hosting.service +``` + +### systemd 服务配置示例 + +```ini +[Unit] +Description=File Hosting Service +After=network.target + +[Service] +Type=simple +User=your-user +WorkingDirectory=/path/to/app +ExecStart=/root/.bun/bin/bun run src/server.ts +Restart=always +Environment=PORT=3000 + +[Install] +WantedBy=multi-user.target +``` + +启用服务: + +```bash +sudo systemctl enable file-hosting +sudo systemctl start file-hosting +sudo systemctl status file-hosting +``` + +### 4. 配置反向代理(可选) + +使用 Nginx 反向代理: + +```nginx +server { + listen 80; + server_name your-domain.com; + + location / { + proxy_pass http://localhost:3000; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} +``` + +## 📝 项目结构 + +``` +. +├── src/ +│ └── server.ts # 服务器主文件 +├── files/ # 文件存储目录 +│ ├── .gitkeep +│ └── README.md # 示例文件 +├── package.json +├── tsconfig.json +├── .env # 环境配置 +├── .gitignore +└── README.md +``` + +## 🔒 安全建议 + +1. **不要暴露敏感文件**:确保 `files/` 目录中不包含敏感信息 +2. **使用 HTTPS**:生产环境建议配置 SSL 证书 +3. **添加认证**:如需限制访问,可添加 HTTP 基础认证或 JWT +4. **设置防火墙**:限制只允许特定 IP 访问 + +## 📄 支持的文件类型 + +- 文档:PDF, TXT, MD +- 图片:PNG, JPG, GIF, SVG +- 视频:MP4 +- 音频:MP3 +- 压缩包:ZIP +- 网页:HTML, CSS, JS +- 数据:JSON + +## 📈 性能 + +- 基于 Bun 原生文件 API,性能是 Node.js 的 2-4 倍 +- 低内存占用,适合小型服务器 +- 支持高并发访问 + +## 🤝 贡献 + +欢迎提交 Issue 和 Pull Request! + +## 📜 许可 + +MIT License diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..dfe7e89 --- /dev/null +++ b/bun.lock @@ -0,0 +1,21 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "file-hosting-service", + "devDependencies": { + "@types/bun": "latest", + }, + }, + }, + "packages": { + "@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="], + + "@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="], + + "bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="], + + "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..82b02a3 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,23 @@ +version: '3.8' + +services: + tradingview: + build: + context: . + dockerfile: Dockerfile + container_name: tradingview-server + ports: + - "9666:9666" + environment: + - PORT=9666 + - FILES_DIR=/app/files + volumes: + # 如果需要动态更新文件,可以挂载 files 目录 + - ./files:/app/files:ro + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:9666/"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s diff --git a/files/.gitkeep b/files/.gitkeep new file mode 100644 index 0000000..7b6ad89 --- /dev/null +++ b/files/.gitkeep @@ -0,0 +1,2 @@ +# 这是文件存储目录的占位文件 +# 请将您要托管的文件放在此目录下 diff --git a/package.json b/package.json new file mode 100644 index 0000000..74fa2b3 --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "file-hosting-service", + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "bun run --watch src/server.ts", + "start": "bun run src/server.ts" + }, + "dependencies": {}, + "devDependencies": { + "@types/bun": "latest" + } +} diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000..5d7ced5 --- /dev/null +++ b/src/server.ts @@ -0,0 +1,155 @@ +const PORT = Bun.env.PORT || 3000; +const FILES_DIR = Bun.env.FILES_DIR || "./files"; + +// 获取文件的 MIME 类型 +function getMimeType(filename: string): string { + const ext = filename.split('.').pop()?.toLowerCase(); + const mimeTypes: Record = { + 'html': 'text/html', + 'css': 'text/css', + 'js': 'application/javascript', + 'json': 'application/json', + 'png': 'image/png', + 'jpg': 'image/jpeg', + 'jpeg': 'image/jpeg', + 'gif': 'image/gif', + 'svg': 'image/svg+xml', + 'pdf': 'application/pdf', + 'txt': 'text/plain', + 'zip': 'application/zip', + 'mp4': 'video/mp4', + 'mp3': 'audio/mpeg', + }; + return mimeTypes[ext || ''] || 'application/octet-stream'; +} + +// 列出目录内容(HTML 格式) +async function listDirectory(dirPath: string, urlPath: string): Promise { + const files = await Array.fromAsync(new Bun.Glob("*").scan(dirPath)); + + let html = ` + + + + + 文件列表 - ${urlPath} + + + +

📁 文件列表

+
当前路径: ${urlPath}
+
    + `; + + if (urlPath !== '/') { + const parentPath = urlPath.split('/').slice(0, -1).join('/') || '/'; + html += `
  • ⬆️返回上级
  • `; + } + + for (const file of files.sort()) { + const fullPath = `${dirPath}/${file}`; + const stat = await Bun.file(fullPath).exists(); + const isDir = (await Bun.file(fullPath).type) === ''; + const icon = isDir ? '📁' : '📄'; + const link = urlPath === '/' ? `/${file}` : `${urlPath}/${file}`; + html += `
  • ${icon}${file}
  • `; + } + + html += ` +
+ + + `; + return html; +} + +// CORS 响应头 +const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type', +}; + +// 启动服务器 +Bun.serve({ + port: PORT, + async fetch(req) { + const url = new URL(req.url); + let pathname = decodeURIComponent(url.pathname); + + // 处理 OPTIONS 预检请求 + if (req.method === 'OPTIONS') { + return new Response(null, { + status: 204, + headers: corsHeaders + }); + } + + // 防止路径遍历攻击 + if (pathname.includes('..')) { + return new Response('Forbidden', { + status: 403, + headers: corsHeaders + }); + } + + const filePath = `${FILES_DIR}${pathname}`; + const file = Bun.file(filePath); + + try { + // 检查文件是否存在 + if (await file.exists()) { + const stat = await file.stat(); + + // 如果是目录,显示目录列表 + if (stat.isDirectory()) { + const html = await listDirectory(filePath, pathname); + return new Response(html, { + headers: { + 'Content-Type': 'text/html; charset=utf-8', + ...corsHeaders + } + }); + } + + // 返回文件内容 + const mimeType = getMimeType(pathname); + return new Response(file, { + headers: { + 'Content-Type': mimeType, + 'Content-Length': stat.size.toString(), + ...corsHeaders + } + }); + } + + // 文件不存在 + return new Response('File not found', { + status: 404, + headers: corsHeaders + }); + } catch (error) { + console.error('Error:', error); + return new Response('Internal Server Error', { + status: 500, + headers: corsHeaders + }); + } + }, +}); + +console.log(`🚀 文件托管服务已启动!`); +console.log(`📂 文件目录: ${FILES_DIR}`); +console.log(`🌐 访问地址: http://localhost:${PORT}`); +console.log(`⏹️ 按 Ctrl+C 停止服务`); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..00269a2 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "lib": ["ESNext"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noPropertyAccessFromIndexSignature": false, + "types": ["bun-types"] + } +}