跳到主內容

LibreNMS && Oxidized 備份網管設備設定檔

  BUBU 之前看到節省哥分享這個服務,曾經試著架設過一次,但後來失敗了。最近又看到相關的文章,因此再次嘗試架設,目前已架設成功並可正常運行。

  這套工具可以定期自動備份每一台網通設備的設定檔,管理人員不必再手動逐台備份。另一個優點是可以和先前的版本比對差異,設備出問題時能找回上一版的設定來還原。

2026.07.11 系統採用 Ubuntu 系統來做建置

2026.10.06 系統採用 Debian 系統來做建置

運行環境


  環境皆架設於「Proxmox VE」虛擬系統,預設以「LXC」模式為主,除非有特殊狀況才會改用「VM」模式。

  • 系統環境:Ubuntu 22.04、Debian 12、13
  • Oxidized 版本:0.37(Ruby 需 3.0 以上)

安裝過程


  以下指令除了切換為 oxidized 使用者的步驟之外,皆以 root 身分執行。

安裝官方套件

  • Ubuntu 系列要先啟用 universe 套件庫
apt install software-properties-common -y && add-apt-repository universe
  • 安裝必要套件
apt install -y ruby ruby-dev libsqlite3-dev libssl-dev pkg-config cmake libssh2-1-dev libicu-dev zlib1g-dev g++ libyaml-dev libzstd-dev
  • 安裝 Oxidized 服務
gem install oxidized
  • 選用套件(要與 LibreNMS 整合須安裝 oxidized-web)
gem install oxidized-web    # Web 介面和 REST API
gem install oxidized-script # 以腳本為基礎的輸入/輸出擴充
  • 建立服務使用者並設定密碼
adduser oxidized
  • 切換使用者
su - oxidized
  • 執行一次 oxidized,系統會在家目錄產生隱藏目錄 .config,並建立預設設定檔 ~/.config/oxidized/config
oxidized
  • 確認目錄是否正常產生
ls -la
LibreNMS && Oxidized 設定

  • 備份 Oxidized 預設設定檔
mv /home/oxidized/.config/oxidized/config /home/oxidized/.config/oxidized/config.bak
  • 建立新的設定檔 vim /home/oxidized/.config/oxidized/config
# Oxidized 設定檔範本(Oxidized 0.37,搭配 LibreNMS)
# 位置:/home/oxidized/.config/oxidized/config;修改後須重啟 oxidized。
# 帳密優先序(低→高):全域 < models < groups < groups 內的 models < 單一設備。

# ===== 全域預設帳密 =====
# 優先序最低。填一組無法登入的假帳密:漏設帳密的設備會直接失敗,不會誤用共用帳密。
username: nologin
password: invalid_placeholder

# ===== 全域預設 model =====
# 來源未提供 model 時才套用;LibreNMS 一律會回傳 os,實務上用不到。
model: junos

# false:設備名稱原樣交給連線模組,不先解析 DNS。
resolve_dns: false

# ===== 抓取排程 =====
# 每台設備的抓取間隔(秒):86400=每天,3600=每小時。
# 由 Oxidized 自行排程;內容未變不會產生新版本。
# 0=停用定期抓取,只在被指定時抓(LibreNMS 重新整理鈕、API /node/next/<設備>)。
interval: 86400

