diff --git a/selfprivacy_api/app.py b/selfprivacy_api/app.py index fc04aeb..3b575db 100644 --- a/selfprivacy_api/app.py +++ b/selfprivacy_api/app.py @@ -16,6 +16,8 @@ from selfprivacy_api.resources.services import services as api_services from selfprivacy_api.restic_controller.tasks import huey, init_restic +from selfprivacy_api.migrations import run_migrations + swagger_blueprint = get_swaggerui_blueprint( "/api/docs", "/api/swagger.json", config={"app_name": "SelfPrivacy API"} ) @@ -59,7 +61,7 @@ def create_app(test_config=None): def spec(): if app.config["ENABLE_SWAGGER"] == "1": swag = swagger(app) - swag["info"]["version"] = "1.1.0" + swag["info"]["version"] = "1.1.1" swag["info"]["title"] = "SelfPrivacy API" swag["info"]["description"] = "SelfPrivacy API" swag["securityDefinitions"] = { @@ -83,6 +85,7 @@ def create_app(test_config=None): if __name__ == "__main__": monkey.patch_all() created_app = create_app() + run_migrations() huey.start() init_restic() created_app.run(port=5050, debug=False) diff --git a/selfprivacy_api/migrations/__init__.py b/selfprivacy_api/migrations/__init__.py new file mode 100644 index 0000000..32c467f --- /dev/null +++ b/selfprivacy_api/migrations/__init__.py @@ -0,0 +1,31 @@ +from selfprivacy_api.utils import ReadUserData +from selfprivacy_api.migrations.fix_nixos_config_branch import FixNixosConfigBranch + +migrations = [FixNixosConfigBranch()] + + +def run_migrations(): + """ + Go over all migrations. If they are not skipped in userdata file, run them + if the migration needed. + """ + with ReadUserData() as data: + if "api" not in data: + skipped_migrations = [] + elif "skippedMigrations" not in data["api"]: + skipped_migrations = [] + else: + skipped_migrations = data["api"].get("skippedMigrations", []) + + if "DISABLE_ALL" in skipped_migrations: + return + + for migration in migrations: + if migration.get_migration_name() not in skipped_migrations: + try: + if migration.is_migration_needed(): + migration.migrate() + except Exception as e: + print(f"Error while migrating {migration.get_migration_name()}") + print(e) + print("Skipping this migration") diff --git a/selfprivacy_api/migrations/fix_nixos_config_branch.py b/selfprivacy_api/migrations/fix_nixos_config_branch.py new file mode 100644 index 0000000..cb1907d --- /dev/null +++ b/selfprivacy_api/migrations/fix_nixos_config_branch.py @@ -0,0 +1,60 @@ +import os +import subprocess + +from selfprivacy_api.migrations.migration import Migration + + +class FixNixosConfigBranch(Migration): + def get_migration_name(self): + return "fix_nixos_config_branch" + + def get_migration_description(self): + return """Mobile SelfPrivacy app introduced a bug in version 0.4.0. + New servers were initialized with a rolling-testing nixos config branch. + This was fixed in app version 0.4.2, but existing servers were not updated. + This migration fixes this by changing the nixos config branch to master. + """ + + def is_migration_needed(self): + """Check the current branch of /etc/nixos and return True if it is rolling-testing""" + current_working_directory = os.getcwd() + try: + os.chdir("/etc/nixos") + nixos_config_branch = subprocess.check_output( + ["git", "rev-parse", "--abbrev-ref", "HEAD"], start_new_session=True + ) + os.chdir(current_working_directory) + if nixos_config_branch.decode("utf-8").strip() == "rolling-testing": + return True + else: + return False + except subprocess.CalledProcessError: + os.chdir(current_working_directory) + return False + + def migrate(self): + """Affected server pulled the config with the --single-branch flag. + Git config remote.origin.fetch has to be changed, so all branches will be fetched. + Then, fetch all branches, pull and switch to master branch. + """ + print("Fixing Nixos config branch") + current_working_directory = os.getcwd() + try: + os.chdir("/etc/nixos") + + subprocess.check_output( + [ + "git", + "config", + "remote.origin.fetch", + "+refs/heads/*:refs/remotes/origin/*", + ] + ) + subprocess.check_output(["git", "fetch", "--all"]) + subprocess.check_output(["git", "pull"]) + subprocess.check_output(["git", "checkout", "master"]) + os.chdir(current_working_directory) + print("Done") + except subprocess.CalledProcessError: + os.chdir(current_working_directory) + print("Error") diff --git a/selfprivacy_api/migrations/migration.py b/selfprivacy_api/migrations/migration.py new file mode 100644 index 0000000..b8ae261 --- /dev/null +++ b/selfprivacy_api/migrations/migration.py @@ -0,0 +1,28 @@ +from abc import ABC, abstractmethod + +""" +Abstract Migration class +This class is used to define the structure of a migration +Migration has a function is_migration_needed() that returns True or False +Migration has a function migrate() that does the migration +Migration has a function get_migration_name() that returns the migration name +Migration has a function get_migration_description() that returns the migration description +""" + + +class Migration(ABC): + @abstractmethod + def get_migration_name(self): + pass + + @abstractmethod + def get_migration_description(self): + pass + + @abstractmethod + def is_migration_needed(self): + pass + + @abstractmethod + def migrate(self): + pass diff --git a/selfprivacy_api/resources/common.py b/selfprivacy_api/resources/common.py index ba7412c..d771372 100644 --- a/selfprivacy_api/resources/common.py +++ b/selfprivacy_api/resources/common.py @@ -24,4 +24,4 @@ class ApiVersion(Resource): 401: description: Unauthorized """ - return {"version": "1.1.0"} + return {"version": "1.1.1"}