diff --git a/README-en.md b/README-en.md index ce4e9542..e0e1eb59 100644 --- a/README-en.md +++ b/README-en.md @@ -41,6 +41,8 @@ Timed task management platform supporting Python3, JavaScript, Shell, Typescript The `latest` image is built on `alpine` and the `debian` image is built on `debian-slim`. If you need to use a dependency that is not supported by `alpine`, it is recommended that you use the `debian` image. +**⚠️ Important**: If you need to run Docker as a **non-root user**, please use the `debian` image. Alpine's `crond` requires root privileges. + ```bash docker pull whyour/qinglong:latest docker pull whyour/qinglong:debian diff --git a/README.md b/README.md index e46d9d79..fadc841b 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,8 @@ Timed task management platform supporting Python3, JavaScript, Shell, Typescript `latest` 镜像是基于 `alpine` 构建,`debian` 镜像是基于 `debian-slim` 构建。如果需要使用 `alpine` 不支持的依赖,建议使用 `debian` 镜像 +**⚠️ 重要提示**: 如果您需要以**非 root 用户**运行 Docker,请使用 `debian` 镜像。Alpine 的 `crond` 需要 root 权限。 + ```bash docker pull whyour/qinglong:latest docker pull whyour/qinglong:debian diff --git a/back/loaders/deps.ts b/back/loaders/deps.ts index ade1bfeb..7e468e15 100644 --- a/back/loaders/deps.ts +++ b/back/loaders/deps.ts @@ -1,8 +1,9 @@ import path from 'path'; import fs from 'fs/promises'; +import os from 'os'; import chokidar from 'chokidar'; import config from '../config/index'; -import { fileExist, promiseExec, rmPath } from '../config/util'; +import { promiseExec } from '../config/util'; async function linkToNodeModule(src: string, dst?: string) { const target = path.join(config.rootPath, 'node_modules', dst || src); @@ -17,8 +18,21 @@ async function linkToNodeModule(src: string, dst?: string) { } async function linkCommand() { - const commandPath = await promiseExec('which node'); - const commandDir = path.dirname(commandPath); + const homeDir = os.homedir(); + const userBinDir = path.join(homeDir, 'bin'); + + try { + await fs.mkdir(userBinDir, { recursive: true }); + } catch (error) { + const commandPath = await promiseExec('which node'); + const commandDir = path.dirname(commandPath); + return await linkCommandToDir(commandDir); + } + + await linkCommandToDir(userBinDir); +} + +async function linkCommandToDir(commandDir: string) { const linkShell = [ { src: 'update.sh', @@ -42,6 +56,7 @@ async function linkCommand() { await fs.unlink(tmpTarget); } } catch (error) { } + await fs.symlink(source, tmpTarget); await fs.rename(tmpTarget, target); } diff --git a/back/services/cron.ts b/back/services/cron.ts index 67bfefd2..58f7cb7b 100644 --- a/back/services/cron.ts +++ b/back/services/cron.ts @@ -669,12 +669,23 @@ export default class CronService { await writeFileWithLock(config.crontabFile, crontab_string); - execSync(`crontab ${config.crontabFile}`); + try { + execSync(`crontab ${config.crontabFile}`); + } catch (error: any) { + const errorMsg = error.message || String(error); + this.logger.error('[crontab] Failed to update system crontab:', errorMsg); + } + await CrontabModel.update({ saved: true }, { where: {} }); } public importCrontab() { - exec('crontab -l', (error, stdout, stderr) => { + exec('crontab -l', (error, stdout) => { + if (error) { + const errorMsg = error.message || String(error); + this.logger.error('[crontab] Failed to read system crontab:', errorMsg); + } + const lines = stdout.split('\n'); const namePrefix = new Date().getTime(); diff --git a/docker/docker-entrypoint.sh b/docker/docker-entrypoint.sh index 547ea19f..c8be232e 100755 --- a/docker/docker-entrypoint.sh +++ b/docker/docker-entrypoint.sh @@ -1,5 +1,8 @@ #!/bin/bash +# Add ~/bin to PATH for non-root users +export PATH="$HOME/bin:$PATH" + dir_shell=/ql/shell . $dir_shell/share.sh diff --git a/shell/share.sh b/shell/share.sh index 25a6a8c9..baa3c81e 100755 --- a/shell/share.sh +++ b/shell/share.sh @@ -59,15 +59,10 @@ list_own_user=$dir_list_tmp/own_user.list list_own_add=$dir_list_tmp/own_add.list list_own_drop=$dir_list_tmp/own_drop.list -## 软连接及其原始文件对应关系 link_name=( task ql ) -original_name=( - task.sh - update.sh -) init_env() { local pnpm_global_path=$(pnpm root -g 2>/dev/null)