Back up portal configuration during explicit development upgrades
This commit is contained in:
1 parent
35537b32c8
commit
46d33751c6
4 files changed
+119
-8
No files matched your search
@@ -267,6 +267,12 @@ git pull --ff-only
|
|||||||
Log out and back in to use the new release. Running sessions keep their existing
|
Log out and back in to use the new release. Running sessions keep their existing
|
||||||
processes and release paths until restarted.
|
processes and release paths until restarted.
|
||||||
|
|
||||||
|
The installer records shipped portal configuration with each release for future
|
||||||
|
upgrades. Modified or untracked administrator files stop installation. To replace
|
||||||
|
Ferese portal configuration from an earlier development install, use
|
||||||
|
`./scripts/install.sh --skip-build --replace-portal-config` after building. Existing
|
||||||
|
files are backed up under the new release's `portal-config.previous/` directory.
|
||||||
|
|
||||||
Installer options:
|
Installer options:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
@@ -8,8 +8,21 @@ session_entry_matches() {
|
|||||||
cmp -s -- <(sed '/^Icon=/d' "$1") <(sed '/^Icon=/d' "$2")
|
cmp -s -- <(sed '/^Icon=/d' "$1") <(sed '/^Icon=/d' "$2")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
managed_file_matches() {
|
||||||
|
local relative=$1 destination=$2
|
||||||
|
cmp -s -- "$repo_dir/packaging/$relative" "$destination" ||
|
||||||
|
cmp -s -- "$install_root/current/installer-files/$relative" "$destination"
|
||||||
|
}
|
||||||
|
|
||||||
repo_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
repo_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
release_id=${1:?usage: install-session.sh RELEASE_ID}
|
release_id=${1:?usage: install-session.sh RELEASE_ID [--replace-portal-config]}
|
||||||
|
replace_portal_config=false
|
||||||
|
if [[ ${2:-} == --replace-portal-config && $# == 2 ]]; then
|
||||||
|
replace_portal_config=true
|
||||||
|
elif (($# > 1)); then
|
||||||
|
echo 'usage: install-session.sh RELEASE_ID [--replace-portal-config]' >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
[[ $release_id =~ ^[a-zA-Z0-9][a-zA-Z0-9._-]*$ ]] || exit 2
|
[[ $release_id =~ ^[a-zA-Z0-9][a-zA-Z0-9._-]*$ ]] || exit 2
|
||||||
[[ $EUID == 0 ]] || { echo 'Run this installer with sudo.' >&2; exit 1; }
|
[[ $EUID == 0 ]] || { echo 'Run this installer with sudo.' >&2; exit 1; }
|
||||||
|
|
||||||
@@ -64,12 +77,14 @@ for entry in \
|
|||||||
'ferese-portals.conf:ferese-portals.conf'; do
|
'ferese-portals.conf:ferese-portals.conf'; do
|
||||||
source=${entry%%:*}
|
source=${entry%%:*}
|
||||||
destination=/usr/share/xdg-desktop-portal/${entry#*:}
|
destination=/usr/share/xdg-desktop-portal/${entry#*:}
|
||||||
if [[ -e $destination ]] && ! cmp -s "$repo_dir/packaging/portal/$source" "$destination"; then
|
if [[ -e $destination ]] && ! managed_file_matches "portal/$source" "$destination" && ! $replace_portal_config; then
|
||||||
echo "Unmanaged portal configuration exists: $destination" >&2; exit 1
|
echo "Unmanaged portal configuration exists: $destination" >&2
|
||||||
|
echo 'Use --replace-portal-config to back up and replace Ferese portal configuration.' >&2
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
service=/usr/local/share/dbus-1/services/org.freedesktop.impl.portal.desktop.ferese.service
|
service=/usr/local/share/dbus-1/services/org.freedesktop.impl.portal.desktop.ferese.service
|
||||||
if [[ -e $service ]] && ! cmp -s "$repo_dir/packaging/portal/$(basename "$service")" "$service"; then
|
if [[ -e $service ]] && ! managed_file_matches "portal/$(basename "$service")" "$service"; then
|
||||||
# Upgrade the exact activation file shipped before systemd supervision.
|
# Upgrade the exact activation file shipped before systemd supervision.
|
||||||
if ! cmp -s <(printf '%s\n' '[D-BUS Service]' \
|
if ! cmp -s <(printf '%s\n' '[D-BUS Service]' \
|
||||||
'Name=org.freedesktop.impl.portal.desktop.ferese' \
|
'Name=org.freedesktop.impl.portal.desktop.ferese' \
|
||||||
@@ -79,12 +94,12 @@ if [[ -e $service ]] && ! cmp -s "$repo_dir/packaging/portal/$(basename "$servic
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
portal_unit=/usr/local/lib/systemd/user/xdg-desktop-portal-ferese.service
|
portal_unit=/usr/local/lib/systemd/user/xdg-desktop-portal-ferese.service
|
||||||
if [[ -e $portal_unit ]] && ! cmp -s "$repo_dir/packaging/systemd/xdg-desktop-portal-ferese.service" "$portal_unit"; then
|
if [[ -e $portal_unit ]] && ! managed_file_matches "systemd/xdg-desktop-portal-ferese.service" "$portal_unit"; then
|
||||||
echo "Unmanaged portal unit exists: $portal_unit" >&2; exit 1
|
echo "Unmanaged portal unit exists: $portal_unit" >&2; exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
session_target=/usr/local/lib/systemd/user/ferese-session.target
|
session_target=/usr/local/lib/systemd/user/ferese-session.target
|
||||||
if [[ -e $session_target ]] && ! cmp -s "$repo_dir/packaging/systemd/ferese-session.target" "$session_target"; then
|
if [[ -e $session_target ]] && ! managed_file_matches "systemd/ferese-session.target" "$session_target"; then
|
||||||
echo "Unmanaged session target exists: $session_target" >&2; exit 1
|
echo "Unmanaged session target exists: $session_target" >&2; exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -92,6 +107,20 @@ desktop-file-validate "$repo_dir/packaging/ferese.desktop"
|
|||||||
desktop-file-validate "$repo_dir/packaging/dev.ferese.Settings.desktop"
|
desktop-file-validate "$repo_dir/packaging/dev.ferese.Settings.desktop"
|
||||||
|
|
||||||
install -d -m 0755 -- "$release_dir" /usr/local/bin /usr/share/wayland-sessions
|
install -d -m 0755 -- "$release_dir" /usr/local/bin /usr/share/wayland-sessions
|
||||||
|
for relative in portals/ferese.portal ferese-portals.conf; do
|
||||||
|
destination=/usr/share/xdg-desktop-portal/$relative
|
||||||
|
if [[ -e $destination ]]; then
|
||||||
|
install -D -m 0644 -- "$destination" "$release_dir/portal-config.previous/$relative"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
for relative in \
|
||||||
|
portal/ferese.portal \
|
||||||
|
portal/ferese-portals.conf \
|
||||||
|
portal/org.freedesktop.impl.portal.desktop.ferese.service \
|
||||||
|
systemd/xdg-desktop-portal-ferese.service \
|
||||||
|
systemd/ferese-session.target; do
|
||||||
|
install -D -m 0644 -- "$repo_dir/packaging/$relative" "$release_dir/installer-files/$relative"
|
||||||
|
done
|
||||||
if [[ -e $session_entry ]]; then
|
if [[ -e $session_entry ]]; then
|
||||||
install -m 0644 -- "$session_entry" "$release_dir/session.previous.desktop"
|
install -m 0644 -- "$session_entry" "$release_dir/session.previous.desktop"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ Run from any directory as your normal user. Existing user config is preserved.
|
|||||||
|
|
||||||
--release-id ID Name the installed release (default: UTC timestamp + PID)
|
--release-id ID Name the installed release (default: UTC timestamp + PID)
|
||||||
--skip-build Install existing target/release binaries
|
--skip-build Install existing target/release binaries
|
||||||
|
--replace-portal-config Back up and replace existing Ferese portal configuration
|
||||||
--offline Build using only cached Cargo dependencies
|
--offline Build using only cached Cargo dependencies
|
||||||
--dry-run Print commands without building or installing
|
--dry-run Print commands without building or installing
|
||||||
-h, --help Show this help
|
-h, --help Show this help
|
||||||
@@ -24,6 +25,7 @@ fail() { echo "ferese installer: $*" >&2; exit 1; }
|
|||||||
repo_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
repo_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
||||||
release_id="$(date -u +%Y%m%d-%H%M%S)-$$"
|
release_id="$(date -u +%Y%m%d-%H%M%S)-$$"
|
||||||
skip_build=false
|
skip_build=false
|
||||||
|
replace_portal_config=false
|
||||||
offline=false
|
offline=false
|
||||||
dry_run=false
|
dry_run=false
|
||||||
while (($#)); do
|
while (($#)); do
|
||||||
@@ -34,6 +36,7 @@ while (($#)); do
|
|||||||
shift 2
|
shift 2
|
||||||
;;
|
;;
|
||||||
--skip-build) skip_build=true; shift ;;
|
--skip-build) skip_build=true; shift ;;
|
||||||
|
--replace-portal-config) replace_portal_config=true; shift ;;
|
||||||
--offline) offline=true; shift ;;
|
--offline) offline=true; shift ;;
|
||||||
--dry-run) dry_run=true; shift ;;
|
--dry-run) dry_run=true; shift ;;
|
||||||
-h|--help) usage; exit 0 ;;
|
-h|--help) usage; exit 0 ;;
|
||||||
@@ -57,6 +60,7 @@ if ! $skip_build; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
installer=(bash "$repo_dir/scripts/install-session.sh" "$release_id")
|
installer=(bash "$repo_dir/scripts/install-session.sh" "$release_id")
|
||||||
|
if $replace_portal_config; then installer+=(--replace-portal-config); fi
|
||||||
if ((EUID != 0)); then
|
if ((EUID != 0)); then
|
||||||
if command -v sudo >/dev/null; then
|
if command -v sudo >/dev/null; then
|
||||||
installer=(sudo "${installer[@]}")
|
installer=(sudo "${installer[@]}")
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ class PortalInstallTest(unittest.TestCase):
|
|||||||
def preflight(self, service=None, unit=None):
|
def preflight(self, service=None, unit=None):
|
||||||
script = (REPO / 'scripts/install-session.sh').read_text()
|
script = (REPO / 'scripts/install-session.sh').read_text()
|
||||||
start = script.index('service=/usr/local/share/dbus-1/services/')
|
start = script.index('service=/usr/local/share/dbus-1/services/')
|
||||||
script = script[start:script.index('session_target=', start)]
|
helpers = script[:script.index('repo_dir=')]
|
||||||
|
script = helpers + script[start:script.index('session_target=', start)]
|
||||||
with tempfile.TemporaryDirectory() as directory:
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
root = Path(directory)
|
root = Path(directory)
|
||||||
if service is not None:
|
if service is not None:
|
||||||
@@ -30,9 +31,80 @@ class PortalInstallTest(unittest.TestCase):
|
|||||||
'portal_unit=/usr/local/lib/systemd/user/xdg-desktop-portal-ferese.service',
|
'portal_unit=/usr/local/lib/systemd/user/xdg-desktop-portal-ferese.service',
|
||||||
'portal_unit="$TEST_ROOT/portal.service"')
|
'portal_unit="$TEST_ROOT/portal.service"')
|
||||||
return subprocess.run(['bash', '-eu', '-c', script],
|
return subprocess.run(['bash', '-eu', '-c', script],
|
||||||
env={'repo_dir': str(REPO), 'TEST_ROOT': directory},
|
env={'repo_dir': str(REPO), 'TEST_ROOT': directory, 'install_root': directory},
|
||||||
capture_output=True, text=True)
|
capture_output=True, text=True)
|
||||||
|
|
||||||
|
def configuration_preflight(self, portal=None, preferences=None, previous=None, replace=False):
|
||||||
|
script = (REPO / 'scripts/install-session.sh').read_text()
|
||||||
|
helpers = script[:script.index('repo_dir=')]
|
||||||
|
start = script.index('for entry in ')
|
||||||
|
check = script[start:script.index('service=/usr/local/share/dbus-1/services/', start)]
|
||||||
|
check = check.replace('/usr/share/xdg-desktop-portal/', '$TEST_ROOT/config/')
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
for relative, content in [('portals/ferese.portal', portal), ('ferese-portals.conf', preferences)]:
|
||||||
|
if content is not None:
|
||||||
|
destination = root / 'config' / relative
|
||||||
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
destination.write_text(content)
|
||||||
|
if previous:
|
||||||
|
for relative, content in previous.items():
|
||||||
|
destination = root / 'current/installer-files/portal' / relative
|
||||||
|
destination.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
destination.write_text(content)
|
||||||
|
return subprocess.run(['bash', '-eu', '-c', helpers + check],
|
||||||
|
env={'repo_dir': str(REPO), 'TEST_ROOT': directory, 'install_root': directory,
|
||||||
|
'replace_portal_config': str(replace).lower()},
|
||||||
|
capture_output=True, text=True)
|
||||||
|
|
||||||
|
def test_existing_development_install_requires_explicit_replacement(self):
|
||||||
|
portal = ('[portal]\nDBusName=org.freedesktop.impl.portal.desktop.ferese\n'
|
||||||
|
'Interfaces=org.freedesktop.impl.portal.ScreenCast;\nUseIn=Ferese;\n')
|
||||||
|
preferences = '[preferred]\ndefault=gtk;\norg.freedesktop.impl.portal.ScreenCast=ferese;\n'
|
||||||
|
result = self.configuration_preflight(portal, preferences)
|
||||||
|
self.assertNotEqual(result.returncode, 0)
|
||||||
|
self.assertIn('--replace-portal-config', result.stderr)
|
||||||
|
result = self.configuration_preflight(portal, preferences, replace=True)
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
|
|
||||||
|
def test_previous_portal_configuration_is_backed_up(self):
|
||||||
|
script = (REPO / 'scripts/install-session.sh').read_text()
|
||||||
|
start = script.index('for relative in portals/ferese.portal')
|
||||||
|
backup = script[start:script.index('for relative in ', start + 1)]
|
||||||
|
backup = backup.replace('/usr/share/xdg-desktop-portal/', '$TEST_ROOT/config/')
|
||||||
|
with tempfile.TemporaryDirectory() as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
for relative in ('portals/ferese.portal', 'ferese-portals.conf'):
|
||||||
|
source = root / 'config' / relative
|
||||||
|
source.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
source.write_text('existing custom content\n')
|
||||||
|
result = subprocess.run(['bash', '-eu', '-c', backup],
|
||||||
|
env={'TEST_ROOT': directory, 'release_dir': str(root / 'release')},
|
||||||
|
capture_output=True, text=True)
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
|
for relative in ('portals/ferese.portal', 'ferese-portals.conf'):
|
||||||
|
self.assertEqual((root / 'release/portal-config.previous' / relative).read_text(),
|
||||||
|
'existing custom content\n')
|
||||||
|
self.assertEqual((root / 'config' / relative).read_text(), 'existing custom content\n')
|
||||||
|
|
||||||
|
def test_upgrades_recorded_previous_release(self):
|
||||||
|
old_portal = '[portal]\nInterfaces=org.freedesktop.impl.portal.Settings;\n'
|
||||||
|
old_preferences = '[preferred]\norg.freedesktop.impl.portal.Settings=ferese;\n'
|
||||||
|
result = self.configuration_preflight(old_portal, old_preferences,
|
||||||
|
{'ferese.portal': old_portal, 'ferese-portals.conf': old_preferences})
|
||||||
|
self.assertEqual(result.returncode, 0, result.stderr)
|
||||||
|
result = self.configuration_preflight(old_portal + 'UseIn=Custom;\n', old_preferences,
|
||||||
|
{'ferese.portal': old_portal, 'ferese-portals.conf': old_preferences})
|
||||||
|
self.assertNotEqual(result.returncode, 0)
|
||||||
|
self.assertIn('Unmanaged portal configuration', result.stderr)
|
||||||
|
|
||||||
|
def test_preserves_custom_configuration(self):
|
||||||
|
portal = (REPO / 'packaging/portal/ferese.portal').read_text()
|
||||||
|
preferences = '[preferred]\ndefault=custom;\n'
|
||||||
|
result = self.configuration_preflight(portal, preferences)
|
||||||
|
self.assertNotEqual(result.returncode, 0)
|
||||||
|
self.assertIn('Unmanaged portal configuration', result.stderr)
|
||||||
|
|
||||||
def test_accepts_fresh_install_and_previous_activation_file(self):
|
def test_accepts_fresh_install_and_previous_activation_file(self):
|
||||||
self.assertEqual(self.preflight().returncode, 0)
|
self.assertEqual(self.preflight().returncode, 0)
|
||||||
self.assertEqual(self.preflight(LEGACY).returncode, 0)
|
self.assertEqual(self.preflight(LEGACY).returncode, 0)
|
||||||
|
|||||||
Reference in new issue
Block a user