fix: panic on flatpak installations
This commit is contained in:
@@ -5,6 +5,7 @@ use gpui_updater::{EngineConfig, GitHubSource, UpdateStatus, Updater, Version};
|
|||||||
use instant::{Duration, Instant};
|
use instant::{Duration, Instant};
|
||||||
|
|
||||||
const COOP_UPDATE_EXPLANATION: &str = "COOP_UPDATE_EXPLANATION";
|
const COOP_UPDATE_EXPLANATION: &str = "COOP_UPDATE_EXPLANATION";
|
||||||
|
const COOP_BUNDLE_TYPE: &str = "COOP_BUNDLE_TYPE";
|
||||||
|
|
||||||
fn get_github_repo_owner() -> String {
|
fn get_github_repo_owner() -> String {
|
||||||
std::env::var("COOP_GITHUB_REPO_OWNER").unwrap_or_else(|_| "reyakov".to_string())
|
std::env::var("COOP_GITHUB_REPO_OWNER").unwrap_or_else(|_| "reyakov".to_string())
|
||||||
@@ -14,18 +15,27 @@ fn get_github_repo_name() -> String {
|
|||||||
std::env::var("COOP_GITHUB_REPO_NAME").unwrap_or_else(|_| "coop".to_string())
|
std::env::var("COOP_GITHUB_REPO_NAME").unwrap_or_else(|_| "coop".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_flatpak_installation() -> bool {
|
/// Whether updates are managed by an external distribution channel
|
||||||
std::env::var("FLATPAK_ID").is_ok() || std::env::var(COOP_UPDATE_EXPLANATION).is_ok()
|
/// (Flatpak/Snap), in which case the in-app updater must not run.
|
||||||
|
fn uses_managed_updates() -> bool {
|
||||||
|
// The Flatpak runtime exports `FLATPAK_ID` inside the sandbox.
|
||||||
|
std::env::var("FLATPAK_ID").is_ok()
|
||||||
|
// Allow opting out of in-app updates via an explicit environment variable.
|
||||||
|
|| std::env::var(COOP_UPDATE_EXPLANATION).is_ok()
|
||||||
|
// The Snap package sets `COOP_BUNDLE_TYPE=snap` (see snapcraft.yaml.in).
|
||||||
|
|| std::env::var(COOP_BUNDLE_TYPE).is_ok_and(|value| value == "snap")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize the auto-update system.
|
/// Initialize the auto-update system.
|
||||||
///
|
///
|
||||||
/// Skips initialization when running as a Flatpak (updates are handled by the
|
/// Skips initialization when updates are handled by an external distribution
|
||||||
/// Flatpak distribution channel). Otherwise creates the global [`AutoUpdater`]
|
/// channel (Flatpak/Snap). Otherwise creates the global [`AutoUpdater`]
|
||||||
/// entity and schedules a check for updates after a 2-minute delay.
|
/// entity and schedules a check for updates after a 2-minute delay.
|
||||||
pub fn init(window: &mut Window, cx: &mut App) {
|
pub fn init(window: &mut Window, cx: &mut App) {
|
||||||
if is_flatpak_installation() {
|
if uses_managed_updates() {
|
||||||
log::info!("Skipping auto-update initialization: App is installed via Flatpak");
|
log::info!(
|
||||||
|
"Skipping auto-update initialization: App is installed via a managed distribution channel (Flatpak/Snap)"
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +67,27 @@ pub struct AutoUpdater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AutoUpdater {
|
impl AutoUpdater {
|
||||||
|
/// Whether auto-update is available for this installation.
|
||||||
|
///
|
||||||
|
/// Returns `false` on managed distribution channels (Flatpak/Snap), where
|
||||||
|
/// updates are handled by the channel and no global updater is created.
|
||||||
|
pub fn is_available(cx: &App) -> bool {
|
||||||
|
cx.try_global::<GlobalAutoUpdater>().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieve the global auto updater instance, if one was initialized.
|
||||||
|
pub fn try_global(cx: &App) -> Option<Entity<Self>> {
|
||||||
|
cx.try_global::<GlobalAutoUpdater>()
|
||||||
|
.map(|global| global.0.clone())
|
||||||
|
}
|
||||||
|
|
||||||
/// Retrieve the global auto updater instance.
|
/// Retrieve the global auto updater instance.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// Panics when auto-update is not available for this installation. Prefer
|
||||||
|
/// [`AutoUpdater::try_global`] when the installation type is not known at
|
||||||
|
/// compile time (e.g. Flatpak/Snap).
|
||||||
pub fn global(cx: &App) -> Entity<Self> {
|
pub fn global(cx: &App) -> Entity<Self> {
|
||||||
cx.global::<GlobalAutoUpdater>().0.clone()
|
cx.global::<GlobalAutoUpdater>().0.clone()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -380,7 +380,9 @@ impl Workspace {
|
|||||||
self.import_encryption(window, cx);
|
self.import_encryption(window, cx);
|
||||||
}
|
}
|
||||||
Command::Update => {
|
Command::Update => {
|
||||||
let auto_updater = AutoUpdater::global(cx);
|
// No-op on managed distribution channels (Flatpak/Snap) where
|
||||||
|
// the in-app updater is never initialized.
|
||||||
|
if let Some(auto_updater) = AutoUpdater::try_global(cx) {
|
||||||
auto_updater.update(cx, |this, cx| {
|
auto_updater.update(cx, |this, cx| {
|
||||||
this.updater.update(cx, |updater, cx| {
|
this.updater.update(cx, |updater, cx| {
|
||||||
updater.check(cx);
|
updater.check(cx);
|
||||||
@@ -389,6 +391,7 @@ impl Workspace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn confirm_reset_encryption(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
fn confirm_reset_encryption(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||||
const ENC_MSG: &str = "Encryption Key is a special key that used to encrypt and decrypt your messages. \
|
const ENC_MSG: &str = "Encryption Key is a special key that used to encrypt and decrypt your messages. \
|
||||||
@@ -559,7 +562,7 @@ impl Workspace {
|
|||||||
.caret()
|
.caret()
|
||||||
.compact()
|
.compact()
|
||||||
.transparent()
|
.transparent()
|
||||||
.dropdown_menu(move |this, _window, _cx| {
|
.dropdown_menu(move |this, _window, cx| {
|
||||||
let avatar = avatar.clone();
|
let avatar = avatar.clone();
|
||||||
let name = name.clone();
|
let name = name.clone();
|
||||||
|
|
||||||
@@ -593,12 +596,15 @@ impl Workspace {
|
|||||||
IconName::Sun,
|
IconName::Sun,
|
||||||
Box::new(Command::ToggleTheme),
|
Box::new(Command::ToggleTheme),
|
||||||
)
|
)
|
||||||
.separator()
|
// Only offer in-app updates when auto-update is
|
||||||
.menu_with_icon(
|
// enabled (managed channels update themselves).
|
||||||
|
.when(AutoUpdater::is_available(cx), |this| {
|
||||||
|
this.separator().menu_with_icon(
|
||||||
"Check for Updates",
|
"Check for Updates",
|
||||||
IconName::Device,
|
IconName::Device,
|
||||||
Box::new(Command::Update),
|
Box::new(Command::Update),
|
||||||
)
|
)
|
||||||
|
})
|
||||||
.menu_with_icon(
|
.menu_with_icon(
|
||||||
"Settings",
|
"Settings",
|
||||||
IconName::Settings,
|
IconName::Settings,
|
||||||
@@ -610,7 +616,6 @@ impl Workspace {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn titlebar_right(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
fn titlebar_right(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
let updater = AutoUpdater::global(cx);
|
|
||||||
let chat = ChatRegistry::global(cx);
|
let chat = ChatRegistry::global(cx);
|
||||||
let nip4e_enabled = AppSettings::get_nip4e(cx);
|
let nip4e_enabled = AppSettings::get_nip4e(cx);
|
||||||
let nostr = NostrRegistry::global(cx);
|
let nostr = NostrRegistry::global(cx);
|
||||||
@@ -622,13 +627,19 @@ impl Workspace {
|
|||||||
let persons = PersonRegistry::global(cx);
|
let persons = PersonRegistry::global(cx);
|
||||||
let profile = persons.read(cx).get(&public_key, cx);
|
let profile = persons.read(cx).get(&public_key, cx);
|
||||||
let announcement = profile.announcement();
|
let announcement = profile.announcement();
|
||||||
let updater_idle = updater.read(cx).idle(cx);
|
|
||||||
|
// Update status is only shown when auto-update is available. On
|
||||||
|
// managed distribution channels (Flatpak/Snap) no updater exists, so
|
||||||
|
// nothing is rendered.
|
||||||
|
let updater_status = AutoUpdater::try_global(cx).and_then(|updater| {
|
||||||
|
let updater = updater.read(cx);
|
||||||
|
(!updater.idle(cx)).then(|| updater.status(cx))
|
||||||
|
});
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.when(!cx.theme().platform.is_mac(), |this| this.pr_2())
|
.when(!cx.theme().platform.is_mac(), |this| this.pr_2())
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.when(!updater_idle, |this| {
|
.when_some(updater_status, |this, status| {
|
||||||
let status = updater.read(cx).status(cx);
|
|
||||||
this.child(div().text_xs().italic().child(status))
|
this.child(div().text_xs().italic().child(status))
|
||||||
})
|
})
|
||||||
.when(nip4e_enabled, |this| {
|
.when(nip4e_enabled, |this| {
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
# Snaps built by snapcraft without Snap Store credentials are unsigned and
|
||||||
|
# cannot be installed without bypassing signature checks. Use:
|
||||||
|
# sudo snap install --dangerous ./coop_<version>_<arch>.snap
|
||||||
|
# For signed installs (`snap install coop`), publish via the Snap Store.
|
||||||
name: coop
|
name: coop
|
||||||
title: Coop
|
title: Coop
|
||||||
base: core24
|
base: core24
|
||||||
|
|||||||
@@ -35,3 +35,7 @@ SNAP_NAME="coop_${1}_${ARCH_SUFFIX}.snap"
|
|||||||
snapcraft --destructive-mode --output "$SNAP_NAME"
|
snapcraft --destructive-mode --output "$SNAP_NAME"
|
||||||
|
|
||||||
echo "Created snap package: $SNAP_NAME"
|
echo "Created snap package: $SNAP_NAME"
|
||||||
|
echo ""
|
||||||
|
echo "This snap is unsigned (built without Snap Store credentials)."
|
||||||
|
echo "To install it locally, use:"
|
||||||
|
echo " sudo snap install --dangerous ./$SNAP_NAME"
|
||||||
|
|||||||
+16
-1
@@ -15,11 +15,26 @@ if [ "$#" -ne 1 ]; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Get system architecture (same mapping as script/bundle-snap)
|
||||||
|
ARCH=$(uname -m)
|
||||||
|
case "$ARCH" in
|
||||||
|
x86_64) ARCH_SUFFIX="x86_64" ;;
|
||||||
|
aarch64) ARCH_SUFFIX="aarch64" ;;
|
||||||
|
*) echo "Unsupported architecture: $ARCH"; exit 1 ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
snap_file="coop_${1}_${ARCH_SUFFIX}.snap"
|
||||||
|
if [ ! -f "$snap_file" ]; then
|
||||||
|
echo "Snap file not found: $snap_file"
|
||||||
|
echo "Build it first with: script/bundle-snap $1"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Rerun as root
|
# Rerun as root
|
||||||
[ "$UID" -eq 0 ] || exec sudo bash -e "$0" "$@"
|
[ "$UID" -eq 0 ] || exec sudo bash -e "$0" "$@"
|
||||||
|
|
||||||
snap remove coop || true
|
snap remove coop || true
|
||||||
mkdir -p snap
|
mkdir -p snap
|
||||||
rm -rf snap/unpacked
|
rm -rf snap/unpacked
|
||||||
unsquashfs -dest snap/unpacked "coop_$1_amd64.snap"
|
unsquashfs -dest snap/unpacked "$snap_file"
|
||||||
snap try --classic snap/unpacked
|
snap try --classic snap/unpacked
|
||||||
|
|||||||
Reference in New Issue
Block a user