Showing Posts From

systemd

systemd Service 设置工作目录:WorkingDirectory 与 ExecStart 相对路径

systemd service 文件通过 WorkingDirectory 指令设置进程启动后的工作目录,等同于 cd 后再执行命令。 基本配置 [Unit] Description=My App Service[Service] Type=simple User=www-data WorkingDirectory=/opt/myapp ExecStart=/usr/bin/python3 main.py Restart=always RestartSec=5[Install] WantedBy=multi-user.target设置 WorkingDirectory=/opt/myapp 后:ExecStart 中的相对路径基于 /opt/myapp 程序内 os.getcwd()(Python)或 process.cwd()(Node.js)返回 /opt/myapp 读写相对路径的文件(如 config.json、logs/)都指向该目录修改配置后重新加载 sudo systemctl daemon-reload sudo systemctl restart myapp.service# 查看服务状态 systemctl status myapp.service每次修改 .service 文件都需要 daemon-reload,否则 systemd 不会读取新配置。 常见问题 User 与目录权限 如果指定了 User=www-data,该用户必须对 WorkingDirectory 有读取权限(通常还需要写权限): sudo chown www-data:www-data /opt/myapp sudo chmod 750 /opt/myapp带有空格的路径 WorkingDirectory=/opt/my app/ # 错误,空格无需引号但会导致解析问题 WorkingDirectory="/opt/my app" # 正确,用引号包裹ExecStart 中用绝对路径 ExecStart 建议始终用绝对路径,避免 $PATH 不同导致找不到命令: ExecStart=/usr/bin/node /opt/myapp/server.js # 不要写:ExecStart=node server.js查看服务文件位置 # 列出所有用户服务文件 ls /etc/systemd/system/# 查看某个服务的完整配置(包括覆盖) systemctl cat myapp.service服务文件通常放在 /etc/systemd/system/ 下,命名为 <name>.service。

systemd 服务里设置工作目录:WorkingDirectory

写 Python / Node 服务的 systemd 单元时,程序里各种相对路径都得基于"工作目录"来解读。systemd 提供了 WorkingDirectory 字段,比在 ExecStart 里 cd 一下再启动规范得多。 最简单的例子 [Unit] Description=My App After=network-online.target[Service] Type=simple User=appuser WorkingDirectory=/opt/myapp ExecStart=/usr/bin/python3 main.py Restart=always RestartSec=3[Install] WantedBy=multi-user.targetWorkingDirectory=/opt/myapp 的效果:进程启动时 cwd 就是这个目录 ExecStart 里的相对路径按它算(比如 python3 main.py 就是 /opt/myapp/main.py) Python 里 os.getcwd() 返回这个目录 写日志到 ./logs/app.log 就是 /opt/myapp/logs/app.log为什么不用 cd 见过这种写法: ExecStart=/bin/bash -c 'cd /opt/myapp && python3 main.py'能跑,但坏处一堆:多一层 shell,进程树多了个 bash 语义没那么直观 systemd 的 Restart / 信号处理会作用在 bash 上,不是真进程 环境变量、User、日志跟你想的不一样能用字段就用字段,cd 是最后的兜底。 常配套的字段 User / Group 以指定用户启动,不用 root 跑业务: User=appuser Group=appuser注意 WorkingDirectory 里的目录必须让这个用户能读、能进: sudo chown -R appuser:appuser /opt/myappEnvironmentFile 把环境变量放外面,方便修改: EnvironmentFile=/etc/myapp/envenv 文件内容: DATABASE_URL=postgres://user:pass@localhost/mydb APP_PORT=8000代码里 os.getenv("DATABASE_URL") 就能拿到。 Restart 崩溃后的行为: Restart=always # 无条件重启 RestartSec=3 # 3 秒后重启 StartLimitInterval=60 # 60 秒内 StartLimitBurst=5 # 最多重启 5 次,再多就放弃生产上都需要——不然崩了以后就得手动来。 日志 默认 stdout / stderr 会走 journald: journalctl -u myapp -f journalctl -u myapp --since "10 minutes ago"想直接写文件: StandardOutput=append:/var/log/myapp/out.log StandardError=append:/var/log/myapp/err.logappend: 需要 systemd 240+,老版本用 file:。 一个"生产可用"的模板 [Unit] Description=My FastAPI App After=network-online.target Wants=network-online.target[Service] Type=simple User=appuser Group=appuser WorkingDirectory=/opt/myapp EnvironmentFile=/etc/myapp/env ExecStart=/opt/myapp/.venv/bin/uvicorn main:app --host 0.0.0.0 --port 8000 Restart=always RestartSec=3 StartLimitBurst=5 StandardOutput=journal StandardError=journal[Install] WantedBy=multi-user.target启用: sudo systemctl daemon-reload sudo systemctl enable --now myapp sudo systemctl status myapp一句话总结 WorkingDirectory=/path 就是 cwd,不要在 ExecStart 里套 bash -c 'cd ... && ...'。搭配 User、EnvironmentFile、Restart 一份 systemd 单元就是完整的服务定义。