* fix(windows): retry busy download renames and log Windows Installer runs
Antivirus scanners often hold a just-written installer open, so renaming
the finished .part file failed with "Access denied (os error 5)". Retry
the rename for a few seconds on Windows errors 5 and 32.
Windows Installer error 1603 carries no detail on its own: run msiexec
with a verbose log beside the package and name it in the error.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
* fix(download): honour pause and cancel while retrying a busy rename
Check the stop flag before every rename attempt, so a cancel that lands
while the checksum is being computed, or during a Windows retry backoff,
stops the download instead of installing it. A cancelled download still
throws its partial file away.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
Craft App releases ship <app>-<ver>-freebsd-aarch64.tar.gz next to the
x86_64 tarball, but the platform tokens had none for FreeBSD on ARM64, so
nothing was picked there and the backend's kept-download tests failed on
the nightly FreeBSD aarch64 job. Pick freebsd-aarch64 on FreeBSD ARM64 the
way freebsd-x86_64 is picked on x86_64 (no cross-arch fallback), with a
core test on SoundCraft v0.10.0's release metadata trimmed to its FreeBSD
tarballs, and add the token to the backend tests' synthetic release.
The inherited-pipe test gave its direct child one second to start a second
test process and fill both pipes; under emulation that takes longer, so
run() killed the child first and the test never reached the case it
checks. Retry with a longer deadline (1 s, 4 s, 16 s) when the child didn't
get that far, let the descendant hold the pipes for 120 s unless stopped,
and still require run() to return within twice its deadline.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Only consider release Arch packages (bundle ID starting with 'ai.storyteller.') eligible for launcher updates.
- Keep AUR packages (<app>, <app>-bin, <app>-git) directed to the user's AUR helper (yay/paru).
- Direct repository packages (e.g. Omarchy) to 'sudo pacman -Syu' and add translations across all 10 locales.
- Only self-update launcher if installed as the release Arch package ('ai.storyteller.artcraft-launcher'), preventing conflicts on AUR installs.
- Check pacman DB presence before assuming ArchPkg availability.
* Drain helper output after reaching the capture limit
* fix: enforce helper deadline while draining inherited pipes
Keep both reader completions under the original shared timeout after the direct child exits. Add a finite self-spawned descendant regression that fails before the fix. Fresh isolated module: 6 tests passed, 2 ignored fixture entry points; strict Clippy and formatting passed. Independent review approved. Timeout detaches readers as before; no descendant-tree cancellation or lifetime resource-cleanup claim. AI assistance: OpenAI Codex for @MMexicann.
---------
Co-authored-by: skylar taylor-barrick <[email protected]>
install_dmg gained a replace parameter in #461; the macOS-only self-update call and its test
weren't updated, so the 0.5.0 release's macOS job failed to compile. Self-update writes over the
launcher's own bundle, which copy_app accepts without replace, so pass false.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* linux: support self-update and restart for Flatpak launcher
Enable self-updating and restarting when ArtCraft Launcher runs inside a Flatpak container.
- Add `Install::Flatpak` to detect user Flatpak bundle installations via sandbox detection, app-path inspection in /.flatpak-info, and host flatpak-spawn availability.
- Cache `process::is_flatpak_available()` via `OnceLock` to prevent redundant host probes.
- Reinstall the downloaded .flatpak bundle with `--reinstall` through the Flatpak host portal.
- Schedule a detached relaunch helper on the host via flatpak-spawn that monitors the running Flatpak container instance/host PID until exit and relaunches via `flatpak run`, timing out after 60s without launching duplicate instances if exit fails.
- Add test coverage for Flatpak instance metadata and PID resolution.
* linux: detect the Flatpak bundle off the startup path
- Work out how the launcher is installed on the check thread, just before a
newer launcher is announced, instead of in NativeBackend:🆕 in the
Flatpak it asks the host's flatpak, which held up the first frame. The UI
re-reads its capabilities on LauncherUpdate.
- Self-update only a per-user copy whose origin is the bundle's own
(artcraft-launcher[N]-origin), asked through `flatpak info --show-origin`;
/.flatpak-info doesn't record it. A copy from a repository keeps Download:
an unsigned bundle can't go into a signed remote and would swap the build
under an unsigned one.
- Read app-path for ~/.local/share/flatpak only; XDG_DATA_HOME points into
~/.var/app inside the sandbox, so dirs::data_local_dir never matched.
- Don't relaunch blind when neither the host PID nor the instance id is known.
- Drop the cached flatpak probe: nothing on the startup path calls it now.
* fix(update): prevent accidental downgrades when releases or repos lag
---------
Co-authored-by: skylartaylor <[email protected]>
* feat(install): choose where Craft apps are installed
Settings gets a default install folder, and a fresh install asks for the
app's folder (prefilled with the default). Updates stay beside the copy
they replace. Works for MSI (INSTALLFOLDER), DMG bundles, and AppImages;
on Linux a custom folder means AppImage. Flatpak and the web build keep
the system location and skip the prompt.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* feat(windows): integrate portable installs with custom locations
* fix(install): ask before writing over something already in the install folder
Staging used a predictable `.<name>.partial` path beside the destination, so a
file the user kept under that name could be overwritten (AppImage) or deleted
(macOS). Stage in a fresh private folder instead.
A new install whose destination already exists now stops before downloading
and asks "Replace what's there?". Replace installs over it: the AppImage or
.app is swapped in, a portable install writes over same-named files only, and
the MSI installs into the existing folder. Cancel leaves everything as it was.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* test(install): a planted .partial symlink can't redirect AppImage staging
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Skyfuller1303 <[email protected]>
Reserve an exclusively owned backup directory while replacing an installed app. Preserve neighboring .old files and directories, and retain the old copy with its recovery path if rollback fails.
Includes regression coverage for backup collisions and failed rollback.
PR #332.
* Add FigCraft to the launcher
FigCraft (storytold/figcraft): interface design and prototyping, the
thirteenth Craft App and the sixth in the New group.
- Catalog row (brand colour #02da96 from FigCraft's icon README), details
page (no pictures yet), Linux desktop categories, icon tables, tagline
in every catalog, xtask local mirror list.
- Icon copied unmodified from FigCraft's repo; ATTRIBUTION.md row.
- The Windows installer art fits five rows of icons.
- Test that a companion figcraft-server tarball is never picked as the app.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Release 0.4.1
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
* Add a hand-written details page for each Craft App
core::details holds, for every app in the suite, a headline, a few
paragraphs, a list of features, where it runs and the list of its
pictures. The copy is the launcher's own: written from each app's page on
getartcraft.com and, for the five newer apps, from its README. It is
compiled in, so it works offline and in the web build, and it leaves out
figures that go stale (effect counts, timings).
The pictures are not in this repository. A page names them (a key and a
caption each) and AppDetails::screenshot_url turns a key into the address
of a 1200 px JPEG in storytold/artcraft-launcher-assets, pinned to a tag
(SCREENSHOTS_REV) so a saved copy never goes stale. It only answers for
keys the page lists, so no address is ever built from anyone else's text.
MAX_SCREENSHOT_BYTES caps what is accepted, and looks_like_jpeg refuses a
login page or error page that arrives as a 200.
PdfCraft lists none yet: its screenshots still show its old name
(PrintCraft). ATTRIBUTION.md points to the pictures repository, which
carries each picture's own provenance.
Tests check that every app has exactly one page, that each is filled in,
and that addresses come only from a page's own pictures.
* Open an app's details in a drawer from its card
Clicking a card (or an app's name in the table) slides a drawer in from
the right over the dimmed grid. It shows the app's headline, version and
main button, links to its website, GitHub and releases, a gallery of
screenshots, its features, the facts and notes of its latest release, and
the same trademark notice as the grid. Esc, the close button or a click
on the grid closes it. The page's text is the hand-written copy in
core::details; the version, date, download size and notes come from the
release the launcher already fetches.
The pictures are fetched when a page is opened, not bundled. The UI sends
Command::FetchScreenshots and gets one Event::Screenshot per picture, the
lead one first. The desktop backend downloads them through the launcher's
Http (its User-Agent, a 2 MB cap, redirects followed) and keeps them under
<cache>/screenshots/<revision>/<app>/<key>.jpg, so later opens, and any
time offline, read from disk. Only a JPEG is kept: a login page or error
page that arrives as a 200 is refused. A missing picture doesn't hold the
others back, but once the network has failed the rest fail at once instead
of each waiting for its own timeout. The web build does the same with
fetch and leaves the caching to the browser.
Until a picture is here, a frame of the same size says "Loading pictures…"
or, when it failed, that it couldn't be loaded and that reopening the page
tries again; nothing moves when the picture arrives. The lead picture
opens full-window, the arrows over the pictures and the arrow keys flip
through them, wrapping around, and a scroll bar stays visible (egui's
floating one only shows under the pointer).
The drawer's labels are tl! strings, with 13 rows in each of ru, zh-CN,
de, fr and pt-BR; details.rs joins the sources the catalog coverage test
reads. Every size taken from the window is kept non-negative: the new
tests resize it from 40 x 30 to 4000 x 2400 with the drawer open.
actions() becomes actions_in(), which can leave the links out of the
... menu where the page already shows them as buttons. The image crate's
jpeg feature is enabled to decode the pictures (zune-jpeg and zune-core
join Cargo.lock). The dim and the enlarged picture's backdrop are colours
in theme.rs.
* Bring the details drawer up to date with main
- Website is always a button now (#198); drop the "Like {name}" line and the trademark notice,
which main removed with the product comparisons (93caf01).
- Bring back widgets::eyebrow for the drawer's section headings (#187 dropped its last caller).
- The drawer's ⋯ menu leaves out only the links, so Hide/Unhide (#185) stays.
- No slide when the OS asks for reduced motion (#201).
- Translate the screen readers' "{name} details" label, and add the drawer's strings to the
Spanish, Czech, Polish, Hungarian and Japanese catalogs.
- AGENTS.md: name the pictures' download origin.
---------
Co-authored-by: Brandon Thomas <[email protected]>
* Match MSI entries and find app executables through pure functions
Windows detection decided which uninstall entry belongs to an app, and
which paths might hold its exe, inline in the registry-reading code
under #[cfg(windows)]. Neither decision needs the registry or the disk,
so move both into module-level functions, is_installer_entry and
exe_candidates, that tests can call on every platform. The windows
module keeps the registry reads and the exists() checks.
No behaviour change: an entry still has to carry the app's display name
and the Learning Machines LLC publisher, exactly, and the candidates are
the same paths in the same order. The new tests pin that down for every
app but PdfCraft, and for PdfCraft's own entry name.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Detect PdfCraft installs made by its PrintCraft-named MSI
PdfCraft's Windows installer shipped as PrintCraft up to 0.2.1
(printcraft-0.2.1-windows-x64.msi). That MSI registers its uninstall
entry with DisplayName "PrintCraft" and installs printcraft.exe into
Program Files\PrintCraft, but Windows detection only accepted an entry
named after the app's current name and looked only for pdfcraft.exe, in
App Paths, the install folder and Program Files\PdfCraft. On a machine
still on that install the launcher showed PdfCraft as not installed,
with no version, and uninstall_msi found nothing to remove.
The catalog already knows the old asset stem (former_ids); add
former_names for the display names an app's installers registered,
since the capitalisation can't be derived from the stem, and names()
to go with stems(). Entry matching now accepts any of the app's names,
still exactly and still only from Learning Machines LLC, and the exe is
looked up under every stem in App Paths and under every name in Program
Files, current first. Apps without former names see the same match and
the same candidates as before.
The impact is limited to early adopters: PdfCraft 0.4.0 and later
register as PdfCraft, and their MSI keeps the same UpgradeCode, so
installing from the launcher already upgraded a PrintCraft install in
place. What this fixes is the wrong status and the missing Uninstall
until then.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
Fedora Atomic variants (Bazzite, Silverblue, etc.) block local dnf install;
prefer Flatpak and AppImage so one-click installs succeed without pkexec dnf.
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
A launcher started from the Dock, the Start menu or at login has no
terminal, and the Windows build has no console, so its log records and
panic reports were lost.
env_logger now writes each record to standard error and to
logs/artcraft-launcher.log in the launcher's data folder (portable mode
included). Each start keeps the previous two runs' logs; the file is
capped at 16 MiB. The file is opened once this is the only launcher
running, so a second start doesn't rotate the first one's log away.
Panics are reported through the logger. The default filter is now info
for the launcher's crates and warn for the rest; RUST_LOG still wins.
Ported from storytold/pdfcraft#239.
Co-authored-by: Fabio Lussi <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Those binaries print a former id (`printcraft 0.2.1`) or the display
name (`SoundCraft 0.5.0`). Detection required an exact machine-id
prefix, so the version was missing and the card said "installed
installed · latest …". Parse --version against stems and the product
name, and stop using the word "installed" as a version placeholder.
Two download-trust gaps from #90.
A repo rename in the release feed only had to look like owner/name, so
a compromised feed could send an app's downloads, or the launcher's own
self-update, to another account's repository with matching links and
checksums, and every check would vouch for them. A rename now has to
stay under `catalog::GITHUB_OWNER` (storytold) at both ends, or it is
dropped and the feed's entry stops matching, so GitHub decides as before.
A release with neither a per-file digest nor a SHA256SUMS.txt line was
still installed, checked by size only, with a note after the fact; a
same-length altered file would have passed. `install::download` now
refuses it before fetching anything, with an error that says why. With
a checksum always in hand, the size-only path goes: `http::download`
takes the SHA-256 as `&str`, and `Verified` and `keep_download` are
removed.
Tests: a rename within storytold is followed, one out of it is dropped
in either direction, and a release without a checksum fetches nothing
and never reaches the installer.
Fixes#90
Co-authored-by: YangXZ2008 <[email protected]>
Bumps the workspace version to 0.3.0 for the new features since 0.2.1, and
applies cargo fmt to the two files from #149 that failed CI's format check.
Co-authored-by: Claude Opus 5.5 <[email protected]>
* macOS: open disk images reliably, and say why when they won't open
Installing or updating an app on macOS could fail with only "couldn't open
the disk image". hdiutil's own reason was thrown away, and every install of
an app mounted its image at the same folder, which an attach that was
interrupted or timed out could still be holding.
- Each install mounts its image at a new, empty folder.
- The attach skips hdiutil's checksum pass (-noverify): the download's
SHA-256 is already checked against the release, and on a large image that
pass alone could run past the time limit, now 5 minutes.
- When the image still won't open, or the app won't copy, the message says
why in hdiutil's or ditto's words ("couldn't open the disk image:
Resource temporarily unavailable").
- hdiutil, ditto and codesign are run by full path: a launcher started from
Finder or at login gets a minimal PATH.
process::run is capture's sibling that returns the helper's error message;
capture is now built on it. The launcher's own macOS self-update installs
through the same code, so it gets all of this too.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Release 0.2.1
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
diskimages-helper turns an attach away ('Resource temporarily unavailable')
while it is busy with another image. The DMG tests added in #51 run in
parallel and made this a flaky failure (about 1 run in 5 locally); the
launcher hits the same thing when several apps update at once. Attaches now
take a process-wide lock (which the tests' hdiutil create shares) and back
off and retry (1, 2, 4, 8 s) when something outside the launcher holds it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Install and update Craft apps from a local mirror
Add an opt-in developer mode for installing the Craft apps from builds on
this machine instead of GitHub:
- ARTCRAFT_LAUNCHER_DOWNLOAD_BASE (loopback http only) points the launcher
at a local mirror. While set, every check reads the mirror's
release_info.json, trusts download links under the mirror, never treats
entries as stale, and never falls back to GitHub or ArtCraft's API.
- cargo xtask local sync pulls every sibling app checkout (git pull
--ff-only --recurse-submodules), packages each app whose version isn't
mirrored yet with its own packaging script, and files the results under
<mirror>/<app>/<version>/ with SHA256SUMS.txt and notes from git log.
- cargo xtask local serve serves the mirror on 127.0.0.1 (with range
requests, so paused downloads resume) and writes the feed fresh from the
mirror's contents on every request.
- cargo xtask local status compares each checkout with what's mirrored.
* Sync the local mirror on an interval while serving it
cargo xtask local serve --sync-every <30m|1h|1d…> runs the same sync as
cargo xtask local sync on a background thread, one after another, so the
mirror follows origin without a second process. Downloads keep being
served during a build, and a version only joins the feed once its build
is complete. Pulls are --no-stat to keep the server's log readable.
* Build every new commit as a dev version with --every-commit
cargo xtask local sync --every-commit (and serve --sync-every … --every-commit)
builds each new commit rather than only new versions. A commit of a 0.4.0
checkout is packaged as 0.4.1-dev.<commit time> through the app's
<APP>_VERSION override, so it orders after 0.4.0, after earlier commits,
and before the real 0.4.1. The full version lands in the bundle's
<App>Version key, which the launcher reads, so it offers each build as an
update and sees it as current once installed.
* Keep the local mirror out of release builds; parse loopback URLs strictly
ARTCRAFT_LAUNCHER_DOWNLOAD_BASE makes the launcher trust the mirror's own
checksums and skip GitHub, so a shipped launcher must not read it: it now
needs the local-mirror cargo feature (cargo run --features local-mirror),
which release builds never enable. The loopback check parsed the URL by
prefix, so http://127.0.0.1:[email protected]/ passed; it now requires the host
to be exactly 127.0.0.1, localhost or [::1] with a numeric port. The same
check guards ARTCRAFT_LAUNCHER_FEED_URL.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: khutchings <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Issue #29's window wasn't 0.1.2: its tagline and header are 0.1.0's, and
it offered Download where a portable 0.1.2 offers Update and restart. A
0.1.0 launcher sat in the tray; starting 0.1.2 asked it to show its
window and exited, and that window still offered 0.1.1, found while 0.1.1
was the latest release.
A second start now says hello with its version and the running launcher
answers with its own. A newer start makes the running one quit (unless
it's downloading or installing; then it shows its window) and takes over
the port. The same version or older shows the running window as before.
Launchers from before the handshake hang up without an answer and can't
be asked to quit, so the new start runs anyway. A bare "show" from an
old launcher is still understood.
Tests: the handshake over loopback, including a running launcher that
predates it.
Co-authored-by: Fabio Lussi <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
* Fix Show in Explorer opening Documents for paths with spaces
reveal() built `/select,<path>` as one argument and passed it with
Command::arg, which quotes any argument containing a space. The Craft
apps' MSIs install under C:\Program Files, so Explorer received
"/select,C:\Program Files\...\app.exe", didn't recognise the switch and
opened its default folder, Documents.
The argument is now `/select,"<path>"`, with only the path quoted and
forward slashes turned into backslashes, passed with raw_arg so Rust
leaves it alone. The argument building is a plain function with unit
tests that run on every platform.
Fixes#39
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Refuse a path with a quote in it; give the Explorer tests their own module
A " would end the quoted /select path early and pass the rest to Explorer as
switches. The module name clashed with the pgrep tests that landed in #81.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Fabio Lussi <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
* Find AppImages outside ~/Applications; add a menu entry for installed ones
Detection only looked in ~/Applications and only for the exact release
file name, so an AppImage the user kept in Gear Lever's ~/AppImages or in
~/.local/bin, or one renamed to photocraft.appimage (Gear Lever's rename,
lower-case extension), showed as not installed. Detection now also looks
in ~/AppImages and ~/.local/bin, accepts either case of the extension, and
matches only the release name or the bare app name (version unknown for
the latter). Updates still install into ~/Applications and only remove
old copies there; AppImages the user keeps elsewhere are never deleted.
Installing an AppImage now writes ai.storyteller.<app>.desktop under
$XDG_DATA_HOME/applications (the host's ~/.local/share when the launcher
runs as a Flatpak) and copies the app's bundled 128 px icon into
icons/hicolor/128x128/apps. Exec is quoted per the Desktop Entry spec;
Categories and StartupWMClass match each app's own .desktop file. The
entry carries X-ArtCraft-Launcher=true and the record lists the files
written, so the launcher only replaces or removes what it wrote (plus an
entry the app wrote for itself that runs the copy being replaced or
removed). The .desktop text is a pure function with unit tests.
Fixes#14Fixes#37
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* Remove only AppImages the launcher installed; rebase onto main
AppImages detected in ~/AppImages or ~/.local/bin belong to the user (or Gear
Lever): Remove now deletes only those in ~/Applications or recorded by the
launcher, and explains otherwise.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Fabio Lussi <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
The launcher offered a .deb whenever apt-get and dpkg were on PATH, and
an .rpm only when dpkg was absent. Fedora can have apt and dpkg
installed (mmdebstrap and friends pull them in), so there it downloaded
the .deb and ran apt-get on it, with .rpm ruled out entirely (#21).
The distro family now comes from ID and ID_LIKE in /etc/os-release:
Debian and Ubuntu get .deb, Fedora, RHEL, CentOS, SUSE and openSUSE get
.rpm, each only with pkexec and the matching tools. Arch and unknown
distros get no system package. After the distro's package, AppImage now
ranks ahead of Flatpak, whose bundles need a Flathub runtime. Installing
a .deb without apt-get fails with a clear message, and removing one falls
back to dpkg -r.
The parsing and the choice are pure functions in core's new distro
module, tested for Fedora, Rocky, openSUSE, Ubuntu, Mint, Arch and an
unknown distro.
Fixes#21
Co-authored-by: Fabio Lussi <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
* macOS: install only apps signed by the Craft team, at the release's version
A Craft App, or the launcher's own update, now replaces the installed copy
only when the staged bundle carries a valid Developer ID signature from team
DJ6XS33FX8 (codesign with a requirement pinning the team, which works
offline) and reports the release's version. A bundle that reports only the
numbers matches a pre-release with the same numbers. On failure the staged
copy is removed and the installed app is left as it was.
* Rebase onto main: keep both sides' tests in install.rs
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: s17sh70x2jfl031ag <[email protected]>
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
ureq only reads HTTPS_PROXY, ALL_PROXY and friends, so a proxy set in
the OS was ignored and GitHub stayed out of reach where it needs one
(#30). Http::new now picks the proxy once and gives it to every agent:
the environment variables first, as before, else the OS setting.
On Windows that is the manual proxy in HKCU Internet Settings, in
either the single host:port form or the per-protocol
http=...;https=... form (the https entry wins), with ProxyOverride as
the bypass list. On macOS it is the HTTPS (else HTTP) proxy from
scutil --proxy with its exceptions list. Linux and the BSDs keep
using the environment. Loopback always goes direct. The chosen proxy
is logged at info level without credentials.
ureq's own win-system-proxy feature treats the whole ProxyServer
value as one address, which breaks the per-protocol form, so the
parsing lives here in pure, tested functions. PAC scripts and WPAD
are not supported.
Fixes#30
Co-authored-by: Fabio Lussi <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
Issue #44: the footer kept saying "GitHub's hourly limit was reached"
when it hadn't been, or long after it stopped mattering.
A 403 or 429 from the API counted as rate limited until
x-ratelimit-reset. GitHub sends that header with every answer, so a
secondary limit (quota left, Retry-After: 60) became up to an hour of
"hourly limit reached". The reset now only counts when
x-ratelimit-remaining is 0; otherwise Retry-After decides, or a minute
on a bare 429, as GitHub documents.
The notice also stayed up after the ArtCraft feed had answered every
app. A check now reports the limit only when some app had to come from
the release pages or the saved answers.
Co-authored-by: Fabio Lussi <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
* linux: prefer Flatpak if available and support Flatpak sandbox execution
* linux: match default package format to launcher format (Flatpak for Flatpak, AppImage for AppImage)
* ui: default package preference selection to launcher format (Flatpak for Flatpak, AppImage for AppImage)
* Add Linux riscv64 release build and switch runners to Ubuntu 26.04
- release.yml: new linux-riscv64 job cross-compiled on ubuntu-26.04 against
ports.ubuntu.com riscv64 libs, tar.gz only, artcraft-launcher --version
smoke-tested under QEMU. Updated flatpak runner matrix to ubuntu-26.04.
Draft-release job now requires linux-riscv64 and checks the tarball.
- packaging/linux/package.sh: CROSS_ARCH / CROSS_TARGET / CROSS_COMPILE / EMULATOR support.
- platform.rs: add Arch::Riscv64 variant, map linux-riscv64 token, and prioritize
tarball format for riscv64 on Linux.
- web.rs: detect riscv64 architecture in visitor user agent.
- README and AGENTS.md: document the riscv64 tarball.
* linux: build deb and rpm packages for riscv64
* ci: configure actionlint to recognize ubuntu-26.04 runner labels
* Keep riscv64 and Flatpak release jobs on Ubuntu 24.04
24.04 is what PhotoCraft's linux-riscv64 job uses, so the riscv64 binaries
need glibc 2.39 (as the README says) rather than 26.04's newer glibc, and
the release pipeline keeps runner images it has already proven on. Drop the
actionlint label config that 26.04 needed, revert the default_kinds and
download_kinds matches to match on the OS alone, simplify the riscv UA check,
and rustfmt.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---------
Co-authored-by: Brandon Thomas <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
When dpkg/rpm don't list the app, the launcher can't know which package
manager installed it, so label it a system binary (open it, leave update
and removal to whatever installed it) instead of Deb/Rpm with a guessed
package name. Require the binary itself; fix clippy and rustfmt.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Include InstallSource::Rpm in ours package filter
- Add bundle_id to candidate names for Deb and Rpm packages
- Strip architecture suffix from package name in parse_dpkg
- Clear AppImage environment variables in process::command to prevent polluting system tools
- Fall back to detecting system desktop entries and binaries with CLI version extraction
- Add unit tests for candidate names, RPM filtering, and binary CLI version
Fixes#97
Each install ran its own pkexec call at the same time; all but the first
lost the dpkg lock and failed with error 100. Installs that reach the
system installer now wait their turn ("Waiting for another app…", with
Cancel), and Linux packages waiting together go into one privileged call,
so they share one password prompt. apt waits up to 60 s for another
package tool's lock. On Windows msiexec runs one at a time (error 1618).
The launcher's own update waits for other installs before it restarts.
Ported onto main and onto "Keep the verified download when the install
step fails", with these changes from the original PR:
- A failed job counts as installed when the new version is found
afterwards only if it failed in a Linux call shared with other apps;
an MSI, AppImage or single-package error, a pause, and either
cancellation (including INSTALL_CANCELLED) stand.
- The launcher's update waits for other installs at most 10 minutes and
can be cancelled from its banner ("Cancel update"); giving up names the
apps still installing and says to answer or close the password prompt.
- Cancellation toasts say what was called off: "Download cancelled",
"Install cancelled" (download kept), "Uninstall cancelled" or
"Update cancelled", instead of a bare "Cancelled".
- The waiting card uses the restyled layout and the app's own colour,
with no hard-coded sizes.
Fixes#6
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A download that passed its SHA-256 check was deleted whether or not the
install succeeded, so every retry downloaded it again (usually after the
password prompt was dismissed). It now stays until the install succeeds,
is hashed again before it is reused, and shows as "Retry install" (or
"Retry update to <version>") with "Download again" in the menu. A
dismissed system prompt now says "Install cancelled" rather than
"Download cancelled". A download checked by size only is still deleted.
Ported onto main from the original PR: the check thread, rewritten for
ArtCraft's release feed, reports kept downloads after it marks the apps
loaded; the Retry button uses the restyled widgets::button; the backend
tests build on main's Shared and ask no real service; the grid test
scrolls the card into view. Finished files kept after a failed install
are now cleaned up after 7 days instead of 30 (paused downloads keep 30).
Fixes#5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On some Windows PCs every app showed "Not available here" / "Offline" and
the tooltip said "invalid peer certificate: UnknownIssuer". We read the
Windows root store once with rustls-native-certs, but Windows only adds
many roots when something asks it to verify a chain. Chrome and Edge
verify on their own, so a PC that only uses them can lack the root
GitHub's certificate needs. Antivirus HTTPS scanning hits the same path.
Use ureq's platform-verifier feature (rustls-platform-verifier) so the OS
checks each chain itself and fetches missing roots. Linux still reads the
system store. No bundled root list comes in.
Co-Authored-By: Claude Opus 5.5 <[email protected]>