Skip to content
Open
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
16 changes: 16 additions & 0 deletions data/artist-packs/dev.sinty.desktop.artist-pack-install.policy
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE policyconfig PUBLIC "-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
"http://www.freedesktop.org/standards/PolicyKit/1/policyconfig.dtd">
<policyconfig>
<vendor>Singularity</vendor>
<action id="dev.sinty.desktop.artist-pack-install">
<description>Install an Artist Pack</description>
<message>Authentication is required to install wallpaper packages</message>
<icon_name>preferences-desktop-wallpaper-symbolic</icon_name>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
</policyconfig>
55 changes: 55 additions & 0 deletions data/artist-packs/singularity-artist-pack-install
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/bin/sh
# singularity-artist-pack-install -- privileged Artist Pack installer.
#
# Invoked via pkexec (dev.sinty.desktop.artist-pack-install), so it runs as
# root with the CALLER's argv but none of the caller's session state -- no
# GSettings/dconf, no desktop D-Bus session. It therefore never trusts the
# `source` argument at face value: it re-derives the set of currently
# configured apt sources itself (root-owned, on disk) and refuses to act
# unless PACKAGE's live apt candidate actually comes from a URI in that set.
# This is what makes the contract safe even though a pkexec-elevated process
# cannot re-read the desktop user's artist-pack-apt-sources GSettings key the
# way the unprivileged inventory script does.
#
# Usage: singularity-artist-pack-install PACKAGE SOURCE_URI
# Idempotent: installing an already-installed package at the current
# candidate version is a safe no-op (apt-get install's own behaviour);
# re-running this against the same PACKAGE/SOURCE_URI never duplicates state.
set -eu

PACKAGE="${1:-}"
SOURCE_URI="${2:-}"
[ -n "$PACKAGE" ] && [ -n "$SOURCE_URI" ] || {
echo "usage: $0 PACKAGE SOURCE_URI" >&2
exit 2
}
# Package names are a fixed, narrow charset -- reject anything else outright
# rather than letting it reach apt-get as a crafted argument.
case "$PACKAGE" in
*[!a-zA-Z0-9.+-]*|"")
echo "refusing: '$PACKAGE' is not a valid package name" >&2
exit 1
;;
esac

command -v apt-cache >/dev/null 2>&1 && command -v apt-get >/dev/null 2>&1 || {
echo "apt is not available on this system" >&2
exit 1
}

POLICY=$(apt-cache policy "$PACKAGE" 2>/dev/null || true)
CANDIDATE=$(printf '%s' "$POLICY" | awk '/Candidate:/{print $2; exit}')
[ -n "$CANDIDATE" ] && [ "$CANDIDATE" != "(none)" ] || {
echo "refusing: '$PACKAGE' has no installation candidate" >&2
exit 1
}
# The policy block lists one "<priority> <URI> <suite>/<component> ..." line
# per source carrying a version of this package; the candidate's own origin
# must be present among them, and it must match the URI the caller supplied --
# not merely "some configured source carries this package somewhere".
printf '%s' "$POLICY" | grep -F "$SOURCE_URI" >/dev/null || {
echo "refusing: '$PACKAGE' candidate $CANDIDATE is not associated with source $SOURCE_URI" >&2
exit 1
}

exec apt-get install -y --no-install-recommends "$PACKAGE"
89 changes: 89 additions & 0 deletions data/artist-packs/singularity-artist-pack-inventory
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/bin/sh
# singularity-artist-pack-inventory -- list Artist Packs available from the
# distro-configured apt source(s), plus which are already installed.
#
# Unprivileged. Reads dev.sinty.desktop's `artist-pack-apt-sources` key: a
# list of one-line apt sources ("deb URI SUITE COMPONENT..."). ANY package
# published by a configured source counts as an Artist Pack -- this script
# never filters on package name, so a distro (or a third-party repo) is free
# to name its packages however it likes; the configured source IS the trust
# boundary. An empty/unset key means no configured sources, so an empty JSON
# array is printed, not an error -- ArtistPackManager.is_available() already
# gates whether this section is shown at all.
#
# Package-manager specific by necessity (this build targets apt), but the
# CONTRACT is not: fixed stdout schema, no apt-specific fields leak into it.
# A distro on a different package manager supplies its own binary satisfying
# this same contract at the same fixed path (see ArtistPackManager).
#
# Output: a JSON array of {package, title, summary, version, source, installed}
# on stdout, one line at most per package. Exit 0 even when there is nothing
# to report; non-zero is reserved for a genuine failure to query apt.
set -eu

json_escape() {
printf '%s' "$1" | sed -e 's/\\/\\\\/g' -e 's/"/\\"/g' -e ':a;N;$!ba;s/\n/\\n/g'
}

