From 953fb04671c485921d8edb8c0cf848f5e50aad69 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Apr 2026 13:48:25 +0000 Subject: [PATCH 1/2] Initial plan From c450dcab9e5b40f09196787221cd3290c5f5b8e7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Apr 2026 13:52:27 +0000 Subject: [PATCH 2/2] refactor: use systemd.tmpfiles for GNOME Keyring, simplify reset scripts Agent-Logs-Url: https://github.com/naturallaw777/staging_alpha/sessions/71dab9c7-081f-4e45-80c2-080e88ae6207 Co-authored-by: naturallaw777 <99053422+naturallaw777@users.noreply.github.com> --- app/sovran_systemsos_web/server.py | 84 ++++++++---------------------- configuration.nix | 9 ++++ modules/credentials.nix | 15 +----- 3 files changed, 32 insertions(+), 76 deletions(-) diff --git a/app/sovran_systemsos_web/server.py b/app/sovran_systemsos_web/server.py index 7e2a912..7f7f482 100644 --- a/app/sovran_systemsos_web/server.py +++ b/app/sovran_systemsos_web/server.py @@ -5,6 +5,7 @@ from __future__ import annotations import asyncio import base64 import contextlib +import glob import hashlib import hmac import json @@ -2060,28 +2061,14 @@ async def api_migration_password_acknowledge(): except Exception as exc: logger.warning("chpasswd exception during migration acknowledge: %s", exc) - # Wipe the old keyring (encrypted with the pre-migration password) and - # seed the default pointer so PAM creates a fresh keyring on next login. + # Clear only the locked keyring databases, leaving the directory and 'default' pointer intact. keyring_dir = "/home/free/.local/share/keyrings" - try: - if os.path.isdir(keyring_dir): - for entry in os.listdir(keyring_dir): - entry_path = os.path.join(keyring_dir, entry) - try: - if os.path.isfile(entry_path) or os.path.islink(entry_path): - os.unlink(entry_path) - elif os.path.isdir(entry_path): - shutil.rmtree(entry_path, ignore_errors=True) - except Exception: - pass - os.makedirs(keyring_dir, exist_ok=True) - default_file = os.path.join(keyring_dir, "default") - with open(default_file, "w") as f: - f.write("login\n") - free_pw = pwd.getpwnam("free") - os.chown(default_file, free_pw.pw_uid, free_pw.pw_gid) - except Exception as exc: - logger.warning("Keyring wipe exception during migration acknowledge: %s", exc) + keyring_files = glob.glob(os.path.join(keyring_dir, "*.keyring")) + for kf in keyring_files: + try: + os.remove(kf) + except OSError as exc: + logger.warning("Could not remove old keyring file %s: %s", kf, exc) # Clear the pending marker try: @@ -3913,34 +3900,14 @@ async def api_security_reset(): except Exception as exc: errors.append(f"write root-password: {exc}") - # Delete GNOME Keyring files so a fresh keyring is created with the new - # password on the next GDM login (PAM unlocks it automatically). + # Clear only the locked keyring databases, leaving the directory and 'default' pointer intact. keyring_dir = "/home/free/.local/share/keyrings" - try: - if os.path.isdir(keyring_dir): - for entry in os.listdir(keyring_dir): - entry_path = os.path.join(keyring_dir, entry) - try: - if os.path.isfile(entry_path) or os.path.islink(entry_path): - os.unlink(entry_path) - elif os.path.isdir(entry_path): - shutil.rmtree(entry_path, ignore_errors=True) - except Exception: - pass - except Exception as exc: - errors.append(f"keyring wipe: {exc}") - - # Recreate the default pointer so pam_gnome_keyring knows which keyring to - # unlock on the next GDM login without prompting the user. - try: - os.makedirs(keyring_dir, exist_ok=True) - default_file = os.path.join(keyring_dir, "default") - with open(default_file, "w") as f: - f.write("login\n") - free_pw = pwd.getpwnam("free") - os.chown(default_file, free_pw.pw_uid, free_pw.pw_gid) - except Exception as exc: - errors.append(f"keyring default recreation: {exc}") + keyring_files = glob.glob(os.path.join(keyring_dir, "*.keyring")) + for kf in keyring_files: + try: + os.remove(kf) + except OSError as exc: + errors.append(f"keyring wipe: {kf}: {exc}") # The user performed a full security reset — the banner's purpose is served. try: @@ -4108,22 +4075,13 @@ async def api_change_password(req: ChangePasswordRequest): except Exception as exc: raise HTTPException(status_code=500, detail=f"Failed to write secrets file: {exc}") - # Delete GNOME Keyring files so a fresh keyring is created with the new - # password on the next GDM login (PAM will unlock it automatically). + # Clear only the locked keyring databases, leaving the directory and 'default' pointer intact. keyring_dir = "/home/free/.local/share/keyrings" - try: - if os.path.isdir(keyring_dir): - for entry in os.listdir(keyring_dir): - entry_path = os.path.join(keyring_dir, entry) - try: - if os.path.isfile(entry_path) or os.path.islink(entry_path): - os.unlink(entry_path) - elif os.path.isdir(entry_path): - shutil.rmtree(entry_path, ignore_errors=True) - except Exception: - pass - except Exception: - pass # Non-fatal: keyring will be re-created on next login regardless + for kf in glob.glob(os.path.join(keyring_dir, "*.keyring")): + try: + os.remove(kf) + except OSError: + pass # Non-fatal: keyring will be re-created on next login regardless return {"ok": True} diff --git a/configuration.nix b/configuration.nix index 9d26d6c..abb5097 100644 --- a/configuration.nix +++ b/configuration.nix @@ -70,6 +70,15 @@ security.pam.services.gdm-password.enableGnomeKeyring = true; security.pam.services.gdm-autologin.enableGnomeKeyring = true; + # Declaratively guarantee the GNOME Keyring default pointer exists. + # The 'f' directive creates the file only when it is absent, so legacy + # machines that already have a valid pointer are never overwritten. + # The content 'login' tells pam_gnome_keyring which keyring to unlock on login. + systemd.tmpfiles.rules = [ + "d /home/free/.local/share/keyrings 0700 free free -" + "f /home/free/.local/share/keyrings/default 0600 free free - login" + ]; + # ── Audio ────────────────────────────────────────────────── services.pulseaudio.enable = false; security.rtkit.enable = true; diff --git a/modules/credentials.nix b/modules/credentials.nix index e1e33e6..7bb3dbe 100644 --- a/modules/credentials.nix +++ b/modules/credentials.nix @@ -33,10 +33,8 @@ let echo "$NEW_PASS" > "$SECRET_FILE" chmod 600 "$SECRET_FILE" echo "Password for 'free' updated and saved." - # Delete the old GNOME Keyring so it is recreated with the new password on next GDM login. - rm -rf /home/free/.local/share/keyrings/* - # Recreate the default pointer so PAM knows which keyring to unlock. - su - free -c 'echo "login" > ~/.local/share/keyrings/default' + # Delete the old GNOME Keyring databases so a fresh one is created on next GDM login. + rm -f /home/free/.local/share/keyrings/*.keyring echo "GNOME Keyring files cleared — a fresh keyring will be created on next login." ''; in @@ -184,15 +182,6 @@ in echo "$FREE_PASS" > "$SECRET_FILE" chmod 600 "$SECRET_FILE" echo "free:$FREE_PASS" | chpasswd - # Seed the GNOME Keyring default pointer so PAM creates a fresh keyring - # on first login without prompting the user. Guard with -f so we never - # overwrite an existing pointer on legacy machines that already have one. - su - free -c ' - mkdir -p ~/.local/share/keyrings - if [ ! -f ~/.local/share/keyrings/default ]; then - echo "login" > ~/.local/share/keyrings/default - fi - ' ''; };