Skip to content

Commit

Permalink
unifier v3
Browse files Browse the repository at this point in the history
  • Loading branch information
greeeen-dev committed Sep 18, 2024
1 parent 8f59d48 commit ae6d3f2
Show file tree
Hide file tree
Showing 8 changed files with 523 additions and 38 deletions.
167 changes: 167 additions & 0 deletions boot/bootloader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import os
import sys
import shutil
import json
import time

reinstall = '--reinstall' in sys.argv

install_options = [
{
'id': 'stable',
'name': '\U0001F48E Standard',
'description': 'Uses the latest stable Nextcord version.',
'default': True,
'prefix': '',
'color': '\x1b[32'
}
]

if os.getcwd().endswith('/boot'):
print('\x1b[31;1mYou are running the bootloader directly. Please run the run.sh file instead.\x1b[0m')
sys.exit(1)

with open('boot/internal.json') as file:
internal = json.load(file)


boot_config = {}
try:
with open('boot_config.json') as file:
boot_config = json.load(file)
except:
if os.path.exists('update'):
shutil.copy2('update/boot_config.json', 'boot_config.json')
with open('boot_config.json') as file:
boot_config = json.load(file)

bootloader_config = boot_config.get('bootloader', {})

binary = bootloader_config.get('binary', 'py -3' if sys.platform == 'win32' else 'python3')
options = bootloader_config.get('options')
boot_file = bootloader_config.get('boot_file', internal["base_bootfile"])
autoreboot = bootloader_config.get('autoreboot', False)
threshold = bootloader_config.get('autoreboot_threshold', 60)

if not options:
options = ''
else:
options = ' ' + ' '.join(options)

if not '.install.json' in os.listdir() or reinstall:
if os.path.isdir('update') and not reinstall:
# unifier was likely updated from v2 or older
print('\x1b[33;1mLegacy installation detected, skipping installer.\x1b[0m')
with open('.install.json', 'w+') as file:
json.dump(
{
'product': internal["product"],
'setup': False,
'option': 'optimized'
},
file
)
else:
# this installation is fresh
if not reinstall:
print('\x1b[33;1mInstallation not detected, running installer...\x1b[0m')

if len(install_options) == 1:
install_option = install_options[0]['id']
else:
print(f'\x1b[33;1mYou have {len(install_options)} install options available.\x1b[0m\n')

for index in range(len(install_options)):
option = install_options[index]
print(f'{option["color"]};1m{option["name"]} (option {index})\x1b[0m')
print(f'{option["color"]}m{option["description"]}\x1b[0m')

print(f'\n\x1b[33;1mWhich installation option would you like to install? (0-{len(install_options)-1})\x1b[0m')

try:
install_option = int(input())

if install_option < 0 or install_option >= len(install_options):
raise ValueError()
except:
print(f'\x1b[31;1mAborting.\x1b[0m')
sys.exit(1)

install_option = install_options[install_option]['id']

print('\x1b[33;1mPlease review the following before continuing:\x1b[0m')
print(f'- Product to install: {internal["product_name"]}')
print(f'- Installation option: {install_option}')
print(f'- Install directory: {os.getcwd()}')
print(f'- Python command/binary: {binary}\n')
print('\x1b[33;1mProceed with installation? (y/n)\x1b[0m')

try:
answer = input().lower()
except:
print(f'\x1b[31;1mAborting.\x1b[0m')
sys.exit(1)

if not answer == 'y':
print(f'\x1b[31;1mAborting.\x1b[0m')
sys.exit(1)

exit_code = os.system(f'{binary} boot/dep_installer.py {install_option}{options}')
if not exit_code == 0:
sys.exit(exit_code)

exit_code = os.system(f'{binary} boot/installer.py {install_option}{options}')

if not exit_code == 0:
print('\x1b[31;1mInstaller has crashed or has been aborted.\x1b[0m')
sys.exit(exit_code)

# sleep to prevent 429s
time.sleep(5)

