折腾笔记[17]-使用rust创建linux系统服务

news/2025/4/19 3:34:06/文章来源:https://www.cnblogs.com/qsbye/p/18790509

摘要

使用rust内嵌配置文件, 创建新用户并创建linux的service服务;实现后台服务循环打印时间到/tmp/log_file_service.
Use Rust to embed configuration files, create new users, and create service services for Systemd; Implement a backend service loop to print time to /tmp/log_file_Service

关键词

rust;linux;systemctl;embed_file;zigbuild;

关键信息

  • ✨项目地址:[https://github.com/ByeIO/byeefree.esp32s3_p2p.embassy/tree/rust/rust_linux2linux_udp]
[package]
name = "rust_linux2linux_udp"
version = "0.1.0"
edition = "2021"
authors = ["qsbye"][dependencies]
# base64编码
base64 = "0.22.1"
# 命令行cli工具
clap = { version = "4.5.32", features = ["derive"] }
# 网络协议栈
edge-net = { version = "0.9.3", features = ["std"] }
# systemd操作接口
# libsystemd = { version = "0.7.0", path = "./static/libsystemd" }
systemctl = "0.4.0"
# 异步框架
tokio = "1.44.1"
# usb设备通信(纯rust)
nusb = { version = "0.2.0-beta", path = "./static/nusb" } 
# 虚拟网卡接口
tappers = "0.4.2"
# 内嵌文件
embed-file = "0.2.0"
# 日志记录
env_logger = "0.11.7"

原理简介

cargo-zigbuild简介

[https://github.com/rust-cross/cargo-zigbuild]
Compile Cargo project with zig as linker for easier cross compiling.

cargo install --locked cargo-zigbuild
pip install cargo-zigbuild

embed-file简介

[https://crates.io/crates/embed-file]
注意, 需要--release参数编译才会嵌入文件
A simplified version of rust-embed for single files instead of folders. Embeds files content into the binary in release mode, but loads it from the fs in debug.

The goal of is to avoid unnecessary recompilations when an included by e.g. [include_str] file change.

let my_string = embed_file::embed_string!("path/to/my-text.txt");
let my_image = embed_file::embed_bytes!("path/to/my-image.jpg");

linux的service/daemon简介

[https://blog.kelu.org/tech/2023/12/11/mac-systemd-like-launchd.html]
[https://docs.rs/systemctl/latest/systemctl/]
[https://www.reddit.com/r/linuxquestions/comments/z9mk8f/whats_the_difference_between_a_daemon_and_a/]
[https://www.cnblogs.com/kljh/p/16743836.html]
你提到的关于“Daemon”和“Service”的讨论确实很有意思。这两个术语确实在操作系统领域有着深厚的历史背景,尽管它们起源于不同的系统(Unix和Windows),但它们的核心概念是相似的:都是指在后台运行的程序,通常不与用户直接交互,且往往在更高的权限下运行。

关于它们是否“一直在做某事”或“等待做某事”的争论,确实有些过于简化了。实际上,无论是Daemon还是Service,它们的设计目的都是为了在后台持续运行,处理系统任务或响应特定事件。至于那些按计划或触发条件运行一次就退出的程序,确实更倾向于被称为“任务”或“作业”,而不是“服务”或“守护进程”。

在Windows中,Task Scheduler是一个强大的工具,用于安排和管理这些一次性任务,而Service Manager则更适合管理那些需要持续运行的服务。类似地,在Linux中,cron和systemd等工具也分别用于处理定时任务和守护进程。

总的来说,术语的使用确实存在一定的模糊性,但理解它们的核心功能和设计目的才是关键。希望这些信息能帮助你更好地理解这些概念!

They are the same thing - and disinformation and misunderstanding surrounds both
I see that some answers are getting fancy with explanations involving systemd.

Both words predate systemd by decades.

Or whether they are "always doing something" or "waiting to do something" - the dumbest thing I've heard so far today.

"Daemon" originated in the unix world, "Service" in Windows. Both refer to programs that run and stay running, usually in a way that the user doesn't interact with (eg in a different user context, often more privileged, eg root or SYSTEM).

If it's designed just run occasionally or on a schedule or trigger and then quit, then yes terminology gets fuzzy and gray. Someone said that's still a service/daemon. Maybe - but many IT pros, possibly most, would disagree - even though such programs may in fact be launched by Windows "Service Manager" or systemd. (The former is often grossly misused/abused. The latter has never been limited to dealing with just daemons.) But sure, there's room for debate, and agreeing on a hard distinction isn't going to make or break society.

There are countless ways to launch tasks at the system level that do something and then quit - eg Windows Task Scheduler, which is complex and heavily used. Countless things stuffed into Windows Service Manager should instead go in there (possibly because Task Scheduler is honestly complex, fiddly, and defined tasks are prone to breaking with system changes). Or with Linux, you have crontab, cron.daily (etc.), etc. Nobody - at least nobody that matters or that I'm aware of - considers those "services" or "daemons". More like "Tasks", "Jobs", "Cronjobs", etc.

In my view, a daemon is just a kind of process that forks and keeps running "in the background" (as a child process of the process which exec'd it) while the "foreground" process exits immediately allowing further execution of the parent program while the child keeps running "in the background".

A service encapsulates a process into a higher abstraction unit that has metadata, surrounding semantics (i.e. what should be done when it fails, what should be done before it starts and a whole lot more) and can be managed by a service manager like systemd.

A daemon can be part of a service and historically was but a service does not need to include a daemon nowadays.
A service's process(es) can keep running without technically forking into the background because systemd can deal with that. It's actually better to not daemonise because, if there are further processes spawned, it's clearer which one is the "main" process of a service. Dunno about other service managers.

macOS的服务配置文件plist简介

[https://blog.kelu.org/tech/2023/12/11/mac-systemd-like-launchd.html]
在 macOS 中,Launchd 是一个系统级别的进程管理工具,用于启动、停止和管理系统和用户级别的进程。Launchd 进程本身是由内核启动的,并负责启动其他进程,包括系统服务和用户级别的进程。macOS 中有两种类型的守护进程,一种是系统级别的(Daemons),一种是用户级别的(Agents),它们的配置文件放的位置是不一样的。

frpc.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict><key>KeepAlive</key><true/><key>Label</key><string>frpc</string><key>ProgramArguments</key><array><string>/Users/kelu/Workspace/bin/frp_0.52.3_darwin_arm64/frpc</string><string>-c</string><string>/Users/kelu/Workspace/bin/frp_0.52.3_darwin_arm64/tmp.toml</string></array><key>RunAtLoad</key><true/>
</dict>
</plist>

rust代码中执行系统命令

[https://doc.rust-lang.org/std/process/struct.Command.html]
A process builder, providing fine-grained control over how a new process should be spawned.

A default configuration can be generated using Command::new(program), where program gives a path to the program to be executed. Additional builder methods allow the configuration to be changed (for example, by adding arguments) prior to spawning:

use std::process::Command;let output = if cfg!(target_os = "windows") {Command::new("cmd").args(["/C", "echo hello"]).output().expect("failed to execute process")
} else {Command::new("sh").arg("-c").arg("echo hello").output().expect("failed to execute process")
};let hello = output.stdout;

实现

  1. 后台服务: log_time
  2. 服务安装器: service_installer
  • 总体流程: service_installer将内嵌的文件放置到对应位置, 然后配置systemctl启动服务并设置开机启动.

交叉编译(使用cargo-zigbuild替代musl解决libc问题):

cargo zigbuild --release --example log_file_service --target aarch64-unknown-linux-gnu
cargo zigbuild --release --example service_installer --target aarch64-unknown-linux-gnu

log_time.rs

// 导入需要的标准库模块
use std::{fs::OpenOptions,io::Write,thread,time::{SystemTime, UNIX_EPOCH}
};fn main() {// 循环写入时间到文件loop {// 获取当前UNIX时间戳(秒)let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).expect("获取时间失败").as_secs();// 以覆写模式打开文件(不存在则创建,存在则清空)let mut file = OpenOptions::new().create(true).write(true).truncate(true).open("/tmp/log_file_service").expect("无法打开文件");// 写入时间戳并换行writeln!(file, "{}", timestamp).expect("写入文件失败");// 每秒执行一次 thread::sleep(std::time::Duration::from_secs(1));}
}

service_installer.rs

#![allow(unused)]//! byeefree_log_time服务安装器// 标准库
use std::{fs, path::Path};
use std::error::Error;
use std::process::{Command, Stdio};
use std::io;
use std::os::unix::fs::PermissionsExt;
use std::env::consts::OS;// 嵌入文件
use embed_file::embed_bytes;fn main()-> Result<(), Box<dyn Error>>{// 新建用户组_ByeIO_和用户_Byeefree_let os_name = std::env::consts::OS;println!("当前操作系统为:{}", os_name);match os_name {"linux" => linux_create_user_group()?,"macos" => macos_create_user_group()?,_ => return Err(Box::new(io::Error::new(io::ErrorKind::Unsupported,format!("Unsupported OS: {}", os_name))))};// 复制可执行文件和.service配置文件到对应目录/// 嵌入二进制文件到编译产物中 let service_file = embed_bytes!("../assets/byeefree_log_time.service");let plist_file = embed_bytes!("../assets/byeefree_log_time.plist");let exec_file = embed_bytes!("../assets/byeefree_log_time_service.exec");let elf_file = embed_bytes!("../assets/byeefree_log_time_service.linux.aarch64");/// 复制文件println!("复制文件中...");// 创建目标目录(如果不存在)let output_dir_1 = Path::new("/tmp/_Byeefree_");let output_dir_2 = Path::new("/etc/systemd/system");let output_dir_3 = Path::new("/Library/LaunchDaemons");let output_dir_4 = Path::new("/usr/local/bin");// 递归创建目录fs::create_dir_all(output_dir_1)?;// 构建输出文件路径 let output_service_path = output_dir_2.join("byeefree_log_time.service");let output_plist_path = output_dir_3.join("byeefree_log_time.plist");let output_exec_path = match os_name{"linux" => { output_dir_4.join("byeefree_log_time_service.exec") },"macos" => { output_dir_1.join("byeefree_log_time_service.exec") },_ => { return Err("unknown OS".into()); }, };// 将嵌入的二进制数据写入文件 match os_name {"linux" => {// 递归创建目录fs::create_dir_all(output_dir_2)?;fs::write(&output_service_path, service_file)?;println!("文件已保存至:{}", output_service_path.display());fs::write(&output_exec_path, elf_file)?;println!("文件已保存至:{}", output_exec_path.display());},"macos" => {// 递归创建目录fs::create_dir_all(output_dir_3)?;fs::write(&output_plist_path, plist_file)?;println!("文件已保存至:{}", output_plist_path.display());fs::write(&output_exec_path, exec_file)?;println!("文件已保存至:{}", output_exec_path.display());},_ => {},}// 配置可执行文件权限及所有权fs::set_permissions(output_exec_path.clone(), fs::Permissions::from_mode(0o755))?;let chown_status = Command::new("chown").arg("_Byeefree_:_ByeIO_").arg(output_exec_path).status()?;if !chown_status.success() {return Err("Failed to set executable ownership".into());}else{println!("文件添加可执行权限成功.");}// 设置服务文件权限(仅限Linux)match os_name {"linux" => {fs::set_permissions(output_service_path.clone(), fs::Permissions::from_mode(0o644))?;},"macos" => {fs::set_permissions(output_plist_path.clone(), fs::Permissions::from_mode(0o644))?;},_ => {},}println!("服务配置文件添加权限成功.");// 启动服务并设置开机自启match os_name {"linux" => {Command::new("systemctl").args(&["daemon-reload"]).status()?;println!("linux服务配置重载成功.");Command::new("systemctl").args(&["start", "byeefree_log_time.service"]).status()?;println!("linux服务启动成功.");Command::new("systemctl").args(&["enable", "byeefree_log_time.service"]).status()?;println!("linux服务设置开机自启成功.");},"macos" => {Command::new("launchctl").args(&["load", "-w", output_plist_path.clone().to_str().unwrap()]).status()?;println!("macOS服务设置开机自启成功.");},_ => {}}// 全部任务完成println!("安装服务完成, 输入`cat /tmp/log_file_service`查看效果");Ok(())
}fn linux_create_user_group() -> io::Result<()> {use std::io::{self, ErrorKind, Error};// 检查用户组是否存在let group_exists = Command::new("getent").arg("group").arg("_ByeIO_").status()?.success();if !group_exists {// 创建用户组let group_output = Command::new("groupadd").arg("_ByeIO_").output()?;if !group_output.status.success() {return Err(Error::new(ErrorKind::Other,format!("用户组创建失败: {}", String::from_utf8_lossy(&group_output.stderr))));}}// 检查用户是否存在let user_exists = Command::new("id").arg("-u").arg("_Byeefree_").status()?.success();if !user_exists {// 创建用户let user_output = Command::new("useradd").args(&["-g", "_ByeIO_", "_Byeefree_"]).output()?;if !user_output.status.success() {return Err(Error::new(ErrorKind::Other,format!("用户创建失败: {}", String::from_utf8_lossy(&user_output.stderr))));}}Ok(())
}fn macos_create_user_group() -> io::Result<()> {use std::io::{self, ErrorKind, Error};// 检查用户组是否存在let group_exists = Command::new("sudo").args(&["dscl", ".", "-read", "/Groups/_ByeIO_"]).status().map(|s| s.success())?;if !group_exists {// 创建用户组let group_create = Command::new("sudo").args(&["dscl", ".", "-create", "/Groups/_ByeIO_"]).status()?;if !group_create.success() {return Err(Error::new(ErrorKind::Other, "macOS用户组创建失败"));}}// 检查用户是否存在let user_exists = Command::new("sudo").args(&["dscl", ".", "-read", "/Users/_Byeefree_"]).status().map(|s| s.success())?;if !user_exists {// 创建用户属性let user_commands = [("/Users/_Byeefree_", "UserShell", "/bin/bash"),("/Users/_Byeefree_", "RealName", "ByeIO Service Account"),("/Users/_Byeefree_", "PrimaryGroupID", "2000"),];for (path, key, value) in &user_commands {let status = Command::new("sudo").args(&["dscl", ".", "-create", path, key, value]).status()?;if !status.success() {return Err(Error::new(ErrorKind::Other,format!("用户属性创建失败: {}/{}/{}", path, key, value)));}}// 将用户加入组let append_status = Command::new("sudo").args(&["dscl", ".", "-append", "/Groups/_ByeIO_", "GroupMembership", "_Byeefree_"]).status()?;if !append_status.success() {return Err(Error::new(ErrorKind::Other, "无法将用户添加到组"));}}Ok(())
}

服务文件:
log_time.service

[Unit]
Description=ByeIO Byeefree Log Time Service
After=network.target[Service]
# 可指定运行用户(建议使用非root用户)
User=_Byeefree_
# 用户组
UserGroup=_ByeIO_
# 工作目录设为/tmp
WorkingDirectory=/tmp
# 可执行文件路径
ExecStart=/usr/local/bin/byeefree_log_time_service.exec
# 异常退出时自动重启
Restart=always
# 重启间隔
RestartSec=3[Install]
WantedBy=multi-user.target

效果

服务配置成功:

office:~:% sudo ./service_installer
_ByeIO_:x:1001:
1001
复制文件中...
文件已保存至:/etc/systemd/system/byeefree_log_time.service
文件已保存至:/tmp/_Byeefree_/byeefree_log_time_service.exec
文件添加可执行权限成功.
服务配置文件添加权限成功.
linux服务配置重载成功.
linux服务启动成功.
Created symlink /etc/systemd/system/multi-user.target.wants/byeefree_log_time.service → /etc/systemd/system/byeefree_log_time.service.
linux服务设置开机自启成功.
安装服务完成, 输入`cat /tmp/log_file_service`查看效果
服务启动成功

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/905104.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[PNPM] 其他包管理器

Yarn Yarn 这个包管理器是在 2016 的时候由 Facebook、Google、Exponent 以及 Tilde 团队共同开发推出的。当时 Yarn 的出现主要是为了解决 npm 在速度、安全性以及一致性方面的一些问题:安装速度确定性:项目A ---> 直接依赖: libraryX(1.0)-----> 间接依赖:librar…

3.24 位运算,哈希,前缀

题目链接 题目背景 我们需要解决的问题是:给定一个整数数组 nums 和两个整数 low 和 high,统计满足条件的“漂亮数对” (i, j) 的数量,其中: 0 <= i < j < nums.length low <= (nums[i] XOR nums[j]) <= high 简单来说,我们要找所有满足条件的数对 (i, j),…

Kioptrix Level_1

Kioptrix Level 1.1 靶场配置 导入靶场时先将vmx后缀文件中的带有ethernet0的配置行全部删除,再导入靶场,添加一个网络适配器即可 信息收集 查找目标主机ip ┌──(root㉿kali)-[~] └─# arp-scan -l Interface: eth0, type: EN10MB, MAC: 00:0c:29:84:b2:cc, IPv4: 192.168…

selenium之八大定位

八大定位 今天我们来学一学,selenium有名的八大定位方式;都有哪八个呢,下面我先列出来;之后再一个一个的实践id,通过id定位元素 name,通过name定位 class_name,通过class类名定位 tag_name,通过标签名称 link_text,通过链接文本 partial_link_text,通过部分链接文本 …

NAS配置iCloudpd

附上链接【教程】使用icloudpd自动同步iCloud照片 纠正两个错误: 1)sync-icloud.sh --Initialise 原文写的是sync-icloud.sh –Initialise是错误的,现在的版本应该如上 需要-- 不然你就会反复出现2分钟后重试的问题 2)文中的 *** 这步很重要!!!成功后,你会发现 config …

滴滴数据仓库工程师面试题

‌一、数据仓库基础与建模‌‌数仓分层设计‌请描述滴滴数仓分层架构及各层核心作用(如ODS、DWD、DWS、ADS)‌。 ‌1. ODS(Operational Data Store)层:原始数据层‌‌数据内容‌:直接从业务系统抽取的原始数据,包括订单流水、用户行为日志、司机接单记录、GPS轨迹等。‌…

20244209韩仕炜《Python程序设计》实验一报告

课程:《Python程序设计》 班级: 2442 姓名:韩仕炜 实验教师:王志强 学号:20244209 实验日期:2025年3月24日 必修/选修:专选课 1. 实验内容 1.熟悉Python开发环境; 2.练习Python运行、调试技能; 3.编写程序,练习变量和类型、字符串、对象、缩进和注释等; 4.编写一…

E1. Canteen (Easy Version)E2 Canteen (Hard Version) 对于旋转操作的深入理解

E1. Canteen (Easy Version) 题解:二分查找 + 模拟 本文大量学习了jiangly的代码对其进行详细的解析并作图对其进行解释 题目链接 深入解析:前缀和最小值旋转的直观意义一、前缀和曲线的数学本质 我们定义前缀和数组为: pre[i+1] = pre[i] + a[i] - b[i]这一公式的物理意义是…