First version that is cron-based
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
venv
|
||||
25
Dockerfile
Normal file
25
Dockerfile
Normal file
@@ -0,0 +1,25 @@
|
||||
FROM python:3.14.7-alpine3.24
|
||||
|
||||
|
||||
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1
|
||||
|
||||
RUN addgroup -g 1600 pyusr \
|
||||
&& adduser -D -u 1600 -G pyusr pyusr
|
||||
|
||||
RUN mkdir -p /app/pbsStartUpper/ && \
|
||||
chown -R 1600:1600 /app/pbsStartUpper/
|
||||
|
||||
WORKDIR /app/pbsStartUpper/
|
||||
|
||||
COPY requirements.txt .
|
||||
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
COPY --chown=pyusr:pyusr main.py .
|
||||
|
||||
USER pyusr
|
||||
|
||||
CMD ["python", "main.py"]
|
||||
|
||||
115
Roadmap
Normal file
115
Roadmap
Normal file
@@ -0,0 +1,115 @@
|
||||
|
||||
# Connect to IDRAC
|
||||
|
||||
# Check if power is on
|
||||
|
||||
# chassis power status | grep fourth element
|
||||
# "Chassis Power is off"
|
||||
|
||||
# if turn on power
|
||||
|
||||
# Send Power on (ipmitool chassis power on)
|
||||
|
||||
# else systemPower is on, continue.
|
||||
|
||||
|
||||
# Store current date as PWR_DATE to later compare in while loop
|
||||
|
||||
# Send ping to HOST every 15 seconds for 10 minutes. It can take some time for the server to start
|
||||
|
||||
# while PWR_DATE is less than 15 minutes compared to CURRENT_DATE, ping
|
||||
# if ping successfull 3 times in a row
|
||||
# break
|
||||
|
||||
# else, exit with error,
|
||||
# exit "Host didn't start after 20 minutes. Check the server"
|
||||
|
||||
|
||||
|
||||
|
||||
# SSH to HOST
|
||||
|
||||
# check when verification was last ran
|
||||
# if not in the last LAST_VERIF_D, run verification
|
||||
# Verify datastore backups
|
||||
|
||||
# else, backups already verified enough, continue
|
||||
# continue
|
||||
|
||||
# check if remote exists
|
||||
# proxmox-backup-manager remote list
|
||||
# ┌──────┬──────────────┬──────────┬───────────────────────────────────────────┬─────────┐
|
||||
# │ name │ host │ userid │ fingerprint │ comment │
|
||||
# ╞══════╪══════════════╪══════════╪═══════════════════════════════════════════╪═════════╡
|
||||
# │ pbs2 │ pbs2.example │ sync@pam │64:d3:ff:3a:50:38:53:5a:9b:f7:50:...:ab:fe │ │
|
||||
# └──────┴──────────────┴──────────┴───────────────────────────────────────────┴─────────┘
|
||||
|
||||
# for each remote host in REMOTE_PBS
|
||||
|
||||
# if remote host not in "host" collumn from "remote list" command
|
||||
|
||||
# exit with "Please first add a remote to PBS"
|
||||
|
||||
# else
|
||||
# print All passed remotes found on PBS
|
||||
|
||||
|
||||
# Get local and remote stores
|
||||
|
||||
|
||||
# Create backup one-shot sync job
|
||||
|
||||
# for syncJob in jobList
|
||||
|
||||
# if AUTO_ADD_NAME in comment collumn of syncJob
|
||||
# remove sync-job by id
|
||||
|
||||
# elif
|
||||
# Add sync job
|
||||
|
||||
# else
|
||||
# idk
|
||||
|
||||
# run one-shop sync jobs
|
||||
|
||||
# for syncJob in jobList
|
||||
# if AUTO_ADD_NAME in syncJob.comment
|
||||
# run sync-job
|
||||
|
||||
# if run succesfull
|
||||
# continue
|
||||
# else
|
||||
# print the error message to stderr
|
||||
|
||||
# elif AUTO_ADD_NAME not in syncJob.comment
|
||||
# skipping a manually addad syncJob
|
||||
# else
|
||||
# either no syncJobs or something broke
|
||||
|
||||
# store current date as PWR_OFF_DATE
|
||||
|
||||
# while ongoing syncJobs or ongoing verifyJobs
|
||||
# if any remaining syncJobs and verifyJobs and garbageCollect finish
|
||||
|
||||
# if PWR_OFF_DATE is longer than 3 hours
|
||||
# brake in order to send shutdown signal nontheless
|
||||
# else
|
||||
# sleep for 30 seconds
|
||||
|
||||
# send power off signal via SSH
|
||||
# if chassis power status is on after 5 minutes
|
||||
# send additional "chassis power off" via ipmitool
|
||||
# else
|
||||
# exit 0
|
||||
|
||||
|
||||
|
||||
# proxmox-backup-manager sync-job create pbs2-local --remote pbs2 --remote-store local --store local --schedule 'Wed 02:30'
|
||||
# proxmox-backup-manager sync-job update pbs2-local --comment 'offsite'
|
||||
# proxmox-backup-manager sync-job list
|
||||
# ┌────────────┬───────┬────────┬──────────────┬───────────┬─────────┐
|
||||
# │ id │ store │ remote │ remote-store │ schedule │ comment │
|
||||
# ╞════════════╪═══════╪════════╪══════════════╪═══════════╪═════════╡
|
||||
# │ pbs2-local │ local │ pbs2 │ local │ Wed 02:30 │ offsite │
|
||||
# └────────────┴───────┴────────┴──────────────┴───────────┴─────────┘
|
||||
# proxmox-backup-manager sync-job remove pbs2-local
|
||||
32
docker-compose.yaml
Normal file
32
docker-compose.yaml
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
|
||||
# sudo crontab -e
|
||||
# 0 2 * * * cd /path/to/docker-compose && /usr/bin/docker compose up -d >> /var/log/pbsupper-docker-compose.log 2>&1
|
||||
|
||||
services:
|
||||
pbsupper-1:
|
||||
container_name: pbsupper
|
||||
user: 1600:1600
|
||||
image: tea.shupogaki.org/yuruc3/pbsupper:v0
|
||||
environment:
|
||||
LOCAL_DATASTORE: somethingSomething
|
||||
REMOTE_DATASTORE: somethingSomething
|
||||
REMOTE_PBS: somethingSomething
|
||||
|
||||
HOST: somethingSomething
|
||||
HOST_SSH_PORT: somethingSomething
|
||||
HOST_USR: somethingSomething
|
||||
HOST_USR_PWD: somethingSomething
|
||||
|
||||
HOST_USR_SSHKEY: |
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
123...
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
HOST_USR_SSHKEY_PASS: somethingSomething
|
||||
|
||||
IDRAC: somethingSomething
|
||||
IDRAC_USR: somethingSomething
|
||||
IDRAC_PWD: somethingSomething
|
||||
|
||||
|
||||
restart: unless-stopped
|
||||
495
main.py
Normal file
495
main.py
Normal file
@@ -0,0 +1,495 @@
|
||||
import os, socket, subprocess, paramiko, io, sys, time
|
||||
from typing import Final
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
IDRAC: Final[str] = os.getenv("IDRAC", None)
|
||||
IDRAC_USR: Final[str] = os.getenv("IDRAC_USR", None)
|
||||
IDRAC_PWD: Final[str] = os.getenv("IDRAC_PWD", None)
|
||||
|
||||
# LAST_VERIFY_DAY: Final[int] = os.getenv("LAST_VERIFY_DAY", 7)
|
||||
# LAST_GARBAGE_DAY: Final[int] = os.getenv("LAST_GARBAGE_DAY", 7)
|
||||
|
||||
HOST: Final[str] = os.getenv("HOST", None)
|
||||
HOST_SSH_PORT: Final[int] = os.getenv("HOST_SSH_PORT", None)
|
||||
HOST_USR: Final[str] = os.getenv("HOST_USR", "root")
|
||||
HOST_USR_PWD: Final[str] = os.getenv("HOST_USR_PWD", None)
|
||||
HOST_USR_SSHKEY: Final[str] = os.getenv("HOST_USR_SSHKEY", None)
|
||||
HOST_USR_SSHKEY_PASS: Final[str] = os.getenv("HOST_USR_SSHKEY_PASS", None)
|
||||
|
||||
LOCAL_DATASTORE: Final[str] = os.getenv("LOCAL_DATASTORE", None)
|
||||
REMOTE_DATASTORE: Final[str] = os.getenv("HOST_UREMOTE_DATASTORESR_SSHKEY_PASS", None)
|
||||
REMOTE_PBS: Final[str] = os.getenv("REMOTE_PBS", None)
|
||||
SYNC_DIR: Final[str] = os.getenv("SYNC_DIR", "push")
|
||||
PBS_RATE_OUT: Final[str] = os.getenv("PBS_RATE_OUT", "")
|
||||
PBS_RATE_IN: Final[str] = os.getenv("PBS_RATE_IN", "")
|
||||
PBS_TASK_LIMIT: Final[int] = os.getenv("PBS_TASK_LIMIT", 50)
|
||||
PBS_MAX_RUNTIME_MIN: Final[int] = os.getenv("PBS_MAX_RUNTIME_MIN", 180)
|
||||
PBS_MAX_SHUT_MIN: Final[int] = os.getenv("PBS_MAX_SHUT_MIN", 10)
|
||||
AUTO_ADD_NAME: Final[str] = os.getenv("AUTO_ADD_NAME", "autoBackupScript_one-shot")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
def exceptionator(exception, message):
|
||||
"""Return an exception that can be custom-used"""
|
||||
return exception(message)
|
||||
|
||||
|
||||
def load_private_key(key_data: str, passphrase: str | None = None):
|
||||
"""Take in SSH key, determine type and return paramiko-compatible element"""
|
||||
key_classes = [
|
||||
paramiko.Ed25519Key,
|
||||
paramiko.ECDSAKey,
|
||||
paramiko.RSAKey,
|
||||
# paramiko.DSSKey,
|
||||
]
|
||||
|
||||
for key_class in key_classes:
|
||||
try:
|
||||
return key_class.from_private_key(
|
||||
io.StringIO(key_data),
|
||||
password=passphrase or None,
|
||||
)
|
||||
except (paramiko.SSHException, ValueError):
|
||||
pass
|
||||
|
||||
raise ValueError("Unsupported or invalid SSH private key")
|
||||
|
||||
|
||||
def getCurrentTasks(allTasks: bool = False, limitOfTasks: int =50) -> dict:
|
||||
"""Get what tasks are running/ran on PBS
|
||||
|
||||
Keyword arguments:
|
||||
allTasks -- if tasks that are finished should be included. (default False)
|
||||
limitOfTasks -- how many tasks to ask PBS for (default 50)
|
||||
"""
|
||||
|
||||
(stdin, stdout, stderr) = ssh.exec_command(f'proxmox-backup-manager task list --limit {limitOfTasks} --all {allTasks}')
|
||||
|
||||
taskList = stdout.read().decode()
|
||||
|
||||
outTaskList = []
|
||||
for line in taskList.splitlines():
|
||||
line = line.strip()
|
||||
|
||||
# Only process actual table rows
|
||||
if not line.startswith("│"):
|
||||
continue
|
||||
|
||||
columns = [col.strip() for col in line.strip("│").split("│")]
|
||||
|
||||
# Skip the header row
|
||||
if columns[0] == "starttime":
|
||||
continue
|
||||
if len(columns) >= 2:
|
||||
outTaskList.append(columns[2].split(":")[6])
|
||||
# exit()
|
||||
|
||||
|
||||
|
||||
# print(outTaskList)
|
||||
|
||||
outTaskDict = {}
|
||||
|
||||
for task in outTaskList:
|
||||
|
||||
match task:
|
||||
case "garbage_collection":
|
||||
outTaskDict["garbage_collection"] = outTaskDict.get("garbage_collection", 0) + 1
|
||||
case "verificationjob":
|
||||
outTaskDict["verificationjob"] = outTaskDict.get("verificationjob", 0) + 1
|
||||
case "syncjob":
|
||||
outTaskDict["syncjob"] = outTaskDict.get("syncjob", 0) + 1
|
||||
case "aptupdate":
|
||||
outTaskDict["aptupdate"] = outTaskDict.get("aptupdate", 0) + 1
|
||||
case "reader":
|
||||
outTaskDict["reader"] = outTaskDict.get("reader", 0) + 1
|
||||
case "logrotate":
|
||||
outTaskDict["logrotate"] = outTaskDict.get("logrotate", 0) + 1
|
||||
case "termproxy":
|
||||
outTaskDict["termproxy"] = outTaskDict.get("termproxy", 0) + 1
|
||||
case _:
|
||||
outTaskDict["otherjob"] = outTaskDict.get("otherjob", 0) + 1
|
||||
|
||||
|
||||
return outTaskDict
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if len(AUTO_ADD_NAME) > 32:
|
||||
raise exceptionator(ValueError, "Maxmimum length of AUTO_ADD_NAME is 32")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# dt = datetime.now.timestamp()
|
||||
|
||||
# epoch_time = dt.timestamp()
|
||||
# print(epoch_time)
|
||||
|
||||
# Check if power is on
|
||||
# PWR_STATE = subprocess.run(["ipmitool", "-I", "lanplus", "-H", IDRAC, "-U", IDRAC_USR, "-P", IDRAC_PWD, "chassis", "power", "status"], capture_output=True)
|
||||
|
||||
try:
|
||||
# power on if needed
|
||||
if "off" in str(PWR_STATE.stdout.decode()[-4:]):
|
||||
subprocess.run(["ipmitool", "-I", "lanplus", "-H", IDRAC, "-U", IDRAC_USR, "-P", IDRAC_PWD, "chassis", "power", "on"]) # , capture_output=True
|
||||
except exception as e:
|
||||
print("Most likely wrong password, username or hostname has been passed for idrac.\nExiting\n")
|
||||
sys.exit(1)
|
||||
# else:
|
||||
# continue
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
PWR_DATE = datetime.now().timestamp()
|
||||
|
||||
pinging = subprocess.run(["ping", "-c", "3", "-W", "5", HOST], capture_output=True).returncode
|
||||
|
||||
if pinging:
|
||||
print("not reachable")
|
||||
|
||||
while datetime.now().timestamp() - PWR_DATE <= 1200:
|
||||
pinging = subprocess.run(["ping", "-c", "3", "-W", "5", HOST], capture_output=True).returncode
|
||||
|
||||
if not pinging:
|
||||
print("reachable")
|
||||
break
|
||||
else:
|
||||
raise exceptionator(TimeoutError, "Host didn't start after 20 minutes.\nCheck the server.")
|
||||
sys.exit(1)
|
||||
|
||||
else:
|
||||
print("reachable")
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Create object of SSHClient and
|
||||
# connecting to SSH
|
||||
ssh = paramiko.SSHClient()
|
||||
|
||||
# Adding new host key to the local
|
||||
# HostKeys object(in case of missing)
|
||||
# AutoAddPolicy for missing host key to be set before connection setup.
|
||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
|
||||
|
||||
if not HOST_USR_SSHKEY:
|
||||
|
||||
ssh.connect(hostname=HOST, port=HOST_SSH_PORT, username=HOST_USR, password=HOST_USR_PWD, timeout=3, )
|
||||
|
||||
else:
|
||||
key = load_private_key(
|
||||
HOST_USR_SSHKEY,
|
||||
HOST_USR_SSHKEY_PASS
|
||||
)
|
||||
|
||||
ssh.connect(
|
||||
hostname=HOST,
|
||||
port=HOST_SSH_PORT,
|
||||
username=HOST_USR,
|
||||
pkey=key,
|
||||
timeout=3,
|
||||
allow_agent=False,
|
||||
look_for_keys=False,
|
||||
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
# Execute command on SSH terminal
|
||||
# using exec_command
|
||||
# (stdin, stdout, stderr) = ssh.exec_command('hostname')
|
||||
|
||||
# # redirecting all the output in cmd_output
|
||||
# # variable
|
||||
# cmd_output = stdout.read().decode()
|
||||
|
||||
# print(cmd_output)
|
||||
|
||||
(stdin, stdout, stderr) = ssh.exec_command('proxmox-backup-manager remote list')
|
||||
|
||||
remoteList = stdout.read().decode()
|
||||
|
||||
remoteDict = {}
|
||||
|
||||
for line in remoteList.splitlines():
|
||||
line = line.strip()
|
||||
|
||||
# Only process actual table rows
|
||||
if not line.startswith("│"):
|
||||
continue
|
||||
|
||||
columns = [col.strip() for col in line.strip("│").split("│")]
|
||||
|
||||
# Skip the header row
|
||||
if columns[0] == "name":
|
||||
continue
|
||||
|
||||
if len(columns) >= 2:
|
||||
name = columns[0]
|
||||
host = columns[1]
|
||||
|
||||
remoteDict[host] = name
|
||||
|
||||
|
||||
|
||||
if REMOTE_PBS in remoteDict:
|
||||
print(f"Found {REMOTE_PBS} as {remoteDict[REMOTE_PBS]}")
|
||||
else:
|
||||
print(f"\n!!!\nRemote {REMOTE_PBS} not found.\nAdd remote {REMOTE_PBS} manually.\n!!!\n", file=sys.stderr)
|
||||
REMOTE_PBS.remove(REMOTE_PBS)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
(stdin, stdout, stderr) = ssh.exec_command('proxmox-backup-manager sync-job list --sync-direction all')
|
||||
|
||||
syncJobList = stdout.read().decode()
|
||||
|
||||
|
||||
for line in syncJobList.splitlines():
|
||||
line = line.strip()
|
||||
|
||||
# Only process actual table rows
|
||||
if not line.startswith("│"):
|
||||
continue
|
||||
|
||||
columns = [col.strip() for col in line.strip("│").split("│")]
|
||||
|
||||
# Skip the header row
|
||||
if columns[0] == "id":
|
||||
continue
|
||||
if len(columns) >= 2:
|
||||
print("Sync job already on Proxmox Backup Server.\nSkipping sync job creation.")
|
||||
# print(columns)
|
||||
break
|
||||
else:
|
||||
|
||||
match SYNC_DIR:
|
||||
case "push":
|
||||
if PBS_RATE_OUT:
|
||||
(stdin, stdout, stderr) = ssh.exec_command(f"proxmox-backup-manager sync-job create {AUTO_ADD_NAME} --remote {remoteDict[REMOTE_PBS]} --remote-store {REMOTE_DATASTORE} --store {LOCAL_DATASTORE} --sync-direction push --rate-out {PBS_RATE_OUT} --comment 'automatic startupper script'")
|
||||
else:
|
||||
(stdin, stdout, stderr) = ssh.exec_command(f"proxmox-backup-manager sync-job create {AUTO_ADD_NAME} --remote {remoteDict[REMOTE_PBS]} --remote-store {REMOTE_DATASTORE} --store {LOCAL_DATASTORE} --sync-direction push --comment 'automatic startupper script'")
|
||||
case "pull":
|
||||
if PBS_RATE_IN:
|
||||
(stdin, stdout, stderr) = ssh.exec_command(f"proxmox-backup-manager sync-job create {AUTO_ADD_NAME} --remote {remoteDict[REMOTE_PBS]} --remote-store {REMOTE_DATASTORE} --store {LOCAL_DATASTORE} --sync-direction pull --rate-in {PBS_RATE_IN} --comment 'automatic startupper script'")
|
||||
else:
|
||||
(stdin, stdout, stderr) = ssh.exec_command(f"proxmox-backup-manager sync-job create {AUTO_ADD_NAME} --remote {remoteDict[REMOTE_PBS]} --remote-store {REMOTE_DATASTORE} --store {LOCAL_DATASTORE} --sync-direction pull --comment 'automatic startupper script'")
|
||||
case _:
|
||||
raise exceptionator(SyncDirectionError, f"No such sync direction as {SYNC_DIR}.\n Either use 'push' or 'pull'\n")
|
||||
|
||||
|
||||
# print(stdout.read().decode())
|
||||
|
||||
|
||||
|
||||
|
||||
(stdin, stdout, stderr) = ssh.exec_command(f"proxmox-backup-manager sync-job run {AUTO_ADD_NAME}")
|
||||
# ssh.exec_command(f"proxmox-backup-manager sync-job run {AUTO_ADD_NAME}")
|
||||
# print("here it runs the sync-job")
|
||||
# print(stdout.read().decode())
|
||||
# print(stderr.read().decode())
|
||||
|
||||
|
||||
|
||||
|
||||
# Maybe add some checks here if the backup was properly scheduled
|
||||
|
||||
|
||||
|
||||
# Get tasks
|
||||
# print(getCurrentTasks(limitOfTasks=100))
|
||||
|
||||
|
||||
|
||||
|
||||
# check ongoing tasks
|
||||
SYNC_JOB_DATE = datetime.now().timestamp()
|
||||
|
||||
currenttasks = getCurrentTasks(limitOfTasks=PBS_TASK_LIMIT)
|
||||
|
||||
while currenttasks:
|
||||
|
||||
currenttasks = getCurrentTasks(limitOfTasks=PBS_TASK_LIMIT)
|
||||
|
||||
print("Waiting 1 minute before checking tasks again")
|
||||
time.sleep(60)
|
||||
|
||||
if SYNC_JOB_DATE - datetime.now().timestamp() >= (PBS_MAX_RUNTIME_MIN * 60):
|
||||
print(SYNC_JOB_DATE - datetime.now().timestamp())
|
||||
# Forcefully skip if these tasks are running. reader task is, I think, PVE backup
|
||||
# Also otherjob is something else and it is better to wait if there is one otherjob
|
||||
if (
|
||||
currenttasks.get("aptupdate", 0)
|
||||
or currenttasks.get("reader", 0)
|
||||
or currenttasks.get("otherjob", 0)
|
||||
):
|
||||
continue
|
||||
# No need to do anything here. It's only to check if these keys exist
|
||||
...
|
||||
break
|
||||
|
||||
|
||||
(stdin, stdout, stderr) = ssh.exec_command(f"sudo shutdown")
|
||||
print("Shuting down PBS")
|
||||
# print("here it runs a shutdown now")
|
||||
print(stdout.read().decode())
|
||||
print(stderr.read().decode(), file=sys.stderr)
|
||||
|
||||
ssh.close()
|
||||
# wait for PBS to safely shutdown
|
||||
time.sleep(PBS_MAX_SHUT_MIN * 60)
|
||||
|
||||
|
||||
if subprocess.run(["ping", "-c", "3", "-W", "5", HOST], capture_output=True).returncode:
|
||||
print("PBS is powered off")
|
||||
|
||||
else:
|
||||
subprocess.run(["ipmitool", "-I", "lanplus", "-H", IDRAC, "-U", IDRAC_USR, "-P", IDRAC_PWD, "chassis", "power", "off"])
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# ▒▒▒▓▓▓▒▒▒▒▓▒▒▒▒▓▓▒▒▓▓▒ ▒ ▒ ▒▒░▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒ ░░░ ████████
|
||||
# ▒▒▒▒▓▓▒▓▒▒▓▒▓▓▒▓▒▒▒▓▓ ▒ ▒░ ▒▒▒▒░▒▒▒▒ ░ ░ ██████
|
||||
# ▒▒▓░▒▓▓▓▓▒▒▓▓▒▒▓▓▒▓▒ ▒░ ▒░ ▒▒ ▒▒ ░░ ████
|
||||
# ▓ ▒▒▒▒▒▓▓▒▓▒▓▓▒░▒ ▒ ░░ ██
|
||||
# ▒▒▓▓▓▒░▒▒▓▒▒▒ ░░ █
|
||||
# ░ ▓▒▓ ▒ ░░
|
||||
# ░
|
||||
# ▒ ▒▓ ░
|
||||
# ░ ▓▒▒ ░▓▓▓▓▓ ▒▒
|
||||
# ▒▓▓▓░▓▓░▓▓▓▓▓▓
|
||||
# ▓▓▓▓▓▓░▓▒░░▒▒ ▒▒▓▓░░░▓▓▓▓▓░▓▓▓▓
|
||||
# ▓▓▓░▒▓░▓▓▒▒▒ ▒▓▓▓▓░▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
# ░░░░░ ▓▓▓▓▓░▓▒▒▒▒▒▓▓▓▓▒ ▓▓▓▓▓▓░░░▒▓▓▓▓
|
||||
# ░░░░░░░░░░░░░░ ▓▓▓▓▓░▒▒ ▒▒▓▓▓▓▓▓▓▓░░▓▒▓▓▓▓▓▓▓▓▓
|
||||
# █ ░░░░░░░░░░░░░░░░ ▓▓▓▓▓▒▒ ▒▒▒░▒▓ ▓▓▓▓▓▓▓▓░▓▓▓▓▓▓▓▓▓
|
||||
# ███ ░░░░░░░░░░░░░░░░░ ▒░▒▒ ▒▓▓▓▓▒▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░
|
||||
# ████ ░░░░░░░░░░▓▓▓▓▓▒░░▒▓▓▓▓░▓░▓▓▓▓▓▓▓▓░▒▒░░▓▓▓▓▓
|
||||
# ██████ ░▓▓▓▓▓▓▓▓▒▒░▓▓▓▓ ▓▓░▓▓▓▓▒░░▓▒▓▓▓▓▓▓▓░▒
|
||||
# ████████ ▓▓▓▓▒▓▓▓▓▓▓▓▒▒░▓▒░▒ ▓▓▒░░▒▓▓▓▓▓░▓▓▓▓▓▓▒░▒ ▒ ▓░▓▓▒
|
||||
# ███████████ ▓▓▓▒▓▓▓▓▓▓▓░▒▒▒ ▒▒ ▓▓▓░▓▓▓▓▓▓▓▓▒▓▓▓▓▓▒▒ ▒▓▓░▓▓ ▓▓▓▓▓▓▒▓▓▓▓▓░
|
||||
# ████████████ ▓▒ ▒▒▒▒▒ ▓▓▓▓▒▓▓▓▓▓▓▓░▓▒▓▓▒░▓░▓▓▓▓▓▓▓▓▓▓░▓▓▓▓▒░░░▓▓▓▓░
|
||||
# ████████████▓ ▓▒ ▒ ▒ ▓▓▓▓░▓▓▓▓▓▓▓▓░▒▓▓▓▓▓░▒▓▓▓░░▒▓ ▒░▒▓▓▓▓▓▓░▓▓▓▓▓
|
||||
# ███████████ ▒▒▓▓▓░▓▓▓░▒▓▓▒▓▓▓▓░▒ ▒▒▓▓▓▓▓▒▒▒░▒▓▓▓▓▓▓▒▓▓▓▓
|
||||
# ████████▓ ▓▓▓▓▓▓▓▓░ ▒▒▓▓▒▓▓░▓▓▓▓▓▓▓░▓▓▓▓
|
||||
# ██████ ▓▓▓▓▓▓▓▓▓▒░░
|
||||
# ████ ▓░ ░▓░░░▓▓▓░▓▓▓
|
||||
# ██ ░░░░░░░░░░░░░░░░░ ░▓▓▓▓▓▓▓▒▓▓▓
|
||||
# ▓ ░░░░░ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ░░░░░░░░░ ▓▓▓▓▓▓▓▓▓░▓▓
|
||||
# ░░ ░ ▓▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ░ ▓▓░▓▓▓▒░░▒░▓▓
|
||||
# ░░░ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▒░ ░░ ▓▒ ▓▓▓▓▓▓▓▓▓▒▓
|
||||
# ▓████▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ░░░ ░░░ ▒▒▒▒▒▒▒▒ ▓▓▓▓▓▓ ▓
|
||||
# ███████ ▒▒▒▒▒▒▒▒▒▒▒▓▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓░ ░░░░░ ░░░░░ ░ ▒▒▒▒▒▒▒▒▒▒▒▒▓ ▒▒▒▒▒▒▓
|
||||
# ███████ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░▒▒▒▒▒▒▒▒▒▒▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▓▒▓▒▒▒▒▒▒▒ ░
|
||||
# ███████ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ▓▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▒▒▒▒▒▒▓ ░▒▒▒▒▒▒▒
|
||||
# ██████ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▒▒▓▒▒▒▓▓▓▓▓▓▓▓▓▒▒▒▓▒░░ ▒▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▓▒▒▒▓▒▒▒▒▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▓ ░▓▒▒▒▓▒▒
|
||||
# ██████ ▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓ █▒░▓▓▓▓▓▓▓▓▓▓█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▒▒▒▒▓▓▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▓ ▒ ▒▒▓▒▒
|
||||
# ██████ ▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ██▒▓▓▓▓▒▓▓██▓█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▒▓▓▓░▓▓▓▓▓▒░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒░▒ ░ ▒▓▓▒
|
||||
# █████ ▒▒▒▒▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█▓▓▓▓▓████ ▓▓▓▓▓▓▓█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓ ▒▓▓▓▓
|
||||
# █████ ▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░▓▓▓▓▓▓▓▓▓▓█████▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓ ▓▓▓
|
||||
# █████ ▓▓▓▓▓▓▒▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓██████▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓█▓▓▓▓▓▓▓▓▓▓█▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓ ▓▓▓
|
||||
# █████ ▓▓▓▓▓▓▒▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓█▓▓▓▓▓▓▓██████ ▓▓▓▓▓▓▓▓▓█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▒▓▓▓▓▓▓█▓▓▒▓▓▓▓▓▓▓▓▓▓▓█▓▓▓▓▓▓▓▓▓▒▓ ▓▓▓
|
||||
# ████▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓███████ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█▓▓▓▓▓▓▓▓▓▓▓ ▒▓▓▓▓
|
||||
# ████▓▓▓▓▓▓▓▓▒▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░███████▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓
|
||||
# █████▓▓▓▓▒▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓ ▒▓▓▓▓▓▓▓▓ ████████▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒░▓▓▓▓▓▓▒ ▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓█▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓
|
||||
# █████▓▓▓▓▒▓▓▒▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓ ▒ ▓▓▓▓▓▓▓▒█████████▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▓▓▓▓▓▓▓▒ ▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓ ░ ▓
|
||||
# █████▓▓▓▓▒▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▓▓▓▒█▓ ▓▓▓▒▓▓▓▓▓█▓▓▓█▓██ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▒██▓▓▓▓▓██▒▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓ ▒▓
|
||||
# █████▓▓▓▓▒▓▓▒▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓ ▓ ▓ ▓ ▓ ▒▓█████▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓████▒▓▓▓████ ▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▓
|
||||
# █████ ▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░ ▓ ▒▒▓▒███▓ ░ ███ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▓▓▓▓ ████░▓▓██▓█▓ ▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓ ░▓
|
||||
# ██ ██ ▓▓▓▓▒▓▓▓▓▓▓▓▒▓▓▓▓▓▓ ▒▒ ░▓█▓▓▓████▒▓▓▓ ▒▓▓▓▓▓▒ █ █████▓▓▓▓▓▓▓▓▓▓▓▓▓▓███▓▓▓▓▓▓▓█▓ ▓▓▓▓█▓█▓▓ ▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓ ▒▓
|
||||
# ▓ ███▓▓▓ ░▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▒▓▓▓██░█████▓▓▓▓▓▓▒▒▒▒▒▓▓▓▓▓▓█████▓▓ ▓▓▓▓▓▓▓▓▓██ █▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓ ▓ ▓
|
||||
# █▒ ██▓▓ ██ ▓▒▓▓▓▓▓▓░▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓████▓█▒▒▓▓ ▓▓ █▒█ ▒▓▓▒███▒▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓ ▒▓▓
|
||||
# ███ ██ ▓░▓██▓▒▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓██████▓█▓█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓ ▓ ░▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓ ▓ ▒█
|
||||
# ███ ███▓▓▓██▒▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ██████▓▓ ░░░▓▓▓▓▒▓▒▓▓▓▓▓▓▒▓ ▓ ▒ ▒
|
||||
# ███████ ▓████▓▒▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓█▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓ ▓ ░ ▒
|
||||
# ██ ████▒█▓██▓▒▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓█▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▒▓▓▓▓▓▓▒▓ ▓▓ ▒ ▒
|
||||
# ██ ██████▒░▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓ ▓▓▓▓▓▓▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓██▓ ▒
|
||||
# ██▓▓ ████████▓▓▓▓▒▓▓▓▓▒▓▓▓▓▓▓▒▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓█▒
|
||||
# ██ ▒██████▒▓█░▓▓▓▓▒▒▒▒▒▓▓▓▓▓▓▓▓▒▒ ░ ░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒
|
||||
# ████░█▒▒███████ ▓▓▓ ▒▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▓▓██▓██▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓
|
||||
# ██▒▒▒▒▒███████ ▓▒▓▓ ▓▓▓▓▓▓█▓▓▓▓▓▓▓ ░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▓▓▓▓▓▓▓▓▓▓▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▓▓▓▓▓▓▓▓▒▓▓▓▓▓ ▒
|
||||
# ████░░▓█████ ▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓█████▓████████░▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒█▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▒▓▓▓▓▓ ▓ ▒ ▓▓
|
||||
# ███████████ ▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒ ██████████████▓▓▓▓▓▓▓▓▓▓▓▓▓▓░███▓▓▓▓▓▓▓▓▓▓▓▓▓ ░ ░▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓ ▒ ▒▓▓
|
||||
# ██████████ ▓▒▓▓▓ ▓▓▓▓▓▓▓▓▓ ▓▓▓░░░▒▒▒▒█████████████▓▓▓▓▓▓▓▓▓▓▓▓▓█████████▓▓▓▓▓▓ ░░░ ░▓▓▓▓▓▓▓▒▒▓▓▓▓ ▓█▒▓░ ▒ ▓▓▒▒░▓▓
|
||||
# ███████▒▓▓█ ▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ░░▒▒▒▒▒ ███████████▓▓▓▓▓▓▓▓▓▒██████████████ ░▒ ░░░░ ▓▓▓▓▓▓▓▓▒▓▓▓ ▓ ░ ▒▒▓▓▓▓▓▒▒ ▓ █
|
||||
# ███▓▒ ░▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓░░ ▒▒▒▒█▒▒ ████████████████████████████ ▒▒▒▒▒▒ ░░░░▓▓ ▓▓▓▓▓▓▓▓▓ ▒▓▓▓▓▓▒▒▓ ▓ █
|
||||
# ▒ ▓░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▓▒▒▒▒▒ ████████████████████▒▒▒▒█ ▒▒▒▒▒ █ ▓▓ ▒▒▓▓▓▓▓▓▓ ▒ ▒ ▓▓▓▒▒▒▒▓ █
|
||||
# ████████████▓ ██ ▓▓▓▓▓▓ ▒▒▒▒▒▒ ▒▓ ▒▒ ▒▒ ██████████ ▒▒▒ ▒▒▒▒▒▒▒▒▒▒▒▒▓▓ ▒▒▒▒▓▓▓▓▓▒░▒▓ ▓░▓█ ▒░▓▓▓▒▒▓▓▓░▓ █
|
||||
# ▒ ▒▓▓▓▒ ███████████████████ ▒▓▓▓▓▓▓▓▒▒▒ ▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒░ ░░░░░░▒ ▒▓▒▒▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▓▒▒░▒░▒▓▒▓░ ▓ ▓▓▓▓▒▓▓▓▓ ▓▓█
|
||||
# █▓ ▒▓█▓▓███ ██████▓█████░░▓█████ ░░ ▒▓▓▓▓▓▓▒ ▒▒▒▒▒▒▒▒ ▒ ▒▒▒▒▒▒░░░░░░░░░▒▒░▒▒▒▒▒▒▒▒▒ ▒▒▒▒▒▒▒▒▒ ▒░▒░▒ ░ ▓ ▒▒▓▒▓▒▓▓▓▓▓ █▓█
|
||||
# ██▓▒██░▓██ █████▒▒░▒░██░░░▒█████ ▒▒▒▒▒ ▒▓▓▓▓▓ ▒▒░▒▒ ▒▒▒▒▒▒▒ ▒▒░░░░░░░▒ ▒ ▒▒▒▒░░░ ▒ ▒▒▒▒▒▒▓▓▓▓░░░░░▒ ▓▒▒▓▓▓▒▓▓ █▓░
|
||||
# ██░▒█▓ ▒ ██████░░░░▒▒░░░▓█████ ▓▓▓▓▒ ▒ ░░░░░░▒ ▒▒▒▒▒▒░░▒▒ ░░░░░░░▒▒ ░▒▒▒▒▒░░ ▒▒▒▒▓▓▓▓▓▓░░░ ▒ ▓▓▓▓▓▓▒░▓ ▓▓
|
||||
# ▓▓ ░ ███████░░░░░░░░██████ ▓▓▓▓▓▓ ░░░░░░░▒ ░░░░░░░░ ▒ ░░░▒▒▒▒░▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓▓▓▓▓▓▓▓▓ ▒ ▒▓▓▓▓▓▓▒▓▓ ▓
|
||||
# ░ ████▒░▒█▒░░░░░▒█░▒▒██ ▓▓▓▓▓▓▓ ░░░░░░░ ░░░░░░░░░░ ▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ░ █ █▒▒▒▒▒▓▓▒▓▓▓▓▓▓▒▓▓ ▓
|
||||
# ██░░░░▒███░░░░██▒▓██ ▓▓▓▓▓▓▓▓▓ ░░░░░░░░░░▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ░ █▒▒ ▓▓▓▒▓▓▓▓▓▓▓▓▓▓ ▓
|
||||
# ███▒▒█████████████▓ ▓▓▓▓▓▓▓▓▓▓ ░░░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ░ ▓ █▒▒▓▒▓▓▒▓▓▓▓▓▓▓▓▓▓ ▓
|
||||
# ████████████████ ▒▓▓▓▓▓▓▓▓▓▓▓ ░▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓ ▒ █ ▒▒▒▓▒▓▒▓▓▓▓▓▓▓▓▓▓▓ ▓
|
||||
# ███████████ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓ ▓ ▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓
|
||||
# █▓ ░ ▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓ ▓ ░ ▒▒▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒▓▓
|
||||
# ▓▓▓▓▓▓▓▓▓▓▓▓▓▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓ ▓ ▓▓ ▒▒▓▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
# █▒▒▒▒▒ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ░ ▒▓ ▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
# ███████ ▓██▓▒▒▒ ▓▓▓ ▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ░░░░ ▒▓▓▓▓▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
# ███████▓ ▒███▒▒▒▒ ▓▓▓░▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ░░░░░ ▒▓▓▓▓▒▒▓▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
# ███████████▓ ▒▓▓▓▓▓▓▓▓▓░ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▒ ▓▓▓▓▓▓▓▓▓▓ ░░░ ░▓▓▓▓▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓
|
||||
# ████████▒ ▓▒▓▓▓▓▓▓▓▓▓▓ ░ ▓▓▓▓▓▓▓▓▓▓▒ ▒▓▒▒ ░░ ▓▓▓▒▒▒▒▓▓▒▒▓▓▓▓▓▓▒▓
|
||||
# █████▒ ▒░ ▒░▓▓▓▓▓▒▓▓▓▓ ░░ ▒▓▓▓▓▓▓ ▓▓▓▓▒▓▒▓▓▓▒▒▓▒▓▓▓▓
|
||||
# ██▓ ▒▒▒▒░ ▓▓▓▓▓▓▓▓▓▓ ░ ░▓▓▓ ░ ▓ ▓ ▒▓▓▓▓▒▓▓▓▓▓▓▓
|
||||
# ▒ ▒▒▒▒▒▒░ ▓▒ ▓▓▓▓▓▓▓▓▒▓▒ ░░░░░ ▓▓ ▓ ▒░ ▒▓▓▓▓▓▓▓▓
|
||||
# ░▒▒▒▒▒▒░ ▓▓ ▓▓▓▓▓▓▓▓▓▒ ░░░ ░ ▒▒▒▒▒ ▒▒ ▒▒▒▒▒▒▓▓▓▓▒
|
||||
# ▒▒▒▓█▒▒▒░ ▓ ▓▒▓▓▓▓▓▓▓▓▓▒▓░░ ▒ ▒▓ ▓ ▒▒▒▒▒░▒▓▓▓▓▒
|
||||
# ▒▓██▒▒▒▒ ▓▓▓▓▓▓▓▓▓▒▓▓ ▒▒▒▒▒▒▒▒▒▓▓▓▓▓ ▓▓
|
||||
# ██▓▒▒▒▒ ░▒▓▓▓▓▓▓ ▓▓▓▓▓▒▒▒ ▓▒
|
||||
# █▓▒▒▒▒░ ▓▓▓▓▓ ▓▓▓▓▓▒▒▒ ▒▓▓▒
|
||||
# ▓▒▒▒▒░ ▓▓ ▒▒▒▒▒▒▒░ ▒▒▓
|
||||
# ▒▒▒▒▒ ░▓▓
|
||||
# ▒▒▒▒▒ ▓▓
|
||||
# ▒▒▒▒▒ ▓▓
|
||||
# ▒▒▒▒▒ ▓▓
|
||||
# ▒▒▒▒▒ ▒▓
|
||||
# ▒▒▒▒▒▒ ▓▓
|
||||
# ▒▒▒▒▒▒ ▒ ▓▓
|
||||
# ▒▒▒▒▒▒ ▒▓▓
|
||||
# ▒▒▒▒▒▒░ ▒ ▓▓▓
|
||||
# ▒▒▒▒▒▒▒ ▒▓▓▓
|
||||
# ▓▒▒▒▒▒▓▓ ░▓▓▓▒
|
||||
# ▓▒▒▒▒▓▓▓▒ ▒▓▓▒▒▓
|
||||
112
manifest.yaml
Normal file
112
manifest.yaml
Normal file
@@ -0,0 +1,112 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: pbsupper-deployment-1-secretmap
|
||||
namespace: CHANGE_ME
|
||||
labels:
|
||||
server: Server-1
|
||||
idrac: v7
|
||||
type: Opaque
|
||||
data:
|
||||
IDRAC_PWD: c29tZXRoaW5nQnV0SW5CYXNlNjQ=
|
||||
HOST_USR_PWD: c29tZXRoaW5nQnV0SW5CYXNlNjQ=
|
||||
HOST_USR_SSHKEY_PASS: c29tZXRoaW5nQnV0SW5CYXNlNjQ=
|
||||
|
||||
# Must be base64 of the ENTIRE private key, as one base64 value.
|
||||
# base64 -w 0 id_ed25519
|
||||
HOST_USR_SSHKEY: c29tZXRoaW5nQnV0SW5CYXNlNjQ=
|
||||
|
||||
|
||||
# Alternatively you can also use this option instead of PRIVATE_KEY: 123...
|
||||
|
||||
stringData:
|
||||
PRIVATE_KEY: |
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
123...
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: pbsupper-deployment-1-configmap
|
||||
namespace: CHANGE_ME
|
||||
labels:
|
||||
server: Server-1
|
||||
idrac: v7
|
||||
data:
|
||||
IDRAC: somethingSomething
|
||||
IDRAC_USR: somethingSomething
|
||||
HOST: somethingSomething
|
||||
HOST_SSH_PORT: somethingSomething
|
||||
HOST_USR: somethingSomething
|
||||
LOCAL_DATASTORE: somethingSomething
|
||||
REMOTE_DATASTORE: somethingSomething
|
||||
REMOTE_PBS: somethingSomething
|
||||
SYNC_DIR: somethingSomething
|
||||
PBS_RATE_OUT: somethingSomething
|
||||
PBS_RATE_IN: somethingSomething
|
||||
PBS_TASK_LIMIT: somethingSomething
|
||||
PBS_MAX_RUNTIME_MIN: somethingSomething
|
||||
PBS_MAX_SHUT_MIN: somethingSomething
|
||||
AUTO_ADD_NAME : somethingSomething
|
||||
|
||||
---
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: pbsupper
|
||||
namespace: CHANGE_ME
|
||||
labels:
|
||||
server: Server-1
|
||||
idrac: v7
|
||||
|
||||
spec:
|
||||
schedule: "*/15 * * * *"
|
||||
timeZone: "Europe/Stockholm"
|
||||
concurrencyPolicy: Forbid
|
||||
|
||||
successfulJobsHistoryLimit: 3
|
||||
failedJobsHistoryLimit: 3
|
||||
|
||||
jobTemplate:
|
||||
spec:
|
||||
backoffLimit: 2
|
||||
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
automountServiceAccountToken: false
|
||||
|
||||
securityContext:
|
||||
appArmorProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
containers:
|
||||
- name: pbsupper
|
||||
image: tea.shupogaki.org/yuruc3/pbsupper:v0
|
||||
imagePullPolicy: IfNotPresent
|
||||
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: pbsupper-deployment-1-secretmap
|
||||
- configMapRef:
|
||||
name: pbsupper-deployment-1-configmap
|
||||
|
||||
securityContext:
|
||||
runAsUser: 1600
|
||||
runAsGroup: 1600
|
||||
runAsNonRoot: true
|
||||
allowPrivilegeEscalation: false
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
resources:
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 128Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 64Mi
|
||||
1
requirements.txt
Normal file
1
requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
paramiko==5.0.0
|
||||
Reference in New Issue
Block a user