if not boot_file in os.listdir():
if os.path.isdir('update'):
print(f'\x1b[33;1m{boot_file} is missing, copying from update folder.\x1b[0m')
try:
shutil.copy2(f'update/{boot_file}', boot_file)
except:
print(f'\x1b[31;1mCould not find {boot_file}. Your installation may be corrupted.\x1b[0m')
print(f'Please install a fresh copy of {internal["product_name"]} from {internal["repo"]}.')
sys.exit(1)

first_boot = False
last_boot = time.time()

print(f'\x1b[36;1mStarting {internal["product_name"]}...\x1b[0m')

if '.restart' in os.listdir():
os.remove('.restart')
print('\x1b[33;1mAn incomplete restart was detected.\x1b[0m')

while True:
exit_code = os.system(f'{binary} {boot_file}{options}')

crash_reboot = False
if not exit_code == 0:
diff = time.time() - last_boot
if autoreboot and first_boot and diff > threshold:
print(f'\x1b[31;1m{internal["product_name"]} has crashed, restarting...\x1b[0m')
crash_reboot = True
else:
print(f'\x1b[31;1m{internal["product_name"]} has crashed.\x1b[0m')
sys.exit(exit_code)

if crash_reboot or '.restart' in os.listdir():
if '.restart' in os.listdir():
os.remove('.restart')

print(f'\x1b[33;1mRestarting {internal["product_name"]}...\x1b[0m')
else:
print(f'\x1b[36;1m{internal["product_name"]} shutdown successful.\x1b[0m')
sys.exit(0)

first_boot = True
last_boot = time.time()

# sleep to prevent 429s
time.sleep(5)
54 changes: 54 additions & 0 deletions boot/dep_installer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import json
import os
import sys

install_option = sys.argv[1] if len(sys.argv) > 1 else None

install_options = [
{
'id': 'stable',
'name': '\U0001F48E Standard',
'description': 'Uses the latest stable Nextcord version.',
'default': True,
'prefix': '',
'color': '\x1b[32'
}
]

prefix = None

if not install_option:
for option in install_options:
if option['default']:
install_option = option['id']
break
else:
for option in install_options:
if option['id'] == install_option:
prefix = option['prefix']
if prefix == '':
prefix = None
break

boot_config = {}
try:
with open('boot_config.json') as file:
boot_config = json.load(file)
except:
pass

binary = boot_config['bootloader'].get('binary', 'py -3' if sys.platform == 'win32' else 'python3')

print('\x1b[36;1mInstalling dependencies, this may take a while...\x1b[0m')

if prefix:
code = os.system(f'{binary} -m pip install --user -U -r requirements_{prefix}.txt')
else:
code = os.system(f'{binary} -m pip install --user -U -r requirements.txt')

if not code == 0:
print('\x1b[31;1mCould not install dependencies.\x1b[0m')
sys.exit(code)

print('\x1b[36;1mDependencies successfully installed.\x1b[0m')
sys.exit(0)
160 changes: 160 additions & 0 deletions boot/installer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import asyncio
import sys
import getpass
import json
import nextcord
import tomli
import tomli_w
import traceback
from nextcord.ext import commands

install_option = sys.argv[1] if len(sys.argv) > 1 else None

install_options = [
{
'id': 'stable',
'name': '\U0001F48E Standard',
'description': 'Uses the latest stable Nextcord version.',
'default': True,
'prefix': '',
'color': '\x1b[32'
}
]

if not install_option:
for option in install_options:
if option['default']:
install_option = option['id']
break

bot = commands.Bot(
command_prefix='u!',
intents=nextcord.Intents.all()
)

user_id = 0
server_id = 0

with open('boot/internal.json') as file:
internal = json.load(file)

if sys.version_info.minor < internal['required_py_version']:
print(f'\x1b[31;49mCannot install {internal["product_name"]}. Python 3.{internal["required_py_version"]} or later is required.\x1b[0m')
sys.exit(1)

@bot.event
async def on_ready():
global server_id

print(f'\x1b[33;1mIs {bot.user.name} ({bot.user.id}) the correct bot? (y/n)\x1b[0m')
answer = input().lower()

if not answer == 'y':
print(f'\x1b[31;1mAborting.\x1b[0m')
sys.exit(1)

print(f'\x1b[36;1mAttempting to DM user {user_id}...\x1b[0m')

