Compare commits

..

No commits in common. "dec7c6a674794866f87bc70724b63f7535d745b7" and "65b6c0a5adeec74f6dbf10999cfada908d9ea1fe" have entirely different histories.

8 changed files with 165 additions and 181 deletions

View file

@ -5,7 +5,7 @@ ENV DOCKER_RUNNING=true
# setup various container properties
VOLUME ["/data"]
CMD ["python3", "-m", "relay", "run"]
CMD ["python3", "-m", "relay"]
EXPOSE 8080/tcp
WORKDIR /opt/activityrelay

View file

@ -2,21 +2,9 @@
case $1 in
install)
if [[ -z ${2#$} ]]; then
host=127.0.0.1
else
host=$2
fi
if [[ -z ${3#$} ]]; then
port=8080
else
port=$3
fi
docker build -f Dockerfile -t activityrelay . && \
docker volume create activityrelay-data && \
docker run -it -p target=8080,published=${host}:${port} -v activityrelay-data:/data --name activityrelay activityrelay
docker run -it -p 8080:8080 -v activityrelay-data:/data --name activityrelay activityrelay
;;
uninstall)
@ -34,10 +22,6 @@ case $1 in
docker stop activityrelay
;;
restart)
docker restart activityrelay
;;
manage)
shift
docker exec -it activityrelay python3 -m relay "$@"
@ -77,7 +61,10 @@ case $1 in
printf "$COLS" "- edit" "Edit the relay's config in \$EDITOR"
printf "$COLS" "- shell" "Drop into a bash shell on the running container"
printf "$COLS" "- rescue" "Drop into a bash shell on a temp container with the data volume mounted"
printf "$COLS" "- install [address] [port]" "Build the image, create a new container and volume, and run relay setup"
printf "$COLS" "- install" "Build the image, create a new container and volume, and run relay setup"
printf "$COLS" "- uninstall" "Delete the relay image, container, and volume"
echo ""
echo "Note: This script may not work. It is recommended to manually install and manage the container if you know what you're doing."
;;
esac

View file

@ -154,8 +154,6 @@ When enabled, instances that try to follow the relay will have to be manually ap
Maximum level of messages to log.
Note: Changing this setting via CLI does not actually take effect until restart.
Valid values: `DEBUG`, `VERBOSE`, `INFO`, `WARNING`, `ERROR`, `CRITICAL`
log-level: INFO

View file

@ -13,9 +13,9 @@ the [official pipx docs](https://pypa.github.io/pipx/installation/) for more in-
python3 -m pip install pipx
Now simply install ActivityRelay from pypi
Now simply install ActivityRelay directly from git
pipx install activityrelay
pipx install git+https://git.pleroma.social/pleroma/relay@0.3.0
Or from a cloned git repo.
@ -36,9 +36,10 @@ be installed via [pyenv](https://github.com/pyenv/pyenv).
## Pip
The instructions for installation via pip are very similar to pipx
The instructions for installation via pip are very similar to pipx. Installation can be done from
git
python3 -m pip install activityrelay
python3 -m pip install git+https://git.pleroma.social/pleroma/relay@0.3.0
or a cloned git repo.
@ -57,11 +58,10 @@ And start the relay when finished
Installation and management via Docker can be handled with the `docker.sh` script. To install
ActivityRelay, run the install command. Once the image is built and the container is created,
you will be asked to fill out some config options for your relay. An address and port can be
specified to change what the relay listens on.
your will be asked to fill out some config options for your relay.
./docker.sh install 0.0.0.0 6942
./docker.sh install
Finally start it up. It will be listening on TCP localhost:8080 by default.
Finally start it up. It will be listening on TCP port 8080.
./docker.sh start

View file

@ -55,7 +55,7 @@ class Application(web.Application):
DEFAULT: Application | None = None
def __init__(self, cfgpath: Path | None, dev: bool = False):
def __init__(self, cfgpath: str | None, dev: bool = False):
web.Application.__init__(self,
middlewares = [
handle_api_path,

View file

@ -33,7 +33,7 @@ else:
DOCKER_VALUES = {
'listen': '0.0.0.0',
'port': 8080,
'sq_path': '/data/relay.sqlite3'
'sq_path': '/data/relay.jsonld'
}
@ -65,7 +65,7 @@ class Config:
rd_prefix: str = 'activityrelay'
def __init__(self, path: Path | None = None, load: bool = False):
def __init__(self, path: str | None = None, load: bool = False):
self.path = Config.get_config_dir(path)
self.reset()
@ -92,12 +92,9 @@ class Config:
@staticmethod
def get_config_dir(path: Path | str | None = None) -> Path:
if isinstance(path, str):
path = Path(path)
if path is not None:
return path.expanduser().resolve()
def get_config_dir(path: str | None = None) -> Path:
if path:
return Path(path).expanduser().resolve()
paths = (
Path("relay.yaml").resolve(),

17
dev.py → relay/dev.py Executable file → Normal file
View file

@ -1,4 +1,3 @@
#!/usr/bin/env python3
import click
import platform
import shutil
@ -8,10 +7,12 @@ import time
from datetime import datetime, timedelta
from pathlib import Path
from relay import __version__, logger as logging
from tempfile import TemporaryDirectory
from typing import Sequence
from . import __version__
from . import logger as logging
try:
from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler
@ -21,7 +22,8 @@ except ImportError:
pass
REPO = Path(__file__).parent
SCRIPT = Path(__file__).parent
REPO = SCRIPT.parent
IGNORE_EXT = {
'.py',
'.pyc'
@ -56,8 +58,8 @@ def cli_lint(path: Path, strict: bool, watch: bool) -> None:
handle_run_watcher([sys.executable, "-m", "relay.dev", "lint", str(path)], wait = True)
return
flake8 = [sys.executable, '-m', 'flake8', "dev.py", str(path)]
mypy = [sys.executable, '-m', 'mypy', "dev.py", str(path)]
flake8 = [sys.executable, '-m', 'flake8', str(path)]
mypy = [sys.executable, '-m', 'mypy', str(path)]
if strict:
mypy.append('--strict')
@ -106,7 +108,7 @@ def cli_build():
cmd.append('--console')
# putting the spec path on a different drive than the source dir breaks
if str(REPO)[0] == tmp[0]:
if str(SCRIPT)[0] == tmp[0]:
cmd.extend(['--specpath', tmp])
else:
@ -134,7 +136,7 @@ def handle_run_watcher(*commands: Sequence[str], wait: bool = False):
handler.run_procs()
watcher = Observer()
watcher.schedule(handler, str(REPO), recursive=True)
watcher.schedule(handler, str(SCRIPT), recursive=True)
watcher.start()
try:
@ -149,6 +151,7 @@ def handle_run_watcher(*commands: Sequence[str], wait: bool = False):
watcher.join()
class WatchHandler(PatternMatchingEventHandler):
patterns = ['*.py']

View file

@ -1,10 +1,11 @@
from __future__ import annotations
import Crypto
import aputils
import asyncio
import click
import json
import os
import platform
import typing
from pathlib import Path
@ -31,152 +32,139 @@ def check_alphanumeric(text: str) -> str:
return text
@click.group('cli', context_settings = {'show_default': True})
@click.option('--config', '-c', type = Path, help = 'path to the relay\'s config')
@click.version_option(version = __version__, prog_name = 'ActivityRelay')
@click.group('cli', context_settings={'show_default': True}, invoke_without_command=True)
@click.option('--config', '-c', help='path to the relay\'s config')
@click.version_option(version=__version__, prog_name='ActivityRelay')
@click.pass_context
def cli(ctx: click.Context, config: Path | None) -> None:
if IS_DOCKER:
config = Path("/data/relay.yaml")
# The database was named "relay.jsonld" even though it's an sqlite file. Fix it.
db = Path('/data/relay.sqlite3')
wrongdb = Path('/data/relay.jsonld')
if wrongdb.exists() and not db.exists():
try:
with wrongdb.open('rb') as fd:
json.load(fd)
except json.JSONDecodeError:
wrongdb.rename(db)
def cli(ctx: click.Context, config: str | None) -> None:
ctx.obj = Application(config)
if not ctx.invoked_subcommand:
if ctx.obj.config.domain.endswith('example.com'):
cli_setup.callback() # type: ignore
else:
click.echo(
'[DEPRECATED] Running the relay without the "run" command will be removed in the ' +
'future.'
)
cli_run.callback() # type: ignore
@cli.command('setup')
@click.option('--skip-questions', '-s', is_flag = True, help = 'Just setup the database')
@click.pass_context
def cli_setup(ctx: click.Context, skip_questions: bool) -> None:
def cli_setup(ctx: click.Context) -> None:
'Generate a new config and create the database'
if ctx.obj.signer is not None:
if not click.prompt('The database is already setup. Are you sure you want to continue?'):
return
if skip_questions and ctx.obj.config.domain.endswith('example.com'):
click.echo('You cannot skip the questions if the relay is not configured yet')
return
if not skip_questions:
while True:
ctx.obj.config.domain = click.prompt(
'What domain will the relay be hosted on?',
default = ctx.obj.config.domain
)
if not ctx.obj.config.domain.endswith('example.com'):
break
click.echo('The domain must not end with "example.com"')
if not IS_DOCKER:
ctx.obj.config.listen = click.prompt(
'Which address should the relay listen on?',
default = ctx.obj.config.listen
)
ctx.obj.config.port = click.prompt(
'What TCP port should the relay listen on?',
default = ctx.obj.config.port,
type = int
)
ctx.obj.config.db_type = click.prompt(
'Which database backend will be used?',
default = ctx.obj.config.db_type,
type = click.Choice(['postgres', 'sqlite'], case_sensitive = False)
while True:
ctx.obj.config.domain = click.prompt(
'What domain will the relay be hosted on?',
default = ctx.obj.config.domain
)
if ctx.obj.config.db_type == 'sqlite' and not IS_DOCKER:
ctx.obj.config.sq_path = click.prompt(
'Where should the database be stored?',
default = ctx.obj.config.sq_path
)
if not ctx.obj.config.domain.endswith('example.com'):
break
elif ctx.obj.config.db_type == 'postgres':
ctx.obj.config.pg_name = click.prompt(
'What is the name of the database?',
default = ctx.obj.config.pg_name
)
click.echo('The domain must not end with "example.com"')
ctx.obj.config.pg_host = click.prompt(
'What IP address, hostname, or unix socket does the server listen on?',
default = ctx.obj.config.pg_host,
type = int
)
ctx.obj.config.pg_port = click.prompt(
'What port does the server listen on?',
default = ctx.obj.config.pg_port,
type = int
)
ctx.obj.config.pg_user = click.prompt(
'Which user will authenticate with the server?',
default = ctx.obj.config.pg_user
)
ctx.obj.config.pg_pass = click.prompt(
'User password',
hide_input = True,
show_default = False,
default = ctx.obj.config.pg_pass or ""
) or None
ctx.obj.config.ca_type = click.prompt(
'Which caching backend?',
default = ctx.obj.config.ca_type,
type = click.Choice(['database', 'redis'], case_sensitive = False)
if not IS_DOCKER:
ctx.obj.config.listen = click.prompt(
'Which address should the relay listen on?',
default = ctx.obj.config.listen
)
if ctx.obj.config.ca_type == 'redis':
ctx.obj.config.rd_host = click.prompt(
'What IP address, hostname, or unix socket does the server listen on?',
default = ctx.obj.config.rd_host
)
ctx.obj.config.port = click.prompt(
'What TCP port should the relay listen on?',
default = ctx.obj.config.port,
type = int
)
ctx.obj.config.rd_port = click.prompt(
'What port does the server listen on?',
default = ctx.obj.config.rd_port,
type = int
)
ctx.obj.config.db_type = click.prompt(
'Which database backend will be used?',
default = ctx.obj.config.db_type,
type = click.Choice(['postgres', 'sqlite'], case_sensitive = False)
)
ctx.obj.config.rd_user = click.prompt(
'Which user will authenticate with the server',
default = ctx.obj.config.rd_user
)
if ctx.obj.config.db_type == 'sqlite':
ctx.obj.config.sq_path = click.prompt(
'Where should the database be stored?',
default = ctx.obj.config.sq_path
)
ctx.obj.config.rd_pass = click.prompt(
'User password',
hide_input = True,
show_default = False,
default = ctx.obj.config.rd_pass or ""
) or None
elif ctx.obj.config.db_type == 'postgres':
ctx.obj.config.pg_name = click.prompt(
'What is the name of the database?',
default = ctx.obj.config.pg_name
)
ctx.obj.config.rd_database = click.prompt(
'Which database number to use?',
default = ctx.obj.config.rd_database,
type = int
)
ctx.obj.config.pg_host = click.prompt(
'What IP address, hostname, or unix socket does the server listen on?',
default = ctx.obj.config.pg_host,
type = int
)
ctx.obj.config.rd_prefix = click.prompt(
'What text should each cache key be prefixed with?',
default = ctx.obj.config.rd_database,
type = check_alphanumeric
)
ctx.obj.config.pg_port = click.prompt(
'What port does the server listen on?',
default = ctx.obj.config.pg_port,
type = int
)
ctx.obj.config.save()
ctx.obj.config.pg_user = click.prompt(
'Which user will authenticate with the server?',
default = ctx.obj.config.pg_user
)
ctx.obj.config.pg_pass = click.prompt(
'User password',
hide_input = True,
show_default = False,
default = ctx.obj.config.pg_pass or ""
) or None
ctx.obj.config.ca_type = click.prompt(
'Which caching backend?',
default = ctx.obj.config.ca_type,
type = click.Choice(['database', 'redis'], case_sensitive = False)
)
if ctx.obj.config.ca_type == 'redis':
ctx.obj.config.rd_host = click.prompt(
'What IP address, hostname, or unix socket does the server listen on?',
default = ctx.obj.config.rd_host
)
ctx.obj.config.rd_port = click.prompt(
'What port does the server listen on?',
default = ctx.obj.config.rd_port,
type = int
)
ctx.obj.config.rd_user = click.prompt(
'Which user will authenticate with the server',
default = ctx.obj.config.rd_user
)
ctx.obj.config.rd_pass = click.prompt(
'User password',
hide_input = True,
show_default = False,
default = ctx.obj.config.rd_pass or ""
) or None
ctx.obj.config.rd_database = click.prompt(
'Which database number to use?',
default = ctx.obj.config.rd_database,
type = int
)
ctx.obj.config.rd_prefix = click.prompt(
'What text should each cache key be prefixed with?',
default = ctx.obj.config.rd_database,
type = check_alphanumeric
)
ctx.obj.config.save()
config = {
'private-key': aputils.Signer.new('n/a').export()
@ -186,11 +174,7 @@ def cli_setup(ctx: click.Context, skip_questions: bool) -> None:
for key, value in config.items():
conn.put_config(key, value)
if IS_DOCKER:
click.echo("Relay all setup! Start the container to run the relay.")
return
if click.confirm('Relay all setup! Would you like to run it now?'):
if not IS_DOCKER and click.confirm('Relay all setup! Would you like to run it now?'):
cli_run.callback() # type: ignore
@ -200,13 +184,28 @@ def cli_setup(ctx: click.Context, skip_questions: bool) -> None:
def cli_run(ctx: click.Context, dev: bool = False) -> None:
'Run the relay'
if ctx.obj.config.domain.endswith('example.com') or ctx.obj.signer is None:
if not IS_DOCKER:
click.echo('Relay is not set up. Please run "activityrelay setup".')
if ctx.obj.config.domain.endswith('example.com') or not ctx.obj.signer:
click.echo(
'Relay is not set up. Please edit your relay config or run "activityrelay setup".'
)
return
vers_split = platform.python_version().split('.')
pip_command = 'pip3 uninstall pycrypto && pip3 install pycryptodome'
if Crypto.__version__ == '2.6.1':
if int(vers_split[1]) > 7:
click.echo(
'Error: PyCrypto is broken on Python 3.8+. Please replace it with pycryptodome ' +
'before running again. Exiting...'
)
click.echo(pip_command)
return
cli_setup.callback() # type: ignore
click.echo('Warning: PyCrypto is old and should be replaced with pycryptodome')
click.echo(pip_command)
return
ctx.obj['dev'] = dev