在Linux上限制儿童使用电脑
目录
作为家长,你可能希望控制孩子使用电脑的时间——比如只能在晚上 6 点到 8 点登录,每天最多用 2 小时,超时后自动踢出。Windows 有专门的家长控制面板,而 Linux 虽然没有这样的集成工具,但它提供的原生机制完全可以实现同样的效果,而且更加灵活。
本文将介绍如何用纯 Linux 原生方案(PAM、cron、iptables 等)实现以下功能:
- 限制允许登录的时间段
- 限制每日累计使用时长
- 超时警告并自动踢出
- 限制可访问的网站
- 限制可运行的程序
本文假设系统中已创建一个名为 geo 的受限用户。所有配置需要 root 权限。
限制允许登录的时间段
Linux 中控制用户登录时间的机制是 PAM(Pluggable Authentication Modules,可插拔认证模块)中的 pam_time 模块。
快速配置
首先,在 PAM 配置文件中启用 pam_time.so 。需要修改的文件取决于你使用的登录方式:
- 控制台登录:
/etc/pam.d/login - 图形界面登录:
/etc/pam.d/lightdm或/etc/pam.d/gdm - SSH 登录:
/etc/pam.d/sshd
在上述文件的 account 部分添加一行:
account required pam_time.so
然后在 /etc/security/time.conf 中添加规则。该文件的格式为:
services;ttys;users;times
各字段含义:
services- 服务名,
*表示所有服务 ttys- 终端类型,
*表示所有终端 users- 用户名
times- 时间规则
时间规则的语法比较特别:
Wk表示工作日(周一到周五),Wd表示周末(周六、周日),Al表示每天- 时间格式为
HHMM-HHMM - 前面加
!表示取反("除此时段外禁止")
例如,限制 geo 用户只能在每天 18:00 到 20:00 登录:
*;*;geo;Al1800-2000
这行规则的意思是:对所有服务、所有终端、geo 用户,允许每天 18:00-20:00 登录( Al 表示每天都适用)。注意, pam_time 的逻辑是:匹配到的允许登录,未匹配到则拒绝。
验证配置:在非允许时段尝试用 geo 账户登录,应该会看到类似 "Permission denied" 的提示。
原理说明
PAM 是 Linux 的可插拔认证框架,它将认证逻辑从应用程序中解耦出来。PAM 定义了四种模块类型:
- auth
- 验证用户身份(如检查密码)
- account
- 检查账户是否允许访问(如检查账户是否过期、是否在允许的时间段内)
- password
- 管理密码更新
- session
- 管理会话的创建和销毁
pam_time 属于 account 类型。它的工作时机是在用户通过了 auth 阶段的密码验证之后、系统创建会话之前。此时 pam_time 会检查当前时间是否匹配 time.conf 中的规则,如果不匹配则拒绝登录。
限制每日累计使用时长
pam_time 只能控制"什么时候能登录",但无法控制"登录后能用多久"。要实现每日使用时长的累计限制,需要自己动手:记录每次登录和登出的时间,然后定期检查是否超限。
快速配置
整个方案由两部分组成:一个记录登录/登出时间的脚本,和一个定期检查累计时长的脚本。
第一步:记录登录/登出时间
创建 /usr/local/bin/session-logger.sh :
#!/bin/bash # /usr/local/bin/session-logger.sh # 由 pam_exec 调用,记录用户登录/登出时间 LOGFILE="/var/log/user-sessions.log" TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') USERNAME="$PAM_USER" TYPE="$PAM_TYPE" if [ -z "$USERNAME" ]; then exit 0 fi case "$TYPE" in open_session) echo "LOGIN $TIMESTAMP $USERNAME" >> "$LOGFILE" ;; close_session) echo "LOGOUT $TIMESTAMP $USERNAME" >> "$LOGFILE" ;; esac exit 0
然后赋予执行权限:
sudo chmod +x /usr/local/bin/session-logger.sh sudo touch /var/log/user-sessions.log sudo chmod 600 /var/log/user-sessions.log
在 PAM 配置文件(如 /etc/pam.d/login 、 /etc/pam.d/lightdm )的 session 部分添加:
session optional pam_exec.so /usr/local/bin/session-logger.sh
第二步:检查累计时长
创建 /usr/local/bin/check-usage.sh :
#!/bin/bash # /usr/local/bin/check-usage.sh # 检查用户当日累计使用时长 LOGFILE="/var/log/user-sessions.log" TODAY=$(date '+%Y-%m-%d') MAX_MINUTES=120 # 每天最多使用 120 分钟(2 小时) TARGET_USER="geo" LOCKFILE="/var/run/check-usage.lock" # 使用锁文件防止 cron 重叠执行 exec 9>"$LOCKFILE" flock -n 9 || exit 0 # 计算今日累计在线分钟数 # 注意:此算法假设 LOGIN/LOGOUT 记录严格按序配对。如果用户同时有多个会话 #(如 SSH + 图形界面),日志可能出现交错序列,导致错误配对。 # 家庭环境通常单会话,可以安全使用。 calculate_usage() { local user=$1 local total_seconds=0 local login_time="" while IFS= read -r line; do local action=$(echo "$line" | awk '{print $1}') local timestamp=$(echo "$line" | awk '{print $2, $3}') local username=$(echo "$line" | awk '{print $4}') [ "$username" != "$user" ] && continue case "$action" in LOGIN) login_time="$timestamp" ;; LOGOUT) if [ -n "$login_time" ]; then local login_ts=$(date -d "$login_time" '+%s') local logout_ts=$(date -d "$timestamp" '+%s') total_seconds=$((total_seconds + logout_ts - login_ts)) login_time="" fi ;; esac done < <(grep "$TODAY" "$LOGFILE" | grep -E "^(LOGIN|LOGOUT)") # 如果用户当前在线(有未配对的 LOGIN),累加到现在 if [ -n "$login_time" ]; then local login_ts=$(date -d "$login_time" '+%s') local now_ts=$(date '+%s') total_seconds=$((total_seconds + now_ts - login_ts)) fi echo $((total_seconds / 60)) } usage=$(calculate_usage "$TARGET_USER") remaining=$((MAX_MINUTES - usage)) if [ "$remaining" -le 0 ]; then echo "使用时间已到,系统将在 2 分钟后将 $TARGET_USER 踢出!" | wall 2>/dev/null sleep 120 pkill -u "$TARGET_USER" sleep 10 pkill -9 -u "$TARGET_USER" elif [ "$remaining" -le 5 ]; then echo "$TARGET_USER 还有 $remaining 分钟的使用时间" | wall 2>/dev/null fi
赋予执行权限,并创建 systemd timer 每分钟运行一次:
sudo chmod +x /usr/local/bin/check-usage.sh
创建 systemd service 文件:
[Unit] Description=Check user daily usage time [Service] Type=oneshot ExecStart=/usr/local/bin/check-usage.sh
创建对应的 timer 文件:
[Unit] Description=Run check-usage every minute [Timer] OnCalendar=*-*-* *:*:00 Persistent=true [Install] WantedBy=timers.target
启用 timer:
sudo systemctl daemon-reload sudo systemctl enable --now check-usage.timer
原理说明
这里用到了两个机制:
- PAM session 管理
- 当用户登录成功后,PAM 会依次调用 session 类型的模块的
open钩子;当用户登出时,调用close钩子。pam_exec.so会将钩子类型通过环境变量PAM_TYPE(值为open或close)传递给外部脚本,同时通过PAM_USER传递用户名。 - systemd timer 定时任务
OnCalendar=*-*-* *:*:00表示每分钟整点执行一次。systemd timer 是 cron 的现代替代方案,几乎所有使用 systemd 的 Linux 发行版都内置支持。它的优势包括:自动记录日志、支持依赖关系、失败后可自动重试。这个频率足够及时地检测到超时,同时不会给系统带来明显负担。
超时警告并踢出
上一节的脚本已经包含了基本的警告和踢出逻辑。这里再详细说明一下各个踢出手段的区别。
多级警告策略
实际使用中,建议采用多级警告,给孩子一个心理准备的过程:
以下是一个示例,展示如何替换脚本中的警告逻辑:
# 在 check-usage.sh 中替换警告逻辑 usage=$(calculate_usage "$TARGET_USER") remaining=$((MAX_MINUTES - usage)) if [ "$remaining" -le 0 ]; then # 已超时,通知并踢出 echo "$TARGET_USER,今日使用时间已到!" | wall 2>/dev/null sleep 120 pkill -u "$TARGET_USER" elif [ "$remaining" -le 2 ]; then # 最后 2 分钟警告 echo "$TARGET_USER,还有 $remaining 分钟,请保存工作!" | wall 2>/dev/null elif [ "$remaining" -le 5 ]; then # 5 分钟提醒 echo "$TARGET_USER,还有 $remaining 分钟的使用时间" | wall 2>/dev/null fi
踢出用户的不同方式
pkill -u geo- 向 geo 用户的所有进程发送 SIGTERM 信号。这是最简单的方式,但如果有进程忽略了 SIGTERM,可能无法完全清理。可以追加
pkill -9 -u geo来强制结束。 loginctl terminate-session SESSION_ID- 通过 systemd-logind 来终止指定的登录会话。这比 pkill 更精确,因为它能正确地清理会话资源。查看当前会话列表可以用
loginctl list-sessions。 shutdown -h +2- 2 分钟后关闭整个系统。这是最彻底的方式,但会影响所有用户。如果电脑是孩子独占使用的,这也是个不错的选择。
原理说明
wall 命令的工作原理是向系统中所有终端设备( /dev/pts/* 和 /dev/tty* )写入消息。只有拥有终端的登录用户才能看到消息。
pkill 通过匹配进程的 UID 来选择目标进程,先发送 SIGTERM(信号 15),允许进程优雅退出。如果进程不响应,可以发送 SIGKILL(信号 9)强制终止。
loginctl 是 systemd-logind 的命令行接口。logind 作为 systemd 的用户登录管理器,跟踪系统中所有的登录会话(包括图形和终端会话)。 terminate-session 会向会话中的所有进程发送终止信号,并清理会话资源。
限制可访问网站
在 HTTPS 普及的今天,我们无法在不安装额外证书的情况下检查 HTTP 请求的内容。因此 Linux 原生方案只能在 IP 和域名级别进行过滤。下面介绍两种方法。
方法一:DNS 级过滤
通过配置本地 DNS 服务器 dnsmasq ,将不希望访问的域名解析到无效地址。
首先安装 dnsmasq :
sudo pacman -S dnsmasq # Arch Linux
创建黑名单文件 /etc/blocked-hosts :
# /etc/blocked-hosts # 每行一个要屏蔽的域名(标准 hosts 格式) 127.0.0.1 example-bad-site.com 127.0.0.1 another-bad-site.com
在 /etc/dnsmasq.conf 中添加配置:
# 将黑名单中的域名解析到 127.0.0.1 addn-hosts=/etc/blocked-hosts
然后启动 dnsmasq 并将系统 DNS 指向本地:
sudo systemctl enable --now dnsmasq # 修改 /etc/resolv.conf echo "nameserver 127.0.0.1" | sudo tee /etc/resolv.conf
这种方法的缺点是:只要孩子知道如何修改 DNS 设置,就能轻松绕过。
方法二:iptables 按用户限制
iptables 的 owner 模块可以根据进程的 UID 来匹配网络流量, time 模块可以按时间段匹配。两者结合,可以限制特定用户在特定时间段内的网络访问。
# 允许 geo 用户在 18:00-20:00 访问网络 sudo iptables -A OUTPUT -m owner --uid-owner geo -m time --timestart 18:00 --timestop 20:00 -j ACCEPT # 禁止 geo 用户在其他时间访问网络 sudo iptables -A OUTPUT -m owner --uid-owner geo -j DROP
注意: --timestop 20:00 实际上不包含 20:00 整点,规则有效时间为 18:00:00 到 19:59:59。如果需要包含 20:00 整点,可以设置为 =--timestop 20:01=。
要持久化这些规则,可以将它们写入一个规则文件,然后在启动时自动恢复:
# /etc/iptables/parental-control.rules *filter :OUTPUT ACCEPT [0:0] -A OUTPUT -m owner --uid-owner geo -m time --timestart 18:00 --timestop 20:00 -j ACCEPT -A OUTPUT -m owner --uid-owner geo -j DROP COMMIT
恢复规则:
sudo iptables-restore < /etc/iptables/parental-control.rules
原理说明
Linux 的网络过滤由 Netfilter 框架实现。Netfilter 在网络协议栈中设置了五个钩子点:
- PREROUTING
- 数据包进入路由之前
- INPUT
- 数据包发往本机进程之前
- FORWARD
- 数据包转发到其他接口之前
- OUTPUT
- 本机进程发出的数据包
- POSTROUTING
- 数据包发出之前
我们使用的是 OUTPUT 链,它处理本机进程发出的所有数据包。 owner 模块通过检查数据包对应的 socket 的 UID 来匹配, time 模块通过系统时间来匹配。规则从上到下依次匹配,匹配到后就执行指定的动作( ACCEPT 或 DROP )。
限制可运行程序
不想让孩子玩某些游戏或使用某些软件?最直接的方法是通过 Linux 的权限系统来限制。
方法一:文件权限控制
Linux 的每个文件都有三组权限:所有者(owner)、所属组(group)、其他人(others)。我们可以利用这个机制来限制特定用户执行特定程序。
假设要禁止 geo 用户运行 steam :
# 创建受限组 sudo groupadd restricted # 将 steam 的组改为 restricted,权限设为 750 sudo chgrp restricted /usr/bin/steam sudo chmod 750 /usr/bin/steam # 确保 geo 不在 restricted 组中 sudo gpasswd -d geo restricted 2>/dev/null || true
这样,只有 root 用户和 restricted 组的成员可以运行 steam ,而 geo 用户会得到 "Permission denied" 的错误。
对于需要限制的每个程序,重复上述步骤即可。也可以写一个脚本来批量处理:
#!/bin/bash # /usr/local/bin/restrict-programs.sh # 限制指定程序只能由 restricted 组执行 PROGRAMS="/usr/bin/steam /usr/bin/wine /usr/bin/discord" for prog in $PROGRAMS; do if [ -f "$prog" ]; then sudo chgrp restricted "$prog" sudo chmod 750 "$prog" echo "Restricted: $prog" fi done
需要注意:当软件包更新时,权限可能会被重置。可以将这个脚本放在 systemd 的 timer 或 pacman 的 hook 中定期执行。
方法二:AppArmor
AppArmor 提供了更强大的强制访问控制(MAC)。与文件权限不同,AppArmor 的策略无法被用户(包括文件所有者)修改。
在 Arch Linux 上安装并启用 AppArmor:
sudo pacman -S apparmor sudo systemctl enable --now apparmor
AppArmor 通过 profile 来定义程序的访问权限。需要注意的是,AppArmor 的 profile 是绑定到程序的,不能直接按用户限制。要实现按用户限制,需要配合 pam_apparmor 模块,在用户登录时为不同用户加载不同的 profile。
首先,创建一个限制性的 profile:
# /etc/apparmor.d/restrict-geo-steam
abi <abi/4.0>,
include <tunables/global>
/usr/bin/steam {
include <abstractions/base>
# 只允许特定用户组执行
# 注意:AppArmor 本身不直接支持按用户过滤,
# owner 关键字匹配进程所有者(即运行 steam 的用户)
owner /usr/bin/steam rx,
}
如果需要更精确的按用户控制,建议结合文件权限方法使用——将程序的组设为受限组,然后在 AppArmor profile 中限制该组的访问。或者使用 pam_apparmor 在用户登录时动态加载不同的 profile。
加载 profile:
sudo apparmor_parser -r /etc/apparmor.d/restrict-geo-steam
原理说明
- DAC(自主访问控制)
- Linux 默认的权限模型。每个文件有三组权限位(读 r、写 w、执行 x),分别对应所有者、所属组、其他人。文件的所有者可以自主修改文件的权限。"自主"的含义就在于此——权限由所有者决定。
- MAC(强制访问控制)
- AppArmor 实现的权限模型。策略由系统管理员定义,普通用户无法绕过或修改。AppArmor 使用基于路径的匹配(而非 SELinux 的基于标签的匹配),配置相对简单。
总结
本文介绍了五种用纯 Linux 原生机制限制儿童使用电脑的方法:
- 限制登录时段
- 使用 PAM 的
pam_time模块,通过/etc/security/time.conf配置 - 限制使用时长
- 使用
pam_exec记录登录/登出时间,配合 cron 定时检查 - 超时踢出
- 使用
wall发送警告,pkill或loginctl踢出用户 - 限制网站访问
- 使用
dnsmasq做 DNS 过滤,或iptables按用户和时间限制网络访问 - 限制可运行程序
- 使用文件权限或 AppArmor 控制程序执行
这些方案组合使用,可以构建一个相当完整的家长控制系统。但需要注意以下几点:
- 这些方案需要 root 权限 来配置。如果你的孩子拥有 sudo 权限,ta 理论上可以绕过所有限制。
pam_time只能控制新登录,无法踢出已经在线的用户。需要配合时长检查脚本来处理。- iptables 的
owner模块只能匹配本机进程发出的流量,对于通过代理或 VPN 的流量可能无效。
最后,技术手段只是辅助。与孩子建立良好的沟通和信任,比任何软件限制都更有效。