user = bot.get_user(user_id)

for guild in bot.guilds:
for member in guild.members:
if member.id == user_id:
server_id = guild.id
break

if not server_id == 0:
break

available = 10
tries = 0

while True:
try:
await user.send('If you can see this message, please return to the console, then type "y".')
break
except:
tries += 1

if tries >= available:
print(f'\x1b[31;1mCould not DM user after {available} attempts, aborting.\x1b[0m')
sys.exit(1)
if user:
print(f'\x1b[33;1mCould not DM user. Please enable your DMs for a server you and the bot share.\x1b[0m')
else:
print(f'\x1b[33;1mCould not find user. Please add the bot to a server you are in.\x1b[0m')
print(f'Use this link to add the bot: https://discord.com/api/oauth2/authorize?client_id={bot.user.id}&permissions=537259008&scope=bot')
print(f'\x1b[33;1mTrying again in 30 seconds, {available-tries} tries remaining. Press Ctrl+C to abort.\x1b[0m')

try:
await asyncio.sleep(30)
except:
print(f'\x1b[31;1mAborting.\x1b[0m')
sys.exit(1)

print(f'\x1b[33;1mDid you receive a DM from the bot? (y/n)\x1b[0m')
answer = input().lower()

if not answer == 'y':
print(f'\x1b[31;1mAborting.\x1b[0m')
sys.exit(1)

print('\x1b[36;1mOwner verified successfully, closing bot.\x1b[0m')
await bot.close()

print('\x1b[33;1mWe need the ID of the user who will be the instance owner. In most cases this is your user ID.\x1b[0m')
print(f'\x1b[33;1mThe owner will have access to special commands for maintaining your {internal["product_name"]} instance.\x1b[0m')
print('\x1b[33;1mTo copy your ID, go to your Discord settings, then Advanced, then enable Developer mode.\x1b[0m')

while True:
try:
user_id = int(input())
break
except KeyboardInterrupt:
print('\x1b[31;49mAborted.\x1b[0m')
sys.exit(1)
except:
print('\x1b[31;49mThis isn\'t an integer, try again.\x1b[0m')

print('\x1b[33;1mWe will now ask for your bot token.\x1b[0m')
print('\x1b[33;1mThe user verifier will use this token to log on to Discord.\x1b[0m\n')
print(f'\x1b[37;41;1mWARNING: DO NOT SHARE THIS TOKEN, NOT EVEN WITH {internal["maintainer"].upper()}.\x1b[0m')
print(f'\x1b[31;49m{internal["maintainer"]} will NEVER ask for your token. Please keep this token to yourself and only share it with trusted instance maintainers.\x1b[0m')
print('\x1b[31;49mFor security reasons, the installer will hide the input.\x1b[0m')

token = getpass.getpass()

print('\x1b[36;1mStarting bot...\x1b[0m')

try:
bot.run(token)
except:
traceback.print_exc()
print('\x1b[31;49mLogin failed. Perhaps your token is invalid?\x1b[0m')
print('\x1b[31;49mMake sure all privileged intents are enabled for the bot.\x1b[0m')
sys.exit(1)

file = open('.env','w+')
file.write(f'TOKEN={token}')

Check failure

Code scanning / CodeQL

Clear-text storage of sensitive information High

This expression stores
sensitive data (password)
as clear text.
file.close()

with open('config.toml', 'rb') as file:
config = tomli.load(file)

config['roles']['owner'] = user_id
config['moderation']['home_guild'] = server_id

with open('config.toml', 'wb') as file:
tomli_w.dump(config, file)

with open('.install.json','w+') as file:
json.dump(
{
'product': internal["product"],
'setup': False,
'option': install_option
},
file
)

print(f'\x1b[36;1m{internal["product_name"]} installed successfully.\x1b[0m')
8 changes: 8 additions & 0 deletions boot/internal.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"maintainer": "UnifierHQ",
"product": "unifier-micro",
"product_name": "Unifier Micro",
"base_bootfile": "microfier.py",
"repo": "https://github.com/UnifierHQ/unifier-micro",
"required_py_version": 9
}
Loading

0 comments on commit ae6d3f2

Please sign in to comment.