Rebase to flake parts #9
This commit is contained in:
@@ -4,46 +4,27 @@
|
||||
|
||||
#TODO[epic=Moderate] Clean this up, move to host's configuration.nix.
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
gpuPassthrough = config.chiasson.system.vm.gpuPassthrough.enable;
|
||||
in
|
||||
{
|
||||
chiasson.desktop.niri.extraSettings = {
|
||||
extraConfig =
|
||||
if gpuPassthrough then
|
||||
''
|
||||
output "DP-1" {
|
||||
mode "2560x1080@144"
|
||||
scale 1.0
|
||||
position x=1920 y=0
|
||||
focus-at-startup
|
||||
}
|
||||
output "HDMI-A-2" {
|
||||
mode "2560x1080@60"
|
||||
scale 1.0
|
||||
position x=0 y=0
|
||||
}
|
||||
''
|
||||
else
|
||||
''
|
||||
output "DP-2" {
|
||||
mode "2560x1080@144"
|
||||
scale 1.0
|
||||
position x=0 y=0
|
||||
focus-at-startup
|
||||
}
|
||||
output "HDMI-A-3" {
|
||||
mode "1920x1080@60"
|
||||
scale 1.0
|
||||
position x=-1920 y=0
|
||||
}
|
||||
output "DP-4" {
|
||||
mode "1920x1080@144"
|
||||
scale 1.0
|
||||
position x=0 y=-1080
|
||||
}
|
||||
extraConfig = ''
|
||||
output "DP-2" {
|
||||
mode "2560x1080@144"
|
||||
scale 1.0
|
||||
position x=0 y=0
|
||||
focus-at-startup
|
||||
}
|
||||
output "HDMI-A-3" {
|
||||
mode "1920x1080@60"
|
||||
scale 1.0
|
||||
position x=-1920 y=0
|
||||
}
|
||||
output "DP-4" {
|
||||
mode "1920x1080@144"
|
||||
scale 1.0
|
||||
position x=0 y=-1080
|
||||
}
|
||||
|
||||
'';
|
||||
'';
|
||||
|
||||
binds."XF86Tools".spawn = [
|
||||
"wpctl"
|
||||
@@ -55,43 +36,22 @@ in
|
||||
|
||||
chiasson.desktop.hyprland.settings = lib.mkIf config.chiasson.desktop.hyprland.enable (
|
||||
let
|
||||
monitorList =
|
||||
if gpuPassthrough then
|
||||
[
|
||||
"DP-1, 2560x1080@144, 0x0, 1"
|
||||
"HDMI-A-2, 1920x1080@60, auto-up, 1"
|
||||
]
|
||||
else
|
||||
[
|
||||
"DP-2, 2560x1080@144, 0x0, 1"
|
||||
"DP-4, 1920x1080@144, 0x-1080, 1"
|
||||
"HDMI-A-3, 1920x1080@60, -1920x0, 1"
|
||||
];
|
||||
workspaceList =
|
||||
if gpuPassthrough then
|
||||
[
|
||||
"1, monitor:DP-1, default:true"
|
||||
"2, monitor:DP-1"
|
||||
"3, monitor:DP-1"
|
||||
"4, monitor:DP-1"
|
||||
"5, monitor:HDMI-A-2, default:true"
|
||||
"6, monitor:HDMI-A-2"
|
||||
"7, monitor:HDMI-A-2"
|
||||
"8, monitor:HDMI-A-2"
|
||||
"9, monitor:DP-1"
|
||||
]
|
||||
else
|
||||
[
|
||||
"1, monitor:DP-3, default:true"
|
||||
"2, monitor:DP-3"
|
||||
"3, monitor:DP-3"
|
||||
"4, monitor:Unknown-2, default:true"
|
||||
"5, monitor:Unknown-2"
|
||||
"6, monitor:Unknown-2"
|
||||
"7, monitor:DP-4"
|
||||
"8, monitor:DP-4"
|
||||
"9, monitor:DP-4"
|
||||
];
|
||||
monitorList = [
|
||||
"DP-2, 2560x1080@144, 0x0, 1"
|
||||
"DP-4, 1920x1080@144, 0x-1080, 1"
|
||||
"HDMI-A-3, 1920x1080@60, -1920x0, 1"
|
||||
];
|
||||
workspaceList = [
|
||||
"1, monitor:DP-3, default:true"
|
||||
"2, monitor:DP-3"
|
||||
"3, monitor:DP-3"
|
||||
"4, monitor:Unknown-2, default:true"
|
||||
"5, monitor:Unknown-2"
|
||||
"6, monitor:Unknown-2"
|
||||
"7, monitor:DP-4"
|
||||
"8, monitor:DP-4"
|
||||
"9, monitor:DP-4"
|
||||
];
|
||||
in
|
||||
{
|
||||
monitor = lib.mkBefore monitorList;
|
||||
|
||||
@@ -26,8 +26,9 @@
|
||||
mountdPort = 4000;
|
||||
lockdPort = 4001;
|
||||
statdPort = 4002;
|
||||
# fsid= stabilizes file handles across server reboots/remounts of this tree (avoids client ESTALE).
|
||||
exports = ''
|
||||
/mnt/test/jellyfin 192.168.2.238(rw,sync,no_subtree_check,crossmnt,root_squash,all_squash,anonuid=990,anongid=990)
|
||||
/mnt/test/jellyfin 192.168.2.238(rw,sync,no_subtree_check,crossmnt,root_squash,all_squash,anonuid=990,anongid=990,fsid=1)
|
||||
'';
|
||||
};
|
||||
|
||||
|
||||
@@ -1,26 +1,19 @@
|
||||
# NVIDIA for host desktop; when `chiasson.system.vm.gpuPassthrough` is enabled, drop NVIDIA for VFIO (port later).
|
||||
# NVIDIA for host desktop.
|
||||
{ config, lib, pkgs, ... }:
|
||||
let
|
||||
passthrough = config.chiasson.system.vm.gpuPassthrough.enable;
|
||||
in
|
||||
{
|
||||
boot.kernelParams = [ "snd_hda_core.gpu_bind=0" ];
|
||||
boot.kernelPackages = lib.mkDefault pkgs.linuxPackages_latest;
|
||||
|
||||
services.xserver.videoDrivers = if passthrough then [ "modesetting" ] else [ "nvidia" ];
|
||||
services.xserver.videoDrivers = [ "nvidia" ];
|
||||
|
||||
hardware.nvidia =
|
||||
if passthrough then
|
||||
lib.mkForce { }
|
||||
else {
|
||||
modesetting.enable = true;
|
||||
powerManagement.enable = false;
|
||||
powerManagement.finegrained = false;
|
||||
open = true;
|
||||
nvidiaSettings = true;
|
||||
package = config.boot.kernelPackages.nvidiaPackages.stable;
|
||||
};
|
||||
hardware.nvidia = {
|
||||
modesetting.enable = true;
|
||||
powerManagement.enable = false;
|
||||
powerManagement.finegrained = false;
|
||||
open = true;
|
||||
nvidiaSettings = true;
|
||||
package = config.boot.kernelPackages.nvidiaPackages.stable;
|
||||
};
|
||||
|
||||
# Needed for `docker compose` GPU passthrough (e.g. `--gpus all` / DEVICE=gpu).
|
||||
hardware.nvidia-container-toolkit.enable = !passthrough;
|
||||
hardware.nvidia-container-toolkit.enable = true;
|
||||
}
|
||||
|
||||
@@ -84,13 +84,7 @@ services.cloudflare-warp.enable = true;
|
||||
};
|
||||
|
||||
chiasson.system = {
|
||||
# libvirt/QEMU + VFIO; host uses Intel iGPU for Niri while NVIDIA is passed through (see
|
||||
# `_private/nvidia.nix`, `_private/displays.nix`). If your GPU is not RTX 2070-class IDs, set
|
||||
# `chiasson.system.vm.gpuPassthrough.vfioIds` from `lspci -nn` (GPU + HDA functions in the same group).
|
||||
vm = {
|
||||
enable = true;
|
||||
gpuPassthrough.enable = false;
|
||||
};
|
||||
ytDlpTelequebecPatch.enable = true;
|
||||
|
||||
audio.enable = true;
|
||||
docker.enable = true;
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
# Cameras on the Lenovo Duet 3 (`lenovo-wormdingler`) — TODO
|
||||
|
||||
The front and rear cameras do **not** work on the current Mobile NixOS image.
|
||||
This file is a starting point for picking the work back up later, so we don't
|
||||
have to re-diagnose from scratch.
|
||||
|
||||
## Current state (May 2026)
|
||||
|
||||
Empirical, taken on the running device:
|
||||
|
||||
```
|
||||
$ uname -a
|
||||
Linux ideapad 6.5.0 #1-mobile-nixos SMP Tue Jan 1 00:00:00 UTC 1980 aarch64 GNU/Linux
|
||||
|
||||
$ ls /dev/video*
|
||||
/dev/video0 ← Qualcomm Venus video decoder (h.264/h.265 dec)
|
||||
/dev/video1 ← Qualcomm Venus video encoder (h.264/h.265 enc)
|
||||
|
||||
$ ls /dev/media* # nothing — no media controller graph at all
|
||||
$ lsmod | grep -i camss # nothing
|
||||
$ wpctl status | grep -A1 Sources # nothing — PipeWire has no camera sources
|
||||
```
|
||||
|
||||
Relevant kernel config bits in `/proc/config.gz` on the running image:
|
||||
|
||||
```
|
||||
CONFIG_MEDIA_CAMERA_SUPPORT=y ← media framework is on
|
||||
# CONFIG_VIDEO_QCOM_CAMSS is not set ← Qualcomm Camera Subsystem driver is OFF
|
||||
# CONFIG_VIDEO_OV5675 is not set ← every relevant sensor driver is OFF
|
||||
# CONFIG_VIDEO_OV13858 is not set
|
||||
# CONFIG_VIDEO_HI556 is not set
|
||||
# (full grep of `VIDEO_OV*` / `VIDEO_HI*` is all `not set`)
|
||||
```
|
||||
|
||||
So the *media framework* is enabled, but the *ISP driver* (CAMSS) and every
|
||||
plausible sensor driver are off, and no out-of-tree modules are shipped. The
|
||||
two `/dev/video*` nodes are the Venus codecs, not cameras — that's why
|
||||
`snapshot` reports "no camera found."
|
||||
|
||||
## Why this is non-trivial
|
||||
|
||||
1. **Kernel rebuild required.** `mobile-nixos` builds its own kernel for this
|
||||
device (see `mobile.kernel.structuredConfig` in `hardware.nix`, where we
|
||||
already enable `CIFS` and `EXFAT_FS`). Adding camera support means adding:
|
||||
- `VIDEO_QCOM_CAMSS = module;`
|
||||
- The right sensor driver(s) — and we don't currently know which ones the
|
||||
Duet 3 actually uses. The `wormdingler` Chromium-OS DT references a pair
|
||||
of OV-series sensors but the exact part numbers can vary by SKU and
|
||||
production batch.
|
||||
- Their dependencies (`I2C`, `V4L2`, `MEDIA_CONTROLLER`, etc. — most are
|
||||
already pulled in by `MEDIA_CAMERA_SUPPORT`).
|
||||
|
||||
2. **Device-tree wiring.** Even with the drivers compiled in, the device tree
|
||||
has to describe the CCI bus, the sensor I²C addresses, the regulators, the
|
||||
reset/enable GPIOs, and the CSI port mapping. Mobile NixOS' DT for
|
||||
wormdingler may or may not include these nodes — needs verification by
|
||||
reading the upstream device file at
|
||||
`${inputs.mobile-nixos}/devices/lenovo-wormdingler/`.
|
||||
|
||||
3. **libcamera is the user-space side.** CAMSS is not a "single v4l2 device"
|
||||
driver — it exposes a media-controller graph that has to be configured by
|
||||
libcamera (per-sensor IPA, format negotiation, etc.). Apps then talk to
|
||||
libcamera via the `libcamerasrc` PipeWire module or directly. So the
|
||||
user-space stack is:
|
||||
- `pkgs.libcamera` (system-wide)
|
||||
- `pkgs.pipewire` already running, but needs the libcamera module enabled
|
||||
(`services.pipewire.libcamera = …` or equivalent — check current option
|
||||
name)
|
||||
- GUI: `snapshot`, `gnome-camera`, or anything that talks to PipeWire
|
||||
video sources.
|
||||
|
||||
## Investigation checklist
|
||||
|
||||
When picking this up again, do these in order:
|
||||
|
||||
1. **Identify the actual sensors.** The cleanest way:
|
||||
- Read the upstream Mobile NixOS device file for wormdingler
|
||||
(`devices/lenovo-wormdingler/default.nix` and any `.dts` overlays).
|
||||
- Cross-check with the Chromium OS overlay at
|
||||
<https://chromium.googlesource.com/chromiumos/overlays/board-overlays/+/refs/heads/main/overlay-trogdor/>
|
||||
and the upstream Linux DTS at
|
||||
`arch/arm64/boot/dts/qcom/sc7180-trogdor-wormdingler-*.dts`.
|
||||
- As a runtime cross-check, when CAMSS is eventually loaded, `dmesg | grep -i
|
||||
-E 'cci|sensor|isp'` will print the I²C probe attempts.
|
||||
|
||||
2. **Enable kernel options** in `modules/hosts/ideapad/hardware.nix` under
|
||||
`mobile.kernel.structuredConfig`:
|
||||
|
||||
```nix
|
||||
(helpers: with helpers; {
|
||||
VIDEO_QCOM_CAMSS = module;
|
||||
# plus whatever sensors step 1 identified, e.g.:
|
||||
# VIDEO_OV5675 = module;
|
||||
# VIDEO_OV13858 = module;
|
||||
})
|
||||
```
|
||||
|
||||
Cross-build on the 14900k via the existing flow (binfmt aarch64 + push back).
|
||||
Reboot, then check:
|
||||
|
||||
```
|
||||
ls /dev/media* # expect at least /dev/media0
|
||||
sudo dmesg | grep -i -E 'camss|sensor|isp|cci' # probe history
|
||||
sudo modprobe -v qcom-camss # if not auto-loaded
|
||||
```
|
||||
|
||||
3. **Install diagnostic tools** for this round of work (do **not** keep these
|
||||
in the long-term config unless cameras actually work):
|
||||
|
||||
```nix
|
||||
environment.systemPackages = with pkgs; [
|
||||
v4l-utils # provides v4l2-ctl, media-ctl
|
||||
libcamera # provides `cam`, `qcam`
|
||||
];
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
```
|
||||
v4l2-ctl --list-devices
|
||||
media-ctl -p # dumps the full media-controller graph
|
||||
cam -l # libcamera's view of available cameras
|
||||
```
|
||||
|
||||
4. **Wire libcamera into PipeWire.** Once `cam -l` shows at least one camera,
|
||||
enable PipeWire's libcamera module (option name may have shifted; current
|
||||
nixpkgs typically has `services.pipewire.wireplumber.extraConfig` or
|
||||
similar). Then `wpctl status` should show new Sources under "Video", and
|
||||
`snapshot` will see them.
|
||||
|
||||
5. **Re-add a camera GUI** to `configuration.nix`. `snapshot` is the simplest
|
||||
touch-first option; `gnome-camera` and `cheese` are alternatives.
|
||||
|
||||
## Why nothing else is being touched right now
|
||||
|
||||
Steps 1–4 above are speculative — there's no guarantee the Duet 3 cameras have
|
||||
working mainline-Linux sensor drivers at all. The conservative move is to
|
||||
leave the config tablet-usable without them, document the dead end, and revisit
|
||||
when there's time for a real spike.
|
||||
@@ -0,0 +1,45 @@
|
||||
{ pkgs, ... }: {
|
||||
# ─────────────────────── Power & thermal ───────────────────────
|
||||
# Snapdragon 7c (sc7180) on a tablet form factor: aim for battery life. `schedutil` is the
|
||||
# right modern cpufreq governor on ARM (responsive + power-aware); use it instead of
|
||||
# `powersave` to avoid pinning the CPU at minimum frequency under interactive load.
|
||||
powerManagement.cpuFreqGovernor = "schedutil";
|
||||
powerManagement.enable = true;
|
||||
|
||||
# ─────────────────────── logind: lid & power button ───────────────────────
|
||||
# Closing the lid suspends, even on AC — Duet 3 is a tablet, treat it like one.
|
||||
# Short press on power: suspend (matches ChromeOS/iOS); long press: poweroff.
|
||||
# The DMS bar power menu is the way to reboot / shut down explicitly.
|
||||
services.logind.settings.Login = {
|
||||
HandleLidSwitch = "suspend";
|
||||
HandleLidSwitchExternalPower = "suspend";
|
||||
HandleLidSwitchDocked = "ignore";
|
||||
HandlePowerKey = "suspend";
|
||||
HandlePowerKeyLongPress = "poweroff";
|
||||
};
|
||||
|
||||
# ─────────────────────── Idle / suspend tuning ───────────────────────
|
||||
# Allow suspend-to-RAM but disable hibernate (ARM swap-resume is unreliable, and we don't
|
||||
# have a swap device by default anyway).
|
||||
systemd.sleep.settings.Sleep = {
|
||||
AllowHibernation = "no";
|
||||
AllowHybridSleep = "no";
|
||||
AllowSuspendThenHibernate = "no";
|
||||
};
|
||||
|
||||
# upower picks the right battery percentages for low/critical out of the box; just make
|
||||
# sure the action on critical is hibernate-then-poweroff fallback (we disabled hibernate
|
||||
# so it'll go straight to poweroff). DMS reads upower for the bar widget.
|
||||
services.upower = {
|
||||
enable = true;
|
||||
criticalPowerAction = "PowerOff";
|
||||
percentageLow = 15;
|
||||
percentageCritical = 7;
|
||||
percentageAction = 3;
|
||||
};
|
||||
|
||||
# ─────────────────────── Bluetooth audio quality of life ───────────────────────
|
||||
# Duet has limited mic/speaker hw — keep wpa_supplicant power-save off so audio doesn't crackle
|
||||
# over Bluetooth when CPU is idle. (Wi-Fi + BT share the chip; aggressive power-save = stutter.)
|
||||
networking.networkmanager.wifi.powersave = false;
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
# Host-only: ideapad tablet ergonomics — touchscreen calibration, IIO sensors, virtual keyboard,
|
||||
# and per-session helper daemons (tablet-mode toggle + auto-rotation via iio-sensor-proxy) for both
|
||||
# Niri and Hyprland.
|
||||
#
|
||||
# Why all of this lives at the *NixOS* layer (not the home-manager catalog under wisdom/):
|
||||
# - The hardware bits (`hardware.sensor.iio.enable`, the udev calibration matrix) are system-wide
|
||||
# and tied to this exact device, so they belong with the host module.
|
||||
# - The compositor helpers run via session-specific autostart hooks (Niri `spawn-at-startup`,
|
||||
# Hyprland `exec-once`); the wiring is gated on the matching `chiasson.desktop.<wm>.enable`,
|
||||
# so picking a different session at the greeter just leaves them dormant.
|
||||
#
|
||||
# Two compositor flavours of each daemon:
|
||||
# - Hyprland (CW transforms via `hyprctl`) — original; matches the old NixOS-New setup.
|
||||
# - Niri (CCW transforms via `niri msg output`) — needed because Niri is the V2 default.
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
# ─────────────────────── Hyprland helpers ───────────────────────
|
||||
ideapadTabletModeDaemon = pkgs.writeShellScriptBin "ideapad-tablet-mode-daemon" ''
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
HYPRCTL="${pkgs.hyprland}/bin/hyprctl"
|
||||
JQ="${pkgs.jq}/bin/jq"
|
||||
PKILL="${pkgs.procps}/bin/pkill"
|
||||
PGREP="${pkgs.procps}/bin/pgrep"
|
||||
WVKBD="${pkgs.wvkbd}/bin/wvkbd-mobintl"
|
||||
|
||||
monitor_name="DSI-1"
|
||||
keyboard_scale="1.25"
|
||||
tablet_scale="1.6"
|
||||
state_file="''${XDG_RUNTIME_DIR:-/run/user/$(${pkgs.coreutils}/bin/id -u)}/ideapad-input-mode.state"
|
||||
|
||||
current_transform() {
|
||||
"$HYPRCTL" -j monitors 2>/dev/null | "$JQ" -r --arg mon "$monitor_name" '
|
||||
([.[] | select(.name == $mon)][0].transform // 1 | tostring)
|
||||
' 2>/dev/null || echo "1"
|
||||
}
|
||||
|
||||
has_attached_pogo_dock() {
|
||||
"$HYPRCTL" -j devices 2>/dev/null | "$JQ" -e '
|
||||
any((([.keyboards[]?.name] + [.mice[]?.name])[]?); (ascii_downcase | test("google-inc\\.-hammer")))
|
||||
' >/dev/null
|
||||
}
|
||||
|
||||
# wvkbd is no longer auto-spawned at session start — this daemon owns its lifecycle so we
|
||||
# only have a virtual keyboard surface in memory when the pogo cover is detached.
|
||||
start_wvkbd() {
|
||||
"$PGREP" -x wvkbd-mobintl >/dev/null 2>&1 && return 0
|
||||
"$WVKBD" --non-exclusive -H 520 -L 360 --fn 'DejaVu Sans 18' >/dev/null 2>&1 &
|
||||
}
|
||||
stop_wvkbd() {
|
||||
"$PKILL" -x wvkbd-mobintl >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
apply_mode() {
|
||||
mode="$1"
|
||||
transform="$(current_transform)"
|
||||
if [ "$mode" = "tablet" ]; then
|
||||
"$HYPRCTL" keyword monitor "$monitor_name,1200x2000@60.0,0x0,$tablet_scale, transform, $transform" >/dev/null 2>&1 || true
|
||||
start_wvkbd
|
||||
else
|
||||
"$HYPRCTL" keyword monitor "$monitor_name,1200x2000@60.0,0x0,$keyboard_scale, transform, $transform" >/dev/null 2>&1 || true
|
||||
stop_wvkbd
|
||||
fi
|
||||
"$HYPRCTL" keyword input:touchdevice:output "$monitor_name" >/dev/null 2>&1 || true
|
||||
"$HYPRCTL" keyword input:touchdevice:transform "$transform" >/dev/null 2>&1 || true
|
||||
printf "%s\n" "$mode" > "$state_file"
|
||||
}
|
||||
|
||||
# Always reapply on startup — the cached state file may lie (e.g., session restart while
|
||||
# pogo state changed) and wvkbd needs to be (re)spawned since nothing else launches it now.
|
||||
previous_mode=""
|
||||
|
||||
while true; do
|
||||
if has_attached_pogo_dock; then
|
||||
mode="keyboard"
|
||||
else
|
||||
mode="tablet"
|
||||
fi
|
||||
|
||||
if [ "$mode" != "$previous_mode" ]; then
|
||||
apply_mode "$mode"
|
||||
previous_mode="$mode"
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
'';
|
||||
|
||||
ideapadAutoRotateDaemon = pkgs.writeShellScriptBin "ideapad-autorotate-daemon" ''
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
HYPRCTL="${pkgs.hyprland}/bin/hyprctl"
|
||||
MONITOR_SENSOR="${pkgs.iio-sensor-proxy}/bin/monitor-sensor"
|
||||
|
||||
monitor_name="DSI-1"
|
||||
keyboard_scale="1.25"
|
||||
tablet_scale="1.6"
|
||||
state_file="''${XDG_RUNTIME_DIR:-/run/user/$(${pkgs.coreutils}/bin/id -u)}/ideapad-input-mode.state"
|
||||
|
||||
scale_for_mode() {
|
||||
mode="$(cat "$state_file" 2>/dev/null || echo keyboard)"
|
||||
if [ "$mode" = "tablet" ]; then
|
||||
printf "%s\n" "$tablet_scale"
|
||||
else
|
||||
printf "%s\n" "$keyboard_scale"
|
||||
fi
|
||||
}
|
||||
|
||||
transform_for_orientation() {
|
||||
orientation="$1"
|
||||
case "$orientation" in
|
||||
normal) printf "0\n" ;;
|
||||
bottom-up) printf "2\n" ;;
|
||||
left-up) printf "1\n" ;;
|
||||
right-up) printf "3\n" ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
apply_orientation() {
|
||||
orientation="$1"
|
||||
transform="$(transform_for_orientation "$orientation")" || return 0
|
||||
scale="$(scale_for_mode)"
|
||||
"$HYPRCTL" keyword monitor "$monitor_name,1200x2000@60.0,0x0,$scale, transform, $transform" >/dev/null 2>&1 || true
|
||||
"$HYPRCTL" keyword input:touchdevice:output "$monitor_name" >/dev/null 2>&1 || true
|
||||
"$HYPRCTL" keyword input:touchdevice:transform "$transform" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
while true; do
|
||||
"$MONITOR_SENSOR" --accel 2>/dev/null | while IFS= read -r line; do
|
||||
case "$line" in
|
||||
*normal*) apply_orientation "normal" ;;
|
||||
*bottom-up*) apply_orientation "bottom-up" ;;
|
||||
*left-up*) apply_orientation "left-up" ;;
|
||||
*right-up*) apply_orientation "right-up" ;;
|
||||
esac
|
||||
done
|
||||
sleep 2
|
||||
done
|
||||
'';
|
||||
|
||||
# ─────────────────────── Niri helpers ───────────────────────
|
||||
# `niri msg output DSI-1 transform <T>` accepts: normal | 90 | 180 | 270.
|
||||
#
|
||||
# Empirical mapping for this exact device + ACCEL_MOUNT_MATRIX (lenovo-wormdingler family):
|
||||
# physical position iio orientation correct niri transform
|
||||
# ────────────────── ──────────────── ──────────────────────
|
||||
# keyboard down (LS) left-up normal
|
||||
# keyboard up (LS) right-up 180
|
||||
# keyboard right (P) bottom-up 90
|
||||
# keyboard left (P) normal 270
|
||||
# iio's "normal" is *not* the natural landscape pose here — the panel-mount matrix in the
|
||||
# mobile-nixos device file biases it toward portrait-with-keyboard-left. Don't trust the
|
||||
# textbook iio→Wayland convention; trust the table above.
|
||||
ideapadNiriTabletModeDaemon = pkgs.writeShellScriptBin "ideapad-niri-tablet-mode-daemon" ''
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
NIRI="${pkgs.niri}/bin/niri"
|
||||
JQ="${pkgs.jq}/bin/jq"
|
||||
PKILL="${pkgs.procps}/bin/pkill"
|
||||
PGREP="${pkgs.procps}/bin/pgrep"
|
||||
WVKBD="${pkgs.wvkbd}/bin/wvkbd-mobintl"
|
||||
|
||||
output_name="DSI-1"
|
||||
keyboard_scale="1.25"
|
||||
tablet_scale="1.6"
|
||||
state_file="''${XDG_RUNTIME_DIR:-/run/user/$(${pkgs.coreutils}/bin/id -u)}/ideapad-input-mode.state"
|
||||
|
||||
has_attached_pogo_dock() {
|
||||
# Niri doesn't expose input devices via IPC, so we look in `/dev/input/by-id` for the
|
||||
# USB-class enumeration of the Google Hammer / Whiskers pogo keyboard. The directory
|
||||
# contains symlinks; presence of either string means the cover is attached.
|
||||
# NOTE: `find` ships in findutils, not coreutils — using the wrong package here makes
|
||||
# the pipeline fail silently (stderr is dropped) and pogo always reads as detached.
|
||||
${pkgs.findutils}/bin/find /dev/input/by-id -maxdepth 1 -type l 2>/dev/null \
|
||||
| ${pkgs.gnugrep}/bin/grep -i -E 'google.*hammer|google.*whiskers' >/dev/null
|
||||
}
|
||||
|
||||
# wvkbd is owned by this daemon: spawn on pogo detach, kill on pogo attach. The DMS bar
|
||||
# `pkill -SIGRTMIN -x wvkbd-mobintl` toggle still works while wvkbd is running (i.e., in
|
||||
# tablet mode). When the pogo cover is on, the pill is a no-op — that's intentional, the
|
||||
# physical keyboard is the input.
|
||||
start_wvkbd() {
|
||||
"$PGREP" -x wvkbd-mobintl >/dev/null 2>&1 && return 0
|
||||
"$WVKBD" --non-exclusive -H 520 -L 360 --fn 'DejaVu Sans 18' >/dev/null 2>&1 &
|
||||
}
|
||||
stop_wvkbd() {
|
||||
"$PKILL" -x wvkbd-mobintl >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
apply_mode() {
|
||||
mode="$1"
|
||||
if [ "$mode" = "tablet" ]; then
|
||||
"$NIRI" msg output "$output_name" scale "$tablet_scale" >/dev/null 2>&1 || true
|
||||
start_wvkbd
|
||||
else
|
||||
"$NIRI" msg output "$output_name" scale "$keyboard_scale" >/dev/null 2>&1 || true
|
||||
stop_wvkbd
|
||||
fi
|
||||
printf "%s\n" "$mode" > "$state_file"
|
||||
}
|
||||
|
||||
# Always reapply on startup — see Hyprland daemon's identical comment.
|
||||
previous_mode=""
|
||||
|
||||
while true; do
|
||||
if has_attached_pogo_dock; then
|
||||
mode="keyboard"
|
||||
else
|
||||
mode="tablet"
|
||||
fi
|
||||
|
||||
if [ "$mode" != "$previous_mode" ]; then
|
||||
apply_mode "$mode"
|
||||
previous_mode="$mode"
|
||||
fi
|
||||
sleep 2
|
||||
done
|
||||
'';
|
||||
|
||||
ideapadNiriAutoRotateDaemon = pkgs.writeShellScriptBin "ideapad-niri-autorotate-daemon" ''
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
NIRI="${pkgs.niri}/bin/niri"
|
||||
MONITOR_SENSOR="${pkgs.iio-sensor-proxy}/bin/monitor-sensor"
|
||||
|
||||
output_name="DSI-1"
|
||||
|
||||
transform_for_orientation() {
|
||||
orientation="$1"
|
||||
case "$orientation" in
|
||||
normal) printf "270\n" ;;
|
||||
bottom-up) printf "90\n" ;;
|
||||
left-up) printf "normal\n" ;;
|
||||
right-up) printf "180\n" ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
apply_orientation() {
|
||||
orientation="$1"
|
||||
transform="$(transform_for_orientation "$orientation")" || return 0
|
||||
"$NIRI" msg output "$output_name" transform "$transform" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
while true; do
|
||||
"$MONITOR_SENSOR" --accel 2>/dev/null | while IFS= read -r line; do
|
||||
case "$line" in
|
||||
*normal*) apply_orientation "normal" ;;
|
||||
*bottom-up*) apply_orientation "bottom-up" ;;
|
||||
*left-up*) apply_orientation "left-up" ;;
|
||||
*right-up*) apply_orientation "right-up" ;;
|
||||
esac
|
||||
done
|
||||
sleep 2
|
||||
done
|
||||
'';
|
||||
in
|
||||
{
|
||||
# ─────────────────────── Hardware ───────────────────────
|
||||
hardware.sensor.iio.enable = true;
|
||||
|
||||
# Touchscreen calibration — solved empirically with `niri msg output DSI-1 transform normal`
|
||||
# in the natural kb-down pose. The panel's touch hardware reports raw coordinates already
|
||||
# aligned with the panel-native frame (HW(visual_top_left) = (0,0), etc.), so identity is
|
||||
# correct. `niri input.touch.map-to-output = "DSI-1"` then handles per-orientation rotation
|
||||
# on top — never re-tune this matrix per orientation; rotate the *output* instead.
|
||||
services.udev.extraRules = ''
|
||||
SUBSYSTEM=="input", ENV{ID_INPUT_TOUCHSCREEN}=="1", ENV{LIBINPUT_CALIBRATION_MATRIX}="1 0 0 0 1 0"
|
||||
'';
|
||||
|
||||
# ─────────────────────── User-facing tools ───────────────────────
|
||||
# System-wide so any user session (Niri or Hyprland) can launch wvkbd / hyprctl / niri-msg helpers.
|
||||
environment.systemPackages = [
|
||||
pkgs.wvkbd
|
||||
pkgs.iio-sensor-proxy
|
||||
ideapadTabletModeDaemon
|
||||
ideapadAutoRotateDaemon
|
||||
ideapadNiriTabletModeDaemon
|
||||
ideapadNiriAutoRotateDaemon
|
||||
];
|
||||
|
||||
# ─────────────────────── Niri session autostart ───────────────────────
|
||||
# Set on the NixOS layer; the `desktopNiri` HM module merges this into per-user `niri/config.kdl`.
|
||||
# Touch input is glued to DSI-1 so it follows whatever transform the autorotate daemon sets.
|
||||
# wvkbd is intentionally NOT spawned here — `ideapad-niri-tablet-mode-daemon` starts/kills it
|
||||
# in response to pogo-cover attach/detach so we don't keep a virtual-keyboard surface alive
|
||||
# while a physical keyboard is plugged in.
|
||||
chiasson.desktop.niri.extraSettings = lib.mkIf config.chiasson.desktop.niri.enable {
|
||||
input.touch.map-to-output = "DSI-1";
|
||||
|
||||
# wrapper-modules schema: each entry is a `command argv` list of strings (or a single string).
|
||||
spawn-at-startup = [
|
||||
[ "ideapad-niri-autorotate-daemon" ]
|
||||
[ "ideapad-niri-tablet-mode-daemon" ]
|
||||
];
|
||||
};
|
||||
|
||||
# ─────────────────────── Hyprland session autostart ───────────────────────
|
||||
# Same lifecycle policy as Niri: the tablet-mode daemon owns wvkbd, no exec-once for it.
|
||||
chiasson.desktop.hyprland.settings = lib.mkIf config.chiasson.desktop.hyprland.enable {
|
||||
exec-once = lib.mkAfter [
|
||||
"${pkgs.procps}/bin/pkill -x ideapad-tablet-mode-daemon >/dev/null 2>&1 || true; ideapad-tablet-mode-daemon &"
|
||||
"${pkgs.procps}/bin/pkill -x ideapad-autorotate-daemon >/dev/null 2>&1 || true; ideapad-autorotate-daemon &"
|
||||
];
|
||||
bind = lib.mkAfter [
|
||||
"Super, K, exec, pkill -SIGRTMIN -x wvkbd-mobintl"
|
||||
];
|
||||
monitor = lib.mkAfter [
|
||||
"DSI-1,1200x2000@60.0,0x0,1.25, transform, 1"
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
{ self, inputs, ... }: {
|
||||
|
||||
# Lenovo Chromebook Duet 3 (`lenovo-wormdingler`) on Mobile NixOS.
|
||||
#
|
||||
# Phase 1 (minimal bootstrap) lived here previously; we now run the full V2 stack:
|
||||
# mobile-nixos device + Niri/Hyprland/DMS, DankGreeter, Waydroid (tablet-class), wvkbd,
|
||||
# IIO sensors, touchscreen calibration, attic cache, sops, and the standard user catalog.
|
||||
flake.nixosModules.ideapadConfiguration =
|
||||
{
|
||||
self,
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
{
|
||||
imports = [
|
||||
# Mobile NixOS device + family + depthcharge system-type.
|
||||
(import "${inputs.mobile-nixos}/lib/configuration.nix" {
|
||||
device = "lenovo-wormdingler";
|
||||
})
|
||||
|
||||
self.nixosModules.ideapadHardware
|
||||
|
||||
inputs.home-manager.nixosModules.home-manager
|
||||
inputs.sops-nix.nixosModules.sops
|
||||
|
||||
self.nixosModules.system
|
||||
self.nixosModules.desktop
|
||||
self.nixosModules.users
|
||||
|
||||
self.nixosModules."client-services"
|
||||
|
||||
# Host-only: IIO + touchscreen calibration + per-compositor tablet/autorotate helpers.
|
||||
./_private/touch-tablet.nix
|
||||
|
||||
# Host-only: cpufreq, lid/power-button policy, upower thresholds.
|
||||
./_private/platform.nix
|
||||
];
|
||||
|
||||
# ─────────────────────── Sops ───────────────────────
|
||||
# `host_ideapad` recipient in `.sops.yaml` derives from the new ed25519 host key (post-reflash).
|
||||
sops = {
|
||||
defaultSopsFile = ../../../secrets/secrets.yaml;
|
||||
defaultSopsFormat = "yaml";
|
||||
age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
|
||||
};
|
||||
|
||||
sops.secrets."users/olivier/hashedPassword".neededForUsers = true;
|
||||
sops.secrets."caching/attic/token" = {
|
||||
owner = "olivier";
|
||||
group = "users";
|
||||
mode = "0400";
|
||||
};
|
||||
sops.secrets."swiftshare/API_KEY" = {
|
||||
owner = "olivier";
|
||||
group = "users";
|
||||
mode = "0400";
|
||||
};
|
||||
|
||||
# ─────────────────────── Mobile NixOS / firmware ───────────────────────
|
||||
# mruby's test-suite breaks on aarch64 in the Nix sandbox; the overlay strips checks and
|
||||
# rebuilds Mobile NixOS' script-loader against the patched mruby.
|
||||
chiasson.system.ideapadMrubyOverlay.enable = true;
|
||||
|
||||
# Wi-Fi modem (qcom-wcn3990) + Bluetooth (QCA crnv32) need binary blobs.
|
||||
nixpkgs.config.allowUnfreePredicate =
|
||||
pkg: builtins.elem (lib.getName pkg) [
|
||||
"chromeos-sc7180-unredistributable-firmware"
|
||||
"chromeos-sc7180-unredistributable-firmware-zstd"
|
||||
];
|
||||
hardware.firmware = [ pkgs.chromeos-sc7180-unredistributable-firmware ];
|
||||
hardware.enableRedistributableFirmware = true;
|
||||
|
||||
# ─────────────────────── Attic (substitution + push + CLI token) ───────
|
||||
chiasson.system.caching.attic = {
|
||||
enable = true;
|
||||
cacheName = "nixos-new";
|
||||
endpoint = "http://192.168.2.238:8080/";
|
||||
publicKey = "nixos-new:8NySIcT0HP7KvGQKgBRWoWESxxRA8BVYo8S85UNpNX0=";
|
||||
tokenFile = config.sops.secrets."caching/attic/token".path;
|
||||
push.enable = true;
|
||||
userCli.enable = true;
|
||||
};
|
||||
|
||||
# ─────────────────────── System bits ───────────────────────
|
||||
chiasson.system = {
|
||||
audio.enable = true;
|
||||
networking = {
|
||||
hostName = "ideapad";
|
||||
networkManager = {
|
||||
enable = true;
|
||||
unmanaged = [ ];
|
||||
};
|
||||
wifi.tools.enabled = true;
|
||||
};
|
||||
extraPackages = with pkgs; [
|
||||
gitMinimal
|
||||
sops
|
||||
ssh-to-age
|
||||
];
|
||||
};
|
||||
|
||||
# ─────────────────────── Desktop ───────────────────────
|
||||
# Both compositors enabled; DankGreeter lets you pick at login. Default = Niri (V2 convention),
|
||||
# Hyprland session is where the tablet-mode + autorotate daemons in `_private/touch-tablet.nix`
|
||||
# actually run (they hook `exec-once`).
|
||||
chiasson.desktop = {
|
||||
niri.enable = true;
|
||||
hyprland.enable = true;
|
||||
|
||||
defaultSession = "niri";
|
||||
shell = "dms";
|
||||
shells.dms = {
|
||||
enableWvkbdToggle = true;
|
||||
# Cross-build on the 14900k via binfmt and push back over LAN — much faster than
|
||||
# rebuilding aarch64 closure on the Snapdragon. Mirrors the old NixOS-New flow:
|
||||
# ssh out to nixdesk, run nixos-rebuild --target-host pointing back at us.
|
||||
rebuildCommand = [
|
||||
"bash"
|
||||
"-lc"
|
||||
''
|
||||
ssh -t olivier@nixdesk \
|
||||
"nixos-rebuild switch --flake path:/home/olivier/NixOS-V2#ideapad --target-host olivier@ideapad --sudo --ask-sudo-password 2>&1"
|
||||
''
|
||||
];
|
||||
};
|
||||
|
||||
# Tablet-class screen → constrain Waydroid to a sane portrait-ish frame and use gesture nav
|
||||
# instead of 3-button so it feels like the ChromeOS tablet UI.
|
||||
#waydroid = {
|
||||
# enable = true;
|
||||
# multiWindows = false;
|
||||
# width = 1600;
|
||||
# height = 960;
|
||||
# navigationMode = "gestures";
|
||||
#};
|
||||
};
|
||||
|
||||
# ─────────────────────── Users / HM ───────────────────────
|
||||
chiasson.users.enabled = [ "olivier" ];
|
||||
|
||||
# Touch-friendly application set, mirroring uConsole's selection (no heavy IDEs / gaming).
|
||||
chiasson.users.extraModules.olivier = [
|
||||
self.homeManagerModules.wisdomFilebrowsersDolphin
|
||||
self.homeManagerModules.wisdomTerminalsKitty
|
||||
self.homeManagerModules.wisdomBrowsersZen
|
||||
self.homeManagerModules.wisdomEditorsKate
|
||||
self.homeManagerModules.wisdomShellFish
|
||||
self.homeManagerModules.wisdomShellOhMyPosh
|
||||
self.homeManagerModules.wisdomAppsSpotify
|
||||
self.homeManagerModules.wisdomAppsLocalsend
|
||||
self.homeManagerModules.wisdomDesktopScreenshot
|
||||
{
|
||||
chiasson.home = {
|
||||
shell = {
|
||||
fish.enable = true;
|
||||
ohMyPosh.enable = true;
|
||||
};
|
||||
terminals.kitty.enable = true;
|
||||
filebrowsers.dolphin.enable = true;
|
||||
browsers.zen.enable = true;
|
||||
editors.kate.enable = true;
|
||||
apps.spotify.enable = true;
|
||||
apps.localsend.enable = true;
|
||||
desktop = {
|
||||
screenshot = {
|
||||
enable = true;
|
||||
swiftshareApiKeyFile = "/run/secrets/swiftshare/API_KEY"; #TODO[epic=sops] redo this by passing sops file output directly
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
# Tablet-class apps: kept inline rather than promoting to wisdom modules — these aren't
|
||||
# part of the broader catalog (no use on uConsole / 14900k / servers) and adding a wisdom
|
||||
# module per single-host package would just be ceremony. If a second tablet host ever
|
||||
# appears, factor them out then.
|
||||
#
|
||||
# NOTE on cameras: no v4l2/libcamera GUI is installed. The Mobile NixOS kernel for
|
||||
# `lenovo-wormdingler` ships with `CONFIG_VIDEO_QCOM_CAMSS` disabled and no
|
||||
# `VIDEO_OV*`/`VIDEO_HI*` sensor drivers, so `/dev/video0`-`/dev/video1` only expose
|
||||
# the Qualcomm Venus codecs (h.264/h.265 enc/dec) and there is no camera source for
|
||||
# PipeWire / libcamera to pick up. See `_private/CAMERA-TODO.md` for the steps that
|
||||
# would (potentially) bring the front/rear cameras online — it's a kernel-rebuild +
|
||||
# device-tree + libcamera project, not a config tweak.
|
||||
(
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
home.packages = with pkgs; [
|
||||
# PDF viewer — fits the existing KDE app set (Dolphin + Kate).
|
||||
kdePackages.okular
|
||||
# ePub reader, GTK4, large touch targets.
|
||||
foliate
|
||||
];
|
||||
}
|
||||
)
|
||||
];
|
||||
|
||||
system.stateVersion = "26.05";
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{ self, inputs, ... }: {
|
||||
|
||||
flake.nixosConfigurations.ideapad = inputs.nixpkgs.lib.nixosSystem {
|
||||
system = "aarch64-linux";
|
||||
specialArgs = {
|
||||
inherit self inputs;
|
||||
host = "ideapad";
|
||||
system = "aarch64-linux";
|
||||
};
|
||||
modules = [
|
||||
self.nixosModules.ideapadConfiguration
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{ ... }: {
|
||||
flake.nixosModules.ideapadHardware =
|
||||
# Mobile NixOS' depthcharge system-type wires up the disk image, kernel partitions and
|
||||
# rootfs entirely; we don't need a generated `hardware-configuration.nix`. This module is a
|
||||
# placeholder so the host follows the standard `<host>Configuration` / `<host>Hardware` shape
|
||||
# and gives us a place to drop kernel-config knobs that aren't covered by the device family.
|
||||
{ ... }:
|
||||
{
|
||||
# Useful on a portable: mounting USB sticks (often exFAT) and SMB shares.
|
||||
boot.supportedFilesystems = [ "exfat" "cifs" ];
|
||||
|
||||
# Mobile NixOS builds its own kernel — the regular `boot.kernelModules` won't help if the
|
||||
# module isn't compiled in. `mobile.kernel.structuredConfig` lives upstream in
|
||||
# `modules/system-types/depthcharge/kernel/` and is the right layer to add features.
|
||||
mobile.kernel.structuredConfig = [
|
||||
(helpers: with helpers; {
|
||||
CIFS = module;
|
||||
EXFAT_FS = module;
|
||||
})
|
||||
];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
{ config, ... }: {
|
||||
sops = {
|
||||
templates."atticd.env" = {
|
||||
owner = "root";
|
||||
group = "root";
|
||||
mode = "0400";
|
||||
content = ''
|
||||
ATTIC_SERVER_TOKEN_RS256_SECRET_BASE64=${config.sops.placeholder."attic/server-token-rs256-secret-base64"}
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
sops.secrets."attic/server-token-rs256-secret-base64" = {
|
||||
sopsFile = ../../../../secrets/attic-secrets.yaml;
|
||||
owner = "root";
|
||||
group = "root";
|
||||
mode = "0400";
|
||||
};
|
||||
|
||||
services.atticd = {
|
||||
enable = true;
|
||||
environmentFile = config.sops.templates."atticd.env".path;
|
||||
settings = {
|
||||
listen = "[::]:8080";
|
||||
jwt = { };
|
||||
};
|
||||
};
|
||||
|
||||
chiasson.system.networking.firewall.allowedTCPPorts = [ 8080 ];
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
# DDRM Flask backend (Widevine / PlayReady decrypt). Extension URL: http://<host>:58239
|
||||
{ pkgs, inputs, ... }:
|
||||
{
|
||||
services.ddrm-media-server = {
|
||||
enable = true;
|
||||
port = 58239;
|
||||
listenAddress = "0.0.0.0";
|
||||
openFirewall = true;
|
||||
package = inputs.ddrm.packages.${pkgs.stdenv.hostPlatform.system}.default;
|
||||
# State: /var/lib/ddrm-media (venv + configs + CDMs on first run — needs network for pip).
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{ ... }:
|
||||
{
|
||||
# FlareSolverr (Cloudflare / JS challenge solver for some indexers).
|
||||
# Typically used by Prowlarr as an HTTP proxy.
|
||||
#
|
||||
# UI/endpoint: http://<host>:8191
|
||||
services.flaresolverr.enable = true;
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 8191 ];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
{ config, ... }:
|
||||
let
|
||||
secretFilePath = ../secrets.yaml;
|
||||
in
|
||||
{
|
||||
sops.secrets."immich/database-password".sopsFile = secretFilePath;
|
||||
|
||||
# Placeholders are expanded only inside template `content` (not in arbitrary Nix strings).
|
||||
sops.templates."immich-db.env" = {
|
||||
content = ''
|
||||
POSTGRES_PASSWORD=${config.sops.placeholder."immich/database-password"}
|
||||
DB_PASSWORD=${config.sops.placeholder."immich/database-password"}
|
||||
'';
|
||||
};
|
||||
|
||||
chiasson.system.services.immich = {
|
||||
enable = true;
|
||||
host = "0.0.0.0";
|
||||
port = 2283;
|
||||
timezone = "America/Moncton";
|
||||
uploadLocation = "/var/lib/immich/library";
|
||||
environmentFiles = [ config.sops.templates."immich-db.env".path ];
|
||||
postgres = {
|
||||
user = "postgres";
|
||||
#password = ""; # Defined in sops.templates."immich-db.env"
|
||||
database = "immich";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
# NFS read-only mount of nixdesk (14900k) bulk storage for extra Jellyfin libraries.
|
||||
# Source: ssh inventory hostName for 14900k. Export is defined in
|
||||
# modules/hosts/14900k/_private/jellyfin-nfs-export.nix
|
||||
#
|
||||
# In Jellyfin (in addition to local /var/lib/media/...), add e.g.:
|
||||
# Movies → /mnt/nixdesk-jellyfin/movies
|
||||
# Shows → /mnt/nixdesk-jellyfin/tv
|
||||
{ ... }:
|
||||
let
|
||||
# Must match LAN IP of the NFS server (flake `sshInventory` → hosts."14900k".hostName).
|
||||
nfsExportHost = "192.168.2.25";
|
||||
in
|
||||
{
|
||||
fileSystems."/mnt/nixdesk-jellyfin" = {
|
||||
device = "${nfsExportHost}:/mnt/test/jellyfin";
|
||||
fsType = "nfs";
|
||||
options = [
|
||||
"rw"
|
||||
"noatime"
|
||||
"nofail"
|
||||
"_netdev"
|
||||
"x-systemd.automount"
|
||||
"x-systemd.idle-timeout=600"
|
||||
];
|
||||
};
|
||||
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
# Jellyfin (native NixOS service). Local media: /var/lib/media (group `media`; jellyfin + server).
|
||||
# Dashboard: Movies → /var/lib/media/movies, Shows → /var/lib/media/tv (see jellyfin-remote-storage.nix
|
||||
# for bulk libraries on nixdesk at /mnt/nixdesk-jellyfin/{movies,tv}).
|
||||
# Do not use "Mixed Movies and Shows" (deprecated): https://jellyfin.org/docs/general/server/media/mixed-movies-and-shows
|
||||
# Dedicated disk: fileSystems."/var/lib/media" in hardware.nix, then fix ownership.
|
||||
{ lib, ... }:
|
||||
{
|
||||
nixpkgs.overlays = [
|
||||
(final: prev: {
|
||||
jellyfin-web = prev.jellyfin-web.overrideAttrs (oldAttrs: {
|
||||
postInstall =
|
||||
(oldAttrs.postInstall or "")
|
||||
+ ''
|
||||
# Blank default Jellyfin banner assets (read-only store otherwise). Wildcards
|
||||
# track hashed filenames across jellyfin-web releases; bump if layout changes.
|
||||
find "$out" -type f \( -name 'banner-light.*.png' -o -name 'banner-dark.*.png' \) \
|
||||
-exec truncate -s 0 {} \;
|
||||
'';
|
||||
});
|
||||
})
|
||||
];
|
||||
|
||||
users.groups.media = { };
|
||||
|
||||
users.users.jellyfin.extraGroups = [ "media" ];
|
||||
users.users.server.extraGroups = [ "media" ];
|
||||
|
||||
systemd.tmpfiles.settings."nix-server-var-lib-media" = {
|
||||
"/var/lib/media"."d" = {
|
||||
mode = "0775";
|
||||
user = "root";
|
||||
group = "media";
|
||||
};
|
||||
"/var/lib/media/movies"."d" = {
|
||||
mode = "0775";
|
||||
user = "root";
|
||||
group = "media";
|
||||
};
|
||||
"/var/lib/media/tv"."d" = {
|
||||
mode = "0775";
|
||||
user = "root";
|
||||
group = "media";
|
||||
};
|
||||
};
|
||||
|
||||
services.jellyfin = {
|
||||
enable = true;
|
||||
openFirewall = true;
|
||||
};
|
||||
|
||||
# `users.users.jellyfin.extraGroups` does not affect systemd; the service must list
|
||||
# supplementary groups explicitly. Without `media`, directories mode 775 root:media are
|
||||
# not writable by uid jellyfin (it only had group `jellyfin`), so deletes fail.
|
||||
systemd.services.jellyfin.serviceConfig = {
|
||||
SupplementaryGroups = [ "media" ];
|
||||
# Jellyfin libraries may live on NFS (e.g. /mnt/nixdesk-jellyfin). PrivateUsers breaks
|
||||
# uid mapping for NFS auth in practice; disable so deletes use the real host jellyfin uid.
|
||||
PrivateUsers = lib.mkForce false;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
# Organizr — homelab dashboard (Docker). UI: http://<host>:8888
|
||||
# Official image: https://github.com/organizr/docker-organizr
|
||||
#
|
||||
# Wizard errors like "API … /default/ not writable" are almost always host permissions on
|
||||
# `/var/lib/organizr`: the first container run may leave root-owned files under `/config`.
|
||||
{ lib, pkgs, ... }:
|
||||
{
|
||||
users.groups.organizr = { gid = 950; };
|
||||
users.users.organizr = {
|
||||
isSystemUser = true;
|
||||
uid = 950;
|
||||
group = "organizr";
|
||||
};
|
||||
|
||||
systemd.tmpfiles.settings."nix-server-organizr-config" = {
|
||||
"/var/lib/organizr"."d" = {
|
||||
mode = "0755";
|
||||
user = "organizr";
|
||||
group = "organizr";
|
||||
};
|
||||
};
|
||||
|
||||
# Recursively reset ownership (handles root-owned files from an earlier container run).
|
||||
systemd.tmpfiles.settings."nix-server-organizr-config-perms" = {
|
||||
"/var/lib/organizr"."Z" = {
|
||||
mode = "0755";
|
||||
user = "organizr";
|
||||
group = "organizr";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.docker-organizr.preStart = lib.mkBefore ''
|
||||
${pkgs.coreutils}/bin/mkdir -p /var/lib/organizr
|
||||
${pkgs.coreutils}/bin/chown -R organizr:organizr /var/lib/organizr
|
||||
'';
|
||||
|
||||
virtualisation.oci-containers.containers.organizr = {
|
||||
image = "ghcr.io/organizr/organizr:latest";
|
||||
ports = [ "8888:80" ];
|
||||
volumes = [
|
||||
"/var/lib/organizr:/config"
|
||||
];
|
||||
environment = {
|
||||
PUID = "950";
|
||||
PGID = "950";
|
||||
TZ = "America/Moncton";
|
||||
# v2-master / master are stable v2; optional override:
|
||||
# branch = "v2-master";
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 8888 ];
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{config, ...}: {
|
||||
virtualisation = {
|
||||
docker.enable = true;
|
||||
oci-containers = {
|
||||
backend = "docker";
|
||||
containers = {
|
||||
portainer = {
|
||||
image = "portainer/portainer-ce:latest";
|
||||
ports = [ "9443:9443" ];
|
||||
volumes = [
|
||||
"/var/run/docker.sock:/var/run/docker.sock"
|
||||
"/var/lib/portainer:/data"
|
||||
];
|
||||
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
networking.firewall.allowedTCPPorts = [ 9443 ];
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
{ lib, ... }:
|
||||
{
|
||||
# Prowlarr (indexer manager). UI: http://<host>:9696
|
||||
# Data dir is /var/lib/prowlarr (see systemd unit ExecStart -data=…), not ~/.config/Prowlarr.
|
||||
services.prowlarr.enable = true;
|
||||
|
||||
# Useful when Prowlarr/Sonarr/Radarr need to write into shared areas (downloads, etc.).
|
||||
users.groups.prowlarr = { };
|
||||
users.users.prowlarr = {
|
||||
isSystemUser = true;
|
||||
group = "prowlarr";
|
||||
extraGroups = [ "media" ];
|
||||
};
|
||||
|
||||
systemd.services.prowlarr.preStart = lib.mkBefore ''
|
||||
mkdir -p /var/lib/prowlarr/Definitions/Custom
|
||||
ln -sf ${./prowlarr/torrent9-custom.yml} /var/lib/prowlarr/Definitions/Custom/torrent9-custom.yml
|
||||
'';
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 9696 ];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
---
|
||||
id: torrent9-custom
|
||||
name: Torrent9 (Custom URL)
|
||||
description: "Torrent9 is a FRENCH Public site for MOVIES / TV / GENERAL"
|
||||
language: fr-FR
|
||||
type: public
|
||||
encoding: UTF-8
|
||||
followredirect: true
|
||||
testlinktorrent: false
|
||||
links:
|
||||
- https://www.torrent9.club/
|
||||
- https://www6.torrent9.to/
|
||||
legacylinks:
|
||||
- https://www.torrent9.pl/ # this is a proxy for torrent9clone
|
||||
- https://torrent9.black-mirror.xyz/ # this is a proxy for torrent9clone
|
||||
- https://torrent9.unblocked.casa/ # this is a proxy for torrent9clone
|
||||
- https://torrent9.proxyportal.fun/ # this is a proxy for torrent9clone
|
||||
- https://torrent9.uk-unblock.xyz/ # this is a proxy for torrent9clone
|
||||
- https://torrent9.ind-unblock.xyz/ # this is a proxy for torrent9clone
|
||||
- https://ww1.torrent9.to/
|
||||
- https://www.torrent9.is/
|
||||
- https://torrent9.li/ # not a proxy for torrent9 or torrent9clone
|
||||
- https://www.oxtorrent.me/
|
||||
- https://www.torrent9.gg/
|
||||
- https://www.torrent9.fi/ # this is the torrent9clone domain
|
||||
- https://www.torrent9.fm/
|
||||
- https://torrent9.se/ # redirect to www.
|
||||
- https://torrent9.ninjaproxy1.com/ # no response data
|
||||
- https://torrent9.proxyninja.org/ # Error 1007
|
||||
- https://www.torrent9.se/
|
||||
- https://torrent9.unblockninja.com/ # 403 forbidden
|
||||
- https://ww1.torrent9.fm/
|
||||
- https://www.torrent9.zone/ # clone? details links are broken
|
||||
- https://torrent9.to/
|
||||
- https://ww2.torrent9.to/
|
||||
- https://www5.torrent9.to/
|
||||
|
||||
caps:
|
||||
# dont forget to update the search fields category case block
|
||||
categorymappings:
|
||||
- {id: films, cat: Movies, desc: "Movies"}
|
||||
- {id: series, cat: TV, desc: "TV"}
|
||||
- {id: musique, cat: Audio, desc: "Music"}
|
||||
- {id: ebook, cat: Books, desc: "Books"}
|
||||
- {id: logiciels, cat: PC, desc: "Software"}
|
||||
- {id: jeux-pc, cat: PC/Games, desc: "PC Games"}
|
||||
- {id: other, cat: Other, desc: "Other"} # dummy cat for results missing icon
|
||||
|
||||
modes:
|
||||
search: [q]
|
||||
tv-search: [q, season, ep]
|
||||
movie-search: [q]
|
||||
music-search: [q]
|
||||
book-search: [q]
|
||||
allowrawsearch: true
|
||||
|
||||
settings:
|
||||
- name: info_flaresolverr
|
||||
type: info_flaresolverr
|
||||
- name: multilang
|
||||
type: checkbox
|
||||
label: Replace MULTi by another language in release name
|
||||
default: false
|
||||
- name: multilanguage
|
||||
type: select
|
||||
label: Replace MULTi by this language
|
||||
default: FRENCH
|
||||
options:
|
||||
FRENCH: FRENCH
|
||||
MULTi FRENCH: MULTi FRENCH
|
||||
ENGLISH: ENGLISH
|
||||
MULTi ENGLISH: MULTi ENGLISH
|
||||
VOSTFR: VOSTFR
|
||||
MULTi VOSTFR: MULTi VOSTFR
|
||||
- name: vostfr
|
||||
type: checkbox
|
||||
label: Replace VOSTFR and SUBFRENCH with ENGLISH
|
||||
default: false
|
||||
- name: sort
|
||||
type: select
|
||||
label: Sort requested from site (Only works for searches with Keywords)
|
||||
default: ".html"
|
||||
options:
|
||||
".html": best
|
||||
".html,trie-date-d": created desc
|
||||
".html,trie-date-a": created asc
|
||||
".html,trie-seeds-d": seeders desc
|
||||
".html,trie-seeds-a": seeders asc
|
||||
".html,trie-poid-d": size desc
|
||||
".html,trie-poid-a": size asc
|
||||
".html,trie-nom-d": title desc
|
||||
".html,trie-nom-a": title asc
|
||||
|
||||
download:
|
||||
selectors:
|
||||
- selector: a[href^="magnet:?"]
|
||||
attribute: href
|
||||
- selector: script:contains("magnet:?")
|
||||
filters:
|
||||
- name: regexp
|
||||
args: "\\s'(magnet:\\?.+?)';"
|
||||
|
||||
search:
|
||||
paths:
|
||||
- path: "{{ if .Keywords }}search_torrent/{{ .Keywords }}{{ .Config.sort }}{{ else }}home/{{ end }}"
|
||||
keywordsfilters:
|
||||
# if searching for season packs with S01 to saison 1 #9712
|
||||
- name: re_replace
|
||||
args: ["(?i)(S0)(\\d{1,2})$", "saison $2"]
|
||||
- name: re_replace
|
||||
args: ["(?i)(S)(\\d{1,3})$", "saison $2"]
|
||||
- name: replace
|
||||
args: [" ", "-"]
|
||||
|
||||
headers:
|
||||
# site blocks all Linux User-Agents, so use a slightly altered Windows Jackett UA here (e.g. Safari/537.36 > Safari/537.35)
|
||||
User-Agent: ["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.35"]
|
||||
|
||||
rows:
|
||||
selector: table.table-striped > tbody > tr
|
||||
filters:
|
||||
- name: andmatch
|
||||
|
||||
fields:
|
||||
category_optional:
|
||||
selector: td:nth-child(1) i
|
||||
optional: true
|
||||
case:
|
||||
i[class="fa fa-video-camera"]: films
|
||||
i[class="fa fa-tv"]: series # search by name
|
||||
i[class="fa fa-desktop"]: series # keywordless search
|
||||
i[class="fa fa-music"]: musique
|
||||
i[class="fa fa-gamepad"]: jeux-pc
|
||||
i[class="fa fa-laptop"]: logiciels
|
||||
i[class="fa fa-book"]: ebook
|
||||
category:
|
||||
text: "{{ if .Result.category_optional }}{{ .Result.category_optional }}{{ else }}other{{ end }}"
|
||||
title_default:
|
||||
selector: td:nth-child(1) a
|
||||
title_optional:
|
||||
selector: td:nth-child(1) a[title]
|
||||
attribute: title
|
||||
optional: true
|
||||
title_phase1:
|
||||
text: "{{ if .Result.title_optional }}{{ .Result.title_optional }}{{ else }}{{ .Result.title_default }}{{ end }}"
|
||||
filters:
|
||||
- name: re_replace
|
||||
args: ["(?i)\\b(FRENCH|MULTI|TRUEFRENCH|VOSTFR|SUBFRENCH)\\b(.+?)(\\b((19|20)\\d{2})\\b)$", "$3 $1$2"]
|
||||
title_vostfr:
|
||||
text: "{{ .Result.title_phase1 }}"
|
||||
filters:
|
||||
- name: re_replace
|
||||
args: ["(?i)\\b(vostfr|subfrench)\\b", "ENGLISH"]
|
||||
title_phase2:
|
||||
text: "{{ if .Config.vostfr }}{{ .Result.title_vostfr }}{{ else }}{{ .Result.title_phase1 }}{{ end }}"
|
||||
title_multilang:
|
||||
text: "{{ .Result.title_phase2 }}"
|
||||
filters:
|
||||
- name: re_replace
|
||||
args: ["(?i)\\b(MULTI(?!.*(?:FRENCH|ENGLISH|VOSTFR)))\\b", "{{ .Config.multilanguage }}"]
|
||||
title:
|
||||
text: "{{ if .Config.multilang }}{{ .Result.title_multilang }}{{ else }}{{ .Result.title_phase2 }}{{ end }}"
|
||||
details:
|
||||
selector: td:nth-child(1) a
|
||||
attribute: href
|
||||
download:
|
||||
selector: td:nth-child(1) a
|
||||
attribute: href
|
||||
date:
|
||||
selector: td:nth-child(2):contains("/")
|
||||
optional: true
|
||||
default: now
|
||||
filters:
|
||||
- name: dateparse
|
||||
args: "dd/MM/yyyy"
|
||||
size:
|
||||
selector: "{{ if .Keywords }}td:nth-child(3){{ else }}td:nth-child(2){{ end }}"
|
||||
filters:
|
||||
- name: re_replace
|
||||
args: ["(\\w)o", "$1B"]
|
||||
seeders:
|
||||
selector: "{{ if .Keywords }}td:nth-child(4){{ else }}td:nth-child(3){{ end }}"
|
||||
optional: true
|
||||
default: 0
|
||||
leechers:
|
||||
selector: "{{ if .Keywords }}td:nth-child(5){{ else }}td:nth-child(4){{ end }}"
|
||||
optional: true
|
||||
default: 0
|
||||
downloadvolumefactor:
|
||||
text: 0
|
||||
uploadvolumefactor:
|
||||
text: 1
|
||||
# engine n/a
|
||||
@@ -0,0 +1,36 @@
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
webPort = 8081;
|
||||
btPort = 51413;
|
||||
downloadsDir = "/var/lib/downloads";
|
||||
in
|
||||
{
|
||||
# qBittorrent (headless). Web UI: http://<host>:8081
|
||||
services.qbittorrent = {
|
||||
enable = true;
|
||||
openFirewall = true;
|
||||
webuiPort = webPort;
|
||||
# Prefer a stable port for NAT/firewall and for easier debugging.
|
||||
torrentingPort = btPort;
|
||||
};
|
||||
|
||||
users.groups.qbittorrent = { };
|
||||
users.users.qbittorrent = {
|
||||
isSystemUser = true;
|
||||
group = "qbittorrent";
|
||||
extraGroups = [ "media" ];
|
||||
};
|
||||
|
||||
systemd.tmpfiles.settings."nix-server-downloads-dir" = {
|
||||
"${downloadsDir}"."d" = {
|
||||
mode = "2775";
|
||||
user = "root";
|
||||
group = "media";
|
||||
};
|
||||
};
|
||||
|
||||
# Some NixOS versions don't open UDP for torrenting even when openFirewall=true.
|
||||
networking.firewall.allowedTCPPorts = [ webPort btPort ];
|
||||
networking.firewall.allowedUDPPorts = [ btPort ];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{ ... }:
|
||||
{
|
||||
# Radarr (movie automation). UI: http://<host>:7878
|
||||
services.radarr.enable = true;
|
||||
|
||||
# Keep permissions aligned with Jellyfin (/var/lib/media via group `media`).
|
||||
users.groups.radarr = { };
|
||||
users.users.radarr = {
|
||||
isSystemUser = true;
|
||||
group = "radarr";
|
||||
extraGroups = [ "media" ];
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 7878 ];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
{ ... }:
|
||||
{
|
||||
# Blank default Seerr branding assets (read-only store otherwise).
|
||||
nixpkgs.overlays = [
|
||||
(final: prev: {
|
||||
seerr = prev.seerr.overrideAttrs (oldAttrs: {
|
||||
postInstall =
|
||||
(oldAttrs.postInstall or "")
|
||||
+ ''
|
||||
find "$out/share/public" -maxdepth 1 -type f \( -name 'logo_full.svg' -o -name 'logo_stacked.svg' \) \
|
||||
-exec truncate -s 0 {} \;
|
||||
'';
|
||||
});
|
||||
})
|
||||
];
|
||||
|
||||
# "Seerr" request management. For Jellyfin, Jellyseerr is the right choice.
|
||||
# UI: http://<host>:5055
|
||||
services.seerr.enable = true;
|
||||
|
||||
users.groups.jellyseerr = { };
|
||||
users.users.jellyseerr = {
|
||||
isSystemUser = true;
|
||||
group = "jellyseerr";
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 5055 ];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{ ... }:
|
||||
{
|
||||
# Sonarr (TV automation). UI: http://<host>:8989
|
||||
services.sonarr.enable = true;
|
||||
|
||||
# Ensure Sonarr can manage the same libraries as Jellyfin.
|
||||
users.groups.sonarr = { };
|
||||
users.users.sonarr = {
|
||||
isSystemUser = true;
|
||||
group = "sonarr";
|
||||
extraGroups = [ "media" ];
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = [ 8989 ];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
{ config, ... }:
|
||||
let
|
||||
secretFilePath = ../secrets.yaml;
|
||||
in
|
||||
{
|
||||
sops.secrets."swiftshare/ghcr-token".sopsFile = secretFilePath;
|
||||
sops.secrets."swiftshare/database-password".sopsFile = secretFilePath;
|
||||
sops.secrets."swiftshare/oauth-discord-client-secret".sopsFile = secretFilePath;
|
||||
sops.secrets."swiftshare/oauth-github-client-secret".sopsFile = secretFilePath;
|
||||
sops.secrets."swiftshare/auth-secret".sopsFile = secretFilePath;
|
||||
sops.secrets."swiftshare/oauth-google-client-id".sopsFile = secretFilePath;
|
||||
sops.secrets."swiftshare/oauth-google-client-secret".sopsFile = secretFilePath;
|
||||
sops.secrets."swiftshare/smtp-pass".sopsFile = secretFilePath;
|
||||
sops.secrets."swiftshare/minio-access-key".sopsFile = secretFilePath;
|
||||
sops.secrets."swiftshare/minio-secret-key".sopsFile = secretFilePath;
|
||||
|
||||
# Docker `--env-file` expects `KEY=value`. Separate snippets for DB/MinIO so only `swiftshare.env` hits the app container.
|
||||
sops.templates."swiftshare-postgres.env" = {
|
||||
content = ''
|
||||
POSTGRES_PASSWORD=${config.sops.placeholder."swiftshare/database-password"}
|
||||
'';
|
||||
};
|
||||
|
||||
sops.templates."swiftshare-minio.env" = {
|
||||
content = ''
|
||||
MINIO_ROOT_USER=${config.sops.placeholder."swiftshare/minio-access-key"}
|
||||
MINIO_ROOT_PASSWORD=${config.sops.placeholder."swiftshare/minio-secret-key"}
|
||||
'';
|
||||
};
|
||||
|
||||
sops.templates."swiftshare.env" = {
|
||||
content = ''
|
||||
DATABASE_URL=postgresql://swiftshare:${config.sops.placeholder."swiftshare/database-password"}@swiftshare-db:5432/swiftshare
|
||||
AUTH_SECRET=${config.sops.placeholder."swiftshare/auth-secret"}
|
||||
AUTH_DISCORD_SECRET=${config.sops.placeholder."swiftshare/oauth-discord-client-secret"}
|
||||
AUTH_GITHUB_SECRET=${config.sops.placeholder."swiftshare/oauth-github-client-secret"}
|
||||
AUTH_GOOGLE_SECRET=${config.sops.placeholder."swiftshare/oauth-google-client-secret"}
|
||||
AUTH_GOOGLE_ID=${config.sops.placeholder."swiftshare/oauth-google-client-id"}
|
||||
SMTP_PASS=${config.sops.placeholder."swiftshare/smtp-pass"}
|
||||
STORAGE_ACCESS_KEY=${config.sops.placeholder."swiftshare/minio-access-key"}
|
||||
STORAGE_SECRET_KEY=${config.sops.placeholder."swiftshare/minio-secret-key"}
|
||||
'';
|
||||
};
|
||||
|
||||
services.swiftshare = {
|
||||
enable = true;
|
||||
|
||||
app = {
|
||||
image = "ghcr.io/olivierchiasson/swiftshare:main";
|
||||
ghcr = {
|
||||
username = "olivierchiasson";
|
||||
passwordFile = config.sops.secrets."swiftshare/ghcr-token".path;
|
||||
};
|
||||
|
||||
origin = "https://swiftshare.cloud";
|
||||
port = 3000;
|
||||
uploadBodySizeLimit = "100mb";
|
||||
disableTelemetry = true;
|
||||
environmentFiles = [ config.sops.templates."swiftshare.env".path ];
|
||||
};
|
||||
|
||||
database = {
|
||||
user = "swiftshare";
|
||||
#password = ""; # Defined in sops.templates."swiftshare-postgres.env"
|
||||
name = "swiftshare";
|
||||
environmentFiles = [ config.sops.templates."swiftshare-postgres.env".path ];
|
||||
#exposePort.enable = true;
|
||||
};
|
||||
|
||||
auth = {
|
||||
#secret = "";
|
||||
|
||||
discord = {
|
||||
clientId = "1400660345068191855";
|
||||
#clientSecret = ""; # Defined in sops.templates."swiftshare.env"
|
||||
};
|
||||
|
||||
# GitHub OAuth App (https://github.com/settings/developers) — replace placeholders.
|
||||
github = {
|
||||
clientId = "Ov23lifcVKR6B1iYDicU";
|
||||
#clientSecret = ""; # Defined in sops.templates."swiftshare.env"
|
||||
};
|
||||
|
||||
# Google Cloud OAuth 2.0 client — replace placeholders.
|
||||
#google = {
|
||||
# clientId = ""; # Defined in sops.templates."swiftshare.env"
|
||||
# clientSecret = ""; # Defined in sops.templates."swiftshare.env"
|
||||
#};
|
||||
|
||||
# SMTP for Better Auth email verification / password reset.
|
||||
smtp = {
|
||||
host = "smtp.purelymail.com";
|
||||
port = 465;
|
||||
secure = true;
|
||||
user = "noreply@swiftshare.cloud";
|
||||
#pass = ""; # Defined in sops.templates."swiftshare.env"
|
||||
from = "noreply@swiftshare.cloud";
|
||||
};
|
||||
};
|
||||
|
||||
minio = {
|
||||
#accessKey = ""; # Defined in sops.templates."swiftshare-minio.env"
|
||||
#secretKey = ""; # Defined in sops.templates."swiftshare-minio.env"
|
||||
bucketName = "swiftshare-assets";
|
||||
environmentFiles = [ config.sops.templates."swiftshare-minio.env".path ];
|
||||
};
|
||||
|
||||
umami = {
|
||||
websiteId = "b4e1240d-a9d8-4075-b64d-0d3e0329cac8";
|
||||
scriptUrl = "https://analytics.chiasson.cloud/script.js";
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -9,14 +9,25 @@
|
||||
}:
|
||||
{
|
||||
imports = [
|
||||
inputs.ddrm.nixosModules.default
|
||||
self.nixosModules.nix-serverHardware
|
||||
inputs.sops-nix.nixosModules.sops
|
||||
self.nixosModules.system
|
||||
self.nixosModules.users
|
||||
./_services/attic-cache-server.nix
|
||||
./_services/portainer.nix
|
||||
./_services/organizr.nix
|
||||
./_services/swiftshare.nix
|
||||
./_services/immich.nix
|
||||
./_services/jellyfin.nix
|
||||
./_services/jellyfin-remote-storage.nix
|
||||
./_services/ddrm-media-server.nix
|
||||
./_services/sonarr.nix
|
||||
./_services/prowlarr.nix
|
||||
./_services/flaresolverr.nix
|
||||
./_services/radarr.nix
|
||||
./_services/qbittorrent.nix
|
||||
./_services/seerr.nix
|
||||
];
|
||||
|
||||
boot.loader.grub = {
|
||||
|
||||
Reference in New Issue
Block a user