SOURCES_RAW=$(gsettings get dev.sinty.desktop artist-pack-apt-sources 2>/dev/null || printf '@as []')
# gsettings prints a GVariant array literal, e.g. ['deb https://...', ...] --
# one source line per output line.
SOURCE_LINES=$(printf '%s\n' "$SOURCES_RAW" \
| sed -e "s/^@as //" -e "s/^\[//" -e "s/\]\s*$//" \
| tr ',' '\n' \
| sed -e "s/^[[:space:]]*'//" -e "s/'[[:space:]]*$//" \
| grep -v '^[[:space:]]*$' || true)

[ -n "$SOURCE_LINES" ] || { printf '[]\n'; exit 0; }
command -v apt-cache >/dev/null 2>&1 || { printf '[]\n'; exit 0; }
command -v apt-get >/dev/null 2>&1 || { printf '[]\n'; exit 0; }

first=1
printf '['
printf '%s\n' "$SOURCE_LINES" | while IFS= read -r source_line; do
[ -n "$source_line" ] || continue
uri=$(printf '%s' "$source_line" | awk '{print $2}')
suite=$(printf '%s' "$source_line" | awk '{print $3}')
[ -n "$uri" ] && [ -n "$suite" ] || continue

# The index file apt actually resolved for this source, so we read
# exactly what apt itself considers to belong to it -- not a re-derived
# guess at the on-disk lists/ filename.
index_file=$(apt-get indextargets --format '$(FILENAME)' \
"Repo-URI: $uri" "Codename: $suite" 2>/dev/null | head -1)
[ -n "$index_file" ] && [ -r "$index_file" ] || continue

pkg=""
version=""
summary=""
while IFS= read -r line; do
case "$line" in
"Package: "*) pkg=${line#Package: } ;;
"Version: "*) version=${line#Version: } ;;
"Description: "*|"Description-en: "*) summary=${line#*: } ;;
"")
[ -n "$pkg" ] || continue
installed=false
dpkg-query -W -f='${Status}' "$pkg" 2>/dev/null | grep -q '^install ok installed$' && installed=true
[ "$first" = 1 ] || printf ','
first=0
printf '{"package":"%s","title":"%s","summary":"%s","version":"%s","source":"%s","installed":%s}' \
"$(json_escape "$pkg")" "$(json_escape "$pkg")" "$(json_escape "$summary")" \
"$(json_escape "$version")" "$(json_escape "$uri")" "$installed"
pkg=""; version=""; summary=""
;;
esac
done < "$index_file"
# A Packages file with no trailing blank line leaves the last stanza
# unflushed by the loop above.
if [ -n "$pkg" ]; then
installed=false
dpkg-query -W -f='${Status}' "$pkg" 2>/dev/null | grep -q '^install ok installed$' && installed=true
[ "$first" = 1 ] || printf ','
first=0
printf '{"package":"%s","title":"%s","summary":"%s","version":"%s","source":"%s","installed":%s}' \
"$(json_escape "$pkg")" "$(json_escape "$pkg")" "$(json_escape "$summary")" \
"$(json_escape "$version")" "$(json_escape "$uri")" "$installed"
fi
done
printf ']\n'
14 changes: 14 additions & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ singularity_core_sources = files(
'src/core/wallpaper_gallery.vala',
'src/core/wallpaper_rotation_state.vala',
'src/core/wallpaper_rotator.vala',
'src/core/artist_pack_manager.vala',
'src/core/wayland_gamma_backend.vala',
'src/core/shortcut_manager.vala',
'src/core/ush_portal.vala',
Expand Down Expand Up @@ -482,6 +483,19 @@ install_data('data/fan-control/dev.sinty.FanControl.conf',
install_data('data/fan-control/dev.sinty.fan-control.policy',
install_dir: get_option('datadir') / 'polkit-1' / 'actions')

# Reference Artist Pack backend (apt-based). ArtistPackManager resolves its
# two helpers at FIXED /usr/local/bin paths, never via PATH (privilege-
# escalation reasons documented in artist_pack_manager.vala), so this
# installs there directly rather than through get_option('bindir') -- a
# distro on a different package manager supplies its own pair of binaries
# satisfying the same contract at the same two paths instead of using these.
install_data('data/artist-packs/singularity-artist-pack-inventory',
install_dir: '/usr/local/bin', install_mode: 'rwxr-xr-x')
install_data('data/artist-packs/singularity-artist-pack-install',
install_dir: '/usr/local/bin', install_mode: 'rwxr-xr-x')
install_data('data/artist-packs/dev.sinty.desktop.artist-pack-install.policy',
install_dir: get_option('datadir') / 'polkit-1' / 'actions')

wallpaper_collections_test = executable('wallpaper-collections-test',
sources: ['src/core/wallpaper_collections.vala', 'tests/wallpaper_collections_test.vala'],
dependencies: [dependency('gobject-2.0'), dependency('glib-2.0'), dependency('gio-2.0'), gee_dep],
Expand Down
Loading