diff --git a/README.md b/README.md index 2f92148..56cc8a8 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,8 @@ The API will be available at `http://localhost:8000` The is also an admin interface at `http://localhost:8000/admin` where you can see what the database contains. +Because of how MCP can't yet handle basic auth, + ## User Management ### Create a User @@ -66,6 +68,18 @@ pip install -e ".[all]" python tools/add_user.py --email user@example.com --password yourpassword --name "Your Name" ``` +### Notes synchronisation + +You can set up notes to be automatically pushed to a git repo whenever they are modified. +Run the following job to do so: + +```bash +python tools/run_celery_task.py notes setup-git-notes --origin ssh://git@github.com/some/repo.git --email bla@ble.com --name +``` + +For this to work you need to make sure you have set up the ssh keys in `secrets` (see the README.md +in that folder), and you will need to add the public key that is generated there to your git server. + ### Authentication The API uses session-based authentication. Login via: diff --git a/src/memory/common/celery_app.py b/src/memory/common/celery_app.py index 19c9cf7..9a36a8a 100644 --- a/src/memory/common/celery_app.py +++ b/src/memory/common/celery_app.py @@ -14,6 +14,7 @@ OBSERVATIONS_ROOT = "memory.workers.tasks.observations" SYNC_NOTES = f"{NOTES_ROOT}.sync_notes" SYNC_NOTE = f"{NOTES_ROOT}.sync_note" +SETUP_GIT_NOTES = f"{NOTES_ROOT}.setup_git_notes" SYNC_OBSERVATION = f"{OBSERVATIONS_ROOT}.sync_observation" SYNC_ALL_COMICS = f"{COMIC_ROOT}.sync_all_comics" SYNC_SMBC = f"{COMIC_ROOT}.sync_smbc" diff --git a/src/memory/workers/tasks/notes.py b/src/memory/workers/tasks/notes.py index 402e461..2a33e8d 100644 --- a/src/memory/workers/tasks/notes.py +++ b/src/memory/workers/tasks/notes.py @@ -7,7 +7,7 @@ import shlex from memory.common import settings from memory.common.db.connection import make_session from memory.common.db.models import Note -from memory.common.celery_app import app, SYNC_NOTE, SYNC_NOTES +from memory.common.celery_app import app, SYNC_NOTE, SYNC_NOTES, SETUP_GIT_NOTES from memory.workers.tasks.content_processing import ( check_content_exists, create_content_hash, @@ -19,15 +19,14 @@ from memory.workers.tasks.content_processing import ( logger = logging.getLogger(__name__) -def git_command(repo_root: pathlib.Path, *args: str): - if not (repo_root / ".git").exists(): +def git_command(repo_root: pathlib.Path, *args: str, force: bool = False): + if not (repo_root / ".git").exists() and not force: return # Properly escape arguments for shell execution escaped_args = [shlex.quote(arg) for arg in args] cmd = f"git -C {shlex.quote(repo_root.as_posix())} {' '.join(escaped_args)}" - logger.info(f"Running git command: {cmd}") res = subprocess.run( cmd, shell=True, @@ -131,3 +130,21 @@ def sync_notes(folder: str): "notes_num": len(all_files), "new_notes": new_notes, } + + +@app.task(name=SETUP_GIT_NOTES) +@safe_task_execution +def setup_git_notes(origin: str, email: str, name: str): + logger.info(f"Setting up git notes in {origin}") + if (settings.NOTES_STORAGE_DIR / ".git").exists(): + logger.info("Git notes already setup") + return {"status": "already_setup"} + + git_command(settings.NOTES_STORAGE_DIR, "init", "-b", "main", force=True) + git_command(settings.NOTES_STORAGE_DIR, "config", "user.email", email) + git_command(settings.NOTES_STORAGE_DIR, "config", "user.name", name) + git_command(settings.NOTES_STORAGE_DIR, "remote", "add", "origin", origin) + git_command(settings.NOTES_STORAGE_DIR, "add", ".") + git_command(settings.NOTES_STORAGE_DIR, "commit", "-m", "Initial commit") + git_command(settings.NOTES_STORAGE_DIR, "push", "-u", "origin", "main") + return {"status": "success"} diff --git a/tools/run_celery_task.py b/tools/run_celery_task.py index 84aace1..6e87f29 100644 --- a/tools/run_celery_task.py +++ b/tools/run_celery_task.py @@ -23,6 +23,7 @@ from typing import Any import click from celery import Celery +from memory.common import settings from memory.common.celery_app import ( SYNC_ALL_ARTICLE_FEEDS, SYNC_ARTICLE_FEED, @@ -47,6 +48,7 @@ from memory.common.celery_app import ( REINGEST_MISSING_CHUNKS, UPDATE_METADATA_FOR_ITEM, UPDATE_METADATA_FOR_SOURCE_ITEMS, + SETUP_GIT_NOTES, app, ) @@ -87,6 +89,9 @@ TASK_MAPPINGS = { "sync_lesswrong": SYNC_LESSWRONG, "sync_lesswrong_post": SYNC_LESSWRONG_POST, }, + "notes": { + "setup_git_notes": SETUP_GIT_NOTES, + }, } QUEUE_MAPPINGS = { "email": "email", @@ -106,7 +111,9 @@ def run_task(app: Celery, category: str, task_name: str, **kwargs) -> str: task_path = TASK_MAPPINGS[category][task_name] queue_name = QUEUE_MAPPINGS.get(category) or category - result = app.send_task(task_path, kwargs=kwargs, queue=queue_name) + result = app.send_task( + task_path, kwargs=kwargs, queue=f"{settings.CELERY_QUEUE_PREFIX}-{queue_name}" + ) return result.id @@ -224,6 +231,23 @@ def ebook_sync_book(ctx, file_path, tags): execute_task(ctx, "ebook", "sync_book", file_path=file_path, tags=tags) +@cli.group() +@click.pass_context +def notes(ctx): + """Notes-related tasks.""" + pass + + +@notes.command("setup-git-notes") +@click.option("--origin", required=True, help="Git origin") +@click.option("--email", required=True, help="Git email") +@click.option("--name", required=True, help="Git name") +@click.pass_context +def notes_setup_git_notes(ctx, origin, email, name): + """Setup git notes.""" + execute_task(ctx, "notes", "setup_git_notes", origin=origin, email=email, name=name) + + @cli.group() @click.pass_context def maintenance(ctx):