NetStacksNetStacks

Config Snapshots

Enterprise

Capture, store, and compare device configurations with versioned config records, SHA-256 change detection, and fleet-wide snapshot collections.

Overview

Controller feature

Config snapshots are part of the NetStacks Enterprise controller. Every endpoint on this page lives under the controller API and requires an operator JWT.

Config snapshots capture the configuration of network devices at a specific point in time. Each capture is stored as a versioned device config record in PostgreSQL with a SHA-256 content hash for change detection, plus metadata such as the source format, transport, and the credential used.

Snapshots serve several purposes:

  • Configuration backup — Preserve known-good configs for disaster recovery and rollback reference.
  • Change detection — Compare two stored versions to see exactly what changed between captures.
  • Compliance auditing — Prove device configs matched a standard at a given date and version.
  • Fleet-wide captures — A snapshot collection captures configs from many devices in one operation, ideal for pre/post maintenance-window verification.
Snapshot vs. device config version

A config snapshot is a collection-level record that groups a single capture operation across many devices and tracks total / successful / failed device counts. Each device that is captured produces an individual device config version (an auto-incrementing version per device) linked back to the snapshot by snapshot_id. Think of a snapshot as "capture configs from these devices now" and a device config version as the resulting config text from one device.

How It Works

Config collection process

When NetStacks captures a device configuration over SSH/CLI, it follows this process:

  1. Resolves the device's credential, decrypts it from the encrypted vault, and connects to device.host:device.port.
  2. Sends the platform-specific paging-disable command (for example terminal length 0 on Cisco IOS, or terminal pager 0 on ASA) followed by show running-config.
  3. Captures the command output and stores the cleaned configuration text.
  4. Computes a SHA-256 hash of the config text for change detection.
  5. Writes a new device config version with metadata: config_format (e.g. cli), config_hash, an auto-incremented version, source, pulled_via (e.g. ssh), created_at, and snapshot_id.

Hash-based change detection

Every device config version stores a config_hash (SHA-256) of the configuration text. To detect change, NetStacks compares hashes between two versions. Identical hashes mean the config is unchanged regardless of when each version was captured; differing hashes mean the config changed. The version-diff endpoint uses this directly — if the two versions' hashes match it returns identical: true and skips diff generation.

Snapshot collections

A snapshot collection targets multiple devices at once. When you create a snapshot you supply:

  • name — A descriptive label (e.g. "Pre-maintenance 2026-03-10").
  • device_filter — Which devices to include. The collect handler reads device_filter.site and device_filter.device_type only.
  • snapshot_type — One of manual, scheduled, pre_change, or post_change (defaults to manual).
device_filter supports site and device_type only

For snapshot collection, the filter honours site and device_type only. Tag-based filtering is not applied when resolving snapshot targets — passing a tag key has no effect. To capture an arbitrary set of devices regardless of site/type, use the bulk config-pull endpoint instead (see Code Examples).

Collection runs concurrently behind a semaphore (default 5 simultaneous devices). Each device is collected independently — if one device fails, the others continue. When the run finishes the snapshot records successful and failed counts and a final status.

Snapshot lifecycle & status

A snapshot moves through these statuses:

  • pending — created, collection not yet started.
  • in_progress — collection running (set when you call /collect).
  • complete — all targeted devices succeeded (failed count is 0).
  • partial — some devices succeeded and some failed.
  • failed — every targeted device failed.

Credential resolution

When collecting configs, NetStacks resolves the credential for each device in priority order:

  1. Per-request credential override (credential_id in the collect call).
  2. Device default credential (default_credential_id on the device record).
  3. Global collection settings default credential.

If no credential is found at any level, that device is recorded as a failed config with the message No credential configured; the rest of the collection continues.

Step-by-Step Guide

Capture a single device config

  1. Open the device in the controller admin UI and trigger a config pull, or call the pull API directly (see Code Examples).
  2. NetStacks connects, runs show running-config, and stores a new device config version with a fresh SHA-256 hash.
  3. The new version appears in the device's config history with its timestamp, version number, format, and hash.

Create a snapshot collection across multiple devices

  1. Create a snapshot record with a descriptive name.
  2. Set device_filter with site and/or device_type to select targets (for example, all devices at site dc-east of type cisco_ios).
  3. Trigger collect. NetStacks resolves matching devices, sets the snapshot to in_progress, and captures each device concurrently.
  4. Poll the snapshot until status becomes complete, partial, or failed. The record shows total, successful, and failed device counts plus an errors array.
Pre/post maintenance workflow

Create a snapshot before a maintenance window (type pre_change) and another afterward (type post_change). Then compare the two device config versions captured for any device with the version-diff endpoint (see Config Diff & History) to verify only intended changes were made.

