Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ jobs:
- name: Run bats (oem-brew hook)
run: bats tests/test_oem_brew.bats

- name: Run bats (damask-setup hook)
run: bats tests/test_damask_setup.bats

- name: Run bats (bonedigger report)
run: bats tests/test_bonedigger_report.bats

Expand Down
1 change: 1 addition & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ test:
bats tests/test_hardware_hooks.bats
bats tests/test_theming_hook.bats
bats tests/test_dynamic_wallpaper_hook.bats
bats tests/test_damask_setup.bats
bats tests/test_nvidia_flatpak_sync.bats
bats tests/test_system_just.bats
bats tests/test_brew_tap_trust.bats
Expand Down
14 changes: 12 additions & 2 deletions docs/skills/dconf-consistency.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: dconf-consistency
version: "1.0"
last_updated: "2026-06-23"
version: "1.1"
last_updated: "2026-09-23"
id: dconf-consistency
one_line_purpose: Keep GSettings overrides and dconf lock files in parity.
entry_point: docs/skills/dconf-consistency.md
Expand Down Expand Up @@ -126,3 +126,13 @@ This means any `gsettings get org.gnome.shell enabled-extensions` call in a test
`enabled-extensions` for `org.gnome.shell` is set in `system_files/bluefin/usr/share/glib-2.0/schemas/zz0-bluefin-modifications.gschema.override` — this sets the schema DEFAULT. It is NOT in `distro.d/` and is not a locked key, so users can override it.

The CI's `local.d/00-ci-testing` write overrides it in every test VM. Tests must use `get_default_value()` to validate this config, not `gsettings get`.

## Flatpak application defaults via GKeyfileSettingsBackend

Flatpak applications run sandboxed and typically use `GKeyfileSettingsBackend` rather than the host system's dconf database.

Key differences from host GSettings:
- Config is stored in a keyfile at `~/.var/app/<app-id>/config/glib-2.0/settings/keyfile`.
- Schema separation: root application preferences are separated from plugin sources (e.g., `[app/drey/Damask]` vs `[app/drey/Damask/sources/slideshow]`).
- Pre-seeding defaults cannot be done via `/etc/dconf/db` or `gschema.override`. Instead, pre-seed defaults in `system_files/bluefin/usr/share/ublue-os/user-setup.hooks.d/` using the `version-script` contract.
- Non-destructive activation: check `[[ ! -f "${KEYFILE}" ]]` before writing so existing user configurations are never overwritten, and set inactive or dormant defaults (e.g., `active-source='none'`) when activating by default would override host user desktop settings or timed wallpaper slideshows.
4 changes: 2 additions & 2 deletions docs/skills/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,8 @@
"configuration"
],
"description": "GSettings override and dconf lock file parity rules \u2014 must edit both files together for locked settings. Use when changing any GSettings key, dconf override, or lock file in system_files/.",
"version": "1.0",
"last_updated": "2026-06-23",
"version": "1.1",
"last_updated": "2026-09-23",
"doc_type": "reference"
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
set -euo pipefail

# shellcheck disable=SC1091
source /usr/lib/ublue/setup-services/libsetup.sh

flatpak info app.drey.Damask &>/dev/null || exit 0

version-script damask-setup user 1 || exit 0

SETTINGS_DIR="${HOME}/.var/app/app.drey.Damask/config/glib-2.0/settings"
KEYFILE="${SETTINGS_DIR}/keyfile"

mkdir -p "${SETTINGS_DIR}"

if [[ ! -f "${KEYFILE}" ]]; then
cat > "${KEYFILE}" << 'EOF'
[app/drey/Damask]
refresh-interval='86400'
enable-automatic-refresh=true
run-in-background=true
active-source='none'

[app/drey/Damask/sources/slideshow]
folder-uri='file:///run/host/usr/share/backgrounds/bluefin'
sort-by='random'
EOF
chmod 0644 "${KEYFILE}"
fi

if systemctl --user list-unit-files damask.service &>/dev/null; then
systemctl --user enable damask.service
fi
160 changes: 160 additions & 0 deletions tests/test_damask_setup.bats
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
#!/usr/bin/env bats
# Tests for system_files/bluefin/usr/share/ublue-os/user-setup.hooks.d/25-damask-setup.sh
#
# Run: bats tests/test_damask_setup.bats

REPO_ROOT="$BATS_TEST_DIRNAME/.."
DAMASK_HOOK="${REPO_ROOT}/system_files/bluefin/usr/share/ublue-os/user-setup.hooks.d/25-damask-setup.sh"
LIBSETUP_REAL="${REPO_ROOT}/system_files/shared/usr/lib/ublue/setup-services/libsetup.sh"

WORKDIR=""
PATCHED_HOOK=""

