十一月 10, 2025

在proxmox ve中用nixos lxc构建尽量无状态的容器

 容器的初次构建和后续用backup文件重新部署差别虽然不大,但是有些一次性操作的关键环节如果漏掉了,重新找起来也挺麻烦,还是都记下。


初始化过程主要参照这个说明

https://nixos.wiki/wiki/Proxmox_Linux_Container


1、nixos的pve lxc镜像:

访问 https://hydra.nixos.org/jobset/nixos/trunk-combined#tabs-jobs

在搜索栏中输入proxmox,选择最近构建的proxmoxlxc任务结果tar文件下载

完了到pve里有ct templates的存储位置里上传

对网络连接有自信的当然也可以在这里download from url


存在local的话可以确认下  /var/lib/vz/template/cache/ 路径下有对应文件


2、创建容器 ,可以抄说明里的命令。其实我用网页创建也没碰到什么问题,无特权容器去掉,回头到options勾上nesting就行。


ctid="99999"

ctname="nixos"

ctt="local:vztmpl/nixos-system-x86_64-linux.tar.xz"

cts="local-lvm"     


pct create ${ctid} ${ctt} \

  --hostname=${ctname} \

  --ostype=nixos --unprivileged=0 --features nesting=1 \

  --net0 name=eth0,bridge=vmbr0,ip=dhcp \

  --arch=amd64 --swap=1024 --memory=2048 \

  --storage=${cts}



nixos大量小文件的模式一般会在磁盘容量塞满之前先把inode塞爆,懒得事先给虚拟磁盘做更小分块的话,就把磁盘调大点,按照jellyfin的经验,8g应该是不够的。


以上命令默认磁盘4g,可以加到16.

pct resize ${ctid} rootfs +12G


3、(可选)如果已经提前准备好了nix配置文件和应用的config以及data文件夹,可以在pve里先妥善组织好,用mountpoint挂载到容器里。没有的话就跳过,回头来弄也没差。

我的目录大致如下

nix/

├── nixfin/

│   ├── nixos-config

│   │   ├── configuration.nix

│   │   ├── smb.nix

│   │   └── jellyfin.nix

│   ├── jellyfin-data

│   └── jellyfin-config

├── nixpi

├── nixlxc

├── ...

└── others

在/etc/pve/lxc/999.conf里添加一行,根据实际情况调整绝对路径

 注意格式

mp0: /nix-test/nixlxc,mp=/etc/nixos


4、启动容器,

进入容器没有环境,得先执行这个才有各种命令

source /etc/set-environment


5、nixchannel,重建

这个就是尽量无状态的尽量了,nixchannel没啥其他地方定义,即便用了flake,你总是得进容器手工跑重建,

nix-channel --add https://mirrors.ustc.edu.cn/nix-channels/nixpkgs-unstable nixpkgs

nix-channel --add https://mirrors.tuna.tsinghua.edu.cn/nix-channels/nixos-unstable/ nixos

nix-channel --update


初次重建的时候可以手工指定镜像

nixos-rebuild switch --option substituters https://mirrors.ustc.edu.cn/nix-channels/store


7、这时候最小nix容器算可以用了,补上应用所需要的nix配置,加一行import就行

如果在这里把容器保存为模板,后续需要新容器可以节省掉上面这一堆步骤

当然也可以不用模板,只要还有一个nix的容器在跑,复制一个改下mountpoint路径,调整下nix文件后重建,自然就算声明了一个新应用的容器


8、nix配置


最小容器的nix文件示例如下,放在mountpoint所指的临时路径/nix-test/nixlxc下:

configuration.nix

{  pkgs,  modulesPath,  ...}: {

  imports = [

    (modulesPath + "/virtualisation/proxmox-lxc.nix")

    ./std.nix

  ];

  proxmoxLXC = {    manageNetwork = false;    privileged = true;  };

  environment.systemPackages = [

    #   pkgs.vim

  ];

  system.stateVersion = "25.11";

}


std.nix ,这个文件实际上大部分可以略过,不影响其他应用,纯个人习惯


{pkgs, ...}: {

  # 时区设置

  time.timeZone = "Asia/Shanghai"; # 设置时区为上海(中国标准时间)


  # zsh start


  programs.zsh = {

    enable = true; # Enable Zsh

    autosuggestions.enable = true;

    syntaxHighlighting.enable = true;


    shellAliases = {

      sc = "sudo systemctl";

      clean = "nix-collect-garbage --delete-older-than ";

      apply = "sudo nixos-rebuild switch";

      update = "nix-channel --update";

      up = "nix flake update";

    };


    ohMyZsh = {

      enable = true; # Enable Oh My Zsh

      plugins = ["git" "z"]; # Add desired plugins

      theme = "random"; # Set your preferred theme (default: robbyrussell)

    };

  };



  users.defaultUserShell = pkgs.zsh; # Set Zsh as the default shell

  environment.shells = [pkgs.zsh]; # Add Zsh to available shells


  # zsh end


  nix.settings = {

    substituters = [

      "https://mirrors.tuna.tsinghua.edu.cn/nix-channels/store"

      "https://mirrors.ustc.edu.cn/nix-channels/store"

    ];

   #    experimental-features = ["nix-command" "flakes"]; # 启用实验性功能:nix命令增强和flakes支持

  };


  environment.systemPackages = with pkgs; [

    vim

  ];

}


