Compare commits

..

4 commits

Author SHA1 Message Date
Izalia Mae f192f1c35c improve switch-backend command 2024-09-28 09:41:25 -04:00
Izalia Mae 85f062c8f3 don't fix timestamps on postgresql 2024-09-28 06:59:09 -04:00
Izalia Mae 098998084d catch ClientConnectorError when fetching actor 2024-09-28 06:56:27 -04:00
Izalia Mae 5a9d1836d0 disable forwarding of Undo messages 2024-09-19 04:37:54 -04:00
5 changed files with 83 additions and 42 deletions

View file

@ -16,7 +16,8 @@ Run the relay.
## Setup ## Setup
Run the setup wizard to configure your relay. Run the setup wizard to configure your relay. For the PostgreSQL backend, the database has to be
created first.
activityrelay setup activityrelay setup
@ -29,6 +30,16 @@ not specified, the config will get backed up as `relay.backup.yaml` before conve
activityrelay convert --old-config relaycfg.yaml activityrelay convert --old-config relaycfg.yaml
## Switch Backend
Change the database backend from the current one to the other. The config will be updated after
running the command.
Note: If switching to PostgreSQL, make sure the database exists first.
activityrelay switch-backend
## Edit Config ## Edit Config
Open the config file in a text editor. If an editor is not specified with `--editor`, the default Open the config file in a text editor. If an editor is not specified with `--editor`, the default

View file

@ -4,7 +4,7 @@ import secrets
from argon2 import PasswordHasher from argon2 import PasswordHasher
from blib import Date, convert_to_boolean from blib import Date, convert_to_boolean
from bsql import Connection as SqlConnection, Row, Update from bsql import BackendType, Connection as SqlConnection, Row, Update
from collections.abc import Iterator from collections.abc import Iterator
from datetime import datetime, timezone from datetime import datetime, timezone
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING, Any
@ -51,6 +51,17 @@ class Connection(SqlConnection):
yield instance yield instance
def drop_tables(self) -> None:
with self.cursor() as cur:
for table in self.get_tables():
query = f"DROP TABLE IF EXISTS {table}"
if self.database.backend.backend_type == BackendType.POSTGRESQL:
query += " CASCADE"
cur.execute(query)
def fix_timestamps(self) -> None: def fix_timestamps(self) -> None:
for app in self.select('apps').all(schema.App): for app in self.select('apps').all(schema.App):
data = {'created': app.created.timestamp(), 'accessed': app.accessed.timestamp()} data = {'created': app.created.timestamp(), 'accessed': app.accessed.timestamp()}

View file

