1. 离线 Ubuntu 上为什么值得走源码编译这条路
一台内网服务器要开文件共享,标准做法就是sudo apt install samba,两分钟的事。但如果这台机器完全没有外网出口——机房隔离网段、实验室内网、保密环境——这条命令会卡在Could not resolve 'archive.ubuntu.com',后面所有事都无从谈起。我最近经手的一个项目就是这种场景:Ubuntu 22.04 的机器在纯内网,需要跑一个 Samba 提供共享目录给 Windows 客户端读写,最后走的是官方.tar.gz源码编译这条路,从 configure 一路做到 smbd 稳定运行。
在动手之前,能选的路其实不只这一条。把它们摊开对比会更容易做决定。
| 方案 | 适用场景 | 主要代价 |
|---|---|---|
| 拷 .deb 包手动 dpkg 安装 | 只装一次,之后不再动 | 依赖树复杂,容易来回补包 |
| 本地 apt 仓库 | 内网要长期维护,后续还会装别的软件 | 首次搭建需要一台完全一致的联网机 |
| 源码编译(.tar) | 需要特定版本,或仓库版本太老 | 编译耗时长,配置项出错概率高 |
| 容器镜像离线导入 | 允许引入容器运行时 | 与宿主机共享目录、权限模型要额外处理 |
后两种都算可行,但容器方案在需要和宿主机文件系统深度交互的场景下会引入新的权限映射问题,反而更绕。源码编译的优势在于把依赖边界收得比较窄:编译期需要的东西集中在几个-dev包上,运行期依赖的是系统里本来就有的一批基础动态库,不会牵扯出一整棵包的依赖树。而且编译出来的版本完全由你决定,不受发行版仓库里那个可能已经落后两三年版本的限制。
代价也要说清楚。一次完整编译在四核机器上大概二十到四十分钟,中间任何一步配置错都要重来;后续升级没有apt upgrade这种一键操作,得自己重新走一遍流程;源码装出来的文件不在包的数据库里,dpkg -l看不到它,卸载只能手工删目录。所以我的实际判断是:如果只是临时用一次,拷 .deb 更省事;如果这台机器的共享服务要长期跑、还要跟着业务版本演进,源码编译反而是更可控的选择。
这篇内容记录的就是完整过程:依赖怎么离线准备、configure 每个参数在解决什么问题、smb.conf 怎么写才真能用、源码装的 Samba 没有 systemd 单元怎么补、以及连不上登不上时按什么顺序排查。
2. 离线环境下的依赖准备:编译期的坑全埋在这一步
2.1 编译期依赖和运行期依赖要分开列
很多人栽在离线编译上,根源是只准备了"看起来需要"的那几个包。实际上 Samba 的依赖要分成两拨看。
编译期需要的是头文件、静态库、构建工具。在 Ubuntu 22.04 / 24.04 上,这份清单大致是这样的:
build-essential:提供 gcc、g++、make、libc6-dev,没有它 configure 第一步就过不去python3-dev:Samba 从 4.0 起构建系统换成了 waf,waf 本身是 Python 写的,缺了它 configure 会说找不到 pythonpkg-config:configure 靠它探测一堆库的版本和编译参数libgnutls28-dev:TLS 和加密支持,这个包最容易漏,漏了要到链接阶段才报错libacl1-dev、libattr1-dev:ACL 和扩展属性支持libpam0g-dev:如果需要走 PAM 做认证libreadline-dev:smbclient 之类的交互式工具要用libbsd-dev、libpopt-dev:一些工具函数的来源libldap2-dev、libkrb5-dev:只有要做域集成时才需要libcups2-dev:只有要共享打印机时才需要
运行期依赖是另一套,主要是libgnutls30、libacl1、libpam0g、libreadline8这些基础动态库。好消息是这些在正常的 Ubuntu 安装里都已经存在,不需要额外准备。但有一点必须注意:talloc、tdb、tevent、ldb这四个库,如果不做特殊处理,Samba 会优先去找系统里那套;而最小化安装的 Ubuntu 上通常没有它们,或者版本偏低。解决办法是在 configure 里显式指定用源码自带的副本,后面会细说。
2.2 把 .deb 从联网机搬到目标机的两种做法
做法一:直接下载再手工安装
在一台和目标机版本、架构完全一致的联网机上执行:
sudo apt-get install --download-only --reinstall \ build-essential python3-dev pkg-config \ libgnutls28-dev libacl1-dev libattr1-dev \ libpam0g-dev libreadline-dev libbsd-dev \ libpopt-dev--download-only表示只下载不安装,包会落在/var/cache/apt/archives/下面。加上--reinstall是为了让那些系统里已经装过的包也重新下载一份,避免拷过去之后发现少了一两个。下载完把这些.deb一起拷到 U 盘。
到目标机上:
sudo dpkg -i /mnt/usb/debs/*.deb sudo dpkg --configure -adpkg -i不处理依赖顺序,一次性装几十个包时经常因为顺序问题失败。报 unmet dependencies 不要慌,多半是顺序问题,多跑两次dpkg --configure -a能自愈;如果反复失败,说明确实缺包。
做法二:本地仓库(我更推荐)
在目标机本地搭一个简易仓库,让 apt 自己算依赖顺序:
sudo mkdir -p /opt/localrepo sudo cp /mnt/usb/debs/*.deb /opt/localrepo/ cd /opt/localrepo sudo dpkg-scanpackages . /dev/null | gzip -9c | sudo tee Packages.gz > /dev/null echo "deb [trusted=yes] file:///opt/localrepo ./" | sudo tee /etc/apt/sources.list.d/local.list sudo apt update sudo apt install build-essential python3-dev pkg-config libgnutls28-dev \ libacl1-dev libattr1-dev libpam0g-dev libreadline-dev libbsd-dev libpopt-dev这个方式多花五分钟,但后面无论装什么都不会再为依赖顺序头疼。唯一要注意的是dpkg-scanpackages属于dpkg-dev包,目标机上可能没有,得提前一起带过来。
2.3 三个让很多人白折腾一下午的版本错配
联网机和目标机的系统版本必须完全一致。用lsb_release -a和uname -m在两台机器上各看一遍,输出要一模一样。差一个小版本号,或者一个是 amd64 一个是 arm64,下载的包就会因为 libc6 版本符号对不上而拒绝安装。这个检查花十秒,能省掉半天的排查。
系统时间要对。目标机如果长期断电,时间可能停在几个月前。这会导致编译过程中生成的文件时间戳乱掉,make的增量判断会失效,甚至某些包安装时的签名校验也会出问题。进目标机第一件事:
sudo date -s "2025-01-15 10:00:00"磁盘空间要留够。Samba 编译的中间文件加上最终的安装产物,两个到五个 G 是常态。df -h看一下源码目录所在分区和/usr/local所在分区,建议各留 5G 以上。空间不够的话make会在跑到一半时报No space left on device,前面的时间全白费。
3. configure 参数不能照抄:每一项都在解决一个具体问题
3.1 解包之后先看清构建系统的形态
tar -xzf samba-4.19.5.tar.gz cd samba-4.19.5 ls根目录里你会看到一个configure脚本,还有buildtools、source3、source4、lib这些目录。Samba 从 4.0 开始把构建系统换成了 waf,但为了照顾大家的习惯,仍然保留了./configure && make这套写法。所以不需要额外学 waf 的命令,按传统套路来就行。
3.2 我的 configure 命令行与逐项解释
./configure \ --prefix=/usr/local/samba \ --sysconfdir=/etc/samba \ --localstatedir=/var \ --with-sockets-dir=/run/samba \ --with-piddir=/run/samba \ --with-logfilebase=/var/log/samba \ --with-lockdir=/var/lib/samba/lock \ --with-statedir=/var/lib/samba/state \ --with-cachedir=/var/cache/samba \ --with-privatedir=/var/lib/samba/private \ --bundled-libraries=talloc,tdb,tevent,ldb先说--prefix=/usr/local/samba。为什么不直接装到/usr?因为源码装的smbd、nmblookup和将来可能通过 apt 装的同名程序会互相覆盖,出问题时你都不知道当前跑的是哪个。装到一个独立目录,卸载的时候rm -rf一下就干净了,系统里不会留下看不见的残渣。这是我踩过坑之后的固定习惯。
--sysconfdir=/etc/samba把配置文件放到系统标准位置。这个不只是习惯问题——很多前端工具和脚本默认去/etc/samba/smb.conf找配置,放在别处会增加额外的适配工作。另外 smbd 启动时如果找不到配置文件会直接失败,放在显眼的地方出问题时好排查。
--localstatedir=/var控制运行时状态数据的根目录。如果不显式指定,默认会落在$prefix/var下面,也就是/usr/local/samba/var,程序文件和运行数据混在一起,备份和权限管理都不方便。
--with-sockets-dir和--with-piddir一起指到/run/samba。为什么不用/tmp?/tmp的清理策略由发行版和 tmpfiles 控制,可能在你意想不到的时候被清掉;多用户环境下/tmp里的文件还有被抢注的风险。/run是 tmpfs,重启清零,但语义清晰,配合 tmpfiles 配置可以保证每次开机自动创建。
--with-logfilebase=/var/log/samba指定日志根目录。注意源码装的 Samba不会自动创建这个目录,make install 之后要手工建,否则启动时会因为写不了日志而直接退出。这是新手最容易卡住的一个点。
--with-lockdir和--with-statedir我特意分成了两个子目录。锁文件和状态数据的生命周期不一样,混在一个目录里时间长了会分不清哪些能清、哪些不能清。
--with-privatedir=/var/lib/samba/private这个参数最要紧。passdb.tdb存的是所有 Samba 用户的密码哈希,secrets.tdb存机器密钥,这两个文件泄露等于把认证体系直接送人。所以这个目录的权限一定要设成 700,后面会再提一次。
--bundled-libraries=talloc,tdb,tevent,ldb是离线环境的关键一条。它告诉构建系统:这四个库用源码里自带的副本编译,不要去找系统里那套。最小化安装的 Ubuntu 上这几个库的开发版通常不存在,不指定的话 configure 阶段就会报not found,或者更糟——找到了一套版本偏低的系统库,编译通过但运行时报符号缺失。代价是编译时间变长一些,但换来的是完全可控。configure 结束时会打印一段摘要,找到其中关于这几个库的行,确认都显示为 bundled 而不是 system。
3.3 make 阶段的资源控制和中断恢复
make -j$(nproc)-j后面的数字不是越大越好。Samba 的单个编译单元里模板实例化很多,内存占用不低。4G 内存的机器用-j4比较稳,16G 的可以用-j8到-j16。如果 make 过程中出现:
gcc: fatal error: Killed signal terminated program cc1基本可以断定是 OOM 了,把-j调小重新来。make是增量的,已经编好的目标文件不会重复编译,中断之后直接重跑就行,不用make clean。
编完之后:
sudo make install验证一下关键产物:
ls /usr/local/samba/sbin/ # 期望看到 smbd nmbd winbindd smbpasswd pdbedit smbcontrol 等如果sbin下只有零星几个文件,多半是 configure 阶段有选项导致某些组件被跳过了,回头检查上面那条命令的完整输出摘要。
然后补建运行时目录:
sudo mkdir -p /var/lib/samba/lock /var/lib/samba/state /var/lib/samba/private sudo mkdir -p /var/log/samba /run/samba sudo chmod 700 /var/lib/samba/privateprivate目录给 700,其他目录保持默认的 755 就够。愿意的话把/usr/local/samba/bin和sbin加进 PATH:
echo 'export PATH=/usr/local/samba/bin:/usr/local/samba/sbin:$PATH' | sudo tee /etc/profile.d/samba.sh这样smbpasswd、pdbedit、testparm这些命令就不用每次写全路径了。
4. smb.conf 写成能用的样子:global 段和共享段的取舍
4.1 global 段里每一行都在对应一个实际需求
一个最小可用的/etc/samba/smb.conf:
[global] workgroup = WORKGROUP server string = Source Built File Server netbios name = FILESRV server role = standalone server security = user passdb backend = tdbsam map to guest = bad user log file = /var/log/samba/log.%m max log size = 5000 logging = file load printers = no printing = bsd printcap name = /dev/null disable spoolss = yessecurity = user是独立服务器模式的默认值,Samba 用自己的密码库做认证。passdb backend = tdbsam表示密码存在passdb.tdb里,也就是前面那个 700 权限的 private 目录下。
map to guest = bad user这一行值得单独说。它的意思是:用户名对、密码错,直接拒绝;用户名根本不存在,才按 guest 处理。如果你完全不希望匿名访问,把这行去掉或者写成never。很多"为什么陌生 IP 能挂上我的共享"的问题,根源就在这里。
load printers、printing、printcap name、disable spoolss这一组是为了彻底关掉打印共享。源码编译的 Samba 如果链接了 CUPS,启动时会尝试连 cups 服务;离线环境里没装 CUPS 的话,日志会被连接失败的报错刷屏,看着吓人但又不影响文件共享。干脆关掉,日志干净得多。反过来,如果你确实要共享打印机,这几行要改成load printers = yes,并且编译前得确认libcups2-dev装了。
log file = /var/log/samba/log.%m里的%m会被替换成客户端主机名,这样一个客户端对应一份日志,排查时不会互相干扰。max log size单位是 KB,写 5000 就是每个日志文件最大 5MB。logging = file强制写文件而不是走 syslog,离线环境下更好追踪。
4.2 共享段的三层权限怎么串起来
一个实际在用的共享:
[data] path = /srv/samba/data comment = Shared Data browseable = yes read only = no guest ok = no valid users = @samba force group = samba create mask = 0660 directory mask = 0770 force create mode = 0660 force directory mode = 0770理解这段配置的关键是分清三层权限的先后关系。
第一层是 Samba 自己的准入检查。valid users = @samba决定谁能挂载这个共享,@前缀表示后面是组名。不在这个组里的人,连共享都看不到。
第二层是 Samba 落到文件上的权限计算。create mask表示把客户端请求的权限按位与一遍,force create mode表示再按位或一遍。最终权限等于(客户端请求 & create mask) | force create mode。为什么要有 force 这一层?因为某些客户端(尤其是老版本 Windows)请求的权限可能过于宽松,直接按它说的来会留下安全隐患,force 一层可以把结果收敛到预期范围。
第三层是 Unix 文件系统的实际权限位。文件创建在哪个组下由force group决定,但如果父目录本身的所有者组不对,光靠 force group 也会出现组归属混乱。
有一个典型故障现场:共享挂载成功、目录也能进,但保存文件时报"权限不足"。九成的根源在path这个目录本身的 Unix 权限。很多人建目录时用的是sudo mkdir -p /srv/samba/data,结果是drwxr-xr-x root root,普通用户根本没有写权限。正确做法是:
sudo groupadd samba sudo mkdir -p /srv/samba/data sudo chown -R root:samba /srv/samba/data sudo chmod -R 2770 /srv/samba/data那个开头的2是 setgid 位,