diff --git a/docs/upgrading.rst b/docs/upgrading.rst
index 9155d76909c906b3c35ab166d208aa2cb804ef20..091b9fafe2a468398432e88b6aca4e6f2df0846f 100644
--- a/docs/upgrading.rst
+++ b/docs/upgrading.rst
@@ -58,7 +58,8 @@ these applications form the script manually.
    # Execute the upgrade preparation script
    ./rename-to-stackspin.sh
 
-After this, you need to manually re-install the applications by running
+After this, you need to update secrets and Flux in the cluster by running
+``install/install-stackspin.sh``. Then re-install applications by running
 ``install/install-app.sh <app>`` from the Stackspin repository. See the
 application specific upgrade guides below.
 
@@ -75,12 +76,17 @@ Nextcloud
 ~~~~~~~~~
 
 Your SSO users will have new usernames, because the OIDC provider has been
-renamed from ``oas`` to ``stackspin``. You can choose from these options:
+renamed from ``oas`` to ``stackspin`` and because the new SSO system uses UUIDs
+to uniquely identify users.
+
+You can choose from these options:
 
 1. Manually re-upload and re-share your files after logging into your new user
    for the first time.
 2. It is possible to transfer files from your previous user to the new user. To
-   do so:
+   do so, find your new username. It is visible in Settings -> Sharing behind
+   "Your Federated Cloud ID" after you've logged out and in to Nextcloud with
+   the new SSO (the part *before* the ``@``).
 
    .. code:: bash
 
@@ -89,7 +95,7 @@ renamed from ``oas`` to ``stackspin``. You can choose from these options:
       # Change to the www-data user
       su www-data -s /bin/bash
       # Repeat this command for each username
-      php occ files:transfer-ownership oas-<username> stackspin-<username>
+      php occ files:transfer-ownership oas-<old username> <new user ID>
       # Note: the files are tranferred to a subfolder in the new user's
       # directory
 
diff --git a/install/generate_secrets.py b/install/generate_secrets.py
index bf6cf0e013fa225ba6afb3c40e1690c64c8b33b9..56666eaf24c2f2e7f869df1a1efaef13888f50ad 100644
--- a/install/generate_secrets.py
+++ b/install/generate_secrets.py
@@ -56,16 +56,30 @@ def create_variables_secret(app_name, env):
         with open(variables_filename) as template_file:
             lines = template_file.read()
             secret_name, secret_namespace = get_secret_metadata(lines)
-            if get_kubernetes_secret(secret_name, secret_namespace) is None:
-                print(f"Adding secret {secret_name} in namespace"
-                      f" {secret_namespace} to cluster.")
-                template = env.from_string(lines)
-                store_kubernetes_secret(template.render(), secret_namespace)
+            new_secret_dict = yaml.safe_load(env.from_string(lines).render())
+            current_secret_data = get_kubernetes_secret_data(secret_name,
+                    secret_namespace)
+            if current_secret_data is None:
+                # Create new secret
+                update_secret = False
+            elif current_secret_data.keys() != new_secret_dict['data'].keys():
+                # Update current secret with new keys
+                update_secret = True
+                print(f"Secret {secret_name} in namespace {secret_namespace}"
+                      " already exists. Merging...")
+                # Merge dicts. Values from current_secret_data take precedence
+                new_secret_dict['data'] |= current_secret_data
             else:
+                # Do Nothing
                 print(f"Secret {secret_name} in namespace {secret_namespace}"
-                      " already exists. Not generating new secrets.")
+                      " is already in a good state, doing nothing.")
+                return
+            print(f"Storing secret {secret_name} in namespace"
+                  f" {secret_namespace} in cluster.")
+            store_kubernetes_secret(new_secret_dict, secret_namespace,
+                                    update=update_secret)
     else:
-        print(f'File {variables_filename} does not exist.')
+        print(f'File {variables_filename} does not exist, no action needed')
 
 
 def create_basic_auth_secret(app_name, env):
@@ -77,7 +91,7 @@ def create_basic_auth_secret(app_name, env):
             lines = template_file.read()
             secret_name, secret_namespace = get_secret_metadata(lines)
 
-            if get_kubernetes_secret(secret_name, secret_namespace) is None:
+            if get_kubernetes_secret_data(secret_name, secret_namespace) is None:
                 basic_auth_username = 'admin'
                 basic_auth_password = generate_password(32)
                 basic_auth_htpasswd = gen_htpasswd(
@@ -91,27 +105,28 @@ def create_basic_auth_secret(app_name, env):
                             'pass': basic_auth_password,
                             'htpasswd': basic_auth_htpasswd
                 })
-                store_kubernetes_secret(template.render(), secret_namespace)
+                secret_dict = yaml.safe_load(template.render())
+                store_kubernetes_secret(secret_dict, secret_namespace)
             else:
                 print(f"Secret {secret_name} in namespace {secret_namespace}"
                       " already exists. Not generating new secrets.")
     else:
-        print(f'File {basic_auth_filename} does not exist.')
+        print(f'File {basic_auth_filename} does not exist, no action needed')
 
 def get_secret_metadata(yaml_string):
     """Returns secret name and namespace from metadata field in a yaml string"""
-    secret_info = yaml.safe_load(yaml_string)
-    secret_name = secret_info['metadata']['name']
+    secret_dict = yaml.safe_load(yaml_string)
+    secret_name = secret_dict['metadata']['name']
     # default namespace is flux-system, but other namespace can be
     # provided in secret metadata
-    if 'namespace' in secret_info['metadata']:
-        secret_namespace = secret_info['metadata']['namespace']
+    if 'namespace' in secret_dict['metadata']:
+        secret_namespace = secret_dict['metadata']['namespace']
     else:
         secret_namespace = 'flux-system'
     return secret_name, secret_namespace
 
 
-def get_kubernetes_secret(secret_name, namespace):
+def get_kubernetes_secret_data(secret_name, namespace):
     """Returns the contents of a kubernetes secret or None if the secret does not exist."""
     try:
         secret = API.read_namespaced_secret(secret_name, namespace).data
@@ -122,15 +137,28 @@ def get_kubernetes_secret(secret_name, namespace):
         return None
     return secret
 
-def store_kubernetes_secret(secret_string, namespace):
-    """Converts secret_string into a yaml object and adds it to the cluster"""
-    secret_yaml = yaml.safe_load(secret_string)
+def store_kubernetes_secret(secret_dict, namespace, update=False):
+    """Stores either a new secret in the cluster, or updates an existing one"""
+    api_client = client.api_client.ApiClient()
+    if update:
+        verb = "updated"
+        api_response = patch_kubernetes_secret(secret_dict, namespace)
+    else:
+        verb = "created"
+        api_response = create_from_yaml(
+                api_client,
+                yaml_objects=[secret_dict],
+                namespace=namespace)
+    print(f"Secret {verb} with api response: {api_response}")
+
+def patch_kubernetes_secret(secret_dict, namespace):
+    """Patches secret in the cluster with new data"""
     api_client = client.api_client.ApiClient()
-    api_response = create_from_yaml(
-            api_client,
-            yaml_objects=[secret_yaml],
-            namespace=namespace)
-    print(f"Secret created with api response: {api_response}")
+    api_instance = client.CoreV1Api(api_client)
+    name = secret_dict['metadata']['name']
+    body = {}
+    body['data'] = secret_dict['data']
+    return api_instance.patch_namespaced_secret(name, namespace, body)
 
 def generate_password(length):
     """Generates a password of "length" characters"""