debug: false            # true:輸出除錯日誌
threads: 30             # 同時抓取的執行緒上限
timeout: 120             # 單一操作的等待上限(秒);設定內容大的設備可調高
timelimit: 300          # 單台抓取的總時間上限(秒),逾時強制中止
retries: 3              # 失敗後的重試次數
# 預設的提示字元樣式;多數 model 自帶,通常不必改。
prompt: !ruby/regexp /^([\w.@-]+[#>]\s?)$/

# ===== 日誌 =====
# 輸出到 stderr,由 systemd journal 收:journalctl -u oxidized
# 0.34 起取代舊的 use_syslog/log。
logger:
  appenders:
    - type: stderr

# ===== Web 介面與 REST API =====
# LibreNMS 由此讀取設定。需 oxidized-web 0.16 以上;0.33 起取代舊的 rest: 寫法,兩者並存時本區段會被忽略。
# oxidized-web 沒有登入驗證,預設只聽 127.0.0.1(與 LibreNMS 同機)。
# 不同機時 listen 改為本機對內的單一位址,並以防火牆只放行 LibreNMS;0.0.0.0 會對所有介面開放。
extensions:
  oxidized-web:
    load: true
    listen: 127.0.0.1
    port: 8888

# false:/node/next 只把設備排到佇列最前;true:立即抓取。
next_adds_job: false

# 全域變數。常用:remove_secret: true(遮蔽密碼)、enable: <密碼>(特權模式)。
vars: {}

# ===== 依機型(model)設定帳密 =====
# 同機型共用一組帳密時使用;名稱須為 Oxidized 的 model 名稱。
models:
  # FortiGate 防火牆(0.36 起為獨立 model)
  fortigate:
    username: fortiadmin
    password: forti_pass
    # Fortinet 每次輸出都會重新加密密碼,造成假異動;啟用後只在實質變更時存檔(只改密碼時不會存檔)。
    # vars:
    #   output_store_mode: on_significant
  # FortiManager、FortiAnalyzer、FortiSwitch
  fortios:
    username: fortiadmin
    password: forti_pass
    # vars:
    #   output_store_mode: on_significant
  # Cisco IOS(IOS-XE、NX-OS 是不同的 model:iosxe、nxos,要用時須另外列)
  ios:
    username: ciscoadmin
    password: cisco_pass
  # Juniper
  junos:
    username: junosadmin
    password: junos_pass
  # MikroTik
  routeros:
    username: admin
    password: mikrotik_pass
  # UniFi AP(Oxidized 沒有名為 unifi 的 model)
  unifiap:
    username: ubnt
    password: ubnt_pass
  # D-Link
  dlink:
    username: dlinkadmin
    password: dlink_pass

# ===== 依群組(group)設定帳密 =====
# 同站點共用一組帳密時使用,會覆寫 models。
# 前提:LibreNMS 有送出 group(見下方 source 與 LibreNMS 的 group_support)。不需要可整段移除。
groups:
  site_a:
    username: site_a_admin
    password: site_a_pass
  site_b:
    username: site_b_admin
    password: site_b_pass

  # 群組內再依機型區分;優先序僅次於單一設備。
  site_c:
    models:
      fortigate:
        username: fortiadmin
        password: forti_pass_site_c
      ios:
        username: ciscoadmin
        password: cisco_pass_site_c

# PID 檔
pid: "/home/oxidized/.config/oxidized/pid"

# 程式異常時的記錄
crash:
  directory: "/home/oxidized/.config/oxidized/crashes"
  hostnames: false

# ===== 連線方式 =====
input:
  default: ssh, telnet    # 依序嘗試;沒有只能用 Telnet 的設備時改為 ssh
  debug: false            # true:記錄每台設備的連線過程,僅供除錯
  ssh:
    secure: false         # true:嚴格驗證 SSH 主機金鑰(設備須已在 known_hosts 內);false:不驗證
  ftp:
    passive: true
  utf8_encoded: true      # 設備輸出視為 UTF-8

# ===== 儲存:Git =====
# 每次變更產生一筆 commit,LibreNMS 據此顯示版本與差異;沒有保留份數上限。
# 使用 group 時預設每個群組一個 repo;加 single_repo: true 可合併為一個。
output:
  default: git
  git:
    user: "your name"                  # commit 作者名稱
    email: [email protected]    # commit 作者信箱
    repo: "/home/oxidized/.config/oxidized/oxidized.git"

# ===== 設備來源:LibreNMS =====
# 設備清單取自 LibreNMS API;帳密不由此帶入,由上方 models/groups 決定(採用補充說明的方式一時例外)。
# LibreNMS 使用自簽憑證時,可在 http 底下加 secure: false 關閉憑證驗證(有被中間人攔截的風險)。
source:
  default: http
  debug: false
  http:
    url: https://librenms/api/v0/oxidized    # 改為 LibreNMS 的位址
    map:
      name: hostname
      model: os          # 以 LibreNMS 的 os 作為 model
      group: group       # 使用 groups 帳密時必須保留
    headers:
      X-Auth-Token: "<LibreNMS API Token>"

# ===== model 名稱對應(來源給的名稱 → Oxidized model) =====
# LibreNMS 會自動把多數 os 對應到 Oxidized 的 model,這裡只需補它沒涵蓋的(例如 unifi);對不上時日誌會出現 ModelNotFound。
# juniper、cisco 兩筆是 Oxidized 的預設值;LibreNMS 的 os 實際為 junos、ios、routeros、dlink,與 model 同名,不需對應。
# fortigate 不可對應到 fortios(0.36 起各為獨立 model)。
model_map:
  juniper: junos
  cisco: ios
  mikrotik: routeros
  unifi: unifiap
  dlink: dlink
  • 設定檔內含明文帳密與 API Token,把權限限縮為只有擁有者可讀寫
chmod 600 /home/oxidized/.config/oxidized/config
  • 在 LibreNMS 主機啟用 Oxidized 整合,也可以從 Web 介面的 Global Settings → External Settings → Oxidized Settings 設定
lnms config:set oxidized.enabled true
lnms config:set oxidized.url http://127.0.0.1:8888      # Oxidized Web 介面的位址,不同機時改為 Oxidized 主機的位址
lnms config:set oxidized.features.versioning true      # 顯示歷史版本與差異(需 Git 輸出)
lnms config:set oxidized.group_support false           # 要使用 groups 帳密須改為 true
lnms config:set oxidized.reload_nodes true             # 新增設備後通知 Oxidized 重新載入清單
lnms config:set oxidized.ignore_os '["ping", "linux", "generic"]'   # 不交給 Oxidized 備份的 os
  • 舊版寫法(備用):修改 LibreNMS 設定檔 config.php。同一個設定不要同時用上面的指令(或 Web 介面)與 config.php 設定
# Oxidized 整合
$config['oxidized']['enabled']                  = true;
$config['oxidized']['url']                      = 'http://oxidized 位置:8888';   # Oxidized Web 介面的位址
$config['oxidized']['features']['versioning']   = true;      # 顯示歷史版本與差異(需 Git 輸出)
$config['oxidized']['group_support']            = false;     # 要使用 groups 帳密須改為 true
$config['oxidized']['default_group']            = false;     # 預設群組;false=不設(官方預設值)
$config['oxidized']['reload_nodes']             = true;      # 新增設備後通知 Oxidized 重新載入清單

# 群組對應範例:os 為 pfsense 的設備歸入 pfsense 群組
#$config['oxidized']['maps']['group']['os'][] = array('match' => 'pfsense', 'group' => 'pfsense');
# 不交給 Oxidized 備份的 os
$config['oxidized']['ignore_os'] = array('ping', 'linux', 'generic');
  • 確認 Oxidized 主機能正常連線到 LibreNMS API(網址與 Token 請改為實際值)。回傳內容須為非空的設備清單,否則 Oxidized 啟動後會直接結束
curl -H 'X-Auth-Token: YOURAPITOKENHERE' https://librenms/api/v0/oxidized
設定 oxidized 服務檔

  • 離開 oxidized 使用者,回到 root
exit
  • 建立服務檔 vim /etc/systemd/system/oxidized.service
# Put this file in /etc/systemd/system.
#
# To set OXIDIZED_HOME instead of the default,
# ~oxidized/.config/oxidized, uncomment (and modify as required) the
# "Environment" variable below so systemd sets the correct
# environment.

[Unit]
Description=Oxidized - Network Device Configuration Backup Tool
After=network-online.target multi-user.target
Wants=network-online.target

[Service]
ExecStart=/usr/local/bin/oxidized
User=oxidized
KillSignal=SIGKILL
#Environment="OXIDIZED_HOME=/etc/oxidized"
Restart=on-failure
RestartSec=300s

[Install]
WantedBy=multi-user.target
  • 啟動服務並設為開機自動啟動
systemctl daemon-reload && systemctl enable --now oxidized.service
  • 確認服務執行狀態
systemctl status oxidized.service
  • 再到 LibreNMS 上確認是否可以看到該網通設備的設定檔

librenms-Oxidized-01.png

  • 也可以到 Oxidized 頁面查看 http://oxidized 位置:8888(listen 為 127.0.0.1 時只能從 Oxidized 主機本機連線)

librenms-Oxidized-02.png

補充說明


  BUBU 公司裡各設備的帳密不盡相同,因此需要在設定檔另外處理,以下提供兩種方式。

  • 方式一:由 LibreNMS 帶入帳密,Oxidized 與 LibreNMS 兩邊都要設定。由此帶入的帳密優先序最高,會蓋過 models、groups 的設定;帳密也會以明文存在 LibreNMS 的設定裡並經由 API 回傳,建議優先採用方式二。先在 Oxidized 設定檔的 source 區段加入 username、password 對應
source:
  default: http
  debug: false
  http:
    url: https://librenms/api/v0/oxidized
    map:
      name: hostname
      model: os
      group: group
      # 新增加以下兩個參數
      username: username
      password: password
    headers:
      # 由 LibreNMS 的 API 設定頁面產生的金鑰
      X-Auth-Token: "<LibreNMS API Token>"
  • 再到 LibreNMS 設定各 os 對應的群組與帳密。match 比對的是 LibreNMS 的 os 值(FortiGate 防火牆的 os 是 fortigate,請依實際設備填寫);群組對應要 group_support 為 true 才會生效
lnms config:set oxidized.group_support true
lnms config:set oxidized.maps.group.os.+ '{"match": "fortios", "value": "fortios"}'
lnms config:set oxidized.maps.username.os.+ '{"match": "fortios", "value": "admin"}'
lnms config:set oxidized.maps.password.os.+ '{"match": "fortios", "value": "PW_A"}'
  • 舊版寫法(備用)
$config['oxidized']['maps']['group']['os'][] = array('match' => 'fortios', 'group' => 'fortios');
$config['oxidized']['maps']['username']['os'][] = array('match' => 'fortios', 'username' => 'admin');
$config['oxidized']['maps']['password']['os'][] = array('match' => 'fortios', 'password' => 'PW_A');
  • 查到的範例(舊版寫法)
$config['oxidized']['ignore_os'] = array('linux','windows');
$config['oxidized']['ignore_types'] = array('server','power');
$config['oxidized']['maps']['group']['os'][] = array('match' => 'ios', 'group' => 'Cisco');
$config['oxidized']['maps']['group']['os'][] = array('match' => 'iosxe', 'group' => 'Cisco');
$config['oxidized']['maps']['group']['os'][] = array('match' => 'nxos', 'group' => 'Nexus');
$config['oxidized']['maps']['group']['os'][] = array('match' => 'procurve', 'group' => 'Aruba');
$config['oxidized']['maps']['group']['os'][] = array('match' => 'arista_eos', 'group' => 'Arista');

$config['oxidized']['maps']['username']['os'][] = array('match' => 'procurve', 'username' => 'admin');
$config['oxidized']['maps']['password']['os'][] = array('match' => 'procurve', 'password' => 'PW_A');
$config['oxidized']['maps']['username']['os'][] = array('match' => 'arista_eos', 'username' => 'admin');
$config['oxidized']['maps']['password']['os'][] = array('match' => 'arista_eos', 'password' => 'PW_B');
$config['oxidized']['maps']['username']['os'][] = array('regex' => '/^(ios|nxos|iosxe)$/', 'username' => 'oxidized');
$config['oxidized']['maps']['password']['os'][] = array('regex' => '/^(ios|nxos|iosxe)$/', 'password' => 'PW_Z');
  • 方式二:直接在 Oxidized 設定檔 vim /home/oxidized/.config/oxidized/config 的 models: 區段依機型設定帳密。設定檔若已有 models: 區段(如上方範本),請在該區段內增修,不要在檔尾再加一個 models:,重複的鍵會互相覆蓋
models:
  fortios:
    username: admin
    password: PW_A
參考相關網頁


補充說明-更新版本


  BUBU 在處理專案時發現客戶的 Oxidized 版本是 0.30,版本較舊,和新版 LibreNMS 搭配時容易報錯,因此協助客戶更新到官方目前最新的 0.37 版。以下記錄整個更新流程,更新之前請先備份資料,並替整台主機做快照。

  • 確認目前的 Oxidized 版本及 Ruby 版本
oxidized --version
ruby -v

Ruby 低於 3.0 時不能直接升級 Oxidized,要先升級 Ruby(通常等於升級作業系統版本)

  • 停止服務
systemctl stop oxidized
  • 備份資料(設定檔、Git 儲存庫與服務檔;Git 儲存庫若放在其他路徑,請一併加入)
tar czf /root/oxidized_backup_$(date +%Y%m%d_%H%M%S).tgz \
    /home/oxidized/.config/oxidized /etc/systemd/system/oxidized.service
  • 記錄升級前的版本
gem list | grep -i oxidized > /root/oxidized_gem_versions_before.txt
  • 升級(兩個套件要一起升,oxidized-web 0.18.1 需要 oxidized 0.34.1 以上)
gem install oxidized oxidized-web
  • 調整 Oxidized 設定檔以符合 0.37 版(啟動服務之前完成,寫法可對照上方範本)

    • rest: 改為 extensions 底下的 oxidized-web 區段(0.33 起),並移除舊的 rest:,兩者並存時新寫法會被忽略
    • use_syslog、log 改為 logger 區段(0.34 起)
    • models 新增 fortigate 區段放 FortiGate 防火牆的帳密,並移除 model_map 裡的 fortigate: fortios(0.36 起兩者為不同的 model)
    • timos model 已於 0.35 移除,改用 sros
  • 啟動服務

systemctl start oxidized
  • 確認服務日誌沒有錯誤訊息
journalctl -u oxidized --since '10 minutes ago'

疑難排解


  備份失敗,或 LibreNMS 上看不到設定檔時,可以依下列順序查看 Oxidized 的記錄,先確認問題出在哪一段。

  • 服務狀態與服務日誌:Oxidized 的日誌輸出到 systemd journal(設定檔 logger 區段的 stderr)
systemctl status oxidized.service
journalctl -u oxidized -f                        # 即時追蹤
journalctl -u oxidized --since '1 hour ago'      # 最近一小時
journalctl -u oxidized --since today | grep -i '設備名稱或 IP'   # 只看特定設備
  • 想把日誌另外寫成檔案,可在設定檔的 logger 區段加一個 file 輸出,修改後重啟 oxidized
logger:
  appenders:
    - type: stderr
    - type: file
      file: /home/oxidized/.config/oxidized/oxidized.log
  • 從 Oxidized 的 REST API 查看設備狀態(在 Oxidized 主機上執行)。last 底下的 status 為 success 表示上次抓取成功;no_connection 表示所有連線方式都失敗(連不上、登入失敗或逾時)。查不到該設備代表它沒有被載入,請回頭看日誌有沒有 ModelNotFound
curl -s http://127.0.0.1:8888/nodes.json                         # 全部設備與上次抓取結果
curl -s 'http://127.0.0.1:8888/node/show/設備名稱?format=json'     # 單一設備
curl -s http://127.0.0.1:8888/node/next/設備名稱                  # 把該設備排到下一個抓取
curl -s http://127.0.0.1:8888/reload.json                        # 重新向 LibreNMS 載入設備清單
  • 想看某台設備的連線過程,把設定檔 input 區段的 debug 改為 true 後重啟 oxidized,每次連線會在 logs 目錄留下記錄檔,檔名為「設備 IP-連線方式-時間」。記錄檔含設備輸出的完整內容,查完記得改回 false 並清掉檔案
ls -lt /home/oxidized/.config/oxidized/logs/ | head
  • 程式異常結束時的記錄,放在設定檔 crash 區段指定的目錄
ls -lt /home/oxidized/.config/oxidized/crashes/
  • LibreNMS 端的記錄(路徑依 LibreNMS 的安裝位置而定)
tail -f /opt/librenms/logs/librenms.log
  • 錯誤訊息: ModelNotFound(例如 fortigate not found for node) 解決方式: LibreNMS 給的 os 名稱在 Oxidized 找不到同名的 model,該設備不會被載入,LibreNMS 的 Config 分頁會顯示「The request failed. Try again.」。確認 Oxidized 的版本是否有該 model(FortiGate 需 0.36 以上),或在 model_map 補上對應。
  • 錯誤訊息: Timeout::Error、execution expired 解決方式: 已經登入設備,但指令的輸出沒有在 timeout 秒數內回來,常見於設定內容較大的設備。調高設定檔的 timeout,BUBU 這次由 20 調到 120 之後就恢復正常。
  • 錯誤訊息: Net::OpenTimeout、timed out while opening a connection to the host 解決方式: 連不上設備的連接埠,確認設備有開 SSH、來源 IP 有被允許。input 設為 ssh, telnet 時,SSH 失敗後改試 Telnet 也會出現這一行,要往前找 SSH 失敗的原因。
  • 錯誤訊息: source returns no usable nodes 解決方式: 向 LibreNMS 取回的設備清單是空的,用安裝過程中的 curl 指令確認 API 網址與 Token 是否正確。
  • 錯誤訊息: oxidized-web not found 解決方式: 設定檔啟用了 extensions 的 oxidized-web 卻沒有安裝該套件,執行 gem install oxidized-web。

備註


  BUBU 最近在公司發現 LibreNMS 上無法正常查看網通設備的設定檔,找了一些相關資訊卻一直沒有找到正確答案。後來向社群裡的節省哥請教,查看 Log 檔之後才知道原因出在 PHP 的記憶體上限預設只有 128M,調整到 512M 就可以正常顯示了。

  • 調整 php.ini 檔 vim /etc/php/8.0/fpm/php.ini(路徑中的版本號依實際安裝的 PHP 版本調整)
; 調整前
; 記憶體用量上限
memory_limit = 128M

; 調整後
; 記憶體用量上限
memory_limit = 512M
  • 重新啟動 PHP-FPM 讓設定生效(服務名稱中的版本號同上)
systemctl restart php8.0-fpm



參考相關網頁