Schedule automatic snapshots

  1. Create a scheduled task that triggers a config-backup snapshot on a cron schedule (for example daily at 02:00).
  2. Define the device targets via the snapshot's device_filter.
  3. The task creates a snapshot with snapshot_type: "scheduled" and triggers collection at the scheduled time.

See Scheduled Tasks for the cron and task model.

Browse config history

  1. List a device's config versions via GET /api/config/devices/{id}/configs.
  2. Fetch full text for the latest version (/configs/latest) or a specific version (/configs/{version}).
  3. Compare any two versions with the version-diff endpoint to see a unified diff with addition and deletion counts.

Code Examples

Create a snapshot

create-snapshot.shbash
curl -X POST http://localhost:3000/api/devices/snapshots \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Pre-maintenance 2026-03-10",
    "description": "Capture all DC East cisco_ios configs before OSPF migration",
    "snapshot_type": "pre_change",
    "device_filter": {
      "site": "dc-east",
      "device_type": "cisco_ios"
    }
  }'

# 201 Created -> { "snapshot": { "id": "...", "status": "pending", ... } }

Trigger config collection

collect-snapshot.shbash
# Start collection for a snapshot (runs in the background).
# credential_id and timeout_seconds are optional.
curl -X POST http://localhost:3000/api/devices/snapshots/${SNAPSHOT_ID}/collect \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "credential_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "timeout_seconds": 60
  }'

# 202 Accepted:
# {
#   "message": "Config collection started",
#   "snapshot_id": "...",
#   "devices_queued": 24
# }

Poll snapshot status

snapshot-status.shbash
curl -s http://localhost:3000/api/devices/snapshots/${SNAPSHOT_ID} \
  -H "Authorization: Bearer ${TOKEN}" | jq '.snapshot | {
    name, status, total_devices, successful_devices, failed_devices,
    started_at, completed_at
  }'

# {
#   "name": "Pre-maintenance 2026-03-10",
#   "status": "complete",
#   "total_devices": 24,
#   "successful_devices": 24,
#   "failed_devices": 0,
#   "started_at": "2026-03-10T14:32:01Z",
#   "completed_at": "2026-03-10T14:33:18Z"
# }

List the device configs captured by a snapshot

snapshot-backups.shbash
curl -s http://localhost:3000/api/devices/snapshots/${SNAPSHOT_ID}/backups \
  -H "Authorization: Bearer ${TOKEN}" | jq '.backups[] | {
    device_name, version, backup_method, status, config_hash, backed_up_at
  }'

# Note: this list endpoint returns size_bytes and line_count as 0
# (they are not stored in the summary). Fetch full text from the
# config endpoints below to compute size or line count.

Capture a single device on demand (config pull)

pull-config.shbash
# Pull a fresh config for one device, storing a new version.
curl -X POST http://localhost:3000/api/config/devices/${DEVICE_ID}/pull \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json"

# Bulk pull for an explicit list of devices (creates a snapshot
# record so the run appears in the Job Monitor):
curl -X POST http://localhost:3000/api/config/devices/bulk-pull \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{
    "device_ids": [
      "11111111-1111-1111-1111-111111111111",
      "22222222-2222-2222-2222-222222222222"
    ]
  }'

# Bulk-pull response:
# { "succeeded": 2, "failed": 0, "errors": [] }

Browse a device's config versions

device-config-history.shbash
# List version history (summaries, no config text)
curl -s "http://localhost:3000/api/config/devices/${DEVICE_ID}/configs?limit=10" \
  -H "Authorization: Bearer ${TOKEN}" | jq '.configs[] | {
    version, config_format, config_hash, pulled_via, status, created_at
  }'

# Fetch the latest version with full text
curl -s http://localhost:3000/api/config/devices/${DEVICE_ID}/configs/latest \
  -H "Authorization: Bearer ${TOKEN}" | jq '{ version, config_format, config_hash }'

# Fetch a specific version with full text
curl -s http://localhost:3000/api/config/devices/${DEVICE_ID}/configs/7 \
  -H "Authorization: Bearer ${TOKEN}" | jq '.config_text' -r

Compare two stored versions (unified diff)

diff-versions.shbash
curl -s "http://localhost:3000/api/config/devices/${DEVICE_ID}/diff-versions?old_version=6&new_version=7" \
  -H "Authorization: Bearer ${TOKEN}" | jq '{
    old_version, new_version, identical, additions, deletions
  }'

# {
#   "old_version": 6,
#   "new_version": 7,
#   "identical": false,
#   "additions": 3,
#   "deletions": 1
# }
# The full unified diff text is in the .diff field.

Example captured config (Cisco IOS)

