Merge pull request #290 from naturallaw777/copilot/refactor-gnome-keyring-setup

Refactor GNOME Keyring management to native NixOS tmpfiles
This commit is contained in:
Sovran Systems
2026-04-30 09:17:14 -05:00
committed by GitHub
3 changed files with 32 additions and 76 deletions
+17 -59
View File
@@ -5,6 +5,7 @@ from __future__ import annotations
import asyncio import asyncio
import base64 import base64
import contextlib import contextlib
import glob
import hashlib import hashlib
import hmac import hmac
import json import json
@@ -2060,28 +2061,14 @@ async def api_migration_password_acknowledge():
except Exception as exc: except Exception as exc:
logger.warning("chpasswd exception during migration acknowledge: %s", exc) logger.warning("chpasswd exception during migration acknowledge: %s", exc)
# Wipe the old keyring (encrypted with the pre-migration password) and # Clear only the locked keyring databases, leaving the directory and 'default' pointer intact.
# seed the default pointer so PAM creates a fresh keyring on next login.
keyring_dir = "/home/free/.local/share/keyrings" keyring_dir = "/home/free/.local/share/keyrings"
keyring_files = glob.glob(os.path.join(keyring_dir, "*.keyring"))
for kf in keyring_files:
try: try:
if os.path.isdir(keyring_dir): os.remove(kf)
for entry in os.listdir(keyring_dir): except OSError as exc:
entry_path = os.path.join(keyring_dir, entry) logger.warning("Could not remove old keyring file %s: %s", kf, exc)
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)
# Clear the pending marker # Clear the pending marker
try: try:
@@ -3913,34 +3900,14 @@ async def api_security_reset():
except Exception as exc: except Exception as exc:
errors.append(f"write root-password: {exc}") errors.append(f"write root-password: {exc}")
# Delete GNOME Keyring files so a fresh keyring is created with the new # Clear only the locked keyring databases, leaving the directory and 'default' pointer intact.
# password on the next GDM login (PAM unlocks it automatically).
keyring_dir = "/home/free/.local/share/keyrings" keyring_dir = "/home/free/.local/share/keyrings"
keyring_files = glob.glob(os.path.join(keyring_dir, "*.keyring"))
for kf in keyring_files:
try: try:
if os.path.isdir(keyring_dir): os.remove(kf)
for entry in os.listdir(keyring_dir): except OSError as exc:
entry_path = os.path.join(keyring_dir, entry) errors.append(f"keyring wipe: {kf}: {exc}")
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}")
# The user performed a full security reset — the banner's purpose is served. # The user performed a full security reset — the banner's purpose is served.
try: try:
@@ -4108,21 +4075,12 @@ async def api_change_password(req: ChangePasswordRequest):
except Exception as exc: except Exception as exc:
raise HTTPException(status_code=500, detail=f"Failed to write secrets file: {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 # Clear only the locked keyring databases, leaving the directory and 'default' pointer intact.
# password on the next GDM login (PAM will unlock it automatically).
keyring_dir = "/home/free/.local/share/keyrings" keyring_dir = "/home/free/.local/share/keyrings"
for kf in glob.glob(os.path.join(keyring_dir, "*.keyring")):
try: try:
if os.path.isdir(keyring_dir): os.remove(kf)
for entry in os.listdir(keyring_dir): except OSError:
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 pass # Non-fatal: keyring will be re-created on next login regardless
return {"ok": True} return {"ok": True}
+9
View File
@@ -70,6 +70,15 @@
security.pam.services.gdm-password.enableGnomeKeyring = true; security.pam.services.gdm-password.enableGnomeKeyring = true;
security.pam.services.gdm-autologin.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 ────────────────────────────────────────────────── # ── Audio ──────────────────────────────────────────────────
services.pulseaudio.enable = false; services.pulseaudio.enable = false;
security.rtkit.enable = true; security.rtkit.enable = true;
+2 -13
View File
@@ -33,10 +33,8 @@ let
echo "$NEW_PASS" > "$SECRET_FILE" echo "$NEW_PASS" > "$SECRET_FILE"
chmod 600 "$SECRET_FILE" chmod 600 "$SECRET_FILE"
echo "Password for 'free' updated and saved." echo "Password for 'free' updated and saved."
# Delete the old GNOME Keyring so it is recreated with the new password on next GDM login. # Delete the old GNOME Keyring databases so a fresh one is created on next GDM login.
rm -rf /home/free/.local/share/keyrings/* rm -f /home/free/.local/share/keyrings/*.keyring
# Recreate the default pointer so PAM knows which keyring to unlock.
su - free -c 'echo "login" > ~/.local/share/keyrings/default'
echo "GNOME Keyring files cleared a fresh keyring will be created on next login." echo "GNOME Keyring files cleared a fresh keyring will be created on next login."
''; '';
in in
@@ -184,15 +182,6 @@ in
echo "$FREE_PASS" > "$SECRET_FILE" echo "$FREE_PASS" > "$SECRET_FILE"
chmod 600 "$SECRET_FILE" chmod 600 "$SECRET_FILE"
echo "free:$FREE_PASS" | chpasswd 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
'
''; '';
}; };