十一月 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 。

十一月 17, 2020

基于bspwm的桌面环境搭建

自打入手mss(Maxtor Shared Storage Plus)以来,也算是个linux用户。不过这将近十年的时间里都没正经用过linux桌面,一方面是自己所需的大部分应用在ssh+网页界面的情况下都可以搞定,比如transmission、jellyfin、calibre-web等等;另一方面也是早期阶段linux的桌面有点复杂,浅尝辄止的试用阶段中也被搞崩过好几次。

最近一段时间没怎么打游戏,开了windows就是jupyter+vscode,正好又听说微软官方发布了vscode的arm版,就决定搞个raspberry pi好好跑下linux的桌面,折腾一通manjaro官方镜像的xfce、i3、bspwm之后,梳理一下思路,为下次继续折腾做准备。

平时使用的各种桌面环境集成程度比较高,在linux下,几个构成组件可以根据需要拆分组合运行。

桌面环境的构成组件

  • 窗口管理:桌面环境最基础的组件,后续的组件一般由窗口管理程序引导运行。窗口管理的方式分为层叠和平铺:windows祖传的层叠,在win10以后通过win+左右方向键也可以快速平铺显示;不过作为平铺窗口管理器,指的一般都是没有最小化和窗口大小调整,所有程序最大化,新建程序后自动分屏,永远铺满整个屏幕,和安卓的多任务分屏类似。gnome、kde、xfce都是层叠,i3和bspwm是平铺窗口。

  • 启动器:对windows来说就是桌面程序图标+快速启动+快捷键+开始菜单,第三方的搜索启动程序比如launchy和listary也有很多支持者,win10之后的win+s也能作为半个启动器使用。xfce的启动器和windows比较类似,i3和bspwm的启动器组合一般都是dmenu+常用程序快捷键。

  • 状态栏:以windows为例,状态栏上会有开始按钮、快速启动、当前运行的程序标签、输入法、音量、网络、时间等等。而配合平铺窗口管理器的状态栏程序,示例配置中一般都只是工作区清单和一堆系统信息,其他都得另外实现和配置。i3打包程序里有个i3statusbar,也可以换其他的;bspwm可选项比较多,我用的是polybar。

  • 通知:windows自然是有的,三大件也是有的。其他万一没有的得装dunst。

  • 快捷键:windows的win键搭配有一整套快捷键组合程序,比如大家都离不开的win+d、win+e和alt+tab这些;i3wm自带了快捷键的响应,在自己的配置文件内可以编辑各项动作;继续简化的bspwm则需要另外的程序sxhkd来接管快捷键。顺便说一句,manjaro官网上的raspberry-pi-i3wm镜像的默认快捷键配置一团糟,甚至都把tmux的快捷键给搞乱了,记得避雷。

  • 桌面:多屏幕、分辨率、壁纸、透明。平铺窗口管理器的工作区模式和多屏幕配合一般非常好;分辨率调整使用xrandr或者其gui程序arandr;不带壁纸设置的wm一般可以用feh来设置桌面;透明度设置是linux美化的重灾区,之前流行的compton已经没人维护,现在在更新的picom是它的一个fork。

  • 常用基本程序:有一些程序过于习以为常,有时候会忘记它居然是得另外配的。

    • 文件管理器 :平铺窗口管理器还是用些简单明快的thunar;或者使用cli的ranger。
    • wifi连接:manjaro的xfce镜像带了nm-applet,或者用命令行的iw
    • 蓝牙连接:blueman-applet。命令行连蓝牙就有点说不过去,对自己好一点。
    • 电源:状态栏程序显示电源也是强项。
    • 输入法:fcitx

bspwm的安装和配置过程

  1. 我用manjaro打包的xfce,默认用lightdm作为登录管理,抛开display manager的话,在x和wm安装完后也可以用ctrl+alt+f1/f2/f3...切换到对应的tty,命令行登录后自己用which bspwm找出路径,然后startx /usr/bin/bspwm来启动桌面。

  2. 安装bspwm、sxhkd

sudo pacman -S bspwm sxhkd install -Dm755 /usr/share/doc/bspwm/examples/bspwmrc ~/.config/bspwm/bspwmrc install -Dm644 /usr/share/doc/bspwm/examples/sxhkdrc ~/.config/sxhkd/sxhkdrc

默认配置下进去bspwm,只会看到一个黑乎乎的桌面,得按照上面的介绍继续挨个安装和配置组件。

  1. dmenu

安装dmenu自己或者它的一堆fork,sudo pacman -S dmenu,sxhkd默认的配置是super+space启动dmenu_run.

当然也可以用rofi。

  1. 终端和浏览器:

    • sxhkd默认配置的终端是urxvt,如果想用其他的或者调整习惯快捷键记得修改配置文件的super+enter部分
    • 浏览器装完后添加一个快捷键,比如alt+i到firefox之类,
  2. polybar

polybar得通过aur安装,yay -S polybar,拷贝默认配置,没有目录记得新建,cp /usr/share/doc/polybar/config ~/.config/polybar/

按照archwiki的建议,写个启动脚本,注意倒数第二行的mybar指的是polybar的配置名称,如果先试用默认配置记得改成example

#!/bin/bash # Terminate already running bar instances killall -q polybar # Wait until the processes have been shut down while pgrep -u $UID -x polybar >/dev/null; do sleep 1; done # Launch Polybar, using default config location ~/.config/polybar/config polybar mybar & echo "Polybar launched..."

保存启动脚本chmod +x,然后把这个脚本路径比如是 $HOME/.config/polybar/launch.sh新建一行添加到bspwmrc里

这样进去后,桌面就有点样子了,其他的根据需要自己补上。