core-rtr-01-running-config.txttext
! Last configuration change at 14:32:07 UTC Tue Mar 10 2026
version 17.9
service timestamps debug datetime msec
service timestamps log datetime msec
service password-encryption
!
hostname core-rtr-01
!
aaa new-model
aaa authentication login default local
aaa authorization exec default local
!
ip domain name dc-east.company.com
ip name-server 10.0.0.53
ip name-server 10.0.0.54
!
interface GigabitEthernet0/0/0
 description Uplink to dist-sw-01
 ip address 10.1.0.1 255.255.255.252
 ip ospf 1 area 0
 no shutdown
!
interface GigabitEthernet0/0/1
 description Uplink to dist-sw-02
 ip address 10.1.0.5 255.255.255.252
 ip ospf 1 area 0
 no shutdown
!
router ospf 1
 router-id 10.255.0.1
 network 10.1.0.0 0.0.0.3 area 0
 network 10.1.0.4 0.0.0.3 area 0
 passive-interface default
 no passive-interface GigabitEthernet0/0/0
 no passive-interface GigabitEthernet0/0/1
!
ip access-list extended MGMT-ACCESS
 permit tcp 10.0.0.0 0.0.0.255 any eq 22
 permit tcp 10.0.0.0 0.0.0.255 any eq 443
 deny   ip any any log
!
line con 0
 logging synchronous
line vty 0 4
 access-class MGMT-ACCESS in
 transport input ssh
!
end

Questions & Answers

Q: How do I take a config snapshot?
A: For one device, pull its config with POST /api/config/devices/{id}/pull, which stores a new version. For many devices, create a snapshot with POST /api/devices/snapshots, set a device_filter (site and/or device_type), then trigger POST /api/devices/snapshots/{id}/collect. To capture an explicit list of devices in one job, use POST /api/config/devices/bulk-pull.
Q: How does change detection work?
A: Each device config version stores a SHA-256 config_hash of its text. To see whether a config changed between two versions, call GET /api/config/devices/{id}/diff-versions?old_version=N&new_version=M. If the hashes match it returns identical: true; otherwise it returns a unified diff with additions and deletions counts.
Q: Does the snapshot device_filter support tags?
A: No. The snapshot collect handler reads only device_filter.site and device_filter.device_type. A tag key is ignored. To capture an arbitrary device set, list the device IDs and call POST /api/config/devices/bulk-pull.
Q: What happens if a device is unreachable during collection?
A: Collection continues for every other device. The unreachable device is recorded as a failed config with an error message, and the snapshot's errors array and failed_devices count are updated. If at least one device also succeeded, the final status is partial; if every device failed it is failed.
Q: How long are config versions retained?
A: Device config versions are retained in PostgreSQL until you delete them or delete the parent snapshot. There is no built-in automatic purge. For large fleets, monitor PostgreSQL disk usage and apply your own retention policy to meet compliance requirements.
Q: Can I export a stored config?
A: Yes. Fetch the latest version via GET /api/config/devices/{id}/configs/latest or a specific version via GET /api/config/devices/{id}/configs/{version} and read config_text. Script these calls to export configs into a filesystem or version-control repository.

Troubleshooting

Collection fails with SSH timeout

The default per-device collection timeout is 30 seconds. Large configs (for example firewalls with thousands of ACL rules) can exceed it. Increase it with the timeout_seconds field in the collect call, or raise the global collection.timeout_seconds setting.

increase-timeout.shbash
curl -X POST http://localhost:3000/api/devices/snapshots/${SNAPSHOT_ID}/collect \
  -H "Authorization: Bearer ${TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"timeout_seconds": 120}'

"No credential configured" for a device

No credential was found at any level for that device: no credential_id in the collect request, no default_credential_id on the device, and no global collection default. Assign a default credential to the device, set a global default, or pass credential_id in the collect call.

Captured config looks malformed or wrong

If output looks malformed, the device type / platform is probably wrong, so the collector sends the wrong paging and show running-config commands. Verify the device's type matches the real platform (see Device Types) and capture again.

Snapshot reports "No devices match the filter"

The collect call returns 400 Bad Request when the device_filter resolves to zero devices. Confirm the site and device_type values exactly match existing devices — and remember that tag filters are ignored for snapshots, so a tag-only filter resolves to every device, not a subset.

Snapshot stuck in in_progress

Collection runs in the background. A snapshot stays in_progress until every queued device finishes. With slow or unreachable devices, allow up to timeout_seconds per device (and retries) before the run completes and the status moves to complete, partial, or failed.

Config snapshots integrate with comparison, automation, and device management features:

  • Config Diff & History — Compare two stored config versions with the version-diff endpoint
  • Bulk Operations — Bulk config pull captures an explicit device list in one job
  • Adding Devices — Devices must exist in the inventory before configs can be captured
  • Scheduled Tasks — Automate periodic config captures with cron schedules
  • Device Types — Platform drivers determine the paging and config-capture commands