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
8 changes: 3 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,14 @@
| <img height="20" src="https://github.com/user-attachments/assets/340d360e-79b1-4c70-bfab-d944085f75df" /> Windows | <img height="20" src="https://github.com/user-attachments/assets/42d7e887-4616-4e8c-b1d3-e44e01340f8c" /> macOS | <img height="20" src="https://github.com/user-attachments/assets/e0cc4f33-4516-408b-9c5c-be71a3ac316b" /> Linux |
| :-- | :-- | :-- |
| **EXE: [x64](https://github.com/BurntToasters/ROSI/releases/download/v5.0.0-beta.1/ROSI-Windows-x64.exe) / [arm64](https://github.com/BurntToasters/ROSI/releases/download/v5.0.0-beta.1/ROSI-Windows-arm64.exe)** | **[Universal DMG](https://github.com/BurntToasters/ROSI/releases/download/v5.0.0-beta.1/ROSI-MacOS-universal.dmg)** | **AppImage:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v5.0.0-beta.1/ROSI-Linux-x64.AppImage) |
| <div align="center"><a href="https://apps.microsoft.com/detail/9p4q134b2jw3?referrer=appbadge&mode=direct"><img src="https://get.microsoft.com/images/en-us%20dark.svg" width="150"/></a></div> | **[Universal ZIP](https://github.com/BurntToasters/ROSI/releases/download/v5.0.0-beta.1/ROSI-MacOS-universal.zip)** | **DEB:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v5.0.0-beta.1/ROSI-Linux-x64.deb) |
| | | **RPM:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v5.0.0-beta.1/ROSI-Linux-x64.rpm) |
| | | **Flatpak:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v5.0.0-beta.1/ROSI-Linux-x64.flatpak) |
| <div align="center"><a href="https://apps.microsoft.com/detail/9p4q134b2jw3?referrer=appbadge&mode=direct"><img src="https://get.microsoft.com/images/en-us%20dark.svg" width="150"/></a></div> | **[Universal ZIP](https://github.com/BurntToasters/ROSI/releases/download/v5.0.0-beta.1/ROSI-MacOS-universal.zip)** | **Flatpak:** [x64](https://github.com/BurntToasters/ROSI/releases/download/v5.0.0-beta.1/ROSI-Linux-x64.flatpak) |

> macOS downloads require macOS 15 or later.

> [!IMPORTANT]
> The `.sig` files in this repo are NOT normal GPG signatures. They are for Tauri V2's updater to verify the integrity of updates before downloading and installing.
> The `.asc` files are my normal GPG signatures which you can verify using my GPG Public Key: https://tuxedo.rosie.run/GPG/BurntToasters_0xF2FBC20F_public.asc
> ⚠️ Linux ARM64 AppImage/DEB/RPM are published only when that release is built for ARM64; Flatpak stays x64.
> ⚠️ ROSI 5 Linux downloads are x64-only: AppImage and Flatpak. ARM64 build wiring remains for future development, but ARM64 downloads are not supported in v5.

### ℹ️ Enjoying ROSI? Consider [❤️ Supporting Me! ❤️](https://rosie.run/support)

Expand All @@ -28,7 +26,7 @@ ROSI! A cross platform yt-dlp GUI built on Tauri V2!
- **Breaking:** ROSI 5 is a new app. The app identifier is now `run.rosie.rosi`, and v4 cannot auto-update to v5. Install v5 manually.
- **NEW - v4 import:** On its first launch, ROSI 5 imports your ROSI 4 settings, queue, lifetime stats, and download activity. Values ROSI 5 does not accept fall back to defaults, and the ROSI 4 files are left untouched.
- **Breaking - macOS:** ROSI 5 requires macOS 15 or later, matching the bundled FFmpeg build.
- **Breaking - Linux:** Linux ARM64 builds are paused; Linux x64 ships as AppImage, DEB, RPM, and Flatpak.
- **Linux - DEB and RPM builds are retired:** ROSI 5 supports Linux x64 AppImage and Flatpak downloads only. Linux ARM64 downloads are not supported in v5, although the build wiring remains for future development. DEB and RPM packaging is retired for v5 releases.
- **Packaging:** Bundled helpers are now named `rosi-yt-dlp`, `rosi-ffmpeg`, and `rosi-ffprobe` so Linux packages never collide with distro `yt-dlp` / `ffmpeg` files.
- **Licenses:** The licenses view now also lists every compiled Rust crate and shows the exact bundled yt-dlp and FFmpeg notices.
- **Windows:** Dragging a link from the browser onto the download card or queue works again, and F5, Ctrl+R, Ctrl+P, and the page right-click menu no longer reload or print the app mid-download.
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ webview, so installers and memory use are much smaller.
the v4 files untouched.
- The in-app updater has **stable** and **beta** channels (Settings > Update
channel). `auto` follows the installed version.
- Linux ships x64 AppImage, DEB, RPM, and a sideloaded Flatpak. Linux ARM64
builds are paused; the build wiring remains for a future release.
- Linux ships an x64 AppImage and a sideloaded Flatpak. DEB and RPM packages
are no longer built or published. Linux ARM64 is not supported in v5; the
build wiring remains available for future development.

## System requirements

Expand Down
12 changes: 6 additions & 6 deletions build-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@

## Linux

- Build public AppImage and DEB artifacts on Ubuntu 24.04, the oldest supported
glibc baseline. Build and test RPM artifacts on Fedora 43.
- Build the public AppImage on Ubuntu 24.04, the oldest supported glibc
baseline.
- `npm run setup:deb` installs the Ubuntu build, E2E (xvfb), 7-Zip (for
`get:ffmpeg`), and Flatpak prerequisites.
- Node.js `^22.22.2 || ^24.15.0 || >=26`
- Rust (rustup)
- Linux releases ship x64 only (`npm run release:linux:x64`). The ARM64 wiring
(`release:linux:arm64`, `build:linux:arm64`) is kept for a future release and
must run on native ARM64 hardware.
- Linux v5 releases ship x64 only (`npm run release:linux:x64`). ARM64 wiring
(`release:linux:arm64`, `build:linux:arm64`) remains for future development
and is not part of the v5 release matrix.

## Rust toolchain policy

Expand Down Expand Up @@ -164,7 +164,7 @@ been cryptographically matched to its artifact with the public key in
The `b`, `r`, and `release:*` scripts intentionally reset and clean their Git
worktrees. Run them only on disposable, isolated build VMs. Before publishing,
verify that the draft contains the Windows x64/ARM64 NSIS installers, the
universal macOS DMG/ZIP, Linux x64 AppImage/DEB/RPM/Flatpak, updater
universal macOS DMG/ZIP, Linux x64 AppImage/Flatpak, updater
manifests/signatures, SHA-256 lists, and GPG detached signatures
(`npm run release:verify:draft`).

Expand Down
7 changes: 3 additions & 4 deletions docs/RELEASE-STABLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,8 @@ npm run release:mac
npm run release:linux
```

Run `release:linux:arm64` only on the supported ARM64 release environment when
that artifact is part of the release. Do not use beta recovery overrides for a
stable release.
Linux ARM64 wiring is retained for future development but is not part of the
v5 release matrix. Do not use beta recovery overrides for a stable release.

The platform release commands intentionally skip GUI E2E on the signing VM.
That is acceptable only because step 4 and protected CI already proved the
Expand All @@ -162,7 +161,7 @@ exact stable commit with E2E enabled.
Before publishing, install each signed artifact on its platform and verify:
launch, a real download, an FFmpeg conversion, the queue, notifications, the
updater (stable and beta channels), macOS notarization and sidecar signatures,
and Linux desktop integration (AppImage/DEB/RPM/Flatpak launchers).
and Linux desktop integration (AppImage/Flatpak launchers).

Fix and rebuild any failing artifact. Do not publish a draft that has not
passed this matrix.
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@
"build:mac:universal": "npm run sync-version && npm run licenses && npm run prepare:sidecars && npm run build:mac:universal:prepared",
"build:mac:zip": "node scripts/zip-macos.js",
"mac:ssh:keychain": "bash scripts/mac-keychain-ssh.sh",
"build:linux:x64:prepared": "dotenv -e .env -- tauri build --target x86_64-unknown-linux-gnu --bundles appimage,deb,rpm -- --locked",
"build:linux:arm64:prepared": "dotenv -e .env -- tauri build --target aarch64-unknown-linux-gnu --bundles appimage,deb,rpm -- --locked",
"build:linux:x64:prepared": "dotenv -e .env -- tauri build --target x86_64-unknown-linux-gnu --bundles appimage -- --locked",
"build:linux:arm64:prepared": "dotenv -e .env -- tauri build --target aarch64-unknown-linux-gnu --bundles appimage -- --locked",
"build:linux:prepared": "npm run build:linux:x64:prepared",
"build:linux:x64": "npm run sync-version && npm run licenses && npm run prepare:sidecars && npm run build:linux:x64:prepared",
"build:linux:arm64": "npm run sync-version && npm run licenses && node scripts/prepare-sidecars.js --target aarch64-unknown-linux-gnu && npm run build:linux:arm64:prepared",
Expand Down
4 changes: 0 additions & 4 deletions scripts/gpg-sign.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,6 @@ export function artifactMatchesVersion(
name: string,
releaseVersion?: string,
): boolean;
export function rpmArtifactMatchesVersion(
name: string,
releaseVersion?: string,
): boolean;
export function checksumTargetKeysForArtifactName(
name: string,
channelVariants?: UpdaterChannelVariant[],
Expand Down
102 changes: 6 additions & 96 deletions scripts/gpg-sign.js
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,6 @@ const REQUIRE_LINUX_AARCH64 = isExplicitTruthy(
const REQUIRED_UPDATER_TARGETS = (
process.env.REQUIRED_UPDATER_TARGETS || ""
).trim();
const ENFORCE_LINUX_X64_PACKAGE_SET = !/^(0|false|no|off)$/i.test(
String(process.env.ENFORCE_LINUX_X64_PACKAGE_SET || "").trim(),
);

const ext = (e) => (n) => n.toLowerCase().endsWith(e);
const rx = (r) => (n) => r.test(n);
Expand All @@ -120,8 +117,6 @@ const ARTIFACT_RULES = [
rx(/^ROSI-Windows-(?:x64|arm64)\.exe$/i),
ext(".msi"),
ext(".dmg"),
ext(".deb"),
ext(".rpm"),
ext(".flatpak"),
rx(/\.appimage$/i),

Expand All @@ -131,7 +126,7 @@ const ARTIFACT_RULES = [
rx(/\.app\.tar\.gz$/i),
rx(/\.appimage\.tar\.gz$/i),

rx(/\.(?:exe|msi|dmg|deb|rpm|flatpak|appimage)\.sig$/i),
rx(/\.(?:exe|msi|dmg|flatpak|appimage)\.sig$/i),
rx(/^ROSI(?:-MacOS-universal)?\.zip\.sig$/i),
rx(/\.nsis\.zip\.sig$/i),
rx(/\.tar\.gz\.sig$/i),
Expand All @@ -144,8 +139,6 @@ const SIGN_RULES = [
ext(".exe"),
ext(".msi"),
ext(".dmg"),
ext(".deb"),
ext(".rpm"),
ext(".flatpak"),
rx(/\.appimage$/i),
rx(/^ROSI(?:-MacOS-universal)?\.zip$/i),
Expand Down Expand Up @@ -182,50 +175,13 @@ const SEARCH_DIRS = releaseArtifactSearchDirs();

function artifactMatchesVersion(name, releaseVersion = VERSION) {
if (name === "latest.json" || isPerTargetManifest(name)) return true;
if (/\.rpm(?:\.sig)?$/i.test(name)) {
return rpmArtifactMatchesVersion(name, releaseVersion);
}
const versions = name.match(
/\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?/g,
);
if (!versions || versions.length === 0) return true;
return versions.some((candidate) => candidate === releaseVersion);
}

function escapeRegExp(value) {
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}

function rpmArtifactMatchesVersion(name, releaseVersion = VERSION) {
if (!/\.rpm(?:\.sig)?$/i.test(name)) return false;

const numericVersions = name.match(/\d+\.\d+\.\d+/g);
if (!numericVersions || numericVersions.length === 0) return true;

const betaMatch = releaseVersion.match(
/^(\d+\.\d+\.\d+)-beta\.(0|[1-9]\d*)$/,
);
const stableMatch = releaseVersion.match(/^(\d+\.\d+\.\d+)$/);
if (!betaMatch && !stableMatch) return false;

const numericVersion = betaMatch?.[1] ?? stableMatch[1];
const escapedNumericVersion = escapeRegExp(numericVersion);
const versionPattern = betaMatch
? `${escapedNumericVersion}(?:-beta\\.${betaMatch[2]}|[._~]beta[._-]${betaMatch[2]})`
: escapedNumericVersion;
// RPM names conventionally end in NAME-VERSION-RELEASE.ARCH.rpm. Tauri and
// distro tooling vary the release and architecture tokens, and updater
// signatures append another .sig. A release must begin with a digit, which
// keeps a sanitized beta marker from matching a stable application version.
const rpmRelease = "[0-9][0-9A-Za-z_+~%^.-]*";
const rpmArch =
"(?:x86_64|amd64|aarch64|arm64|i[3-6]86|noarch|ppc64le|ppc64|s390x|riscv64|armv[67]hl)";
return new RegExp(
`(?:^|[^0-9A-Za-z])${versionPattern}(?:-${rpmRelease})?(?:\\.${rpmArch})?\\.rpm(?:\\.sig)?$`,
"i",
).test(name);
}

const SIDECAR_MANIFEST = path.join(
root,
"src-tauri",
Expand Down Expand Up @@ -358,12 +314,6 @@ function cleanArtifactBaseName(name) {
if (/amd64\.AppImage$/i.test(name)) return "ROSI-Linux-x64.AppImage";
if (/aarch64\.AppImage$/i.test(name)) return "ROSI-Linux-arm64.AppImage";

if (/amd64\.deb$/i.test(name)) return "ROSI-Linux-x64.deb";
if (/aarch64\.deb$/i.test(name)) return "ROSI-Linux-arm64.deb";

if (/x86_64\.rpm$/i.test(name)) return "ROSI-Linux-x64.rpm";
if (/aarch64\.rpm$/i.test(name)) return "ROSI-Linux-arm64.rpm";

// The public Flatpak release currently targets Linux x64 only. Normalize
// generic files left by older build scripts to the documented asset name.
if (/^ROSI-Linux\.flatpak$/i.test(name)) return "ROSI-Linux-x64.flatpak";
Expand All @@ -382,7 +332,7 @@ function cleanArtifactName(name) {

const FALLBACK_INSTALLER_PRIORITY = {
windows: { nsis: 3, msi: 2 },
linux: { appimage: 3, deb: 2, rpm: 1 },
linux: { appimage: 3 },
darwin: { app: 3 },
};

Expand Down Expand Up @@ -446,28 +396,6 @@ function canPopulateFallbackTarget(_target) {
return true;
}

function assertLinuxX64PackageSet(byName) {
if (!ENFORCE_LINUX_X64_PACKAGE_SET) return;
const installers = new Set();
for (const [name] of byName) {
if (name.endsWith(".sig")) continue;
const targets = resolveUpdaterTargets(name);
for (const target of targets) {
if (target.os === "linux" && target.arch === "x86_64") {
installers.add(target.installer);
}
}
}
if (installers.size === 0) return;
const requiredInstallers = ["appimage", "deb", "rpm"];
const missing = requiredInstallers.filter((i) => !installers.has(i));
if (missing.length > 0) {
throw new Error(
`Incomplete Linux x86_64 bundle set: missing ${missing.join(", ")} artifact(s).`,
);
}
}

function resolveUpdaterTargets(name) {
const targets = [];
if (/\.app\.tar\.gz$/i.test(name)) {
Expand Down Expand Up @@ -500,20 +428,6 @@ function resolveUpdaterTargets(name) {
return targets;
}

if (/\.deb$/i.test(name)) {
const arch = inferArchFromName(name);
if (!arch) return targets;
targets.push({ os: "linux", arch, installer: "deb" });
return targets;
}

if (/\.rpm$/i.test(name)) {
const arch = inferArchFromName(name);
if (!arch) return targets;
targets.push({ os: "linux", arch, installer: "rpm" });
return targets;
}

return targets;
}

Expand All @@ -539,7 +453,6 @@ function generateUpdaterManifests(files) {
for (const filePath of files) {
byName.set(path.basename(filePath), filePath);
}
assertLinuxX64PackageSet(byName);

const signatureByBaseName = new Map();
for (const [name, filePath] of byName) {
Expand Down Expand Up @@ -605,8 +518,8 @@ function generateUpdaterManifests(files) {
manifest.platforms[installerKey] = { url, signature };
if (channel.targetSuffix) {
// A beta check uses the full installer-aware key as its custom Tauri
// target. Give that key its own endpoint manifest so DEB/RPM installs
// never fall through to the AppImage fallback (and vice versa).
// target. Give that key its own endpoint manifest so installers do
// not fall through to the generic fallback.
const installerManifestName = `latest-${installerKey}.json`;
manifests.set(installerManifestName, {
version: VERSION,
Expand Down Expand Up @@ -1420,11 +1333,9 @@ function requiredPublishedBetaManifestNames() {
"darwin-beta-aarch64-app",
"linux-beta-x86_64",
"linux-beta-x86_64-appimage",
"linux-beta-x86_64-deb",
"linux-beta-x86_64-rpm",
]);
if (REQUIRE_LINUX_AARCH64) {
for (const suffix of ["", "-appimage", "-deb", "-rpm"]) {
for (const suffix of ["", "-appimage"]) {
targets.add(`linux-beta-aarch64${suffix}`);
}
}
Expand All @@ -1442,7 +1353,7 @@ function requiredPublishedBetaManifestNames() {
function expectedPublishedBetaManifestNames(actualNames = []) {
const expected = new Set(requiredPublishedBetaManifestNames());
const optionalGroups = [
["", "-appimage", "-deb", "-rpm"].map(
["", "-appimage"].map(
(suffix) => `latest-linux-beta-aarch64${suffix}.json`,
),
];
Expand Down Expand Up @@ -1804,7 +1715,6 @@ export {
isGitHubConflict,
isTransactionalStagingAssetName,
listAllGithubPages,
rpmArtifactMatchesVersion,
expectedPublishedBetaManifestNames,
requiredPublishedBetaManifestNames,
requiredLinuxTargetKeys,
Expand Down
10 changes: 0 additions & 10 deletions scripts/release-policy.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,6 @@ const STABLE_FORBIDDEN_ENV = [
"ROSI_ALLOW_STUB_SIDECARS",
];

// Stable must keep the Linux x64 deb/rpm/AppImage completeness assertion;
// only explicit disabling (ENFORCE_*=0/false) is refused, opt-in is fine.
const STABLE_FORBIDDEN_FALSY_ENV = ["ENFORCE_LINUX_X64_PACKAGE_SET"];

// Stable uploads must target the canonical repository only; env retargeting
// is a beta-fork recovery path and must not ship a stable feed elsewhere.
const STABLE_CANONICAL_ENV = {
Expand Down Expand Up @@ -52,11 +48,6 @@ function assertStableReleaseOverridesAllowed(
const blocked = STABLE_FORBIDDEN_ENV.filter((name) =>
isExplicitTruthy(env[name]),
);
for (const name of STABLE_FORBIDDEN_FALSY_ENV) {
if (env[name] !== undefined && isExplicitFalsy(env[name])) {
blocked.push(name);
}
}
if (blocked.length > 0) {
throw new Error(
`Stable release ${version} refuses ${blocked.join(", ")}. Those overrides are beta recovery paths only.`,
Expand Down Expand Up @@ -96,7 +87,6 @@ if (require.main === module) {

module.exports = {
STABLE_FORBIDDEN_ENV,
STABLE_FORBIDDEN_FALSY_ENV,
isExplicitFalsy,
isExplicitTruthy,
isStableReleaseVersion,
Expand Down
14 changes: 8 additions & 6 deletions scripts/validate-flatpak-dry-run.js
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,14 @@ if (fs.existsSync(tauriConfPath)) {
fail("tauri.conf.json identifier must be run.rosie.rosi");
}
const linux = tauriConf.bundle?.linux ?? {};
for (const kind of ["deb", "rpm"]) {
if (linux[kind]?.desktopTemplate !== "linux/desktop-template.hbs") {
fail(
`bundle.linux.${kind}.desktopTemplate must be linux/desktop-template.hbs`,
);
}
if (linux.appimage?.bundleMediaFramework !== false) {
fail("bundle.linux.appimage.bundleMediaFramework must be false");
}
if (
tauriConf.bundle?.targets?.includes("deb") ||
tauriConf.bundle?.targets?.includes("rpm")
) {
fail("bundle.targets must not include retired DEB/RPM targets");
}
const externalBin = tauriConf.bundle?.externalBin ?? [];
for (const sidecar of ["rosi-yt-dlp", "rosi-ffmpeg", "rosi-ffprobe"]) {
Expand Down
Loading
Loading