9、其他


也不知道为啥用pct enter的方式进入容器,设置的默认shell不生效,ssh或者pve网页端的console都正常;ai说是Pct enter是直接执行/bin/sh的缘故,我也就信了。


在命令里存一个 source /etc/set-environment && zsh 手工跑下也不费事



10、jellyfin

底子铺好后,跑jellyfin或者其他应用,基本也都是加几行配置重建的事情,提前把应用的config,应用的data,和应用需要的外部数据这三个路径组织好。按照无状态要求,这些一般都放在共享存储的环境里,可以在pve主机挂载好mountpoint到lxc里,也可以让lxc处理挂载。


nix文档有时候比较杂,好多介绍材料或者wiki页面里的配置项可能都依赖了home manager,比较干净的还是去mynixos查,比如 https://mynixos.com/search?q=jellyfin


jellyfin和smb挂载的配置,在configuration.nix添加import引入


Jellyfin.nix


{ pkgs, lib,config, ... }:

{

  services.jellyfin = {

    enable = true;

    openFirewall = true;

    configDir = "/jellyfin-config";

    dataDir = "/jellyfin-data";

  };


    environment.systemPackages = [

    pkgs.jellyfin

    pkgs.jellyfin-web

    pkgs.jellyfin-ffmpeg

  ];

}


Smb.nix ,其中有些选项用不上,可以根据smb共享服务端的配置调整


{ config, pkgs, ... }: {

  # SMB 挂载配置

  fileSystems."/mnt/dsm" = { device = "//192.168.1.8/dsm"; fsType =

    "cifs"; options = [

    "guest"

    "iocharset=utf8"

    "vers=3.0"

    "uid=568"

    "gid=568"

    "file_mode=0666"

    "dir_mode=0777"

#"x-systemd.automount"

  ];

  };

  # 创建符号链接

  system.activationScripts.create-symlinks = ''

    ln -sf /mnt/dsm/pub /mnt

 '';

      environment.systemPackages = with pkgs; [ cifs-utils ];

}


11、jellyfin的媒体路径错误


jellyfin数据库比较老或者经过环境迁移(尤其是docker和原生来来去去调整)的话,在编辑library设置的时候可能会报一个媒体库路径和数据库不符的错误,从而无法保存任何更改。


解决办法是手工修改数据库里保存的媒体库路径


10.10.7以前,数据库是jellyfin的data文件夹里的library.db,表名是TypedBaseItems

10.11以后,数据文件是data文件夹里的jellyfin.db,表名是BaseItems


用sqlite打开,


防止出错的话可以先查一下

Select path from TypedBaseItems WHERE type = 'MediaBrowser.Controller.Entities.CollectionFolder';


然后修改路径,path里的内容修改成data里root文件夹所在的正确路径就行。


UPDATE TypedBaseItems SET path = '/绝对路径/jellyfin-data/root/default/' || name WHERE type = 'MediaBrowser.Controller.Entities.CollectionFolder';


十月 21, 2025

在raspberry pi上运行nixos

 一觉醒来听说linux桌面又能打了,赶紧从角落里请出吃灰的raspberry pi 4b 。经过之前几轮raspberry os、armbian和manjaro arm的轮番入驻,本次嘉宾是nixos,计划跑个niri桌面管理器试试。

Pi4本体,sd卡,读卡器,micro hdmi线,无线键鼠套装,充电宝或充电头(可选),必要的网络连接这些都还在,不详细展开了

第一步老规矩请出raspberry pi os lite,先升级下eeprom 。

os下载页面 https://www.raspberrypi.com/software/operating-systems/

找个写卡工具把镜像写入sd卡 ,我用的是rufus

在pi上插sd卡、网线、键盘、充电宝、hdmi线,进入系统后

Sudo rpi-eeprom-update

Sudo rpi-eeprom-update -a


第二步开装nixos ,过程参照这个手册, Installing NixOS on a Raspberry Pi — nix.dev documentation ,主要记录下几个坑:

1、安装镜像在这里下载,解压完用写卡工具写入就行  Hydra - nixos:trunk-combined:nixos.sd_image.aarch64-linux

