Compare commits

...

15 Commits

Author SHA1 Message Date
edf415bf03 Merge pull request 'brian/flake' (#4) from brian/flake into main
Reviewed-on: #4
2026-03-07 09:49:57 -05:00
1911081355 added flake.lock and readme 2026-03-07 09:49:20 -05:00
dabf6c0f60 adding flake option 2026-03-07 09:36:50 -05:00
8e716b5fe8 Merge pull request 'added module options' (#3) from brian/nix-module into main
Reviewed-on: #3
2026-03-07 09:16:46 -05:00
d7419bcf3b added module options 2026-03-07 09:15:30 -05:00
7d9ecb8823 Merge pull request 'add desktop path and fix icon' (#2) from brian/fix-icon-desktop into main
Reviewed-on: #2
2026-03-06 17:23:36 -05:00
6084fcca27 add desktop path and fix icon 2026-03-06 13:51:21 -05:00
22ab193f83 Merge pull request 'clean up and final touches' (#1) from brian/final into main
Reviewed-on: #1
2026-03-06 13:28:53 -05:00
3bb0ac8aee clean up and final touches 2026-03-06 13:22:54 -05:00
d51bb39659 working 2026-03-03 21:09:34 -05:00
f0e7b822eb builds correctly, errors on open 2026-03-03 19:12:18 -05:00
288a60cfa1 updated 2026-03-03 14:53:21 -05:00
108b714fcb . 2026-03-03 11:00:57 -05:00
a146a2b5c3 added gitignore 2026-03-03 10:59:10 -05:00
169df46bc2 added cargo files 2026-03-03 10:57:43 -05:00
11 changed files with 8778 additions and 14 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/PinePods-0.8.2
PinePods-0.8.2/
result

3245
Cargo.frontend.lock Normal file

File diff suppressed because it is too large Load Diff

4918
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

228
README.md Normal file
View File

@@ -0,0 +1,228 @@
# PinePods Nix Package
A Nix/NixOS package for [PinePods](https://github.com/madeofpendletonwool/PinePods), a self-hosted podcast manager. This packages the PinePods desktop client (Tauri v2) for NixOS.
> **Note:** PinePods is a client/server application. You will need a running PinePods server instance to use this package. See the [PinePods documentation](https://github.com/madeofpendletonwool/PinePods) for server setup instructions.
---
## Requirements
- NixOS
- A running PinePods server (self-hosted)
---
## Installation
### Option 1: NixOS Module via Flake (recommended)
Add PinePods as a flake input in your system `flake.nix`:
```nix
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
pinepods.url = "git+https://git.briannelson.dev/brian/PinePods-nix";
pinepods.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { self, nixpkgs, pinepods, ... }: {
nixosConfigurations.mymachine = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
pinepods.nixosModules.pinepods
{
services.pinepods = {
enable = true;
# Your server address
server = "https://pinepods.example.com";
};
}
];
};
};
}
```
Then rebuild your system:
```bash
sudo nixos-rebuild switch
```
---
### Option 2: NixOS Module without Flakes
Clone this repository:
```bash
git clone https://git.briannelson.dev/brian/PinePods-nix.git
```
Add the module to your `/etc/nixos/configuration.nix`:
```nix
{ config, pkgs, ... }:
{
imports = [
/path/to/pinepods-nix/pinepods-module.nix
];
services.pinepods = {
enable = true;
# Your server address
server = "https://pinepods.example.com";
};
}
```
Then rebuild your system:
```bash
sudo nixos-rebuild switch
```
---
### Option 3: Install to user profile with nix-env
Clone this repository:
```bash
git clone https://git.briannelson.dev/brian/PinePods-nix.git
cd pinepods-nix
```
Install to your user profile:
```bash
nix-env -f default.nix -iA pinepods
```
This is persistent across reboots. To uninstall:
```bash
nix-env -e pinepods
```
> **Note:** With this option the `server` URL cannot be pre-configured. You will need to enter it manually in the app on first launch.
---
## Configuration
### Module Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `services.pinepods.enable` | bool | `false` | Enable the PinePods desktop client |
| `services.pinepods.server` | string | `""` | URL of your PinePods server (e.g. `https://pinepods.example.com`) |
When `server` is set, the app will navigate directly to your server on launch. If left empty, the app will open to a blank page and you will need to navigate to your server manually in the address bar.
---
## Project Structure
```
pinepods-nix/
├── flake.nix # Nix flake definition
├── default.nix # Package definition entry point (non-flake)
├── pinepods.nix # Main package derivation
├── pinepods-module.nix # NixOS module definition
├── patch-lib.py # Build-time patch for server URL support
├── example-usage-flake.nix # Example flake for users
├── Cargo.lock # Cargo lockfile for src-tauri (Tauri binary)
└── Cargo.frontend.lock # Cargo lockfile for web/ (Yew/WASM frontend)
```
---
## How It Works
PinePods is a multi-component application. This package builds two components from source:
**Frontend (pinepods-frontend):** The Yew/WASM web frontend is compiled using `trunk` targeting `wasm32-unknown-unknown`. The output is a static `dist/` directory containing HTML, WASM, and CSS assets.
**Desktop client (pinepods):** The Tauri v2 desktop binary is compiled with `cargo`, with the pre-built frontend assets embedded directly into the binary via the `custom-protocol` feature. At build time, `tauri.conf.json` is patched to remove the `devUrl` so Tauri serves the embedded assets instead of connecting to a development server. The Rust source is also patched to read the `PINEPODS_SERVER` environment variable and navigate the app window to that URL on launch.
**Runtime dependencies** are provided via `wrapProgram`:
- WebKit2GTK 4.1 (Tauri v2 requirement)
- GStreamer + plugins (audio playback)
- libayatana-appindicator (system tray icon)
- GLib/GTK3/Cairo stack
---
## Building from Source
To build manually without installing:
```bash
git clone https://git.briannelson.dev/brian/PinePods-nix.git
cd pinepods-nix
nix-build -A pinepods
./result/bin/pinepods
```
To use the dev shell with all build tools available:
```bash
nix develop
```
---
## Updating to a New Version of PinePods
*This project is still a WIP so I have not been able to test these step until a new version of PinePods gets released*
To update to a new PinePods release:
1. Update the `version` and `sha256` in `pinepods.nix`:
```nix
version = "0.8.3"; # new version
sha256 = "..."; # run nix-build and paste the correct hash from the error
```
2. Replace the Cargo lockfiles with the new versions:
```bash
curl -L https://github.com/madeofpendletonwool/PinePods/archive/refs/tags/0.8.3.tar.gz | tar xz
cp PinePods-0.8.3/web/Cargo.lock ./Cargo.frontend.lock
cp PinePods-0.8.3/web/src-tauri/Cargo.lock ./Cargo.lock
```
3. Check if the `wasm-bindgen` version changed:
```bash
grep "wasm-bindgen" PinePods-0.8.3/web/Cargo.toml
```
If it changed, update `wasm-bindgen-cli` in `default.nix` and `TRUNK_TOOLS_WASM_BINDGEN` in `pinepods.nix` accordingly.
4. Rebuild and test:
```bash
nix-build -A pinepods
./result/bin/pinepods
```
---
## Troubleshooting
**White screen on launch**
Make sure `WEBKIT_DISABLE_SANDBOX_THIS_IS_DANGEROUS=1` is set. This is handled automatically by the package wrapper.
**No audio playback**
GStreamer plugins are included but if audio still doesn't work, check that your system has the required codec support:
```bash
gst-inspect-1.0 autoaudiosink
gst-inspect-1.0 appsink
```
**Login doesn't persist between sessions**
This is a known limitation of the WebKit localStorage implementation. As a workaround, set `services.pinepods.server` in your NixOS configuration so the app always navigates to your server on launch.
**Tray icon shows generic icon**
The icon is installed under the app's reverse-DNS identifier `com.gooseberrydevelopment.pinepods`. Make sure your icon theme cache is up to date:
```bash
gtk-update-icon-cache
```
---
## License
This Nix packaging is provided as-is. PinePods itself is licensed under GPL-3.0. See the [PinePods repository](https://github.com/madeofpendletonwool/PinePods) for details.

View File

@@ -2,6 +2,12 @@ let
nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-25.11"; nixpkgs = fetchTarball "https://github.com/NixOS/nixpkgs/tarball/nixos-25.11";
pkgs = import nixpkgs { config = {}; overlays = []; }; pkgs = import nixpkgs { config = {}; overlays = []; };
in in
{ serverUrl ? "" }:
{ {
pinepods = pkgs.callPackage ./pinepods.nix { }; pinepods = pkgs.callPackage ./pinepods.nix {
# Pin wasm-bindgen-cli to exactly the version PinePods requires
wasm-bindgen-cli = pkgs.wasm-bindgen-cli_0_2_105;
inherit (pkgs) binaryen tailwindcss_3 libayatana-appindicator gst_all_1 python3;
inherit serverUrl;
};
} }

34
example-useage-flake.nix Normal file
View File

@@ -0,0 +1,34 @@
{
description = "Example NixOS configuration using the PinePods flake";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
pinepods.url = "git+https://git.briannelson.dev/brian/pinepods-nix";
# If you want to ensure pinepods uses the same nixpkgs as your system
pinepods.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { self, nixpkgs, pinepods, ... }: {
nixosConfigurations.mymachine = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
# Import the PinePods NixOS module
pinepods.nixosModules.pinepods
({ config, pkgs, ... }: {
# Enable PinePods and point it at your server
services.pinepods = {
enable = true;
server = "https://pinepods.example.com";
};
# The rest of your normal NixOS configuration goes here...
# networking.hostName = "mymachine";
# users.users.youruser = { ... };
# etc.
})
];
};
};
}

27
flake.lock generated Normal file
View File

@@ -0,0 +1,27 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1772822230,
"narHash": "sha256-yf3iYLGbGVlIthlQIk5/4/EQDZNNEmuqKZkQssMljuw=",
"owner": "NixOs",
"repo": "nixpkgs",
"rev": "71caefce12ba78d84fe618cf61644dce01cf3a96",
"type": "github"
},
"original": {
"owner": "NixOs",
"ref": "nixos-25.11",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

66
flake.nix Normal file
View File

@@ -0,0 +1,66 @@
{
description = "Pinepods desktop client - self-hosted podcast manager";
inputs = {
nixpkgs.url = "github:NixOs/nixpkgs/nixos-25.11";
};
outputs = { self, nixpkgs }:
let
system = "x86_64-linux";
pkgs = import nixpkgs { inherit system; config = {}; overlays = []; };
makePinepods = { serverUrl ? "" }:
pkgs.callPackage ./pinepods.nix {
wasm-bindgen-cli = pkgs.wasm-bindgen-cli_0_2_105;
inherit (pkgs) binaryen tailwindcss_3 libayatana-appindicator gst_all_1 python3;
inherit serverUrl;
};
in
{
packages.${system} = {
pinepods = makePinepods { };
default = makePinepods { };
};
nixosModules.pinepods = { config, lib, pkgs, ... }:
let
cfg = config.services.pinepods;
in
{
options.services.pinepods = {
enable = lib.mkEnableOption "PinePods podcast manager";
server = lib.mkOption {
type = lib.types.str;
default = "";
example = "https://pinepods.example.com";
description = ''
The URL oof your self-hosted PinePods server.
The app will navigate directly to this address on launch.
'';
};
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [
(makePinepods { serverUrl = cfg.server; })
];
};
};
nixosModules.default = self.nixosModules.pinepods;
devShells.${system}.default = pkgs.mkShell {
buildInputs = with pkgs; [
trunk
wasm-bindgen-cli_0_2_105
cargo
rustc
llvmPackages.lld
binaryen
tailwindcss_3
];
};
};
}

28
patch-lib.py Normal file
View File

@@ -0,0 +1,28 @@
import sys
target = '.run(tauri::generate_context!())'
replacement = '''.setup(|app| {
if let Ok(server_url) = std::env::var("PINEPODS_SERVER") {
if !server_url.is_empty() {
use tauri::Manager;
let window = app.get_webview_window("main").unwrap();
window.navigate(server_url.parse().unwrap()).unwrap();
}
}
Ok(())
})
.run(tauri::generate_context!())'''
with open(sys.argv[1], 'r') as f:
content = f.read()
if target not in content:
print(f"ERROR: Could not find target string in {sys.argv[1]}")
sys.exit(1)
content = content.replace(target, replacement)
with open(sys.argv[1], 'w') as f:
f.write(content)
print("Successfully patched lib.rs")

24
pinepods-module.nix Normal file
View File

@@ -0,0 +1,24 @@
{ config, lib, pkgs, ... }:
let
cfg = config.services.pinepods;
pinepods-pkg = (import "${toString ./.}/default.nix" {
serverUrl = cfg.server;
}).pinepods;
in
{
options.services.pinepods = {
enable = lib.mkEnableOption "PinePods podcast manager";
server = lib.mkOption {
type = lib.types.str;
default = "";
example = "https://pinepods.example.com";
description = "The URL of your PinePods server.";
};
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [ pinepods-pkg ];
};
}

View File

@@ -1,25 +1,210 @@
{ { lib
stdenv, , fetchFromGitHub
fetchFromGitHub, , rustPlatform
, pkg-config
, wrapGAppsHook3
, llvmPackages
, binaryen
, tailwindcss_3
, webkitgtk_4_1
, gtk3
, glib
, cairo
, gdk-pixbuf
, pango
, atk
, libsoup_3
, librsvg
, dbus
, openssl
, at-spi2-atk
, at-spi2-core
, glib-networking
, gsettings-desktop-schemas
, wasm-bindgen-cli
, trunk
, makeWrapper
, libayatana-appindicator
, gst_all_1
, python3
, serverUrl ? ""
}: }:
stdenv.mkDerivation { let
pname = "PinePods"; pname = "pinepods";
version = "0.8.2"; version = "0.8.2";
src = fetchFromGitHub { src = fetchFromGitHub {
owner = "madeofpendletonwool"; owner = "madeofpendletonwool";
repo = "PinePods"; repo = "PinePods";
rev = "0.8.2"; rev = version;
sha256 = "1ivqazhzg3wwlqzz0dv0g48hv265c3qqhvxl02j20361j4mqn6rp"; sha256 = "1ivqazhzg3wwlqzz0dv0g48hv265c3qqhvxl02j20361j4mqn6rp";
}; };
buildInputs = [ ]; # ── Step 1: Build the Yew/WASM frontend with trunk ──────────────────────────
frontend = rustPlatform.buildRustPackage {
pname = "pinepods-frontend";
inherit version src;
sourceRoot = "source/web";
cargoRoot = ".";
cargoLock = {
lockFile = ./Cargo.frontend.lock;
outputHashes = {
"i18nrs-0.1.7" = "sha256-LEHQ8GI3eVKwhtxetxyIY4HV16TS3D9n+2Hh+sm5j74=";
};
};
nativeBuildInputs = [ trunk wasm-bindgen-cli llvmPackages.lld binaryen tailwindcss_3 ];
buildPhase = ''
cp -r . $NIX_BUILD_TOP/web-build
chmod -R u+w $NIX_BUILD_TOP/web-build
cd $NIX_BUILD_TOP/web-build
mkdir -p dist
export HOME=$NIX_BUILD_TOP
export XDG_CACHE_HOME=$NIX_BUILD_TOP/cache
mkdir -p $NIX_BUILD_TOP/cache
export TRUNK_SKIP_VERSION_CHECK=true
export TRUNK_TOOLS_WASM_BINDGEN=0.2.105
export TRUNK_TOOLS_WASM_OPT=version_124
RUSTFLAGS="--cfg=web_sys_unstable_apis" trunk build --release
'';
installPhase = ''
cp -r $NIX_BUILD_TOP/web-build/dist $out
'';
doCheck = false;
};
in
# ── Step 2: Build the Tauri binary, pointing it at the pre-built frontend ────
rustPlatform.buildRustPackage {
inherit pname version src;
sourceRoot = "source/web/src-tauri";
cargoRoot = ".";
cargoExtraArgs = "--bin app --features custom-protocol";
cargoLock = {
lockFile = ./Cargo.lock;
};
nativeBuildInputs = [
pkg-config
wrapGAppsHook3
makeWrapper
python3
];
buildInputs = [
openssl
dbus
glib
gtk3
cairo
gdk-pixbuf
pango
atk
at-spi2-atk
at-spi2-core
libsoup_3
webkitgtk_4_1
librsvg
glib-networking
gsettings-desktop-schemas
libayatana-appindicator
gst_all_1.gstreamer
gst_all_1.gst-plugins-base
gst_all_1.gst-plugins-good
gst_all_1.gst-plugins-bad
gst_all_1.gst-plugins-ugly
gst_all_1.gst-libav
];
OPENSSL_NO_VENDOR = "1";
TAURI_SKIP_DEVSERVER_CHECK = "true";
preBuild = ''
chmod -R u+w $NIX_BUILD_TOP/source
ln -s ${frontend} $NIX_BUILD_TOP/source/web/dist
sed -i '/"devUrl"/d' $NIX_BUILD_TOP/source/web/src-tauri/tauri.conf.json
# Patch lib.rs to read PINEPODS_SERVER env var and navigate to it on startup
python3 ${./patch-lib.py} $NIX_BUILD_TOP/source/web/src-tauri/src/lib.rs
'';
installPhase = '' installPhase = ''
runHook preInstall runHook preInstall
mkdir -p $out/bin
cp PinePods $out/bin install -Dm755 target/x86_64-unknown-linux-gnu/release/app $out/bin/pinepods
# Desktop entry
if [ -f ../../pinepods.desktop ]; then
install -Dm644 ../../pinepods.desktop \
$out/share/applications/pinepods.desktop
fi
# Icons
for size in 32x32 128x128 256x256; do
if [ -f icons/''${size}.png ]; then
install -Dm644 icons/''${size}.png \
$out/share/icons/hicolor/''${size}/apps/pinepods.png
fi
done
# Tray icon install as the app icon so the taskbar uses it
if [ -f icons/icon.png ]; then
install -Dm644 icons/icon.png \
$out/share/icons/hicolor/256x256/apps/com.gooseberrydevelopment.pinepods.png
fi
# AppStream metadata
if [ -f com.gooseberrydevelopment.pinepods.metainfo.xml ]; then
install -Dm644 com.gooseberrydevelopment.pinepods.metainfo.xml \
$out/share/metainfo/com.gooseberrydevelopment.pinepods.metainfo.xml
fi
# Create desktop entry
mkdir -p $out/share/applications
cat > $out/share/applications/pinepods.desktop << EOF
[Desktop Entry]
Name=Pinepods
Comment=A self-hosted podcast manager
Exec=pinepods
Icon=com.gooseberrydevelopment.pinepods
Type=Application
Categories=Audio;Music;
StartupNotify=true
StartupWMClass=Pinepods
EOF
runHook postInstall runHook postInstall
''; '';
postFixup = ''
wrapProgram $out/bin/pinepods \
--set WEBKIT_DISABLE_COMPOSITING_MODE 1 \
--set WEBKIT_DISABLE_SANDBOX_THIS_IS_DANGEROUS 1 \
${lib.optionalString (serverUrl != "") "--set PINEPODS_SERVER \"${serverUrl}\""} \
--prefix LD_LIBRARY_PATH : "${libayatana-appindicator}/lib" \
--prefix GST_PLUGIN_SYSTEM_PATH_1_0 : "${gst_all_1.gstreamer}/lib/gstreamer-1.0:${gst_all_1.gst-plugins-base}/lib/gstreamer-1.0:${gst_all_1.gst-plugins-good}/lib/gstreamer-1.0:${gst_all_1.gst-plugins-bad}/lib/gstreamer-1.0:${gst_all_1.gst-plugins-ugly}/lib/gstreamer-1.0:${gst_all_1.gst-libav}/lib/gstreamer-1.0" \
--prefix XDG_DATA_DIRS : "$out/share" \
--prefix XDG_DATA_DIRS : "${gsettings-desktop-schemas}/share/gsettings-schemas/${gsettings-desktop-schemas.name}:${gtk3}/share/gsettings-schemas/${gtk3.name}" \
--prefix GIO_EXTRA_MODULES : "${glib-networking}/lib/gio/modules"
'';
doCheck = false;
meta = with lib; {
description = "PinePods desktop client - self-hosted podcast manager (Tauri v2)";
homepage = "https://github.com/madeofpendletonwool/PinePods";
license = licenses.gpl3Only;
platforms = platforms.linux;
mainProgram = "pinepods";
};
} }