手头正好有个项目,现场是一台离线内网的Ubuntu服务器,既不能连外网也没有内部apt源,却要给几十台Windows终端开文件共享。apt装samba这条路直接断掉,唯一的办法就是把samba源码包和一整套编译依赖打包,用.tar方式离线装。折腾了一整天,踩了无数个坑之后总算跑通了,这里把整个流程和关键教训完整记录下来,给后面遇到类似情况的兄弟省点时间。
这篇内容适合两种人:一是纯离线环境下需要装samba的运维或工控工程师,二是对samba源码编译不熟悉、想搞明白configure参数和依赖关系的人。我会从依赖分析、源码准备、编译安装、smb.conf配置、systemd托管到排错实战一步步讲清楚,全部基于Ubuntu LTS版本实测验证过。
1. 离线安装的整体思路与依赖分析
1.1 为什么绕不开.tar源码包
很多人第一反应是“离线装samba?直接把联网机器上下载好的deb包拷过去不就行了”。确实,如果内网有一台可以联网的同版本Ubuntu机器,用apt-get download把samba和所有依赖的deb拉下来再离线dpkg安装,是最省事的路径。但现实往往没这么理想——很多隔离环境的机器自己就是内网,根本没有“同版本联网机”可以用来下载,或者下载下来的deb包依赖版本对不上,dpkg装到一半就报一串依赖缺失错误。这时候源码编译反而是最稳定的方案,因为.tar包里自带了大部分第三方库的版本控制,只要你把需要的源码包凑齐,在离线机器上逐个编译就没有版本匹配问题。
源码编译还有一个好处,就是可以自己指定安装路径。默认的samba源码编译支持--prefix参数,把它丢到/opt/samba或者/usr/local/samba,不污染系统原有的/usr目录。对那种“轻易不动系统自带二进制”的现场环境来说,这个隔离性很重要,万一出了问题直接删掉整个目录就能回滚,比dpkg卸载干净得多。
1.2 源码编译samba到底需要哪些东西
samba不是那种孤零零一个包就能编译的软件,它的核心底层依赖是talloc、tdb、tevent、ldb这一组C库,分别负责内存池、轻量数据库、事件循环和轻量目录访问。这几个库都来自Samba团队自己的配套项目,编译samba时如果没有系统安装它们,源码树的3rdparty里通常有捆绑版本,但最稳妥的做法还是单独下载对应版本源码,先装好这几个依赖再编译samba本体。
除了Samba自家的库,还需要popt(命令行参数解析)、libbsd、libaio、python3-dev、gnutls开发头文件、libreadline-dev、zlib1g-dev这些基础编译依赖。其中gnutls这条链最头疼,它自己又依赖nettle和gmp,如果离线机器上连这些都没有,就得把gmp、nettle、gnutls三个包全部下载好一起编译。看到这里不用慌,后面我会给出完整的依赖清单和推荐编译顺序。
如果你的场景只是做简单的文件共享,不需要加入域控,那么可以在configure时显式关掉AD DC相关的组件,能省掉大量额外依赖。这一点在实际编译时非常重要,很多人失败的根源就是尝试编译了全部组件,结果缺了一堆无关紧要的开发库。
2. 离线安装准备:源码下载与依赖预处理
2.1 在一台联网机器上把源码包准备齐
先找一台能联网的Ubuntu机器,建议系统和目标机保持同一个大版本(比如都是Ubuntu 22.04),这样编译出来的二进制兼容性最好。然后准备下面这些源码包:
- samba-4.x.x.tar.gz(去Samba官网下载,选当前stable版本)
- talloc-2.x.x.tar.gz
- tdb-1.x.x.tar.gz
- tevent-0.x.x.tar.gz
- ldb-2.x.x.tar.gz
- popt-1.x.tar.gz(有些系统自带popt,保险起见也下一份)
- gnutls、nettle、gmp的源码包(如果目标机器没有libgnutls28-dev的话)
把这些包全部放到一个目录下,然后用一条命令打包:
tar czvf samba-offline-all.tar.gz samba-*.tar.gz talloc-*.tar.gz tdb-*.tar.gz tevent-*.tar.gz ldb-*.tar.gz popt-*.tar.gz gnutls-*.tar.gz nettle-*.tar.gz gmp-*.tar.gzU盘或者内部传输工具把这个.tar.gz拷到离线目标机上,后面所有操作都在目标机上进行。
有人可能会问,为什么不直接在一台联网机器上编译好samba可执行文件,然后整个目录拷过去?这当然也可以,但涉及动态库路径和系统环境差异,在工控现场这种“不知道对方用的什么系统镜像”的场景下风险较大。源码包安装虽然慢一点,但是每一步都看得见摸得着,出问题也能准确定位。
2.2 目标机器的系统环境确认
开始编译之前,先检查目标机的硬件架构和基础工具链:
uname -m cat /etc/os-release which gcc make python3如果是x86_64架构,下载源码时选对应的generic包即可;如果是ARM架构(比如国产化机器或嵌入式设备),源码编译方式完全一样,只是编译时间会长不少。gcc和make是硬性条件,Python3也是samba编译脚本必需的,如果这三样都没有,得先从操作系统安装光盘里把这些基础工具补上。
这里有个非常关键的小技巧:在编译前先设置一个环境变量,固定编译进程数,比如:
export MAKEFLAGS="-j4"如果你不确定机器CPU核数,用nproc查看。设太高的编译并发数在内存较小的机器上会直接OOM,我就在一台4G内存的旧机器上被-j8干崩溃过,swap都救不回来。
2.3 推荐先编译的基础依赖
samba本体编译前,我建议按“gmp → nettle → gnutls → talloc → tdb → tevent → ldb”的顺序把底层依赖全部编译安装到同一个前缀目录。统一使用/usr/local/samba-deps作为前缀,这样所有依赖库都集中在同一个地方,samba的configure脚本扫描时也能一次性找到。
每个依赖包的编译套路基本一致,拿talloc举例:
tar xzf talloc-2.3.4.tar.gz cd talloc-2.3.4 ./configure --prefix=/usr/local/samba-deps make make install注意,talloc、tdb、tevent、ldb这四个Samba系库各自还带了一个python绑定模块,编译时如果报找不到Python.h之类的错误,说明缺少python3-dev。如果离线机器上没有这个包,一个临时替代方案是在configure时不要单独编译python绑定,但这些库相对独立,用默认配置一般不会出大问题。
gnutls这条链要稍微多说两句。gmp编译时只需要标准C工具链,nettle需要gmp的头文件,gnutls需要nettle和gmp两者。如果顺序不对,gnutls的configure会报找不到nettle,这就是典型的“依赖顺序错误”问题。另外,编译完每个库后临时把/usr/local/samba-deps/lib加进LD_LIBRARY_PATH再继续下一个,防止链接时找不到上一层的库文件。
我记得当时在安装时曾经漏装了libaio-dev,然后在编译samba本体阶段报了一个特别隐蔽的错误——一直提示找不到aio.h,排查了半天,最后才想起来是基础依赖没装全。后来学聪明了,把所有可能需要的基础开发库提前检查一遍:zlib1g-dev、libreadline-dev、libpam0g-dev、libacl1-dev、libattr1-dev、libblkid-dev、libgnutls28-dev、libpopt-dev。如果你的离线系统里确实没有这些deb包,而apt源也访问不了,那就在联网机器上把对应deb下载过来dpkg安装,或者干脆绕开这些可选依赖,用configure参数显式禁用不需要的功能。
3. Samba本体编译安装流程
3.1 解压与配置参数
依赖库全部安装完之后,终于可以进入正题。先把samba源码包解压:
tar xzf samba-4.19.1.tar.gz cd samba-4.19.1然后创建一个单独的构建目录,避免源码树里混入大量编译中间文件(samba官方官方推荐用build目录):
mkdir build cd build接下来是重头戏:configure参数。针对纯文件共享场景,这是我稳定复现过的配置:
../configure \ --prefix=/opt/samba \ --sysconfdir=/etc/samba \ --localstatedir=/var \ --with-configdir=/etc/samba \ --with-privatedir=/var/lib/samba/private \ --with-piddir=/var/run/samba \ --with-logfilebase=/var/log/samba \ --without-ad-dc \ --without-ldap \ --with-system-talloc \ --with-system-tdb \ --with-system-tevent \ --with-system-ldb \ --bundled-libraries=NONE逐个解释一下这些参数的含义。--prefix指定samba的安装根目录,我习惯放到/opt/samba,方便识别和卸载;--sysconfdir和--with-configdir都指向/etc/samba,这样smb.conf配置文件就放在系统约定俗成的位置,别让它散落到/usr/local目录深处;--without-ad-dc是关闭域控功能,这一项能省掉大量编译工作;--with-system-*告诉编译器去用之前编译好的系统级talloc、tdb、tevent、ldb,而不是samba自带的第三方捆绑版本。
最关键的是--bundled-libraries=NONE。这个参数的意思是强制不使用源码包内嵌的第三方库,而是去系统路径和/usr/local/samba-deps里寻找依赖。配合前面提前安装好的依赖,这个参数能避免samba源码树内部的库版本和系统库版本相互干扰。如果你编译时缺依赖,可以临时去掉这个参数让samba自己用捆绑库,但那样会导致依赖库重复编译,而且后续升级维护很别扭。
3.2 编译与安装
configure成功后,直接make。首次编译samba,即使只编译文件共享相关组件,通常也要10到20分钟。中途遇到那个经典的"Waf"编译系统报错,不要慌,绝大多数情况是某个依赖没找到,回到第2节检查一遍即可。
make make install安装完成后检查一下关键文件是否齐全:
ls -l /opt/samba/sbin/smbd ls -l /opt/samba/sbin/nmbd /opt/samba/sbin/smbd --version这步能正常输出版本号,说明samba编译安装成功。注意,samba的配置文件目录是/etc/samba,但二进制工具都在/opt/samba,需要把这个bin目录加入PATH,否则后续执行smbpasswd、testparm等命令时会提示command not found。建议改一下全局环境变量:
echo 'export PATH=/opt/samba/sbin:/opt/samba/bin:$PATH' > /etc/profile.d/samba.sh source /etc/profile.d/samba.sh3.3 安装后的目录规划与用户准备
samba跑起来需要两个基础条件:存在系统用户、存在可写权限的共享目录。建议在安装完成后立刻把这些备好。
先创建系统用户(samba的valid users验证要落到系统用户上):
useradd -M -s /sbin/nologin smbuser-M表示不创建家目录,-s /sbin/nologin表示不允许该用户登录Shell,这是一个安全建议。以后凡是需要通过samba访问的人都映射到这个系统账户上。如果要给多个Windows用户分配不同权限,可以创建多个系统用户,通过samba的组来控制。
然后建共享目录:
mkdir -p /data/share chown -R smbuser:smbuser /data/share chmod -R 0750 /data/share这里权限我故意设成0750而不是0777,后面讲配置时会解释原因。Windows客户端看到这个共享后,能不能读写不完全取决于这个目录权限,还取决于smb.conf里掩码与用户映射的配合,这一步先把自己的数据目录权限控制好,别一上来就图省事对外开放。
4. smb.conf配置与权限模型
4.1 文件共享基础配置
samba编译安装完成后,系统不会自动生成/etc/samba/smb.conf,需要自己动手创建。先把最基础的一版写出来:
[global] workgroup = WORKGROUP server string = Samba Offline Server security = user map to guest = Bad User log file = /var/log/samba/log.%m max log size = 1000 socket options = TCP_NODELAY server role = standalone server disable netbios = no load printers = no printing = bsd printcap name = /dev/null [share] path = /data/share browseable = yes read only = no valid users = @smbgroup create mask = 0660 directory mask = 0770 force group = smbgroup[global]段的含义是全局生效的配置。workgroup对应Windows同一局域网里的工作组名,保持默认WORKGROUP最省事。security = user是samba最常用的安全模式,用户访问共享时需要提供用户名和密码,而这个账户由系统用户+samba独立密码组成。map to guest = Bad User的含义是,如果用户名合法但密码错误,就当作guest来映射——注意这只是说“该用户不存在或密码错时映射为guest”,并没有开放匿名访问。如果不想要任何匿名访问,可以将这个值改为Never。
还要注意我关闭了打印机共享,这能避免一些无谓的CVE暴露面。工控现场根本用不上samba的打印服务器,能关就关。
[share]段是具体共享目录的定义。path指向真实目录;valid users = @smbgroup限定了只有smbgroup组里的用户才能访问这个共享,比单纯依赖系统目录权限更直观。create mask和directory mask决定了Windows客户端新建文件或目录时对应的权限掩码,0660和0770的含义是“用户和组可读写,其他人没有权限”。
4.2 用户密码与权限控制
配置文件写完之后,最重要的一个环节就是给系统用户设置samba独立密码。先创建组并把系统用户加进去:
groupadd smbgroup usermod -a -G smbgroup smbuser然后设置samba密码:
smbpasswd -a smbuser这里我经常遇到有人问“为什么我明明设置了系统用户密码,Windows连的时候还是提示密码错误”。原因是samba的用户认证并不使用系统登录密码,而是单独存放在samba自己的密码数据库里(默认在/var/lib/samba/private/passdb.tdb)。smbpasswd -a这条命令的作用正是把用户和samba密码写入这个库。这一步漏掉,后面登录必然失败。
如果要管理多个用户,可以用pdbedit命令查看当前生效的samba账户:
pdbedit -L设置密码后再次检查共享目录权限和smb.conf里的valid users是否匹配。正确的顺序是:系统用户先存在→加入组→设置samba密码→smb.conf引用该用户或组→testparm检查配置→启动服务。
4.3 配置检查与基本验证
每次改完smb.conf,都要先跑一遍testparm检查语法问题:
testparm -s /etc/samba/smb.conf如果输出里出现“Load smb config files from /etc/samba/smb.conf”后面没有Error字样,说明配置合法。如果有Error,它会明确告诉你哪一行有问题。
配置没问题后,可以在本机先用smbclient验证一下:
smbclient //localhost/share -U smbuser%你的密码如果进入smb: >交互提示符,就说明本机认证和共享服务都正常。接下来再通过Windows的\192.168.1.100访问,走到这一步基本就通了。
5. 服务托管、开机自启与网络放行
5.1 用systemd管理smbd/nmbd
samba源码安装完成后不会自动注册systemd服务,需要手动编写unit文件。先创建smbd.service:
[Unit] Description=Samba SMB Daemon After=network.target [Service] Type=forking ExecStart=/opt/samba/sbin/smbd -D ExecReload=/bin/kill -HUP $MAINPID PIDFile=/var/run/samba/smbd.pid LimitNOFILE=16384 [Install] WantedBy=multi-user.target再创建nmbd.service:
[Unit] Description=Samba NetBIOS Daemon After=network.target [Service] Type=forking ExecStart=/opt/samba/sbin/nmbd -D ExecReload=/bin/kill -HUP $MAINPID PIDFile=/var/run/samba/nmbd.pid [Install] WantedBy=multi-user.target然后重载并启动:
systemctl daemon-reload systemctl start smbd nmbd systemctl enable smbd nmbd启动后用smbclient或Windows测试,同时用systemctl status smbd查看服务状态。如果服务起不来,赶紧去/var/log/samba/目录看日志,这个目录下的log.smbd和log.<客户端IP>记录了大量排错线索。
有个容易忽略的坑是samba默认的pid目录/var/run/samba在重启后可能不存在。如果启动报pid写入失败,先手动创建并赋权:mkdir -p /var/run/samba && chmod 1777 /var/run/samba,再重启服务。
5.2 防火墙与Windows客户端连接
Ubuntu默认可能开着ufw防火墙,先把samba需要的端口放行:
ufw allow 137/udp ufw allow 138/udp ufw allow 139/tcp ufw allow 445/tcp如果平时习惯直接关闭防火墙,那当我没说,但工控环境不建议裸奔,按端口精确放行更稳妥。
Windows客户端连接时,在资源管理器地址栏输入\服务器IP,比如\192.168.1.100,然后会弹出认证窗口。如果之前用别的凭据登录过,可能缓存了旧密码,在Windows命令行执行net use * /delete之后重新连接即可。
打开“网络”刷不出机器是一种常见反馈。别惊讶,Windows 10和11已经默认关掉了SMB1下的NetBIOS发现,跨网段时间还能看到算运气。这不影响直接输IP访问,所以不用纠结。
6. 常见问题排查与避坑记录
6.1 总是提示用户名或密码错误
这个现象在离线安装后出现的频率最高,而且原因有好几种可能。我把实际排查顺序整理成表格,按这个顺序查基本一摸一个准:
| 排错步骤 | 操作命令或检查项 | 常见结果 |
|---|---|---|
| 1 | 确认系统用户存在 | useradd后的用户才有效 |
| 2 | 确认设置了samba密码 | smbpasswd -a smbuser |
| 3 | 确认安全模式不是share | security = user |
| 4 | 确认valid users没有把该用户排除 | valid users = @smbgroup |
| 5 | 查看日志 | tail -f /var/log/samba/log.smbd |
| 6 | 检查密码库 | pdbedit -L |
还有一个很容易踩的细节是:当你在客户端连接时填写的用户名“smbuser”必须和服务器上的系统用户名完全一致,大小写都要一样。很多Windows弹窗默认会带上“^”或“.\”,如果你的机器加入了某个域环境,那么要在用户名前加服务器主机名,例如:UBUNTU-SVR\smbuser,否则samba会把“.\smbuser”解析成错误的用户。
如果日志里出现“Failed to start service”或者“NT_STATUS_LOGON_FAILURE”,别怀疑日志,直接看smbpasswd有没有执行成功。这是90%的情况。
6.2 共享目录大小限制如何做
离线环境里经常有需求要给共享目录设置容量上限。samba本身没有直接在smb.conf里限制目录大小的参数,但可以通过系统层“配额”实现。需要先在/etc/fstab里,把目录挂载时启用配额选项,然后使用setquota命令限制用户或组的磁盘用量。
比如共享目录是/data/share,而/data是独立分区,可以在fstab里加上usrquota,grpquota挂载选项,然后:
quotacheck -cug /data edquota -u smbuser在交互界面里设置soft和hard限制。设置完成后,Windows客户端写入超过配额时会出现“磁盘已满”的提示,达到限制目的。这个方法比第三方工具靠谱,而且完全离线可用。
如果你不想动底层配额,也可以在samba层面用allow/deny写出限制,但那是对目录权限的控制,不是空间大小限制,两者不是一个维度的东西。
6.3 其他我踩过的坑
再记录几个不常见但一踩就耽误半天的坑:
第一,系统时间不同步导致认证异常。samba做Kerberos相关校验时对时间敏感,虽然standalone文件共享模式影响不大,但如果你开了某些高级选项,Windows客户机和服务器的系统时间差了太多,会报“访问被拒绝”。解决办法是离线环境里手动校正服务器时间,或者配置局域网NTP时间源。
第二,SMB协议版本兼容问题。Windows XP和Win7默认走SMB1,而新版samba里SMB1可能默认关闭。如果现场还有老设备,在[global]里显式加一行:
server min protocol = NT1注意这是往下兼容老设备,不是鼓励开SMB1,能用新协议就不用开。可以根据现场设备情况灵活调整最低协议等级。
第三,LD_LIBRARY_PATH被清空导致smbd起不来。源码编译安装的东西往往会依赖/usr/local/samba-deps/lib下的so库,如果重启后自启动时LD_LIBRARY_PATH还没设置好,服务就会报“error while loading shared libraries”。解决办法是在systemd unit文件的[Service]段里加一行:
Environment=LD_LIBRARY_PATH=/usr/local/samba-deps/lib这样一个服务单元就能拿到正确环境,比全局变量可靠。
第四,跨网段客户端无法访问。samba默认监听所有网卡,但如果服务器有多块网卡,可能只监听其中一块。检查/etc/hosts里主机名解析到哪块网卡,必要时在[global]段用interfaces和bind interfaces only强制绑定内网业务网卡。
第五,Windows防火墙弹窗拦截。局域网内访问时,Windows自带的防御软件可能把入站连接拦掉,但这个问题通常在服务器端或客户端的安全策略软件上被放大。遇到客户端能ping通但无法访问共享的情况,先在Windows上关闭防火墙尝试一次,如果马上好了,就是防守策略拦了SMB端口。
最后讲讲升级维护的问题。源码编译安装的samba,后续如果要对smb.conf调整,直接编辑/etc/samba/smb.conf,然后systemctl reload smbd即可,不需要重新编译。如果samba要升级新版本,必须重新走一遍编译安装流程,但因为依赖库不变,只需要重新准备samba源码包,执行configure、make、make install三步,安装目录一样的话配置文件和密码库都不会丢,存量共享可以无缝衔接。
我个人在实际项目里的体会是:离线编译samba这件事,真正的难点从来不是samba本身的编译,而是整个依赖链的梳理和排错耐心。第一次做最好拿个小本子把每一步的configure输出、编译错误和最终命令都记下来,形成自己的安装脚本。这样下次在现场机器上,从解压源码到启动共享,十分钟左右就能搞定,不用再走一遍漫长的排错流程。