Compare commits

..

7 commits

Author SHA1 Message Date
Izalia Mae dec7c6a674 Merge branch 'dev' into 'main'
version 0.3.1

See merge request pleroma/relay!58
2024-04-02 18:31:22 +00:00
Izalia Mae b1a2989ba7 update installation docs to replace git+http url with pypi package 2024-04-02 14:29:49 -04:00
Izalia Mae fd0b9fb723 add shebang to dev script 2024-04-02 13:56:11 -04:00
Izalia Mae ffed955ab3 add note for changing log-level via cli 2024-04-02 13:38:23 -04:00
Izalia Mae 71de40dfca various cli changes
* ensure database file is correctly named in docker container
* add `--skip-questions` option to setup command
* ask to proceed with setup if private key already exists
2024-04-02 13:36:08 -04:00
Izalia Mae 7064adb000 move dev script out of module 2024-04-02 12:36:06 -04:00
Izalia Mae 437075e512 Fix running via docker 2024-04-02 12:32:49 -04:00
8 changed files with 181 additions and 165 deletions

View file

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

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

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

View file

@ -2,9 +2,21 @@
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 8080:8080 -v activityrelay-data:/data --name activityrelay activityrelay
docker run -it -p target=8080,published=${host}:${port} -v activityrelay-data:/data --name activityrelay activityrelay
;;
uninstall)
@ -22,6 +34,10 @@ case $1 in
docker stop activityrelay
;;
restart)
docker restart activityrelay
;;
manage)
shift
docker exec -it activityrelay python3 -m relay "$@"
@ -61,10 +77,7 @@ 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" "Build the image, create a new container and volume, and run relay setup"
printf "$COLS" "- install [address] [port]" "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,6 +154,8 @@ 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 directly from git
Now simply install ActivityRelay from pypi
pipx install git+https://git.pleroma.social/pleroma/relay@0.3.0
pipx install activityrelay
Or from a cloned git repo.
@ -36,10 +36,9 @@ be installed via [pyenv](https://github.com/pyenv/pyenv).
## Pip
The instructions for installation via pip are very similar to pipx. Installation can be done from
git
The instructions for installation via pip are very similar to pipx
python3 -m pip install git+https://git.pleroma.social/pleroma/relay@0.3.0
python3 -m pip install activityrelay
or a cloned git repo.
@ -58,10 +57,11 @@ 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,
your will be asked to fill out some config options for your relay.
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.
./docker.sh install
./docker.sh install 0.0.0.0 6942
Finally start it up. It will be listening on TCP port 8080.
Finally start it up. It will be listening on TCP localhost:8080 by default.
./docker.sh start

View file

@ -55,7 +55,7 @@ class Application(web.Application):
DEFAULT: Application | None = None
def __init__(self, cfgpath: str | None, dev: bool = False):
def __init__(self, cfgpath: Path | 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.jsonld'
'sq_path': '/data/relay.sqlite3'
}
@ -65,7 +65,7 @@ class Config:
rd_prefix: str = 'activityrelay'
def __init__(self, path: str | None = None, load: bool = False):
def __init__(self, path: Path | None = None, load: bool = False):
self.path = Config.get_config_dir(path)
self.reset()
@ -92,9 +92,12 @@ class Config:
@staticmethod
def get_config_dir(path: str | None = None) -> Path:
if path:
return Path(path).expanduser().resolve()
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()
paths = (
Path("relay.yaml").resolve(),

View file

@ -1,11 +1,10 @@
from __future__ import annotations
import Crypto
import aputils
import asyncio
import click
import json
import os
import platform
import typing
from pathlib import Path
@ -32,139 +31,152 @@ def check_alphanumeric(text: str) -> str:
return text
@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.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.pass_context
def cli(ctx: click.Context, config: str | None) -> None:
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)
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) -> None:
def cli_setup(ctx: click.Context, skip_questions: bool) -> None:
'Generate a new config and create the database'
while True:
ctx.obj.config.domain = click.prompt(
'What domain will the relay be hosted on?',
default = ctx.obj.config.domain
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)
)
if not ctx.obj.config.domain.endswith('example.com'):
break
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
)
click.echo('The domain must not end with "example.com"')
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
)
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.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)
)
ctx.obj.config.port = click.prompt(
'What TCP port should the relay listen on?',
default = ctx.obj.config.port,
type = int
)
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.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_port = click.prompt(
'What port does the server listen on?',
default = ctx.obj.config.rd_port,
type = int
)
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_user = click.prompt(
'Which user will authenticate with the server',
default = ctx.obj.config.rd_user
)
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_pass = click.prompt(
'User password',
hide_input = True,
show_default = False,
default = ctx.obj.config.rd_pass or ""
) or None
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_database = click.prompt(
'Which database number to use?',
default = ctx.obj.config.rd_database,
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.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_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()
ctx.obj.config.save()
config = {
'private-key': aputils.Signer.new('n/a').export()
@ -174,7 +186,11 @@ def cli_setup(ctx: click.Context) -> None:
for key, value in config.items():
conn.put_config(key, value)
if not IS_DOCKER and click.confirm('Relay all setup! Would you like to run it now?'):
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?'):
cli_run.callback() # type: ignore
@ -184,28 +200,13 @@ def cli_setup(ctx: click.Context) -> None:
def cli_run(ctx: click.Context, dev: bool = False) -> None:
'Run the relay'
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".'
)
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".')
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
click.echo('Warning: PyCrypto is old and should be replaced with pycryptodome')
click.echo(pip_command)
cli_setup.callback() # type: ignore
return
ctx.obj['dev'] = dev