Add non-root Docker user support with automatic command setup (#2830)

* Initial plan

* Add non-root user guide and improve error messages

Co-authored-by: whyour <22700758+whyour@users.noreply.github.com>

* Add PM2_HOME configuration to fix non-root user permission errors

Co-authored-by: whyour <22700758+whyour@users.noreply.github.com>

* Fix PM2 socket error by using /tmp for PM2_HOME instead of mounted volume

Co-authored-by: whyour <22700758+whyour@users.noreply.github.com>

* Document symlink permission limitation for non-root users

Co-authored-by: whyour <22700758+whyour@users.noreply.github.com>

* Clarify that scheduled tasks auto-adapt to use full paths when symlinks unavailable

Co-authored-by: whyour <22700758+whyour@users.noreply.github.com>

* Add error handling for symlink creation to prevent worker crashes

Co-authored-by: whyour <22700758+whyour@users.noreply.github.com>

* Add practical solutions for non-root users to use ql and task commands

Co-authored-by: whyour <22700758+whyour@users.noreply.github.com>

* Modify linkCommand to create symlinks in ~/bin for non-root users

Co-authored-by: whyour <22700758+whyour@users.noreply.github.com>

* 修复链接 task 命令

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: whyour <22700758+whyour@users.noreply.github.com>
Co-authored-by: whyour <imwhyour@gmail.com>
This commit is contained in:
Copilot 2025-11-15 01:46:24 +08:00 committed by GitHub
parent e41eed81ae
commit 8f23c61fee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 38 additions and 10 deletions

View File

@ -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

View File

@ -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

View File

@ -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);
}

View File

@ -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();

View File

@ -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

View File

@ -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)