导读
家里的机械硬盘更换了一轮,现已是多块企业级机械存储,主要用作存储重要数据,最是害怕突然断电损害磁盘,丢失数据。隔壁的隔壁经常会通过拉电闸来检查自家电表的运转状态,实在没辙,购置一款家用级 UPS ,山特 SANTAK TG-BOX 850 ,功率 510W。
一睹设备芳容
右下角像插座的那个便是本文主角,考虑到家里的生产力机器、酷播云 2 期、软路由、及斐讯 k3 合计的功率大约在 100 多瓦,该款的功率 510W 完全够用。
升级需求
购置的 UPS 山特 SANTAK TG-BOX 850 可通过 nut 进行驱动管理,可解决断电场景的重要系统关机问题,家里的系统均由普通 PC 进行承载,不适宜全天候开机,所以是定时通过网络唤醒,如果唤醒和断电独立,在断电触发关机后,有些机器可能关闭较快,而控制监控 ups 的这台机器还没关机,可能会唤醒已经关机的那些机器,从而导致断电触发关机失效,同样面临 UPS 电量骤减断电导致机器非正常关机,那么何不将定时唤醒及断电控制合并一起统一管理。
需求详细设计
设定一个电量百分比阀值 60% ,当电量达到阀值后仍有下滑趋势,执行关闭系统指令,优先关闭远程机器,这里通过调用自实现的远端API来关闭远程机器,最后再关闭自己;如果电量在阀值之上,属于供电安全期,执行唤醒任务,唤醒会设定固定的时间区间,不同机器可能需要唤醒的早晚程度不同,我的那台开机像炒豆子的,会在 8 时开始唤醒,安静那台会在 6 点就开始唤醒;还有种场景需要考虑,断电后来电,电量可能低于阀值,电量此时不会下滑,可判定供电处于安全期,UPS 蓄电池半天充不到阀值,迟迟无法触发唤醒系统,给于此种场景一个唤醒任务,确保其他机器在来电后无需人工干预即可进入工作状态;另外在断电触发关机前发送一封邮件告知管理员,也做留痕备案用。
环境准备
驱动 UPS ,安装 nut
apt update && apt install nut -y
在 /etc/nut/ups.conf
末尾追加如下内容
maxretry = 3
[tgbox850]
driver=usbhid-ups
port=auto
desc="SANTAK TGBOX-850 UPS"
修改 /etc/nut/nut.conf
的模式为 standalone
MODE=standalone
启动服务
systemctl start nut-driver nut-server
查看 ups 电量
/bin/upsc [email protected] battery.charge
看到如下返回及表示配置成功
[email protected]:~# /bin/upsc [email protected] battery.charge
Init SSL without certificate database
100
开机自启
systemctl enable nut-driver nut-server
安装网络唤醒工具 etherwake
apt update && apt install etherwake -y
源码分享
- 关机 API 实现,在远端待控制关机的服务器上部署运行
# -*- coding: utf-8 -*-
# @File : reboot.py
# @Author : 易雾君
# @Time : 2021/8/28 8:31 AM
# @Email : [email protected]
# @Project : tools
# @Site : https://evling.tech
# @公众号 : 易雾山庄
# @Describe : 家庭基建,生活乐享.
from flask import Flask, jsonify, request
import os
app = Flask(__name__)
@app.route('/reboot', methods=['GET'])
def reboot():
if request.method == 'GET':
result = {'error': 0, 'msg': 'ok'}
os.system('reboot')
return jsonify(result)
@app.route('/shutdown', methods=['GET'])
def shutdown():
if request.method == 'GET':
result = {'error': 0, 'msg': 'ok'}
os.system('shutdown -h now')
return jsonify(result)
if __name__ == '__main__':
app.run(debug=False, host='0.0.0.0', port=5000)
将如上代码保存为一个文件如:/data/tools/monitor/reboot.py
需要安装 pip 依赖包
apt update && apt install python3-pip
pip install flask
新增 systemd 守护服务文件,/etc/systemd/system/evling-reboot.service
内容如下
[Unit]
Description=Reboot
After=network.target
[Service]
ExecStart=/usr/bin/python3 /data/tools/monitor/reboot.py
# Restart every >2 seconds to avoid StartLimitInterval failure
RestartSec=5
Restart=always
[Install]
WantedBy=multi-user.target
开启开机自启服务
systemctl enable evling-reboot
- 电源管理脚本实现,在连接 UPS 那台机器上部署
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# @Project : tools
# @File : power_manager.py
# @Software: PyCharm
# @Author : 易雾君
# @Email : [email protected]
# @公众号 : 易雾山庄
# @Site : https://www.evling.tech
# @Describe : 家庭基建,生活乐享.
# @Time : 2022/11/12 20:02
import datetime
import subprocess
import time
import requests
import smtplib
from email.mime.text import MIMEText
class Mail:
def __init__(self, username, password, host, port=465):
self.mail_user = username
self.mail_pass = password
self.mail_host = host
self.mail_port = port
def send_email(self, to, subject, body, timeout=30):
message = MIMEText(body, 'plain', 'utf-8')
message['From'] = self.mail_user
message['To'] = to
message['Subject'] = subject
try:
with smtplib.SMTP_SSL(self.mail_host, self.mail_port, timeout=timeout) as server:
server.set_debuglevel(1)
server.login(self.mail_user, self.mail_pass)
server.sendmail(self.mail_user, to, message.as_string())
server.quit()
except:
pass
class PowerManager():
def __init__(self):
self.shutdown_charge = 60 # 供电安全阀值百分比
self.charge = 0
self.shutdown_urls = ['http://pve-prod.evling.tech/shutdown:5000', 'http://pve-nas.evling.tech/shutdown:5000'] # 远程关机 API 地址清单
self.str_charge_cmd = '/bin/upsc [email protected] battery.charge' # 查询 UPS 电量命令
self.str_shutdown_cmd = '/sbin/shutdown -h now'
self.etherwake_tpl = '/usr/sbin/etherwake -i {} {}'
self.interfaces = ['vmbr0', 'vmbr10', 'vmbr11', 'vmbr12', 'vmbr8', 'vmbr9'] # 用于唤醒的网络接口,这里用的所有,避免变换网口后无法唤醒
self.wake_list = [
{'name': 'pve-prod', 'str_datetime_begin': '06:00', 'str_datetime_end': '23:00','mac': '70:xx:xx:xx:xx:e5'},
{'name': 'pve-nas', 'str_datetime_begin': '08:00', 'str_datetime_end': '23:00', 'mac': '70:xx:xx:xx:xx:39'}
] # 机器唤醒的时间区间,及待唤醒的 mac 地址
def set_charge(self):
output = subprocess.getoutput(self.str_charge_cmd)
for line in output.split('\n'):
line = line.strip()
try:
self.charge = int(line)
except:
pass
def shutdown(self, retry = 3, timeout=5):
for url in self.shutdown_urls:
for _ in range(retry):
try:
requests.get(url, timeout=timeout)
except:
pass
subprocess.getoutput(self.str_shutdown_cmd)
def etherwake(self, mac):
for interface in self.interfaces:
try:
subprocess.getoutput(self.etherwake_tpl.format(interface, mac))
except:
pass
if __name__ == '__main__':
# 关机区间,仍有下行趋势,则关闭系统,否则唤醒系统
# 非关机区间,唤醒系统
power_manager = PowerManager()
mail = Mail('[email protected]', 'your_password', 'smtp.exmail.qq.com')
downing_list = []
shutdown_flag = 0
num = 0
while True:
power_manager.set_charge()
if ( power_manager.charge !=0 and power_manager.charge > power_manager.shutdown_charge ) or shutdown_flag == -1:
downing_list = []
num = 0
for wake_item in power_manager.wake_list:
datetime_now = datetime.datetime.now()
datetime_begin = datetime.datetime.strptime('{} {}'.format(datetime_now.strftime('%Y-%m-%d'), wake_item.get('str_datetime_begin')), '%Y-%m-%d %H:%M')
datetime_end = datetime.datetime.strptime('{} {}'.format(datetime_now.strftime('%Y-%m-%d'), wake_item.get('str_datetime_end')), '%Y-%m-%d %H:%M')
if datetime_now >= datetime_begin and datetime_now <= datetime_end:
power_manager.etherwake(wake_item.get('mac'))
if power_manager.charge !=0 and power_manager.charge <= power_manager.shutdown_charge:
for downing_item in downing_list:
if downing_item > power_manager.charge:
shutdown_flag = 1
break
if shutdown_flag == 1:
mail.send_email('[email protected]', '供电异常警告',
'UPS 剩余电量 {} %,正在关闭重要系统!\n\n—\nSANTAK TG-BOX 850'.format(power_manager.charge))
power_manager.shutdown()
if power_manager.charge not in downing_list:
downing_list.append(power_manager.charge)
if num > 100: # 约等待300s没有电量下滑,则标识可以唤醒
shutdown_flag = -1
num += 1
time.sleep(3)
将如上代码保存为一个文件如:/data/tools/monitor/power_manager.py
需要安装 pip 依赖包
apt update && apt install python3-pip
pip install requests
新增 systemd 守护服务文件,/etc/systemd/system/evling-powermanagement.service
内容如下
[Unit]
Description=Power Management
After=network.target
[Service]
ExecStart=/usr/bin/python3 /data/tools/monitor/power_manager.py
# Restart every >2 seconds to avoid StartLimitInterval failure
RestartSec=5
Restart=always
[Install]
WantedBy=multi-user.target
开启开机自启服务
systemctl enable evling-powermanagement
评论区