2、启动系统后,默认是nixos用户登录,这时候碰到第一个问题:手册里的配置文件太长,tinyurl访问又有点问题。根据内网是否已有ssh或者http服务,可以用scp或者curl处理,或者也可以调整下网络连接。我是新建了个文本文件,把配置内容复制进去,然后用filebrowser做分享,nixos curl下来的。

3、开始时没理解configuration.nix文件的作用,把文件头根据自己情况编辑了下,抄了个 源配置塞进去,以为就算换源了,但实际上,这些配置是在rebuild完才会生效的。要改当前源配置还是得手工修改 /etc/nix/nix.conf 的substituters行 ,重启nix-daemon即可生效。

nix.settings ={

  substituters = [

     "https://mirrors.tuna.tsinghua.edu.cn/nix-channels/store"

     "https://cache.nixos.org"

];

};

4、中间手工修改配置文件漏逗号分号关门之类就不说了,没有工具辅助和直接黏贴文本就是惨,还是以最小化的ssh服务先起来再弄后面的。命令用nixos-rebuild boot 然后重启

5、重建终于完成之后,可以用刚才配置文件里用户的登录,确认网络正常后可以拔掉hdmi线和键盘,改用ssh登录。修改pkgs部分,把需要用的软件先加上去,再次重建,这时候用sudo nixos-rebuild switch 就行。


第三步其他配置

6、这时候发现声明式配置的优势了,为了方便后续的备份和迁移,得把配置文件集中起来。暂时先放在home目录下: 

Mkdir ~/nixos-config

Sudo mv  /etc/nixos/configuration.nix  ~/nixos-config/

Sudo ln -sf (绝对路径) etc/nixos/configuration.nix

7、参照手册最后的部分,把hardware也配置起来,配置文件里先声明好git:

Cd ~/nixos-config

Git clone --depth 1  ttps://github.com/nixos/nixos-hardware.git

Vim configuration.nix


加入这块配置

imports = [

  ./nixos-hardware/raspberry-pi/4

];


其他七七八八的环境项 

  # 时区设置

  time.timeZone = "Asia/Shanghai";  # 设置时区为上海(中国标准时间)


补上zsh


# zsh start

  programs.zsh = {

    enable = true; # Enable Zsh

    autosuggestions.enable = true;

    syntaxHighlighting.enable = true;


    shellAliases = {

      update = "sudo nixos-rebuild switch";

    };


    ohMyZsh = {

      enable = true; # Enable Oh My Zsh

      plugins = [ "git" "z" ]; # Add desired plugins

      theme = "random"; # Set your preferred theme (default: robbyrussell)

    };

  };


  system.userActivationScripts.zshrc = "touch .zshrc";

  users.defaultUserShell = pkgs.zsh; # Set Zsh as the default shell

  environment.shells = [ pkgs.zsh ]; # Add Zsh to available shells


# zsh end


重建。


nix配置文件还是从最小环境开始根据实际需要一个一个加上去比较合适,能给你一种一切尽在掌握的错觉。


第四步开始niri


首先确认显卡v3d有没有正确加载,没有的话会黑屏

Lsmod | grep v3d #别是空的

Ls /dev/dri  #看看有没有renderD128

没有的话在配置文件里加上

  hardware.raspberry-pi."4".fkms-3d.enable = true;


因为niri有图形环境,跟其他情况差别比较大,还是单独建立nix文件,通过import导入,方便随时注释

Vim niri.nix


{ pkgs , lib, ...}:

{

programs.niri.enable = true;

programs.waybar.enable = true; # top bar

security.polkit.enable = true; # polkit

services.gnome.gnome-keyring.enable = true; # secret service

security.pam.services.swaylock = {};

#注意这里的packages得用lib.mkafter ,不然导入后有两个pkg环境项可能会报错的

environment.systemPackages = lib.mkAfter (   

        with pkgs; [

                alacritty

                fuzzel

                swaylock mako swayidle

        ]

    );

}


Configuration.nix里的import部分加上相对目录后大致是这样的

imports = [

  ./nixos-hardware/raspberry-pi/4

#  ./niri.nix

];


重建。


目前没有dm选择器,需要手工运行 niri-session进入桌面


发生错误的话,黑屏、卡死之类的,可以用ctrl + alt +f2之类进入其他tty,或者其他设备ssh登录进来,

Ps -aux |grep niri 找到进程,pkill -9掉 ,然后看日志journalctl --user -xe |grep niri -A5 -B5,找到error或者wanning之类的那一大段复制下来喂给ai跟着处理就行。


截至目前,niri可以在raspberry pi上正常启动,不过暂时我的键盘快捷键它认不出来,这些小问题后面有空再弄。


因为我打算趁着jellyfin 10.11发布,踩一下这个大坑:在pve里依赖lxc配置文件、mountpoint、nix声明,构建无状态nixos lxc容器来跑jellyfin 。