@ -102,34 +102,7 @@ def cli_setup(ctx: click.Context, skip_questions: bool) -> None:
) )
elif ctx.obj.config.db_type == 'postgres': elif ctx.obj.config.db_type == 'postgres':
ctx.obj.config.pg_name = click.prompt( config_postgresql(ctx.obj.config)
'What is the name of the database?',
default = ctx.obj.config.pg_name
)
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( ctx.obj.config.ca_type = click.prompt(
'Which caching backend?', 'Which caching backend?',
@ -214,21 +187,18 @@ def cli_run(ctx: click.Context, dev: bool = False) -> None:
@cli.command('db-maintenance') @cli.command('db-maintenance')
@click.option('--fix-timestamps', '-t', is_flag = True,
help = 'Make sure timestamps in the database are float values')
@click.pass_context @click.pass_context
def cli_db_maintenance(ctx: click.Context, fix_timestamps: bool) -> None: def cli_db_maintenance(ctx: click.Context) -> None:
'Perform maintenance tasks on the database' 'Perform maintenance tasks on the database'
if fix_timestamps:
with ctx.obj.database.session(True) as conn:
conn.fix_timestamps()
if ctx.obj.config.db_type == "postgres": if ctx.obj.config.db_type == "postgres":
return return
with ctx.obj.database.session(False) as conn: with ctx.obj.database.session(False) as s:
with conn.execute("VACUUM"): with s.transaction():
s.fix_timestamps()
with s.execute("VACUUM"):
pass pass
@ -347,9 +317,24 @@ def cli_switchbackend(ctx: click.Context) -> None:
config = Config(ctx.obj.config.path, load = True) config = Config(ctx.obj.config.path, load = True)
config.db_type = "sqlite" if config.db_type == "postgres" else "postgres" config.db_type = "sqlite" if config.db_type == "postgres" else "postgres"
if config.db_type == "postgres":
if click.confirm("Setup PostgreSQL configuration?"):
config_postgresql(config)
order = ("SQLite", "PostgreSQL")
click.pause("Make sure the database and user already exist before continuing")
else:
order = ("PostgreSQL", "SQLite")
click.echo(f"About to convert from {order[0]} to {order[1]}...")
database = get_database(config, migrate = False) database = get_database(config, migrate = False)
with database.session(True) as new, ctx.obj.database.session(False) as old: with database.session(True) as new, ctx.obj.database.session(False) as old:
if click.confirm("All tables in the destination database will be dropped. Continue?"):
new.drop_tables()
new.create_tables() new.create_tables()
for table in schema.TABLES.keys(): for table in schema.TABLES.keys():
@ -357,7 +342,7 @@ def cli_switchbackend(ctx: click.Context) -> None:
new.insert(table, row).close() new.insert(table, row).close()
config.save() config.save()
click.echo(f"Converted database to {repr(config.db_type)}") click.echo("Done!")
@cli.group('config') @cli.group('config')
@ -1018,6 +1003,35 @@ def cli_whitelist_import(ctx: click.Context) -> None:
click.echo('Imported whitelist from inboxes') click.echo('Imported whitelist from inboxes')
def config_postgresql(config: Config) -> None:
config.pg_name = click.prompt(
'What is the name of the database?',
default = config.pg_name
)
config.pg_host = click.prompt(
'What IP address, hostname, or unix socket does the server listen on?',
default = config.pg_host,
)
config.pg_port = click.prompt(
'What port does the server listen on?',
default = config.pg_port,
type = int
)
config.pg_user = click.prompt(
'Which user will authenticate with the server?',
default = config.pg_user
)
config.pg_pass = click.prompt(
'User password',
hide_input = True,
show_default = False,
default = config.pg_pass or ""
) or None
def main() -> None: def main() -> None:
cli(prog_name='activityrelay') cli(prog_name='activityrelay')

View file

@ -171,9 +171,9 @@ async def handle_follow(view: ActorView, conn: Connection) -> None:
async def handle_undo(view: ActorView, conn: Connection) -> None: async def handle_undo(view: ActorView, conn: Connection) -> None:
# If the object is not a Follow, forward it
if view.message.object['type'] != 'Follow': if view.message.object['type'] != 'Follow':
await handle_forward(view, conn) # forwarding deletes does not work, so don't bother
# await handle_forward(view, conn)
return return
# prevent past unfollows from removing an instance # prevent past unfollows from removing an instance

View file

@ -1,6 +1,7 @@
import aputils import aputils
import traceback import traceback
from aiohttp import ClientConnectorError
from aiohttp.web import Request from aiohttp.web import Request
from blib import HttpError from blib import HttpError
@ -104,6 +105,10 @@ class ActorView(View):
logging.debug('HTTP Status %i: %s', e.status, e.message) logging.debug('HTTP Status %i: %s', e.status, e.message)
raise HttpError(400, 'failed to fetch actor') raise HttpError(400, 'failed to fetch actor')
except ClientConnectorError as e:
logging.warning('Error when trying to fetch actor: %s, %s', self.signature.keyid, str(e))
raise HttpError(400, 'failed to fetch actor')
except Exception: except Exception:
traceback.print_exc() traceback.print_exc()
raise HttpError(500, 'unexpected error when fetching actor') raise HttpError(500, 'unexpected error when fetching actor')