<think>在 Linux 下,通过 PyInstaller 打包的 Python 程序在启动时自动授权串口和网口属于**特权操作**,需要系统管理员权限。以下是两种常用方案:
---
### 方法 1:通过启动脚本包装(推荐)
**原理**:创建独立的启动脚本(如 `start.sh`),在其中添加授权命令后运行程序,原理流程如下:
1. 创建包含授权命令 + 运行程序的 Bash 脚本
2. 用户通过该脚本启动应用
流程图解:
```
用户运行 start.sh → 授权设备权限 → 启动PyInstaller程序
```
**步骤**:
1. 创建启动脚本 `start.sh`:
```bash
#!/bin/bash
# 授权串口设备(示例:/dev/ttyUSB0)
sudo chmod 666 /dev/ttyUSB0
# 授权网口(需根据实际需求配置)
# sudo ip link set eth0 promisc on
# 运行打包后的程序
./dist/your_app_name
# 可选:程序结束后恢复权限
# sudo chmod 660 /dev/ttyUSB0
```
2. 添加执行权限:
```bash
chmod +x start.sh
```
3. 用户运行:
```bash
./start.sh # 需输入sudo密码
```
> **注意**:
> - 每次启动都需要输入 sudo 密码会影响用户体验。
> - 若需避免密码输入(安全问题),参考下面的 sudoers 配置部分。
---
### 方法 2:嵌入程序内部授权(需初始 sudo 权限运行)
**原理**:让程序启动时调用系统命令修改权限。但需注意:**程序本身必须带 sudo 运行才能授权**
实现流程:
1. 在主程序中添加授权代码
2. 打包程序
3. 用户通过 `sudo` 运行程序
**代码示例**(在 Python 入口文件中添加):
```python
import os
import sys
def grant_device_permissions():
# 授权串口
os.system('sudo chmod 666 /dev/ttyUSB0')
# 授权网口(示例:启用混杂模式)
# os.system('sudo ip link set eth0 promisc on')
if __name__ == '__main__':
if os.geteuid() != 0:
print("请使用sudo重新运行此程序!")
sys.exit(1)
grant_device_permissions()
# 正常启动你的应用
main() # 替换为你的主函数
```
**运行方式**:
```bash
sudo ./dist/your_app_name # 以root权限运行
```
> **缺点**:整个程序都以 root 权限运行(安全风险较大)。
---
### ⚠ 安全优化:免密码 sudo 授权(针对方法1)
若希望 `start.sh` 中的 `sudo` 无需输入密码:
1. 编辑 sudoers 文件:
```bash
sudo visudo
```
2. 添加规则(示例允许当前用户免密执行 chmod 和 ip 命令):
```bash
# 将your_username替换为实际用户名
your_username ALL=(ALL) NOPASSWD: /bin/chmod, /sbin/ip
```
3. 保存后,`start.sh` 运行时无需密码。
---
### 最终方案建议
| 方法 | 适用场景 | 安全等级 |
|----------------------|-----------------------------------|----------|
| 启动脚本 + sudoers免密 | 常规桌面应用 | ★★★★ |
| 程序内授权 + sudo运行 | 需全程特权操作 | ★★ |
> 推荐采用 **启动脚本 + sudoers免密配置** 方案
> 1. 用户启动脚本申请权限,主程序以普通权限运行
> 2. 权限申请范围通过 sudoers 精确控制
§§ 相关问题 §
1. 如何避免每次运行PyInstaller程序都输入sudo密码?
2. 如何限制PyInstaller程序只在特定设备访问串口?
3. Linux下PyInstaller打包后程序如何访问受限硬件?
参考:
[^1]:Linux系统内Python打包程序pyinstaller
[^2]:Linux打包python(pyinstaller)及问题解决
</think>
## Linux下PyInstaller打包程序启动时自动授权串口/网口的方法
要让PyInstaller打包的程序启动时自动授权串设备,需要使用**系统级权限管理方案**。以下是两种安全可靠的实现方法:
### 方法一:通过setuid位直接授权(推荐)
1. **给可执行文件设置setuid位**:
```bash
chmod +s dist/your_program # 设置setuid位
sudo chown root dist/your_program # 设置拥有者为root
```
2. **在Python程序中添加设备操作代码**:
```python
import os
import serial
import fcntl
# 串口授权命令
def grant_serial_permission(device="/dev/ttyUSB0"):
DEVICE = device.encode()
TIOCEXCL = 0x540C # Linux ioctl命令编号
with open(DEVICE, 'wb') as port:
fcntl.ioctl(port, TIOCEXCL)
# 网口授权
def grant_network_permission():
os.system("sudo setcap 'cap_net_admin,cap_net_raw+eip' " + sys.argv[0])
if __name__ == "__main__":
if os.geteuid() == 0: # root权限检查
grant_serial_permission()
grant_network_permission()
os.seteuid(1000) # 降权到普通用户
# 主程序逻辑
main()
```
> 此方法需确保安装setcap工具:
> ```bash
> sudo apt-get install libcap2-bin
> ```
### 方法二:通过系统服务授权
1. **创建systemd服务文件** (`/etc/systemd/system/myapp.service`):
```ini
[Service]
ExecStartPre=/bin/sh -c "chmod 666 /dev/ttyUSB0; setcap 'cap_net_admin,cap_net_raw+eip' /path/to/your_app"
ExecStart=/path/to/your_app
User=appuser
[Install]
WantedBy=multi-user.target
```
2. **部署流程**:
```bash
sudo systemctl daemon-reload
sudo systemctl enable myapp
sudo systemctl start myapp
```
### 关键安全实践:
1. **权限降级原则**:
```python
import os, pwd
def drop_privileges():
if os.getuid() != 0:
return
# 获取目标用户ID
user_name = "appuser"
user_info = pwd.getpwnam(user_name)
os.setgroups([])
os.setgid(user_info.pw_gid)
os.setuid(user_info.pw_uid)
os.environ['HOME'] = user_info.pw_dir
```
2. **MAC地址过滤**:
```python
import netifaces
# 只允许特定MAC的设备
ALLOWED_MAC = "00:11:22:33:44:55"
if netifaces.ifaddresses('eth0')[netifaces.AF_LINK][0]['addr'] != ALLOWED_MAC:
exit("Unauthorized hardware interface!")
```
### 调试步骤:
1. 检查权限:
```bash
getcap /path/to/your_app
ls -l /dev/ttyUSB0
```
2. 检查服务日志:
```bash
journalctl -u myapp -f
```
> **注意**:直接使用`setuid`可能被SELinux限制,需配置安全策略:
> ```bash
> sudo semodule -i myapp.pp
> ```