power/power.py

208 lines
6.3 KiB
Python

import time
import config
import logging
import smtplib
import asyncio
import discord
import paramiko
from ping3 import ping
from nut2 import PyNUTClient
from proxmoxer import ProxmoxAPI
from email.mime.text import MIMEText
from wakeonlan import send_magic_packet
from logging.handlers import TimedRotatingFileHandler
# Config
ups_id = config.UPS_ID
gmail_user = config.GMAIL_USER
gmail_password = config.GMAIL_PASS
recipient_email = config.GMAIL_ADDR
bot_token = config.BOT_TOKEN
user_id = config.USER_ID
pve_host = config.PVE_HOST
pve_user = config.PVE_USER
pve_pass = config.PVE_PASS
servers = ['pve', 'c530']
nas_ip = config.HOST1_IP
wol_targets = {
'host1': {'mac': config.HOST1_MAC, 'ip': config.HOST1_IP},
'host2': {'mac': config.HOST2_MAC, 'ip': config.HOST2_IP},
'host3': {'mac': config.HOST3_MAC, 'ip': config.HOST3_IP}
}
# Log Setup
def setup_logging():
logger = logging.getLogger('powerlog')
logger.setLevel(logging.INFO)
handler = TimedRotatingFileHandler(
'power.log',
when='midnight',
interval=1,
backupCount=7,
delay=True
)
formatter = logging.Formatter(
'%(asctime)s [%(levelname)s] %(message)s',
datefmt='%Y-%m-%d %H:%M:%S'
)
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
logger = setup_logging()
# Email
def send_email(subject, body):
msg = MIMEText(body)
msg['Subject'] = subject
msg['From'] = gmail_user
msg['To'] = recipient_email
try:
with smtplib.SMTP('smtp.gmail.com', 587) as server:
server.starttls()
server.login(gmail_user, gmail_password)
server.send_message(msg)
logger.info(f"Email message sent: {subject}")
except Exception as e:
logger.info(f"Failed to send email: {e}")
intents = discord.Intents.default()
client = discord.Client(intents=intents)
# Discord
def send_discord(message):
async def message_send():
intents = discord.Intents.default()
client = discord.Client(intents=intents)
@client.event
async def on_ready():
logger.info(f"Client connected as {client.user}")
try:
user = await client.fetch_user(user_id)
await user.send(message)
logger.info(f"Discord message sent: {message}")
except Exception as e:
logger.error(e)
finally:
await client.close()
await client.start(bot_token)
asyncio.run(message_send())
# UPS Monitor
def monitor_ups():
client = PyNUTClient()
try:
ups_status = client.list_vars('ups')
return {
'status': ups_status.get('ups.status'),
'battery_charge': float(ups_status.get('battery.charge'))
}
except Exception as e:
logger.error(f"Error communicating with NUT: {e}")
return None
# Server Power Management
proxmox = ProxmoxAPI(pve_host, user=pve_user, password=pve_pass, verify_ssl=False)
def shutdown_pve(node):
try:
proxmox.nodes(node).status.post(command='shutdown')
logger.warning(f"Node {node} is shutting down.")
except Exception as e:
logger.error(e)
def shutdown_nas():
try:
logger.warning("Nas is shutting down.")
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(nas_ip)
stdin, stdout, stderr = client.exec_command('sudo shutdown')
for line in stdout:
print(line.strip())
except Exception as e:
logger.error(e)
def wake_up(battery):
for target, addr in wol_targets.items():
mac = addr['mac']
ip = addr['ip']
while True:
response = ping(ip)
if response:
logger.info(response)
logger.info(f"Host {mac} at {ip} is up. Ping {response}.")
time.sleep(5)
break
else:
send_magic_packet(mac)
logger.warning(f"Host {mac} at {ip} is offline. Sending magic packet.")
time.sleep(10)
# Status Communications
def pwr_online(battery):
message = f"{ups_id} UPS power has been restored. {battery}% charge remaining."
logger.info(message)
send_email(f"{ups_id}: Power On Line", message)
send_discord(message)
if ups_id == "Server" and battery > 90:
wake_up(battery)
def pwr_offline(battery):
message = f"{ups_id} UPS is running on battery power! {battery}% charge remaining."
logger.warning(message)
send_email(f"{ups_id}: Power Outage Detected!", message)
send_discord(message)
def batt_low(battery):
message = f"{ups_id} battery level low: {battery}% charge remaining."
logger.warning(message)
send_email(f"{ups_id} battery low.", message)
send_discord(message)
def batt_crit(battery):
message = f"{ups_id} battery level critial: {battery}% charge remaining, Shutting down PVE."
logger.warning(message)
send_email(f"{ups_id} battery critical!!", message)
send_discord(message)
if ups_id == "Server":
for node in servers:
shutdown_pve(node)
time.sleep(120)
shutdown_nas()
# Main Loop
def main():
logger.info("Starting UPS monitoring service.")
prev_status = "OL"
while True:
ups_data = monitor_ups()
if ups_data:
status = ups_data['status']
battery = ups_data['battery_charge']
if status and status != prev_status:
if status == "OB DISCHRG":
pwr_offline(battery)
elif status == "OL" or status == "OL CHRG":
pwr_online(battery)
else:
logger.info(f"UPS status changed to: {status}")
prev_status = status
elif status == "OB DISCHRG" and battery < 70:
if battery < 65:
batt_crit(battery)
if ups_id == "Server":
for node in servers:
shutdown_pve(node)
time.sleep(300)
else:
batt_low(battery)
time.sleep(60)
time.sleep(5)
if __name__ == "__main__":
main()