use click subgroups for cli
This commit is contained in:
parent
51984573da
commit
d9e5b9b6d3
3 changed files with 171 additions and 76 deletions
|
@ -5,6 +5,14 @@ from pathlib import Path
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
|
||||||
|
relay_software_names = [
|
||||||
|
'activityrelay',
|
||||||
|
'aoderelay',
|
||||||
|
'social.seattle.wa.us-relay',
|
||||||
|
'unciarelay'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class DotDict(dict):
|
class DotDict(dict):
|
||||||
def __getattr__(self, k):
|
def __getattr__(self, k):
|
||||||
try:
|
try:
|
||||||
|
|
235
relay/manage.py
235
relay/manage.py
|
@ -9,14 +9,15 @@ import platform
|
||||||
from aiohttp.web import AppRunner, TCPSite
|
from aiohttp.web import AppRunner, TCPSite
|
||||||
from cachetools import LRUCache
|
from cachetools import LRUCache
|
||||||
|
|
||||||
from . import app, misc, views
|
from . import app, misc, views, __version__
|
||||||
from .config import DotDict, RelayConfig
|
from .config import DotDict, RelayConfig, relay_software_names
|
||||||
from .database import RelayDatabase
|
from .database import RelayDatabase
|
||||||
from .misc import check_open_port, follow_remote_actor, unfollow_remote_actor
|
from .misc import check_open_port, follow_remote_actor, unfollow_remote_actor
|
||||||
|
|
||||||
|
|
||||||
@click.group('cli', context_settings={'show_default': True}, invoke_without_command=True)
|
@click.group('cli', context_settings={'show_default': True}, invoke_without_command=True)
|
||||||
@click.option('--config', '-c', default='relay.yaml', help='path to the relay\'s config')
|
@click.option('--config', '-c', default='relay.yaml', help='path to the relay\'s config')
|
||||||
|
@click.version_option(version=__version__, prog_name='ActivityRelay')
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def cli(ctx, config):
|
def cli(ctx, config):
|
||||||
app['is_docker'] = bool(os.environ.get('DOCKER_RUNNING'))
|
app['is_docker'] = bool(os.environ.get('DOCKER_RUNNING'))
|
||||||
|
@ -42,61 +43,42 @@ def cli(ctx, config):
|
||||||
relay_run.callback()
|
relay_run.callback()
|
||||||
|
|
||||||
|
|
||||||
@cli.command('list')
|
@cli.group('inbox')
|
||||||
@click.argument('type', required=False, default='inbox')
|
@click.pass_context
|
||||||
def relay_list(type):
|
def cli_inbox(ctx):
|
||||||
'List all following instances'
|
'Manage the inboxes in the database'
|
||||||
|
pass
|
||||||
assert type in [None, 'inbox', 'ban', 'whitelist']
|
|
||||||
|
|
||||||
config = app['config']
|
|
||||||
database = app['database']
|
|
||||||
|
|
||||||
if not type or type == 'inbox':
|
|
||||||
click.echo('Connected to the following instances or relays:')
|
|
||||||
|
|
||||||
for inbox in database.inboxes:
|
|
||||||
click.echo(f'- {inbox}')
|
|
||||||
|
|
||||||
elif type == 'ban':
|
|
||||||
click.echo('Banned instances:')
|
|
||||||
|
|
||||||
for instance in config.blocked_instances:
|
|
||||||
click.echo(f'- {instance}')
|
|
||||||
|
|
||||||
click.echo('\nBanned software:')
|
|
||||||
|
|
||||||
for software in config.blocked_software:
|
|
||||||
click.echo(f'- {software}')
|
|
||||||
|
|
||||||
elif type == 'whitelist':
|
|
||||||
click.echo('Whitelisted instances:')
|
|
||||||
|
|
||||||
for instance in config.whitelist:
|
|
||||||
click.echo(f'- {instance}')
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command('follow')
|
@cli_inbox.command('list')
|
||||||
|
def cli_inbox_list():
|
||||||
|
'List the connected instances or relays'
|
||||||
|
|
||||||
|
click.echo('Connected to the following instances or relays:')
|
||||||
|
|
||||||
|
for inbox in app['database'].inboxes:
|
||||||
|
click.echo(f'- {inbox}')
|
||||||
|
|
||||||
|
|
||||||
|
@cli_inbox.command('follow')
|
||||||
@click.argument('actor')
|
@click.argument('actor')
|
||||||
def relay_follow(actor):
|
def cli_inbox_follow(actor):
|
||||||
'Follow an actor (Relay must be running)'
|
'Follow an actor (Relay must be running)'
|
||||||
|
|
||||||
loop = asyncio.new_event_loop()
|
run_in_loop(handle_follow_actor, actor)
|
||||||
loop.run_until_complete(handle_follow_actor(actor))
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command('unfollow')
|
@cli_inbox.command('unfollow')
|
||||||
@click.argument('actor')
|
@click.argument('actor')
|
||||||
def relay_follow(actor):
|
def cli_inbox_unfollow(actor):
|
||||||
'Unfollow an actor (Relay must be running)'
|
'Unfollow an actor (Relay must be running)'
|
||||||
|
|
||||||
loop = asyncio.new_event_loop()
|
run_in_loop(handle_unfollow_actor(actor))
|
||||||
loop.run_until_complete(handle_unfollow_actor(actor))
|
|
||||||
|
|
||||||
|
|
||||||
@cli.command('add')
|
@cli_inbox.command('add')
|
||||||
@click.argument('inbox')
|
@click.argument('inbox')
|
||||||
def relay_add(inbox):
|
def cli_inbox_add(inbox):
|
||||||
'Add an inbox to the database'
|
'Add an inbox to the database'
|
||||||
|
|
||||||
database = app['database']
|
database = app['database']
|
||||||
|
@ -122,9 +104,9 @@ def relay_add(inbox):
|
||||||
click.echo(f'Added inbox to the database: {inbox}')
|
click.echo(f'Added inbox to the database: {inbox}')
|
||||||
|
|
||||||
|
|
||||||
@cli.command('remove')
|
@cli_inbox.command('remove')
|
||||||
@click.argument('inbox')
|
@click.argument('inbox')
|
||||||
def relay_remove(inbox):
|
def cli_inbox_remove(inbox):
|
||||||
'Remove an inbox from the database'
|
'Remove an inbox from the database'
|
||||||
|
|
||||||
database = app['database']
|
database = app['database']
|
||||||
|
@ -139,58 +121,158 @@ def relay_remove(inbox):
|
||||||
click.echo(f'Removed inbox from the database: {inbox}')
|
click.echo(f'Removed inbox from the database: {inbox}')
|
||||||
|
|
||||||
|
|
||||||
# todo: add nested groups
|
@cli.group('instance')
|
||||||
@cli.command('ban')
|
def cli_instance():
|
||||||
@click.argument('type')
|
'Manage instance bans'
|
||||||
@click.argument('target')
|
pass
|
||||||
def relay_ban(type, target):
|
|
||||||
'Ban an instance or software'
|
|
||||||
|
|
||||||
assert type in ['instance', 'software']
|
|
||||||
|
@cli_instance.command('list')
|
||||||
|
def cli_instance_list():
|
||||||
|
'List all banned instances'
|
||||||
|
|
||||||
|
click.echo('Banned instances or relays:')
|
||||||
|
|
||||||
|
for domain in app['config'].blocked_instances:
|
||||||
|
click.echo(f'- {domain}')
|
||||||
|
|
||||||
|
|
||||||
|
@cli_instance.command('ban')
|
||||||
|
@click.argument('target')
|
||||||
|
def cli_instance_ban(target):
|
||||||
|
'Ban an instance and remove the associated inbox if it exists'
|
||||||
|
|
||||||
config = app['config']
|
config = app['config']
|
||||||
database = app['database']
|
database = app['database']
|
||||||
inbox = database.get_inbox(target)
|
inbox = database.get_inbox(target)
|
||||||
|
|
||||||
bancmd = getattr(config, f'ban_{type}')
|
if config.ban_instance(target):
|
||||||
|
|
||||||
if bancmd(target):
|
|
||||||
config.save()
|
config.save()
|
||||||
|
|
||||||
if inbox:
|
if inbox:
|
||||||
database.del_inbox(inbox)
|
database.del_inbox(inbox)
|
||||||
database.save()
|
database.save()
|
||||||
|
|
||||||
click.echo(f'Banned {type}: {target}')
|
click.echo(f'Banned instance: {target}')
|
||||||
return
|
return
|
||||||
|
|
||||||
click.echo(f'{type.title()} already banned: {target}')
|
click.echo(f'Instance already banned: {target}')
|
||||||
|
|
||||||
|
|
||||||
@cli.command('unban')
|
@cli_instance.command('unban')
|
||||||
@click.argument('type')
|
|
||||||
@click.argument('target')
|
@click.argument('target')
|
||||||
def relay_unban(type, target):
|
def cli_instance_unban(target):
|
||||||
'Unban an instance or software'
|
'Unban an instance'
|
||||||
|
|
||||||
assert type in ['instance', 'software']
|
|
||||||
|
|
||||||
config = app['config']
|
config = app['config']
|
||||||
database = app['database']
|
|
||||||
|
|
||||||
unbancmd = getattr(config, f'unban_{type}')
|
if config.unban_instance(target):
|
||||||
|
|
||||||
if unbancmd(target):
|
|
||||||
config.save()
|
config.save()
|
||||||
|
|
||||||
return click.echo(f'Unbanned {type}: {target}')
|
click.echo(f'Unbanned instance: {target}')
|
||||||
|
return
|
||||||
|
|
||||||
return click.echo(f'{type.title()} is not banned: {target}')
|
click.echo(f'Instance wasn\'t banned: {target}')
|
||||||
|
|
||||||
|
|
||||||
@cli.command('allow')
|
@cli.group('software')
|
||||||
|
def cli_software():
|
||||||
|
'Manage banned software'
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@cli_software.command('list')
|
||||||
|
def cli_software_list():
|
||||||
|
'List all banned software'
|
||||||
|
|
||||||
|
click.echo('Banned software:')
|
||||||
|
|
||||||
|
for software in app['config'].blocked_software:
|
||||||
|
click.echo(f'- {software}')
|
||||||
|
|
||||||
|
|
||||||
|
@cli_software.command('ban')
|
||||||
|
@click.option('--fetch-nodeinfo/--ignore-nodeinfo', '-f', 'fetch_nodeinfo', default=False,
|
||||||
|
help='Treat NAME like a domain and try to fet the software name from nodeinfo'
|
||||||
|
)
|
||||||
|
@click.argument('name')
|
||||||
|
def cli_software_ban(name, fetch_nodeinfo):
|
||||||
|
'Ban software. Use RELAYS for NAME to ban relays'
|
||||||
|
|
||||||
|
config = app['config']
|
||||||
|
|
||||||
|
if name == 'RELAYS':
|
||||||
|
for name in relay_software_names:
|
||||||
|
config.ban_software(name)
|
||||||
|
|
||||||
|
config.save()
|
||||||
|
return click.echo('Banned all relay software')
|
||||||
|
|
||||||
|
if fetch_nodeinfo:
|
||||||
|
software = run_in_loop(fetch_nodeinfo, name)
|
||||||
|
|
||||||
|
if not software:
|
||||||
|
click.echo(f'Failed to fetch software name from domain: {name}')
|
||||||
|
|
||||||
|
name = software
|
||||||
|
|
||||||
|
if config.ban_software(name):
|
||||||
|
config.save()
|
||||||
|
return click.echo(f'Banned software: {name}')
|
||||||
|
|
||||||
|
click.echo(f'Software already banned: {name}')
|
||||||
|
|
||||||
|
|
||||||
|
@cli_software.command('unban')
|
||||||
|
@click.option('--fetch-nodeinfo/--ignore-nodeinfo', '-f', 'fetch_nodeinfo', default=False,
|
||||||
|
help='Treat NAME like a domain and try to fet the software name from nodeinfo'
|
||||||
|
)
|
||||||
|
@click.argument('name')
|
||||||
|
def cli_software_unban(name, fetch_nodeinfo):
|
||||||
|
'Ban software. Use RELAYS for NAME to unban relays'
|
||||||
|
|
||||||
|
config = app['config']
|
||||||
|
|
||||||
|
if name == 'RELAYS':
|
||||||
|
for name in relay_software_names:
|
||||||
|
config.unban_software(name)
|
||||||
|
|
||||||
|
config.save()
|
||||||
|
return click.echo('Unbanned all relay software')
|
||||||
|
|
||||||
|
if fetch_nodeinfo:
|
||||||
|
software = run_in_loop(fetch_nodeinfo, name)
|
||||||
|
|
||||||
|
if not software:
|
||||||
|
click.echo(f'Failed to fetch software name from domain: {name}')
|
||||||
|
|
||||||
|
name = software
|
||||||
|
|
||||||
|
if config.unban_software(name):
|
||||||
|
config.save()
|
||||||
|
return click.echo(f'Unbanned software: {name}')
|
||||||
|
|
||||||
|
click.echo(f'Software wasn\'t banned: {name}')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@cli.group('whitelist')
|
||||||
|
def cli_whitelist():
|
||||||
|
'Manage the instance whitelist'
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@cli_whitelist.command('list')
|
||||||
|
def cli_whitelist_list():
|
||||||
|
click.echo('Current whitelisted domains')
|
||||||
|
|
||||||
|
for domain in app['config'].whitelist:
|
||||||
|
click.echo(f'- {domain}')
|
||||||
|
|
||||||
|
|
||||||
|
@cli_whitelist.command('add')
|
||||||
@click.argument('instance')
|
@click.argument('instance')
|
||||||
def relay_allow(instance):
|
def cli_whitelist_add(instance):
|
||||||
'Add an instance to the whitelist'
|
'Add an instance to the whitelist'
|
||||||
|
|
||||||
config = app['config']
|
config = app['config']
|
||||||
|
@ -202,9 +284,9 @@ def relay_allow(instance):
|
||||||
click.echo(f'Instance added to the whitelist: {instance}')
|
click.echo(f'Instance added to the whitelist: {instance}')
|
||||||
|
|
||||||
|
|
||||||
@cli.command('deny')
|
@cli_whitelist.command('remove')
|
||||||
@click.argument('instance')
|
@click.argument('instance')
|
||||||
def relay_deny(instance):
|
def cli_whitelist_remove(instance):
|
||||||
'Remove an instance from the whitelist'
|
'Remove an instance from the whitelist'
|
||||||
|
|
||||||
config = app['config']
|
config = app['config']
|
||||||
|
@ -293,6 +375,11 @@ def relay_run():
|
||||||
loop.run_forever()
|
loop.run_forever()
|
||||||
|
|
||||||
|
|
||||||
|
def run_in_loop(func, *args, **kwargs):
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
return loop.run_until_complete(func(*args, **kwargs))
|
||||||
|
|
||||||
|
|
||||||
async def handle_follow_actor(app, target):
|
async def handle_follow_actor(app, target):
|
||||||
config = app['config']
|
config = app['config']
|
||||||
|
|
||||||
|
|
|
@ -136,7 +136,7 @@ async def fetch_actor_key(actor):
|
||||||
async def fetch_nodeinfo(domain):
|
async def fetch_nodeinfo(domain):
|
||||||
nodeinfo_url = None
|
nodeinfo_url = None
|
||||||
|
|
||||||
wk_nodeinfo = await request(f'https://{domain}/.well-known/nodeinfo', sign=False)
|
wk_nodeinfo = await request(f'https://{domain}/.well-known/nodeinfo', sign_headers=False)
|
||||||
|
|
||||||
if not wk_nodeinfo:
|
if not wk_nodeinfo:
|
||||||
return
|
return
|
||||||
|
@ -149,7 +149,7 @@ async def fetch_nodeinfo(domain):
|
||||||
if not nodeinfo_url:
|
if not nodeinfo_url:
|
||||||
return
|
return
|
||||||
|
|
||||||
nodeinfo_data = await request(nodeinfo_url, sign=False)
|
nodeinfo_data = await request(nodeinfo_url, sign_headers=False)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return nodeinfo_data['software']['name']
|
return nodeinfo_data['software']['name']
|
||||||
|
|
Loading…
Reference in a new issue