diff --git a/admin/securedrop_admin/__init__.py b/admin/securedrop_admin/__init__.py index 5c197337a1..c1375c3ec8 100755 --- a/admin/securedrop_admin/__init__.py +++ b/admin/securedrop_admin/__init__.py @@ -1110,6 +1110,19 @@ def get_logs(args: argparse.Namespace) -> int: return 0 +@update_check_required("noble_migration") +def noble_migration(args: argparse.Namespace) -> int: + """Upgrade to Ubuntu Noble""" + sdlog.info("Beginning the upgrade to Ubuntu Noble") + ansible_cmd = ansible_command() + [ + os.path.join(args.ansible_path, "securedrop-noble-migration.yml"), + ] + + subprocess.check_call(ansible_cmd, cwd=args.ansible_path) + sdlog.info("Upgrade to Ubuntu Noble complete!") + return 0 + + def set_default_paths(args: argparse.Namespace) -> argparse.Namespace: if not args.ansible_path: args.ansible_path = args.root + "/install_files/ansible-base" @@ -1209,6 +1222,9 @@ class ArgParseFormatterCombo( parse_logs = subparsers.add_parser("logs", help=get_logs.__doc__) parse_logs.set_defaults(func=get_logs) + parse_noble_migration = subparsers.add_parser("noble_migration", help=noble_migration.__doc__) + parse_noble_migration.set_defaults(func=noble_migration) + parse_reset_ssh = subparsers.add_parser("reset_admin_access", help=reset_admin_access.__doc__) parse_reset_ssh.set_defaults(func=reset_admin_access) diff --git a/install_files/ansible-base/roles/noble-migration/tasks/main.yml b/install_files/ansible-base/roles/noble-migration/tasks/main.yml new file mode 100644 index 0000000000..b9dc47a63d --- /dev/null +++ b/install_files/ansible-base/roles/noble-migration/tasks/main.yml @@ -0,0 +1,92 @@ +--- + +- name: Check migration JSON on mon server + ansible.builtin.slurp: + src: /etc/securedrop-noble-migration-state.json + register: migration_json + ignore_errors: yes + +- name: Skip migration if already done + set_fact: + already_finished: "{{ not migration_json.failed and (migration_json.content | b64decode | from_json)['finished'] == 'Done' }}" + +- name: Perform migration + when: not already_finished + block: + - name: Instruct upgrade to begin + ansible.builtin.copy: + # It's okay to enable both app and mon here to simplify the logic, + # as this only affects the server the file is updated on. + content: | + { + "app": {"enabled": true, "bucket": 5}, + "mon": {"enabled": true, "bucket": 5} + } + dest: /usr/share/securedrop/noble-upgrade.json + + # Start the systemd service manually to avoid waiting for the timer to pick it up + - name: Start upgrade systemd service + ansible.builtin.systemd: + name: securedrop-noble-migration-upgrade + state: started + + # Wait until we've finished the PendingUpdates stage. It's highly unlikely + # we'll ever successfully complete this stage because as soon as the script + # reaches finishes that stage, it reboots. Most likely this step will fail + # as unreachable, which we ignore and wait_for_connection. + - name: Wait for pending updates to be applied + ansible.builtin.wait_for: + path: /etc/securedrop-noble-migration-state.json + search_regex: '"finished":"PendingUpdates"' + sleep: 1 + timeout: 300 + ignore_unreachable: yes + ignore_errors: yes + + - name: Wait for the first reboot + ansible.builtin.wait_for_connection: + connect_timeout: 20 + sleep: 5 + delay: 10 + timeout: 300 + + # Start the systemd service manually to avoid waiting for the timer to pick it up + - name: Resume upgrade systemd service + ansible.builtin.systemd: + name: securedrop-noble-migration-upgrade + state: started + + - debug: + msg: "The upgrade is in progress; it may take up to 30 minutes." + + # Same as above, this will most likely fail as unreachable when the server + # actually reboots. + - name: Wait for system upgrade to noble + ansible.builtin.wait_for: + path: /etc/securedrop-noble-migration-state.json + search_regex: '"finished":"Reboot"' + sleep: 5 + # Should finish in less than 30 minutes + timeout: 1800 + ignore_unreachable: yes + ignore_errors: yes + + - name: Wait for the second reboot + ansible.builtin.wait_for_connection: + connect_timeout: 20 + sleep: 5 + delay: 10 + timeout: 300 + + - name: Re-resume upgrade systemd service + ansible.builtin.systemd: + name: securedrop-noble-migration-upgrade + state: started + + # This final check should actually succeed. + - name: Wait for migration to complete + ansible.builtin.wait_for: + path: /etc/securedrop-noble-migration-state.json + search_regex: '"finished":"Done"' + sleep: 5 + timeout: 300 diff --git a/install_files/ansible-base/securedrop-noble-migration.yml b/install_files/ansible-base/securedrop-noble-migration.yml new file mode 100644 index 0000000000..d3bc3b5ac4 --- /dev/null +++ b/install_files/ansible-base/securedrop-noble-migration.yml @@ -0,0 +1,66 @@ +--- +- name: Disable OSSEC notifications + hosts: securedrop_monitor_server + max_fail_percentage: 0 + any_errors_fatal: yes + environment: + LC_ALL: C + tasks: + - name: Disable OSSEC notifications + ansible.builtin.lineinfile: + path: /var/ossec/etc/ossec.conf + regexp: '7' + line: '15' + register: ossec_config + + - name: Restart OSSEC service + ansible.builtin.systemd: + name: ossec + state: restarted + when: ossec_config.changed + become: yes + +- name: Perform upgrade on application server + hosts: securedrop_application_server + max_fail_percentage: 0 + any_errors_fatal: yes + environment: + LC_ALL: C + roles: + - role: noble-migration + tags: noble-migration + become: yes + +- name: Perform upgrade on monitor server + hosts: securedrop_monitor_server + max_fail_percentage: 0 + any_errors_fatal: yes + environment: + LC_ALL: C + roles: + - role: noble-migration + tags: noble-migration + become: yes + +# This is not really necessary since the mon migration will restore the old +# configuration back, but let's include it for completeness. +- name: Restore OSSEC notifications + hosts: securedrop_monitor_server + max_fail_percentage: 0 + any_errors_fatal: yes + environment: + LC_ALL: C + tasks: + - name: Re-enable OSSEC email alerts + ansible.builtin.lineinfile: + path: /var/ossec/etc/ossec.conf + regexp: '15' + line: '7' + register: ossec_config + + - name: Restart OSSEC service + ansible.builtin.systemd: + name: ossec + state: restarted + when: ossec_config.changed + become: yes