setup() {
WORKDIR="$(mktemp -d)"
mkdir -p "${WORKDIR}/bin" "${WORKDIR}/home"

echo 0 > "${WORKDIR}/systemctl.rc"
echo 0 > "${WORKDIR}/systemctl-list.rc"

cat > "${WORKDIR}/bin/systemctl" << MOCK
#!/bin/bash
echo "\$*" >> "${WORKDIR}/systemctl.log"
if [[ "\$*" == *"list-unit-files damask.service"* ]]; then
exit "\$(cat "${WORKDIR}/systemctl-list.rc" 2>/dev/null || echo 0)"
fi
exit "\$(cat "${WORKDIR}/systemctl.rc")"
MOCK
chmod +x "${WORKDIR}/bin/systemctl"
echo 0 > "${WORKDIR}/flatpak.rc"
cat > "${WORKDIR}/bin/flatpak" << MOCK
#!/bin/bash
echo "\$*" >> "${WORKDIR}/flatpak.log"
exit "\$(cat "${WORKDIR}/flatpak.rc")"
MOCK
chmod +x "${WORKDIR}/bin/flatpak"

# Patch the absolute source path so hook runs against the real libsetup.sh in the repo.
PATCHED_HOOK="${WORKDIR}/25-damask-setup.sh"
sed \
-e "s|source /usr/lib/ublue/setup-services/libsetup.sh|source ${LIBSETUP_REAL}|g" \
"${DAMASK_HOOK}" > "${PATCHED_HOOK}"
chmod +x "${PATCHED_HOOK}"

export PATH="${WORKDIR}/bin:${PATH}"
export HOME="${WORKDIR}/home"
unset XDG_CONFIG_HOME
export SETUP_CHECKER_FILE="${WORKDIR}/setup_versioning.json"
}

teardown() {
rm -rf "${WORKDIR}"
}

@test "25-damask-setup: first run creates keyfile with expected content and permissions" {
run bash "${PATCHED_HOOK}"
[ "${status}" -eq 0 ]

local keyfile="${HOME}/.var/app/app.drey.Damask/config/glib-2.0/settings/keyfile"
[ -f "${keyfile}" ]

# Verify file permissions are 0644
local perms
perms="$(stat -c "%a" "${keyfile}")"
[ "${perms}" = "644" ]

# Verify root preferences section
grep -qx "\[app/drey/Damask\]" "${keyfile}"
grep -qx "refresh-interval='86400'" "${keyfile}"
grep -qx "enable-automatic-refresh=true" "${keyfile}"
grep -qx "run-in-background=true" "${keyfile}"
grep -qx "active-source='none'" "${keyfile}"

# Verify slideshow plugin source section
grep -qx "\[app/drey/Damask/sources/slideshow\]" "${keyfile}"
grep -qx "folder-uri='file:///run/host/usr/share/backgrounds/bluefin'" "${keyfile}"
grep -qx "sort-by='random'" "${keyfile}"
}

@test "25-damask-setup: first run enables damask.service for current user" {
run bash "${PATCHED_HOOK}"
[ "${status}" -eq 0 ]

grep -qx -- "--user enable damask.service" "${WORKDIR}/systemctl.log"
! grep -q -- "--system" "${WORKDIR}/systemctl.log"
}

@test "25-damask-setup: records version 1 under user service namespace" {
run bash "${PATCHED_HOOK}"
[ "${status}" -eq 0 ]
[ "$(jq -r '.version.user."damask-setup"' "${SETUP_CHECKER_FILE}")" = "1" ]
}

@test "25-damask-setup: version-script gate makes a second run a no-op" {
run bash "${PATCHED_HOOK}"
[ "${status}" -eq 0 ]

# Modify the keyfile to verify second run doesn't overwrite it
local keyfile="${HOME}/.var/app/app.drey.Damask/config/glib-2.0/settings/keyfile"
echo "# user modified" >> "${keyfile}"

run bash "${PATCHED_HOOK}"
[ "${status}" -eq 0 ]

# The hook body must not run again: exactly one enable, and no second
# unit-file probe. Counting every logged call would also count the
# list-unit-files probe the first run makes.
[ "$(grep -c -x -- "--user enable damask.service" "${WORKDIR}/systemctl.log")" -eq 1 ]
[ "$(grep -c -x -- "--user list-unit-files damask.service" "${WORKDIR}/systemctl.log")" -eq 1 ]

# user modification should still be present
grep -qx "# user modified" "${keyfile}"
}

@test "25-damask-setup: non-destructive when keyfile already exists before first run" {
local keyfile_dir="${HOME}/.var/app/app.drey.Damask/config/glib-2.0/settings"
mkdir -p "${keyfile_dir}"
cat > "${keyfile_dir}/keyfile" << 'EXISTING'
[app/drey/Damask]
active-source='wallhaven'
EXISTING

run bash "${PATCHED_HOOK}"
[ "${status}" -eq 0 ]

# Existing content preserved
grep -qx "active-source='wallhaven'" "${keyfile_dir}/keyfile"
run grep -qx "active-source='none'" "${keyfile_dir}/keyfile"
[ "${status}" -ne 0 ]

# systemctl was still called
grep -qx -- "--user enable damask.service" "${WORKDIR}/systemctl.log"
}

@test "25-damask-setup: a failing systemctl causes the hook to exit with failure" {
echo 1 > "${WORKDIR}/systemctl.rc"

run bash "${PATCHED_HOOK}"
[ "${status}" -ne 0 ]
}

@test "25-damask-setup: gracefully skips enable if damask.service does not exist" {
echo 1 > "${WORKDIR}/systemctl-list.rc"

run bash "${PATCHED_HOOK}"
[ "${status}" -eq 0 ]

grep -q -- "list-unit-files damask.service" "${WORKDIR}/systemctl.log"
! grep -q -- "--user enable damask.service" "${WORKDIR}/systemctl.log"
}

@test "25-damask-setup: exits cleanly without creating keyfile or stamping when Damask is not installed" {
echo 1 > "${WORKDIR}/flatpak.rc"

run bash "${PATCHED_HOOK}"
[ "${status}" -eq 0 ]

local keyfile="${HOME}/.var/app/app.drey.Damask/config/glib-2.0/settings/keyfile"
[ ! -f "${keyfile}" ]
[ ! -f "${SETUP_CHECKER_FILE}" ]
}
Loading