Add various scripts for server power management and build process

- Introduced .gitignore to exclude unnecessary files
- Added scripts for powering on/off servers via IPMI and iLO
- Implemented multi_powercycle script to manage multiple servers
- Created build script for packaging multi_powercycle with PyInstaller
- Enhanced logging functionality across scripts

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-04-26 08:55:19 -04:00
parent 9ea54cfc60
commit 75bd96098a
9 changed files with 515 additions and 15 deletions
@@ -0,0 +1,66 @@
#!/usr/bin/python3
import argparse
import configparser
import os
import sys
import urllib3
import redfish
from datetime import datetime
def log(message, is_error=False):
"""Print message with timestamp"""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
if is_error:
print(f"[{timestamp}] {message}", file=sys.stderr)
else:
print(f"[{timestamp}] {message}")
# Suppress SSL warnings for self-signed iLO certificates
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
parser = argparse.ArgumentParser(description="Power off an HPE server via iLO Redfish")
parser.add_argument("hostname", help="iLO hostname or IP address")
parser.add_argument("-c", "--credentials", default="/etc/ilo_credentials",
help="Path to credentials file (default: /etc/ilo_credentials)")
args = parser.parse_args()
# Read credentials from file (chmod 600 recommended: sudo chmod 600 /etc/ilo_credentials)
# File format:
# [ilo]
# username = root
# password = yourpassword
cred_path = args.credentials
if not os.path.exists(cred_path):
log(f"Error: credentials file not found: {cred_path}", is_error=True)
sys.exit(1)
config = configparser.ConfigParser()
config.read(cred_path)
try:
LOGIN_ACCOUNT = config.get("ilo_credentials", "username", fallback="root")
LOGIN_PASSWORD = config.get("ilo_credentials", "password")
except (configparser.NoSectionError, configparser.NoOptionError) as e:
log(f"Error reading credentials file: {e}", is_error=True)
sys.exit(1)
BASE_URL = f"https://{args.hostname}"
REDFISHOBJ = redfish.RedfishClient(
base_url=BASE_URL,
username=LOGIN_ACCOUNT,
password=LOGIN_PASSWORD,
)
REDFISHOBJ.login(auth="session")
# Discover the correct reset action URI from the system resource
sys_response = REDFISHOBJ.get("/redfish/v1/Systems/1/")
reset_uri = sys_response.dict["Actions"]["#ComputerSystem.Reset"]["target"]
body = {"ResetType": "PushPowerButton"} # iLO 4: PushPowerButton = graceful shutdown; use "ForceOff" for hard power cut
response = REDFISHOBJ.post(reset_uri, body=body)
log(f"Status: {response.status}")
if response.status >= 400:
log(f"Error: {response.read}", is_error=True)
REDFISHOBJ.logout()
@@ -0,0 +1,66 @@
#!/usr/bin/python3
import argparse
import configparser
import os
import sys
import urllib3
import redfish
from datetime import datetime
def log(message, is_error=False):
"""Print message with timestamp"""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
if is_error:
print(f"[{timestamp}] {message}", file=sys.stderr)
else:
print(f"[{timestamp}] {message}")
# Suppress SSL warnings for self-signed iLO certificates
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
parser = argparse.ArgumentParser(description="Power off an HPE server via iLO Redfish")
parser.add_argument("hostname", help="iLO hostname or IP address")
parser.add_argument("-c", "--credentials", default="/etc/ilo_credentials",
help="Path to credentials file (default: /etc/ilo_credentials)")
args = parser.parse_args()
# Read credentials from file (chmod 600 recommended: sudo chmod 600 /etc/ilo_credentials)
# File format:
# [ilo]
# username = root
# password = yourpassword
cred_path = args.credentials
if not os.path.exists(cred_path):
log(f"Error: credentials file not found: {cred_path}", is_error=True)
sys.exit(1)
config = configparser.ConfigParser()
config.read(cred_path)
try:
LOGIN_ACCOUNT = config.get("ilo", "username", fallback="root")
LOGIN_PASSWORD = config.get("ilo", "password")
except (configparser.NoSectionError, configparser.NoOptionError) as e:
log(f"Error reading credentials file: {e}", is_error=True)
sys.exit(1)
BASE_URL = f"https://{args.hostname}"
REDFISHOBJ = redfish.RedfishClient(
base_url=BASE_URL,
username=LOGIN_ACCOUNT,
password=LOGIN_PASSWORD,
)
REDFISHOBJ.login(auth="session")
# Discover the correct reset action URI from the system resource
sys_response = REDFISHOBJ.get("/redfish/v1/Systems/1/")
reset_uri = sys_response.dict["Actions"]["#ComputerSystem.Reset"]["target"]
body = {"ResetType": "PushPowerButton"} # iLO 4: PushPowerButton = graceful shutdown; use "ForceOff" for hard power cut
response = REDFISHOBJ.post(reset_uri, body=body)
log(f"Status: {response.status}")
if response.status >= 400:
log(f"Error: {response.read}", is_error=True)
REDFISHOBJ.logout()
@@ -0,0 +1,159 @@
#!/usr/bin/env python3
import argparse
import configparser
import os
import subprocess
import sys
import datetime
# Examples:
# ./ipmi_multipoweron.py host1 host2 host3
# ./ipmi_multipoweron.py host1 host2 host3 -c /path/to/credentials
# ./ipmi_multipoweron.py -f serverlist.txt
# ./ipmi_multipoweron.py -f serverlist.txt -c /path/to/credentials
def ts_print(msg):
"""Print message prefixed with a timestamp (YYYY-MM-DD HH:MM:SS)."""
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print(f"[{now}] {msg}")
DEFAULT_EK = "0000000000000000000000000000000000000000"
def find_credentials_file(provided_path):
"""Find credentials file in standard locations if provided path doesn't exist."""
# If provided path exists, use it
if os.path.exists(provided_path):
return provided_path
# Check standard locations
standard_paths = [
os.path.expanduser("~/credentials"),
"/etc/credentials",
]
for path in standard_paths:
if os.path.exists(path):
return path
# Return the provided path (will fail with proper error message later)
return provided_path
def load_credentials(path):
cred_path = find_credentials_file(path)
if not os.path.exists(cred_path):
ts_print(f"Error: credentials file not found: {cred_path}")
sys.exit(1)
config = configparser.ConfigParser()
config.read(cred_path)
try:
username = config.get("credentials", "username", fallback="root")
password = config.get("credentials", "password")
encryption_key = config.get("credentials", "encryption_key", fallback=DEFAULT_EK)
except (configparser.NoSectionError, configparser.NoOptionError) as e:
ts_print(f"Error reading credentials file: {e}")
sys.exit(1)
return username, password, encryption_key
def power_on_host(host, ipmi_user, ipmi_pw, ipmi_ek):
"""Power on a single host via IPMI. Returns True if successful."""
cmd = [
"ipmitool",
"-I", "lanplus",
"-H", host,
"-U", ipmi_user,
"-P", ipmi_pw,
"-y", ipmi_ek,
"power", "on",
]
proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
if proc.returncode != 0:
ts_print(f"ERROR: Failed to power on {host}: {proc.stderr.strip()}")
return False
else:
ts_print(f"SUCCESS: {host} - {proc.stdout.strip()}")
return True
def load_servers_from_file(filepath):
"""Load server list from a file (one server per line, lines starting with # are ignored)."""
if not os.path.exists(filepath):
ts_print(f"Error: server list file not found: {filepath}")
sys.exit(1)
servers = []
try:
with open(filepath, 'r') as f:
for line in f:
line = line.strip()
# Skip empty lines and comments
if line and not line.startswith('#'):
servers.append(line)
except Exception as e:
ts_print(f"Error reading server list file: {e}")
sys.exit(1)
return servers
def main():
p = argparse.ArgumentParser(description="Send IPMI power on to multiple servers")
# Either provide servers as arguments or a file
group = p.add_mutually_exclusive_group(required=True)
group.add_argument(
"servers",
nargs="*",
help="List of IPMI host addresses (space-separated)"
)
group.add_argument(
"-f", "--file",
help="Path to file containing list of servers (one per line)"
)
p.add_argument(
"-c",
"--credentials",
default="credentials",
help="Path to credentials file (default: credentials; also checks ~/ and /etc/ if not found)",
)
args = p.parse_args()
# Determine server list
if args.file:
servers = load_servers_from_file(args.file)
else:
servers = args.servers
if not servers:
ts_print("Error: No servers specified")
sys.exit(1)
ts_print(f"Starting power on sequence for {len(servers)} server(s)")
ipmi_user, ipmi_pw, ipmi_ek = load_credentials(args.credentials)
success_count = 0
failed_count = 0
for host in servers:
if power_on_host(host, ipmi_user, ipmi_pw, ipmi_ek):
success_count += 1
else:
failed_count += 1
ts_print(f"Completed: {success_count} successful, {failed_count} failed")
if failed_count > 0:
sys.exit(1)
if __name__ == "__main__":
main()
@@ -0,0 +1,70 @@
#!/usr/bin/env python3
import argparse
import configparser
import os
import subprocess
import sys
from datetime import datetime
DEFAULT_EK = "0000000000000000000000000000000000000000"
def log(message, is_error=False):
"""Print message with timestamp"""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
if is_error:
print(f"[{timestamp}] {message}", file=sys.stderr)
else:
print(f"[{timestamp}] {message}")
def load_credentials(path):
if not os.path.exists(path):
log(f"Error: credentials file not found: {path}", is_error=True)
sys.exit(1)
config = configparser.ConfigParser()
config.read(path)
try:
username = config.get("credentials", "username", fallback="root")
password = config.get("credentials", "password")
encryption_key = config.get("credentials", "encryption_key", fallback=DEFAULT_EK)
except (configparser.NoSectionError, configparser.NoOptionError) as e:
log(f"Error reading credentials file: {e}", is_error=True)
sys.exit(1)
return username, password, encryption_key
def main():
p = argparse.ArgumentParser(description="Send IPMI power off")
p.add_argument("-H", "--host", required=True, help="IPMI host address")
p.add_argument(
"-c",
"--credentials",
default="credentials",
help="Path to credentials file (default: credentials)",
)
args = p.parse_args()
ipmi_user, ipmi_pw, ipmi_ek = load_credentials(args.credentials)
cmd = [
"ipmitool",
"-I", "lanplus",
"-H", args.host,
"-U", ipmi_user,
"-P", ipmi_pw,
"-y", ipmi_ek,
"power", "off",
]
proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
if proc.returncode != 0:
log("ipmitool failed: " + proc.stderr.strip(), is_error=True)
sys.exit(proc.returncode)
log(proc.stdout.strip())
if __name__ == "__main__":
main()
@@ -0,0 +1,65 @@
#!/usr/bin/env python3
import argparse
import configparser
import os
import subprocess
import sys
import datetime
def ts_print(msg):
"""ts_print message prefixed with a timestamp (YYYY-MM-DD HH:MM:SS)."""
now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
print(f"{now} {msg}")
DEFAULT_EK = "0000000000000000000000000000000000000000"
def load_credentials(path):
if not os.path.exists(path):
print(f"Error: credentials file not found: {path}", file=sys.stderr)
sys.exit(1)
config = configparser.ConfigParser()
config.read(path)
try:
username = config.get("ilo", "username", fallback="root")
password = config.get("ilo", "password")
encryption_key = config.get("ilo", "encryption_key", fallback=DEFAULT_EK)
except (configparser.NoSectionError, configparser.NoOptionError) as e:
print(f"Error reading credentials file: {e}", file=sys.stderr)
sys.exit(1)
return username, password, encryption_key
def main():
p = argparse.ArgumentParser(description="Send IPMI power on")
p.add_argument("-H", "--host", required=True, help="IPMI host address")
p.add_argument(
"-c",
"--credentials",
default="credentials",
help="Path to credentials file (default: credentials)",
)
args = p.parse_args()
ipmi_user, ipmi_pw, ipmi_ek = load_credentials(args.credentials)
cmd = [
"ipmitool",
"-I", "lanplus",
"-H", args.host,
"-U", ipmi_user,
"-P", ipmi_pw,
"-y", ipmi_ek,
"power", "on",
]
print(f"Sending power on to {args.host}")
proc = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
if proc.returncode != 0:
print("ipmitool failed:", proc.stderr.strip(), file=sys.stderr)
sys.exit(proc.returncode)
print(proc.stdout.strip())
if __name__ == "__main__":
main()