mirror of
https://github.com/hrfee/jellyfin-accounts.git
synced 2024-12-22 17:10:11 +00:00
Harvey Tindall
e8ad3f98d6
The admin page now has the option to send an invite to an email address. Since there are now two email types (invites and pw resets), the new sections have been added to config.ini, and email_template and email_plaintext have been renamed to email_html and email_text respectively.
164 lines
5.7 KiB
Python
Executable File
164 lines
5.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import secrets
|
|
import configparser
|
|
import shutil
|
|
import argparse
|
|
import logging
|
|
import threading
|
|
import signal
|
|
import sys
|
|
from pathlib import Path
|
|
from flask import Flask, g
|
|
|
|
parser = argparse.ArgumentParser(description="jellyfin-accounts")
|
|
|
|
parser.add_argument("-c", "--config",
|
|
help="specifies path to configuration file.")
|
|
parser.add_argument("-d", "--data",
|
|
help=("specifies directory to store data in. " +
|
|
"defaults to ~/.jf-accounts."))
|
|
parser.add_argument("--host",
|
|
help="address to host web ui on.")
|
|
parser.add_argument("-p", "--port",
|
|
help="port to host web ui on.")
|
|
parser.add_argument("-g", "--get_policy",
|
|
help=("tool to grab a JF users " +
|
|
"policy (access, perms, etc.) and " +
|
|
"output as json to be used as a user template."),
|
|
action='store_true')
|
|
|
|
args, leftovers = parser.parse_known_args()
|
|
|
|
if args.data is not None:
|
|
data_dir = Path(args.data)
|
|
else:
|
|
data_dir = Path.home() / '.jf-accounts'
|
|
|
|
local_dir = (Path(__file__).parents[2] / 'data').resolve()
|
|
|
|
if not data_dir.exists():
|
|
Path.mkdir(data_dir)
|
|
print(f'Config dir not found, so created at {str(data_dir)}')
|
|
if args.config is None:
|
|
config_path = data_dir / 'config.ini'
|
|
shutil.copy(str(local_dir / 'config-default.ini'),
|
|
str(config_path))
|
|
print("Edit the configuration and restart.")
|
|
raise SystemExit
|
|
else:
|
|
config_path = Path(args.config)
|
|
print(f'config.ini can be found at {str(config_path)}')
|
|
else:
|
|
config_path = data_dir / 'config.ini'
|
|
|
|
config = configparser.RawConfigParser()
|
|
config.read(config_path)
|
|
|
|
def create_log(name):
|
|
log = logging.getLogger(name)
|
|
handler = logging.StreamHandler(sys.stdout)
|
|
if config.getboolean('ui', 'debug'):
|
|
log.setLevel(logging.DEBUG)
|
|
handler.setLevel(logging.DEBUG)
|
|
else:
|
|
log.setLevel(logging.INFO)
|
|
handler.setLevel(logging.INFO)
|
|
fmt = ' %(name)s - %(levelname)s - %(message)s'
|
|
format = logging.Formatter(fmt)
|
|
handler.setFormatter(format)
|
|
log.addHandler(handler)
|
|
log.propagate = False
|
|
return log
|
|
|
|
log = create_log('main')
|
|
email_log = create_log('emails')
|
|
web_log = create_log('waitress')
|
|
auth_log = create_log('auth')
|
|
|
|
if args.host is not None:
|
|
log.debug(f'Using specified host {args.host}')
|
|
config['ui']['host'] = args.host
|
|
if args.port is not None:
|
|
log.debug(f'Using specified port {args.port}')
|
|
config['ui']['port'] = args.port
|
|
|
|
for key in config['files']:
|
|
if config['files'][key] == '':
|
|
log.debug(f'Using default {key}')
|
|
config['files'][key] = str(data_dir / (key + '.json'))
|
|
|
|
if ('email_html' not in config['password_resets'] or
|
|
config['password_resets']['email_html'] == ''):
|
|
log.debug('Using default password reset email HTML template')
|
|
config['password_resets']['email_html'] = str(local_dir / 'email.html')
|
|
if ('email_text' not in config['password_resets'] or
|
|
config['password_resets']['email_text'] == ''):
|
|
log.debug('Using default password reset email plaintext template')
|
|
config['password_resets']['email_text'] = str(local_dir / 'email.txt')
|
|
|
|
if ('email_html' not in config['invite_emails'] or
|
|
config['invite_emails']['email_html'] == ''):
|
|
log.debug('Using default invite email HTML template')
|
|
config['invite_emails']['email_html'] = str(local_dir /
|
|
'invite-email.html')
|
|
if ('email_text' not in config['invite_emails'] or
|
|
config['invite_emails']['email_text'] == ''):
|
|
log.debug('Using default invite email plaintext template')
|
|
config['invite_emails']['email_text'] = str(local_dir /
|
|
'invite-email.txt')
|
|
|
|
|
|
if args.get_policy:
|
|
import json
|
|
from jellyfin_accounts.jf_api import Jellyfin
|
|
jf = Jellyfin(config['jellyfin']['server'],
|
|
config['jellyfin']['client'],
|
|
config['jellyfin']['version'],
|
|
config['jellyfin']['device'],
|
|
config['jellyfin']['device_id'])
|
|
# No auth needed.
|
|
print("Make sure the user is publicly visible!")
|
|
users = jf.getUsers()
|
|
for index, user in enumerate(users):
|
|
print(f'{index+1}) {user["Name"]}')
|
|
success = False
|
|
while not success:
|
|
try:
|
|
policy = users[int(input(">: "))-1]['Policy']
|
|
success = True
|
|
except (ValueError, IndexError):
|
|
pass
|
|
with open(config['files']['user_template'], 'w') as f:
|
|
f.write(json.dumps(policy, indent=4))
|
|
print(f'Policy written to "{config["files"]["user_template"]}".')
|
|
print('In future, this policy will be copied to all new users.')
|
|
else:
|
|
def signal_handler(sig, frame):
|
|
print('Quitting...')
|
|
sys.exit(0)
|
|
|
|
signal.signal(signal.SIGINT, signal_handler)
|
|
signal.signal(signal.SIGTERM, signal_handler)
|
|
app = Flask(__name__, root_path=str(local_dir))
|
|
app.config['DEBUG'] = config.getboolean('ui', 'debug')
|
|
app.config['SECRET_KEY'] = secrets.token_urlsafe(16)
|
|
|
|
if __name__ == '__main__':
|
|
import jellyfin_accounts.web_api
|
|
import jellyfin_accounts.web
|
|
from waitress import serve
|
|
host = config['ui']['host']
|
|
port = config['ui']['port']
|
|
log.info(f'Starting web UI on {host}:{port}')
|
|
if config.getboolean('password_resets', 'enabled'):
|
|
def start_pwr():
|
|
import jellyfin_accounts.pw_reset
|
|
jellyfin_accounts.pw_reset.start()
|
|
pwr = threading.Thread(target=start_pwr, daemon=True)
|
|
log.info('Starting email thread')
|
|
pwr.start()
|
|
|
|
serve(app,
|
|
host=host,
|
|
port=int(port))
|