简介:Nexus Repository Manager 3.50.0-01 的 Windows 64 位安装包,面向需要在团队内搭建 Maven 私服、统一管理构建产物的开发与运维人员,也适用于内网离线部署和制品仓库学习场景。包内共 690 个文件,以 419 个 jar 依赖、82 个 dll 本地库、22 个 exe 可执行程序及 29 个 xml 配置为主,另有 properties、cfg、security 等配置与证书文件,压缩包约 240.15MB,解压后可直接部署运行。已有 494 人学习下载,说明其在私服搭建场景中具备一定参考价值。借助该安装包,读者可完成 Nexus 仓库服务的本地部署,实践 Maven、npm、Docker 等制品的存储、发布与权限划分,并了解细粒度权限控制、仓库内容完整性保护以及存储检索性能优化的相关配置思路。通过内置的仓库管理界面与启动脚本,还能熟悉代理仓库、宿主仓库和仓库组的规划方式,掌握构建产物的上传、版本管理与访问控制配置,为后续迁移和日常运维提供参考。
1. 拿到 nexus-3.50.0-01-win64.zip 之后:内网构件仓库为什么值得自己搭一套
一个很具体的场景:项目组三个人同时执行mvn clean package,构建日志里同一批依赖被反复从公网拉了三遍,其中一个人的网络质量差,卡在下载阶段二十分钟;另一头 CI 机器磁盘只剩 8G。这时候有人丢给你一个nexus-3.50.0-01-win64.zip,说「内网搞个构件仓库吧」。这个 zip 就是 nexus 3.x 的 Windows 64 位免安装包,解压后跑起来就是一套完整的仓库服务,管 Maven、npm、Docker、NuGet、PyPI 这些包格式,把它们统一收进你自己机器的目录里。它解决的不是「能不能下载」的问题,而是「重复下载、版本漂移、外网断了构建就停摆、谁把 SNAPSHOT 覆盖了」这四类麻烦。适合的人群也很清楚:内网有若干台构建机、依赖体积上 GB、需要留一份可回溯的构件存档的团队。一个人写小脚本不必上,超过五个人共用一套构建链路,这东西的价值就压不住了。
需要先说清一件事:这个包本身不含 JDK,它只是启动器和一堆 jar,机器上必须先有可用的 Java 运行时,后面的所有步骤都建立在这个前提上。
2. win64 zip 解压到第一次登录:三个目录、一条命令、两个必看文件
选 zip 而不是 exe 安装包,是我自己踩过之后固定下来的习惯。exe 向导会替你建服务、写注册表、把数据目录丢到某个默认位置,装完你只知道「能用了」,不知道它把东西放哪了;zip 相反,所有路径都是你决定的,出问题时能一眼看出「配置文件改了没生效」还是「数据目录根本没写对」。代价是 Windows 服务要自己装、JVM 参数要自己改,这两件事在下一章处理。
2.1 文件名里的 3.50.0-01 到底是什么
版本号拆开看:3是主版本,整个 3.x 系列共享同一套仓库模型和 REST API;50是特性版本,决定你手上这份包支持哪些格式、有没有某个清理策略字段;-01是构建批次号,同一个特性版本重新打包一次就会变成-02,功能上通常没有差别。这个认知直接影响升级判断:从3.50.0-01换到3.50.0-02基本是替换目录,从3.50跳到3.6x才需要认真读发布说明,看有没有要求逐级过渡、有没有 REST 字段改名。
顺便说清取舍:如果你所在的环境要求「装完就能被运维的监控系统认出来」,exe 包省事;如果你要求「这套东西能整目录复制到另一台机器复现」,只有 zip 能做到,因为它的全部状态都在你指定的两个目录里——程序目录和数据目录。
2.2 把包解压到哪里:路径里的三个禁区
解压路径我一般固定成D:\nexus\nsf,数据目录固定成D:\nexus-data,两者分开放。程序目录将来会被整个替换掉做升级,数据目录要长期保留,混在一起升级时手一抖就麻烦了。
路径上有三个禁区,全是血泪经验:不要放C:\Program Files这类受 UAC 保护的目录,服务账户写文件会被拒;路径里不要有空格和中文,某些构建工具的 URL 拼接会直接崩;不要放在用户目录下,因为服务是以系统账户跑的,读不到某个人的C:\Users\xxx。
解压完你会看到几个目录,只需要记住下面四个:
| 目录 | 里面有什么 | 你会不会改它 |
|---|---|---|
bin | nexus.exe启动器、nexus.vmoptionsJVM 参数 | 会,改内存必动 |
etc | nexus-default.properties,端口与访问路径 | 会,改端口必动 |
lib/system | 组件与插件 | 不动,升级时整个替换 |
public | 静态页面资源 | 不动 |
2.3 先别装服务,用前台模式跑一次
新手最容易犯的错是一上来就装服务,然后服务起不来,日志也不知道去哪看。正确顺序是先前台跑通,看到控制台把启动日志吐完,再去装服务。
# 1) 确认 Java 可用,注意 JAVA_HOME 必须指向 JDK 而不是 JRE java -version echo $env:JAVA_HOME # 2) 进入 bin 目录,前台启动(关掉窗口就停) cd D:\nexus\nsf\bin .\nexus.exe /run/run是前台模式,日志直接打在控制台,任何「JAVA_HOME 没配」「端口被占」「数据目录无写权限」都会立刻以异常形式出现,比事后翻日志快十倍。这一代 3.x 的运行基线是 JDK 8,能不能上 11 要看具体特性版本的发布说明,17 及以上基本别试,会在一堆反射相关的地方直接报错。
启动完成后另开一个窗口验证,别靠肉眼看浏览器标题:
# 健康检查接口,healthy 为 true 才算真起来了 Invoke-WebRequest -Uri http://127.0.0.1:8081/service/rest/v1/status -UseBasicParsing | Select-Object -ExpandProperty Content # 首次启动会生成一次性管理员密码,路径以 nexus.vmoptions 里的 -Dkaraf.data 为准 Get-Content D:\nexus-data\admin.password/service/rest/v1/status返回{"healthy":true,...}说明内核已就绪,这时候浏览器访问http://127.0.0.1:8081才能出登录页。admin.password是一次性文件,登录后系统会要求改密码,改完这个文件就没用了,我习惯把它立刻删掉或者挪进密码管理系统,避免留一台「默认密码还在磁盘上」的机器。
3. 把 nexus.exe 装成开机自启服务:/install 参数与 4 个必须改的 JVM 数字
3.1 /install 与 /run 不能同时存在
前台验证通过后,先Ctrl+C把/run停掉,再装服务。这一步顺序错了,症状特别容易误判:服务装上了、显示正在运行,浏览器却打不开,事件日志里是端口被占——两个进程在抢 8081。
cd D:\nexus\nsf\bin # 注册为 Windows 服务,默认开机自启 .\nexus.exe /install # 启动、查状态 .\nexus.exe /start .\nexus.exe /status # 需要卸载时,先停再卸,顺序反了会留下僵死服务 .\nexus.exe /stop .\nexus.exe /uninstall/install会把当前目录登记成服务的工作目录,所以装服务必须在 bin 目录下执行,换个目录执行会装出一个找不到配置的服务。服务名默认就是nexus,在「服务」管理器里能直接搜到。默认以本地系统账户运行,权限足够读写D:\nexus-data;如果你的数据目录落在网络盘上,得去服务属性里改成有权限的域账户,这一步经常被忘,表现是服务能起、一上传构件就报写入失败。
改完nexus.vmoptions或nexus-default.properties之后必须重启服务才生效。如果重启后起不来,别在服务管理器里反复点「重新启动」,直接用/run前台跑一遍——服务模式把标准输出吞掉了,前台模式才看得见真正的报错。
3.2 四个必须改的数字:内存、直接内存、数据目录、临时目录
默认的 JVM 参数是给「能跑起来」准备的,不是给「长期扛住几十个并发构建」准备的。打开bin\nexus.vmoptions,需要动的其实就四类:
| 参数 | 默认的意义 | 我这边的取值习惯 |
|---|---|---|
-Xms/-Xmx | 堆的初始与上限 | 相等,避免运行期扩堆抖动 |
-XX:MaxDirectMemorySize | 直接内存上限,校验和大文件传输走这里 | 与堆 1:1 |
-Dkaraf.data | 数据目录 | 指向独立盘,如D:/nexus-data |
-Djava.io.tmpdir | 临时目录 | 跟数据目录同盘,别留系统盘 |
# 8G 物理内存的机器,给 nexus 一半 -Xms4096m -Xmx4096m -XX:MaxDirectMemorySize=4096m # 数据与临时文件全部挪出系统盘,注意这里用正斜杠 -Dkaraf.data=D:/nexus-data -Djava.io.tmpdir=D:/nexus-data/tmp内存这块最容易翻车的点是只改-Xmx不改MaxDirectMemorySize。nexus 上传大构件时走的是直接内存,堆里看着很空闲,进程却被系统杀掉或者抛OutOfMemoryError: Direct buffer memory,日志里看不到堆溢出的经典堆栈,纯黑匣子。经验值是把堆和直接内存设成一样大,然后保证物理内存至少是两者之和的两倍,剩下的一半留给文件系统缓存——不然上传个几百 MB 的构件,磁盘 IO 会把整台机器拖住。
-Dkaraf.data用了正斜杠,不是笔误。Windows 上这个配置文件里写反斜杠,某些路径解析会把它当转义符吃掉,表现是数据莫名其妙写到了 bin 目录下面。统一用正斜杠,省掉一整类玄学问题。
3.3 端口、绑定地址与防火墙:三个参数对应三种症状
etc\nexus-default.properties里常改的就三行:
# 对外服务端口 application-port=8081 # 0.0.0.0 表示监听所有网卡;只想本机访问就写 127.0.0.1 application-host=0.0.0.0 # 访问前缀,默认根路径;改成 /nexus 后所有 URL 都要带这个前缀 nexus-context-path=/这三个参数引发的故障很典型:改完application-port没重启,页面还是老端口;改了application-host=127.0.0.1之后同事访问不了,自己本机一切正常;改了nexus-context-path=/nexus之后,仓库地址、REST API 地址、admin.password都没问题,但所有客户端配置里的 URL 全要加前缀,忘一个就报 404。
端口通了但外部访问不了,通常是防火墙。开一个入站规则,别直接把防火墙关掉:
New-NetFirewallRule -DisplayName "nexus-8081" -Direction Inbound ` -Protocol TCP -LocalPort 8081 -Action Allow -Profile Domain,Private-Profile限定域和专用网络,公共网络不开——这不是洁癖,内网仓库暴露在不受控网段上是实打实的风险。8081 之外,如果后面要上 Docker 仓库,还要单独放行一个 connector 端口(常见是 8082),下一章会说。
4. 仓库规划落地:blob store 拆分、Maven group 组装与 npm/Docker 的两个 realm 开关
4.1 先用 blob store 把「存哪儿」和「存什么」拆开
默认只有一个default存储,所有仓库都往里塞。生产上我至少拆两个:一个存正式发布构件,一个存缓存型和快照型内容。理由很实在——备份策略不一样,发布构件要长期留存甚至异地拷贝,缓存件丢了重新拉一遍就行;配额和清理策略也不一样,谁涨得快一目了然。拆开之后将来要迁到对象存储,也是按存储整体迁,不用一个仓库一个仓库搬。
建存储用 REST 接口,比点界面可靠,也方便写进初始化脚本:
$pair = "admin:换成你的密码" $auth = @{ Authorization = "Basic " + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes($pair)) } $blob = @{ name = "blob-maven" # 全局唯一,后面建仓库要引用这个名字 path = "D:/nexus-data/blob-maven" # 必须可写,且不要和别的存储重叠 } | ConvertTo-Json Invoke-RestMethod -Uri http://127.0.0.1:8081/service/rest/v1/blobstores/file ` -Method Post -Headers $auth -ContentType "application/json" -Body $blobname是后续所有仓库配置里的引用键,建完不要改名。path一旦有数据就不要手动移动或重命名目录,想换位置得在界面上做迁移,硬搬目录会让存储里的索引和实际文件对不上。配额字段softQuota在不同小版本里的写法变过,如果带上它报 400,就先把这段去掉,建完仓库再回头在界面里补配额。
4.2 Maven 仓库三件套:releases、snapshots、group
Maven 生态里最稳的结构是两个 hosted 加一个 group:maven-releases存正式版本,maven-snapshots存快照,maven-public作为 group 把内网仓库和上游缓存拼成一个入口给所有客户端。客户端只配 group 地址,谁也不用记多个 URL。
# 正式版本仓库:重复发布直接拒绝 $rel = @{ name = "maven-releases" format = "maven2" type = "hosted" online = $true storage = @{ blobStoreName = "blob-maven" strictContentTypeValidation = $true writePolicy = "allow_once" # 同版本号第二次发布会被拒,防止偷偷覆盖 } maven = @{ versionPolicy = "RELEASE" layoutPolicy = "STRICT" contentDisposition = "ATTACHMENT" } } | ConvertTo-Json -Depth 5 Invoke-RestMethod -Uri http://127.0.0.1:8081/service/rest/v1/repositories ` -Method Post -Headers $auth -ContentType "application/json" -Body $rel参数里真正影响日常行为的是writePolicy。allow_once意味着同一个 GAV 坐标只能发布一次,重发报 400;allow允许覆盖,看着方便,实际上是给「有人手滑把正式包覆盖了,下游缓存全乱」开门。正式仓库一律allow_once,快照仓库才用allow,因为快照本来就该被覆盖。
versionPolicy也要和工作流对应:RELEASE仓拒收-SNAPSHOT版本,SNAPSHOT仓只收快照,两者搞混的直接后果是构建到最后一步发布失败,前面十分钟白等。layoutPolicy用STRICT,它会校验路径和坐标是否匹配,能挡住一部分手动上传放错目录的情况。
group 仓库本身不存文件,只做路由,成员顺序决定查找顺序:
$grp = @{ name = "maven-public" format = "maven2" type = "group" online = $true storage = @{ blobStoreName = "blob-maven"; strictContentTypeValidation = $true } group = @{ memberNames = @("maven-releases", "maven-snapshots") } } | ConvertTo-Json -Depth 5 Invoke-RestMethod -Uri http://127.0.0.1:8081/service/rest/v1/repositories ` -Method Post -Headers $auth -ContentType "application/json" -Body $grpmemberNames的顺序有讲究:把内网正式仓库放第一位,命中就走,不再往下问;快照仓放后面。如果团队还需要从上游公开仓库拉第三方依赖,就在成员里加一个缓存型的远程仓库——它的作用是第一次拉取后把构件留在本地,之后内网重复请求不再走公网。这类仓库的清理策略要单独配,否则它会成为磁盘增长的绝对主力。
4.3 npm 与 Docker:两个必须开的 realm 和一条客户端配置
npm 和 Docker 用不上,多半不是仓库没建,而是 realm 没开。realm 是 nexus 的认证插件链,关着的时候客户端拿着 token 来,服务端根本不认,表现是401或者E401 Unable to authenticate,而你在界面里怎么点用户权限都没用。
进「安全 → Realms」,把npm Bearer Token Realm和Docker Bearer Token Realm加到右侧已启用列表,保存。Docker 还有一个额外前提:它必须走独立的 connector 端口,不能和 8081 共用,因为 Docker Registry 协议要求根路径就是/v2/。
客户端侧,npm 只改一个文件:
# 项目根目录或用户目录下的 .npmrc registry=http://nexus.内网域名:8081/repository/npm-group/ always-auth=truealways-auth=true让每次请求都带凭据,避免匿名可读、发布时才要认证的半吊子状态。Docker 的客户端配置在 daemon 侧:
{ "insecure-registries": ["nexus.内网域名:8082"] }内网仓库走 HTTP 时,Docker 默认拒绝,必须显式声明为不受信任的注册表,改完重启 Docker 服务生效。这一步漏掉,docker push报的是http: server gave HTTP response to HTTPS client,看着像服务端问题,其实是客户端压根没打算用明文。
4.4 清理策略:别等到磁盘告警才想起来
仓库不会自己瘦身。缓存型仓库会一直存着历史上拉过的每一个版本,快照仓库每构建一次就多一套时间戳目录,半年下来几十 GB 很常见。清理靠两样东西配合:清理策略定义「什么样的构件算垃圾」,定时任务负责「按策略真删」。
| 策略条件 | 适用对象 | 建议值 |
|---|---|---|
| 最后下载时间超过 N 天 | 缓存型仓库 | 60~90 天 |
| 最后更新 blob 超过 N 天 | 快照仓库 | 30 天 |
| 保留最近 N 个版本 | 快照仓库 | 3~5 个 |
先建策略,再在仓库的配置里把策略挂上去,最后建一个定时任务跑清理,频率一周一次,放业务低峰。只建策略不挂仓库是最常见的空转——策略列表里躺着好几条规则,一个仓库都没关联,磁盘照涨不误。清理任务执行后不会立刻释放磁盘,它删的是存储里的 blob 引用,真正的文件回收要等下一次紧凑化任务,所以看完任务成功就去看磁盘占用没下降,属于误判。
5. 五个真会翻车的地方:从端口抢占到磁盘涨满的排查路径
5.1 前台模式和服务模式抢 8081
现象:./nexus.exe /run报端口被占用,或者服务显示运行中但页面打不开,netstat -ano | findstr 8081能看到两个 PID。
原因:先前台验证时窗口没关干净,或者命令窗口关了但进程还在(Windows 终端关闭不一定杀子进程),之后又执行了/install+/start。
解决:先Get-Process -Name java看清有几个进程,全部停掉,用/stop停服务,再用/run确认能起,最后才/start。养成习惯:装服务之前一定先Ctrl+C停前台,别直接关窗口。
5.2 端口在监听,浏览器却打不开
现象:Test-NetConnection 127.0.0.1 -Port 8081返回成功,浏览器转圈或者报连接被重置;换一台机器访问则完全连不上。
原因:三种,按概率排序——application-host被改成127.0.0.1只监听本机;防火墙没放开 8081;nexus-context-path改过但访问 URL 没带前缀。
解决:先看etc\nexus-default.properties里application-host是不是0.0.0.0,再查防火墙规则有没有生效,最后确认访问路径。三个都排掉还不行,去看log目录下的请求日志,有没有请求打进来一目了然。
5.3 admin.password 找不到,或者密码根本不对
现象:第一次登录时按提示去数据目录找admin.password,文件不存在;或者文件在,密码贴进去报错。
原因:数据目录被改过(-Dkaraf.data指到了别处),文件在新路径下;或者这个实例之前已经初始化过,一次性密码文件在改密后就被删了;还有一种低级情况——多个实例共用同一个数据目录,密码被另一台改了。
解决:先在nexus.vmoptions里确认-Dkaraf.data的真实路径,去那个目录找admin.password。文件确实没了,最省事的办法是停服务、把数据目录整体挪走留档、重新初始化一遍(代价是重建仓库配置);能进系统但忘了密码,就走重置流程而不是删数据。教训是:首次登录后立刻改密并删文件,这件事别拖。
5.4 磁盘一周涨几十 GB
现象:数据目录体积一周内从 5G 涨到 40G,清理任务显示成功但磁盘没降。
原因:缓存型仓库没有挂清理策略,历史版本全留着;或者清理任务删了引用但没跑紧凑化,文件还占着盘;或者把 blob store 目录和备份目录放在了同一个卷上,备份把盘吃满。
解决:先按仓库看体积(界面上每个仓库都有存储信息),锁定增长最快的那一个;给它挂上「最后下载时间」策略;确认定时任务真的包含清理动作;最后再跑一次紧凑化。日常预防是把数据目录和备份目录分盘,配额直接设在 blob store 上,到阈值就拒绝写入而不是把盘写满——写满之后服务直接不可用,比拒绝上传严重得多。
5.5 构建报 401/403,docker push 报 405
现象:mvn deploy报 401,npm publish报E401,docker push报405 Method Not Allowed或unauthorized。
原因:匿名访问被关掉但客户端没配凭据;npm 的 realm 没启用;Docker 用了 8081 而不是专用 connector 端口;账号没有对应仓库的写权限。
解决:按顺序查——先看匿名访问开关是否符合预期(只读场景应该开,发布场景必须关),再看 realm 列表里 npm/Docker 是否已启用,然后确认 Docker 走的是独立的xxxx:8082,最后在仓库的权限页确认账号在nx-repository-view-<format>-<repo>-add/edit上有权限。四个都排掉还报 401,去看请求是不是被上游的反向转发层改写过路径,前缀丢了同样认证失败。
6. 升级到下一个 3.x 之前:一份体检脚本和两条备份习惯
升级这件事,真正花时间的不是替换目录,而是「怎么证明替换之后什么都没丢」。我的做法是升级前后各跑一次快照,把仓库清单和存储清单存成 JSON,用 diff 对比。这份脚本不依赖第三方库,机器上有 Python 就能跑:
import base64, json, sys, urllib.request from pathlib import Path BASE = "http://127.0.0.1:8081" USER, PWD = "admin", "换成你的密码" # 建议改成从环境变量读 def api(path): req = urllib.request.Request(BASE + path) token = base64.b64encode(f"{USER}:{PWD}".encode()).decode() req.add_header("Authorization", "Basic " + token) with urllib.request.urlopen(req, timeout=15) as r: return json.loads(r.read().decode()) def snapshot(tag): data = { "status": api("/service/rest/v1/status"), "repositories": sorted(api("/service/rest/v1/repositories"), key=lambda x: x["name"]), "blobstores": api("/service/rest/v1/blobstores"), } out = Path(f"nexus-snapshot-{tag}.json") out.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8") print(f"{out.name}: 仓库 {len(data['repositories'])} 个,健康 {data['status'].get('healthy')}") return data if __name__ == "__main__": snapshot(sys.argv[1] if len(sys.argv) > 1 else "before")脚本做三件事:确认服务健康、拉全量仓库定义、拉存储列表,然后落到文件。tag参数就是文件名后缀,升级前跑python check.py before,升级后跑python check.py after,直接fc或者 diff 两个文件。仓库数量对不上,说明升级过程中配置丢了;某个仓库的blobStoreName从blob-maven变成default,说明存储映射被重置,这个错误不会在启动日志里报,但会让所有历史构件「消失」——索引指向了空存储。status里的healthy是关键判据,服务起来不代表可用,健康检查通过才算真的活。
备份习惯我只坚持两条。第一条:升级前先停服务再拷贝,不要一边跑一边用文件同步工具复制数据目录,那样拷出来的 blob 和索引可能不一致,恢复时才发现坏了一半,后悔药没得吃。拷贝用robocopy加/E(复制子目录含空目录),别用/MIR,镜像模式会删掉目标端多出来的文件,备份盘上留的历史版本会被一起清掉:
cd D:\nexus\nsf\bin .\nexus.exe /stop # 程序目录和数据目录分开备,目录名带上日期 robocopy D:\nexus-data E:\backup\nexus-data-20240601 /E /R:1 /W:1 /LOG:E:\backup\data.log robocopy D:\nexus\nsf\etc E:\backup\etc-20240601 /E /R:1 /W:1 .\nexus.exe /start第二条:etc目录单独备一份。升级时程序目录整个换掉,你手改过的端口、访问路径、nexus.vmoptions里的内存参数全在新包之外,靠记忆重填迟早漏。我现在的做法是把nexus.vmoptions和nexus-default.properties两个文件在备份目录里按日期存着,升级后直接比对,两分钟搞定。
最后留一个习惯:每次动配置之前,先把当前能用的那份nexus.vmoptions复制成nexus.vmoptions.bak放在旁边。听起来很土,但我在一台机器上因为少写了一个m后缀把堆设成了 4G 的千分之一,服务起来十秒就退出,靠这个.bak五分钟回滚,而不是重新读一遍所有参数。仓库这种东西,平时没存在感,出问题的时候是整个团队停摆,多留一份副本的成本几乎为零。希望帮到你。
本文还有配套的精品资源,点击获取