fix: 修复 docker 方式部署宿主机数据库目录权限
This commit is contained in:
1 parent
9b49dade41
commit
1c1d34df02
8 files changed
+81
-22
No files matched your search
+1
-1
@@ -41,4 +41,4 @@ USER node
|
||||
EXPOSE 3000
|
||||
|
||||
# Start the server
|
||||
CMD ["node", "backend/dist/server.js"]
|
||||
CMD ["node", "backend/dist/server.js"]
|
||||
@@ -91,8 +91,18 @@
|
||||
|
||||
如果你想把数据完全掌控在自己手里,或者需要在内网环境运行。
|
||||
|
||||
**⚠️ 核心安全须知**:为了提供最高级别的安全保障,本项目的 Docker 镜像严格遵循**非特权用户(Rootless)**运行标准(内部以 `node` 用户,UID 1000 运行),从而有效防止潜在的容器逃逸风险。
|
||||
在运行容器前,你**必须**预先在宿主机创建目录并分配正确的权限,否则会出现 `Permission Denied` 报错。
|
||||
|
||||
#### 0. 准备数据目录 (必做)
|
||||
在你想运行容器的目录下,执行以下命令:
|
||||
```bash
|
||||
mkdir -p data && sudo chown -R 1000:1000 data
|
||||
```
|
||||
|
||||
#### 1. 使用 Docker Compose (推荐)
|
||||
在你的服务器上创建 `docker-compose.yml`:
|
||||
|
||||
1. 在服务器上创建 `docker-compose.yml`:
|
||||
```yaml
|
||||
services:
|
||||
2fauth:
|
||||
@@ -103,6 +113,7 @@ services:
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
environment:
|
||||
# 核心密钥
|
||||
- ENCRYPTION_KEY=32位以上随机密钥
|
||||
- JWT_SECRET=32位以上随机JWT密钥
|
||||
- OAUTH_ALLOWED_USERS=你的邮箱@example.com
|
||||
@@ -112,7 +123,7 @@ services:
|
||||
- OAUTH_GITHUB_REDIRECT_URI=https://你的域名/oauth/callback
|
||||
restart: unless-stopped
|
||||
```
|
||||
运行 `docker compose up -d` 即可。
|
||||
2. 运行 `docker compose up -d` 即可。
|
||||
|
||||
#### 2. 使用 Docker Run
|
||||
```bash
|
||||
@@ -124,8 +135,8 @@ docker run -d --name 2fauth-worker \
|
||||
-e OAUTH_ALLOWED_USERS=你的邮箱 \
|
||||
-e OAUTH_GITHUB_CLIENT_ID= \
|
||||
-e OAUTH_GITHUB_CLIENT_SECRET= \
|
||||
-e OAUTH_GITHUB_CLIENT_REDIRECT_URI= \
|
||||
nap0o/2fauth-worker:latest
|
||||
-e OAUTH_GITHUB_REDIRECT_URI=https://你的域名/oauth/callback \
|
||||
nap0o/2fauth-worker:latest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
+16
-6
@@ -93,8 +93,17 @@ The most hassle-free way. No server required; Cloudflare hosts it for you for fr
|
||||
|
||||
If you want absolute control over your data or need to run it in a local network.
|
||||
|
||||
**⚠️ Important Security Notice**: To provide the highest level of security, this image runs strictly as a **non-privileged user (node, UID 1000)** to prevent container escape risks.
|
||||
You **MUST** create the data directory and set the correct permissions on your host machine before starting the container, otherwise you will encounter `Permission Denied` errors.
|
||||
|
||||
#### 0. Prepare Data Directory (Required)
|
||||
```bash
|
||||
mkdir -p data && sudo chown -R 1000:1000 data
|
||||
```
|
||||
|
||||
#### 1. Using Docker Compose (Recommended)
|
||||
Create `docker-compose.yml` on your server:
|
||||
|
||||
1. Create `docker-compose.yml` on your server:
|
||||
```yaml
|
||||
services:
|
||||
2fauth:
|
||||
@@ -105,16 +114,17 @@ services:
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
environment:
|
||||
# Core Secrets
|
||||
- ENCRYPTION_KEY=Your_32_Character_Random_Key
|
||||
- JWT_SECRET=Your_Random_JWT_Secret
|
||||
- JWT_SECRET=Your_32_Character_Random_JWT_Secret
|
||||
- OAUTH_ALLOWED_USERS=your_email@example.com
|
||||
# Configure at least one provider (GitHub example)
|
||||
- OAUTH_GITHUB_CLIENT_ID=your_id
|
||||
- OAUTH_GITHUB_CLIENT_SECRET=your_secret
|
||||
- OAUTH_GITHUB_REDIRECT_URI=https://your-domain.com/oauth/callback
|
||||
- OAUTH_GITHUB_REDIRECT_URI=[https://your-domain.com/oauth/callback](https://your-domain.com/oauth/callback)
|
||||
restart: unless-stopped
|
||||
```
|
||||
Run `docker compose up -d`.
|
||||
2. Run `docker compose up -d`.
|
||||
|
||||
#### 2. Using Docker Run
|
||||
```bash
|
||||
@@ -126,8 +136,8 @@ docker run -d --name 2fauth-worker \
|
||||
-e OAUTH_ALLOWED_USERS=your_email@example.com \
|
||||
-e OAUTH_GITHUB_CLIENT_ID= \
|
||||
-e OAUTH_GITHUB_CLIENT_SECRET= \
|
||||
-e OAUTH_GITHUB_CLIENT_REDIRECT_URI= \
|
||||
nap0o/2fauth-worker:latest
|
||||
-e OAUTH_GITHUB_REDIRECT_URI=[https://your-domain.com/oauth/callback](https://your-domain.com/oauth/callback) \
|
||||
nap0o/2fauth-worker:latest
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"drizzle-orm": "^0.30.10",
|
||||
"hono": "^4.12.3",
|
||||
"node-cron": "^4.2.1",
|
||||
"nodemailer": "^6.10.1",
|
||||
"nodemailer": "^7.0.11",
|
||||
"webdav": "^5.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -22,7 +22,7 @@
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"@types/node": "^25.3.5",
|
||||
"@types/node-cron": "^3.0.11",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"@types/nodemailer": "^7.0.11",
|
||||
"drizzle-kit": "^0.31.9",
|
||||
"typescript": "^5.9.3",
|
||||
"wrangler": "^4.71.0"
|
||||
|
||||
@@ -18,14 +18,46 @@ const dataDir = path.resolve(baseDir, 'data');
|
||||
console.log(`[Docker Server] Base directory: ${baseDir}`);
|
||||
console.log(`[Docker Server] Frontend dist path: ${frontendDistPath}`);
|
||||
|
||||
// 2. Ensure data directory exists
|
||||
// 2. Ensure data directory exists and is writable
|
||||
if (!fs.existsSync(dataDir)) {
|
||||
fs.mkdirSync(dataDir, { recursive: true });
|
||||
}
|
||||
|
||||
try {
|
||||
fs.accessSync(dataDir, fs.constants.W_OK);
|
||||
} catch (err) {
|
||||
console.error(`\n❌ ERROR: Data directory "${dataDir}" is NOT writable!`);
|
||||
console.error(` Please run on your host: sudo chown -R 1000:1000 ./data\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 3. Initialize local SQLite Database
|
||||
const dbFile = process.env.SQLITE_DB_PATH || path.join(dataDir, '2fauth.db');
|
||||
const sqlite = new Database(dbFile);
|
||||
|
||||
// Check if database file is writable if it exists
|
||||
if (fs.existsSync(dbFile)) {
|
||||
try {
|
||||
fs.accessSync(dbFile, fs.constants.W_OK);
|
||||
} catch (err) {
|
||||
console.error(`\n❌ ERROR: Database file "${dbFile}" is NOT writable!`);
|
||||
console.error(` Please run on your host: sudo chown 1000:1000 ${dbFile}\n`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
let sqlite: Database.Database;
|
||||
try {
|
||||
sqlite = new Database(dbFile);
|
||||
} catch (err: any) {
|
||||
if (err.code === 'SQLITE_CANTOPEN') {
|
||||
console.error(`\n❌ ERROR: SQLite cannot open database file "${dbFile}".`);
|
||||
console.error(` This is likely a permission issue on the mounted volume.`);
|
||||
console.error(` Please run on your host: sudo chown -R 1000:1000 ./data\n`);
|
||||
} else {
|
||||
console.error(`\n❌ ERROR: Failed to open database:`, err.message);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
sqlite.pragma('journal_mode = WAL');
|
||||
|
||||
// 4. Initialize Drizzle ORM
|
||||
|
||||
@@ -9,7 +9,7 @@ import { WebAuthnService } from '@/features/auth/webAuthnService';
|
||||
const auth = new Hono<{ Bindings: EnvBindings, Variables: { user: any } }>();
|
||||
|
||||
const getService = (c: any) => new AuthService(c.env);
|
||||
const getWebAuthnService = (c: any) => new WebAuthnService(c.env, c.req.url);
|
||||
const getWebAuthnService = (c: any) => new WebAuthnService(c.env, c.req.url, c.req.header());
|
||||
|
||||
// 获取可用登录方式列表
|
||||
auth.get('/providers', (c) => {
|
||||
|
||||
@@ -15,11 +15,17 @@ export class WebAuthnService {
|
||||
private rpID: string;
|
||||
private origin: string;
|
||||
|
||||
constructor(env: EnvBindings, url: string) {
|
||||
constructor(env: EnvBindings, url: string, headers?: Record<string, string | undefined>) {
|
||||
this.env = env;
|
||||
const parsedUrl = new URL(url);
|
||||
this.rpID = parsedUrl.hostname;
|
||||
this.origin = `${parsedUrl.protocol}//${parsedUrl.host}`;
|
||||
|
||||
// Handle Reverse Proxy (Docker/Nginx/Cloudflare)
|
||||
// If X-Forwarded-Proto exists, use it as the real protocol
|
||||
const proto = headers?.['x-forwarded-proto'] || parsedUrl.protocol.replace(':', '');
|
||||
const host = headers?.['x-forwarded-host'] || parsedUrl.host;
|
||||
|
||||
this.rpID = host.split(':')[0]; // Domain only for RP ID
|
||||
this.origin = `${proto.includes('://') ? proto : proto + '://'}${host}`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+3
-3
@@ -25,12 +25,12 @@ services:
|
||||
# Google 登录配置
|
||||
# - OAUTH_GOOGLE_CLIENT_ID=
|
||||
# - OAUTH_GOOGLE_CLIENT_SECRET=
|
||||
# - OAUTH_GOOGLE_REDIRECT_URI=https://<your_domain>/api/oauth/callback
|
||||
# - OAUTH_GOOGLE_REDIRECT_URI=https://<your_domain>/oauth/callback
|
||||
|
||||
# Github 登录配置
|
||||
# - OAUTH_GITHUB_CLIENT_ID=
|
||||
# - OAUTH_GITHUB_CLIENT_SECRET=
|
||||
# - OAUTH_GITHUB_REDIRECT_URI=https://<your_domain>/api/oauth/callback
|
||||
# - OAUTH_GITHUB_REDIRECT_URI=https://<your_domain>/oauth/callback
|
||||
|
||||
# Telegram 登录配置
|
||||
# - OAUTH_TELEGRAM_BOT_NAME=your_bot_name
|
||||
@@ -50,4 +50,4 @@ services:
|
||||
# Nodeloc 登录配置
|
||||
# - OAUTH_NODELOC_CLIENT_ID=
|
||||
# - OAUTH_NODELOC_CLIENT_SECRET=
|
||||
# - OAUTH_NODELOC_REDIRECT_URI=https://<your_domain>/api/oauth/callback
|
||||
# - OAUTH_NODELOC_REDIRECT_URI=https://<your_domain>/oauth/callback
|
||||
Reference in new issue
Block a user