Home

Awesome

FreeZTP FreeZTP

A Zero-Touch Provisioning system built for Cisco Catalyst switches.


VERSION

The version of FreeZTP documented here is: v1.5.1


TABLE OF CONTENTS

  1. What is FreeZTP
  2. Requirements
  3. Installation
  4. Getting Started
  5. Terminology
  6. ZTP Process
  7. Command Interface
  8. DHCP Functionality
  9. Advanced Usage
  10. Upgrading
  11. External Keystores
  12. Integrations
  13. Usage Tips
  14. Versions
  15. Contributing

WHAT IS FREEZTP

FreeZTP is a dynamic TFTP server built to automatically configure Cisco Catalyst switches upon first boot (Zero-Touch Provisioning). FreeZTP does this using the 'AutoInstall' feature built into Cisco IOS and automatically enabled by default. FreeZTP configures switches with individual, "templatized" configurations based upon the unique ID of the switch (usually the serial number).


REQUIREMENTS

OS: Tested on CentOS/RHEL 7 and 8 (Recommended), Ubuntu 16 and 18, and Raspbian

Interpreter: Python 2.7.5+


INSTALLATION

<a href="https://www.youtube.com/watch?v=x4wZvtHiyvE"> <img src="https://packetpushers.net/wp-content/uploads/2018/04/image.png" alt="FreeZTP Installation Video" align="right" width="200" height="150"> </a>

The installation of FreeZTP is quick and easy using the built-in installer. Click the picture on the right to watch a quick 7 minute video on the installation, initial configuration, and first switch deployment with FreeZTP.

Make sure you are logged in as root or are able to sudo su to install and operate FreeZTP.

  1. Install OS with appropriate IP and OS settings and update to latest patches (recommended). Check out the below links for easy Post-Install processes for OS's supported by FreeZTP.
    • CentOS 7: CentOS Minimal Server - Post-Install Setup
      • Make sure to install Git for a CentOS install
        • sudo yum install git -y
      • Install Python2 PIP
        • sudo yum install python2-pip
    • CentOS 8:
      • Make sure to install Git for a CentOS install
        • sudo yum install git -y
      • Install Python2 PIP
        • sudo yum install python2-pip
      • Create python Symlink
        • sudo ln -s /usr/bin/python2.7 /usr/bin/python
      • Create pip Symlink
        • sudo ln -s /usr/bin/pip2 /usr/bin/pip
    • Ubuntu 16: Ubuntu Minimal Server - Post-Install Setup
      • Make sure to install python-pip and git for Ubuntu
        • sudo apt install -y python-pip
        • sudo apt-get install -y git
    • Ubuntu 18 or 20:
      • Install Git and Curl Clients
        • apt install git
        • apt install curl
      • Install Python2 and create a symlink for it
        • sudo apt install python2
          • Use sudo apt-get install python-pip on Ubuntu 18
        • sudo ln -s /usr/bin/python2.7 /usr/bin/python
      • Install PIP for Python2
        • curl https://bootstrap.pypa.io/pip/2.7/get-pip.py --output get-pip.py
        • sudo python get-pip.py
    • Raspbian: Raspbian Minimal Server - Post-Install Setup
      • Make sure to install python-pip and git for Raspbian
        • sudo apt install -y python-pip
        • sudo apt-get install -y git
  2. Download the FreeZTP repository using Git
    • git clone https://github.com/packetsar/freeztp.git
  3. Change to the directory where the FreeZTP main code file (ztp.py) is stored: cd freeztp
  4. Run the FreeZTP program in install mode to perform the installation: sudo python ztp.py install. Make sure the machine has internet access as this process will download and install several packages for this to happen.
    • FreeZTP will perform the install of the packages and services for full operation.
    • The installation will also install a CLI completion (helper) script. You will need to logout and log back into your SSH session to activate this completion script.

GETTING STARTED

The ZTP server comes with an [almost] fully functional default configuration, ready to serve out a basic config to switches. You can view the configuration (after installation) by issuing the command ztp show config.

The only part missing on the configuration is an IP lease range for DHCPD. You will need to add this IP range in order to enable the DHCPD service to hand out IP addresses (see below instructions). After this range is added and the DHCPD service restarted, you can connect the ZTP server directly to the switch (on any of its ports), power on the switch, and watch it go!

  1. Configure DHCPD using the ZTP commands
    • During installation, ZTP will install the DHCPD service, detect the network interfaces in Linux, and configure DHCPD scopes for each of the interfaces. The created DHCPD scopes will be inactive to serve DHCPD as they will have no addresses available to lease.

    • If you want to use the automatically generated DHCPD scope (the new switches will be on the same VLAN as one of FreeZTPs interfaces), you just need to specify a first and last address for the lease range. After configured, you will need to commit the ZTP DHCPD configuration. Committing the DHCPD configuration (ztp request dhcpd-commit) automatically compiles/saves the DHCP configuration and restarts the DHCPD service. Below is example of how to do this.

        ztp set dhcpd INTERFACE-ETH0 first-address 192.168.1.100
        ztp set dhcpd INTERFACE-ETH0 last-address 192.168.1.200
        #
        ztp request dhcpd-commit
      
    • If the switches will not be on the same VLAN, then create a new scope (you can use the existing scope configuration commands as a reference).

    • There are other basic DHCPD options included in the scope settings like dns-servers, domain-name, and gateway which can be set as needed. Make sure to do a ztp request dhcpd-commit after any changes to DHCPD configurations.

  2. Start configuring switches!
    • FreeZTP comes with a default configuration which is ready to configure switches. A default-keystore is configured which will hand out a basic (non-individualized) config to a newly booted switch.
    • There are a few example templates, keystores, associations, and an ID array already in the default config which you can reference. Feel free to modify them to do what you want, or blow them away and customize everything.
    • Once you have the ZTP configuration set, restart the ZTP service (ztp service restart) for your changes to take effect.
    • Boot up a switch (with a blank configuration) and watch it grab a DHCP address and contact ZTP for image upgrades and configurations.
      • Be patient when the switch boots up as it sometimes takes a few (1-3) minutes after the "press RETURN to get started" message to begin the AutoInstall process.
      • You can watch for action in ZTP by checking the DHCP leases (ztp show dhcpd leases) and watching the active logs (ztp show log tail).
      • You can also see the history of devices which have gone through the provisioning process using ztp show provisioning.

TERMINOLOGY

Due to the unique nature of how FreeZTP works and performs discovery of switches, there are a few terms you will need to know to understand the application.


ZTP PROCESS

FreeZTP relies on the 'AutoInstall' function of a Cisco Catalyst switch to configure the switch upon first boot. The process followed to configure the switch is outlined below.

The new switch should have one of its ports connected to a network (likely an upstream switch) which has the FreeZTP server accessible. The FreeZTP server can be on the same VLAN as the new switch (so it can serve up DHCP addresses directly) or on a different VLAN which has a gateway and an IP helper pointed at the FreeZTP server so it can serve up DHCP.

1. STEP 1 - POWER ON: Initial boot up and DHCP leasing

2. STEP 2 - IOS Upgrade: The imagediscoveryfile is used to discover the IOS image file

3. STEP 3 - INITIAL-CONFIG: An initial config is generated, sent, and loaded for switch (target) discovery

4. STEP 4 - SNMP DISCOVERY: The ZTP server discovers the switch's "Real ID" (ie: serial number) using SNMP

5. STEP 5 - FINAL CONFIG REQUEST: The switch requests the final configuration file and the ZTP server generates it based on the ZTP configuration

Ladder Diagram

SWITCH -----> Step 2.1: File "freeztp_ios_upgrade" requested -------------------------------> ZTP Server

SWITCH <----- Step 2.1.1 or 2.2: Auto-generated "freeztp_ios_upgrade" file sent to switch <-- ZTP Server

SWITCH -----> Step 2.3: .bin or .tar file requested ----------------------------------------> ZTP Server

SWITCH <----- Step 2.4: .bin or .tar file sent to switch if it exists <---------------------- ZTP Server

SWITCH -----> Step 3.1: File "network-confg" requested -------------------------------------> ZTP Server

SWITCH <----- Step 3.3: Auto-generated initial config passed to switch <--------------------- ZTP Server

SWITCH <----- Step 4: ZTP server performs discovery of Real ID using SNMP <---------------- ZTP Server

SWITCH -----> Step 5.1: Switch requests new file based on new hostname ---------------------> ZTP Server

SWITCH <----- Step 5.4: ZTP responds to TFTP request with final config <--------------------- ZTP Server


COMMAND INTERFACE

The FreeZTP service runs in the background as a system service. All commands to the FreeZTP service start with ztp at the command line. For example: you can check the status of the background service using the command ztp show status, you can check the current configuration of the ZTP server with the command ztp show config.

The command interface is fully featured with helpers which can be seen either by hitting ENTER with an incomplete command in the prompt, or by using the TAB key to display available options/autocomplete the command (similar to a Cisco IOS command line, but without the use of the question mark).

All commands which change the ZTP configuration use the set or clear arguments. Commands issued with the set argument will overwrite an existing configuration item if that item already exists. The clear argument allows you to remove configuration items.

The initial and final template configurations are entered as multi-line text blocks. To facilitate this, you must specify a delineation character in the set command. As an example, you will issue the command ztp set template MY_TEMPLATE ^ where the carat (^) character is set as the delineation character. After that command is issued, you can paste in the multi-line Cisco IOS template text. Once finished, enter that delineation character (^ in this case) on a line by itself to exit the text block entry mode.

For configuration changes to take effect, you must always restart the ZTP service using the ztp service restart command.

Below is the CLI guide for FreeZTP. You can see this at the command line by entering ztp and hitting ENTER (after installation).

----------------------------------------------------------------------------------------------------------------------------------------------
										 ARGUMENTS                                 |                                  DESCRIPTIONS
----------------------------------------------------------------------------------------------------------------------------------------------
 - run                                                         |  Run the ZTP main program in shell mode begin listening for TFTP requests
----------------------------------------------------------------------------------------------------------------------------------------------
 - install                                                     |  Run the ZTP installer
 - upgrade                                                     |  Run the ZTP upgrade process to update the software
----------------------------------------------------------------------------------------------------------------------------------------------
 - show (config|run) (raw)                                     |  Show the current ZTP configuration
 - show status                                                 |  Show the status of the ZTP background service
 - show version                                                |  Show the current version of ZTP
 - show log (tail) (<num_of_lines>)                            |  Show or tail the log file
 - show downloads (live)                                       |  Show list of TFTP downloads
 - show dhcpd leases (current|all|raw)                         |  Show DHCPD leases
 - show provisioning                                           |  Show list of provisioned devices
----------------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------- SETTINGS YOU PROBABLY SHOULDN'T CHANGE ---------------------------------------------------
 - set suffix <value>                                          |  Set the file name suffix used by target when requesting the final config
 - set initialfilename <value>                                 |  Set the file name used by the target during the initial config request
 - set community <value>                                       |  Set the SNMP community you want to use for target ID identification
 - set snmpoid <name> <value>                                  |  Set named SNMP OIDs to use to pull the target ID during identification
 - set initial-template <end_char>                             |  Set the initial configuration j2 template used for target identification
 - set tftproot <tftp_root_directory>                          |  Set the root directory for TFTP files
 - set imagediscoveryfile <filename>                           |  Set the name of the IOS image discovery file used for IOS upgrades
 - set file-cache-timeout <timeout>                            |  Set the timeout for cacheing of files. '0' disables caching.
--------------------------------------------------------- SETTINGS YOU SHOULD CHANGE ---------------------------------------------------------
 - set template <template_name> <end_char>                     |  Create/Modify a named J2 tempate which is used for the final config push
 - set keystore <id/arrayname> <keyword> <value>               |  Create a keystore entry to be used when merging final configurations
 - set idarray <arrayname> <id_#1> <id_#2> ...                 |  Create an ID array to allow multiple real ids to match one keystore id
 - set association id <id/arrayname> template <template_name>  |  Associate a keystore id or an idarray to a specific named template
 - set default-keystore (none|keystore-id)                     |  Set a last-resort keystore and template for when target identification fails
 - set default-template (none|template_name)                   |  Set a last-resort template for when no keystore/template association is found
 - set imagefile <binary_image_file_name>                      |  Set the image file name to be used for upgrades (must be in tftp root dir)
 - set image-supression <seconds-to-supress>                   |  Set the seconds to supress a second image download preventing double-upgrades
 - set delay-keystore <msec-to-delay>                          |  Set the miliseconds to delay the processing of a keystore lookup
 - set dhcpd <scope-name> [parameters]                         |  Configure DHCP scope(s) to serve IP addresses to ZTP clients
----------------------------------------------------------------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------------------------------------------------
 - clear snmpoid <name>                                        |  Delete an SNMP OID from the configuration
 - clear template <template_name>                              |  Delete a named configuration template
 - clear keystore <id> (all|<keyword>)                         |  Delete an individual key or a whole keystore ID from the configuration
 - clear idarray <arrayname>                                   |  Delete an ID array from the configuration
 - clear association <id/arrayname>                            |  Delete an association from the configuration
 - clear dhcpd <scope-name>                                    |  Delete a DHCP scope
 - clear log                                                   |  Delete the logging info from the logfile
 - clear downloads                                             |  Delete the list of TFTP downloads
 - clear provisioning                                          |  Delete the list of provisioned devices
----------------------------------------------------------------------------------------------------------------------------------------------
 - request merge-test <id>                                     |  Perform a test jinja2 merge of the final template with a keystore ID
 - request initial-merge                                       |  See the result of an auto-merge of the initial-template
 - request default-keystore-test                               |  Check that the default-keystore is fully configured to return a template
 - request snmp-test <ip-address>                              |  Run a SNMP test using the configured community and OID against an IP
 - request dhcp-option-125 (windows|cisco)                     |  Show the DHCP Option 125 Hex value to use on the DHCP server for OS upgrades
 - request dhcpd-commit                                        |  Compile the DHCP config, write to config file, and restart the DHCP service
 - request auto-dhcpd                                          |  Automatically detect local interfaces and build DHCP scopes accordingly
 - request ipc-console                                         |  Connect to the IPC console to run commands (be careful)
----------------------------------------------------------------------------------------------------------------------------------------------
 - service (start|stop|restart|status)                         |  Start, Stop, or Restart the installed ZTP service
----------------------------------------------------------------------------------------------------------------------------------------------
 - version                                                     |  Show the current version of ZTP
----------------------------------------------------------------------------------------------------------------------------------------------

The following is the default configuration seen on the ZTP server after installation when doing a ztp show config.

#######################################################
#
#
#
ztp set suffix -confg
ztp set initialfilename network-confg
ztp set community secretcommunity
ztp set tftproot /etc/ztp/tftproot/
ztp set imagediscoveryfile freeztp_ios_upgrade
ztp set file-cache-timeout 10
ztp set snmpoid WS-C2960_SERIAL_NUMBER 1.3.6.1.2.1.47.1.1.1.1.11.1001
ztp set snmpoid WS-C3850_SERIAL_NUMBER 1.3.6.1.2.1.47.1.1.1.1.11.1000
#
#
ztp set initial-template ^
hostname {{ autohostname }}
!
snmp-server community {{ community }} RO
!
end
^
#
#
#
#######################################################
#
#
#
ztp set dhcpd INTERFACE-ETH0 subnet 192.168.1.0/24
ztp set dhcpd INTERFACE-ETH0 lease-time 3600
ztp set dhcpd INTERFACE-ETH0 imagediscoveryfile-option enable
ztp set dhcpd INTERFACE-ETH0 ztp-tftp-address 192.168.1.11
#
#
#
#######################################################
#
#
#
ztp set template SHORT_TEMPLATE ^
hostname {{ hostname }}
!
interface Vlan1
 ip address {{ vl1_ip_address }} 255.255.255.0
 no shut
!
end
^
#
#
#
#######################################################
#
#
#
ztp set template LONG_TEMPLATE ^
hostname {{ hostname }}
!
interface Vlan1
 ip address {{ vl1_ip_address }} {{ vl1_netmask }}
 no shut
!
!{% for interface in range(1,49) %}
interface GigabitEthernet1/0/{{interface}}
 description User Port (VLAN 1)
 switchport access vlan 1
 switchport mode access
 no shutdown
!{% endfor %}
!
ip domain-name test.com
!
username admin privilege 15 secret password123
!
aaa new-model
!
!
aaa authentication login CONSOLE local
aaa authorization console
aaa authorization exec default local if-authenticated
!
crypto key generate rsa modulus 2048
!
ip ssh version 2
!
line vty 0 15
login authentication default
transport input ssh
line console 0
login authentication CONSOLE
end
^
#
#
#
#######################################################
#
#
#
ztp set keystore DEFAULT_VALUES vl1_ip_address dhcp
ztp set keystore DEFAULT_VALUES hostname UNKNOWN_HOST
#
ztp set keystore SERIAL100 vl1_ip_address 10.0.0.201
ztp set keystore SERIAL100 hostname SOMEDEVICE
#
ztp set keystore STACK1 vl1_netmask 255.255.255.0
ztp set keystore STACK1 vl1_ip_address 10.0.0.200
ztp set keystore STACK1 hostname CORESWITCH
#
#
#
ztp set idarray STACK1 SERIAL1 SERIAL2 SERIAL3
#
#
#
ztp set association id SERIAL100 template SHORT_TEMPLATE
ztp set association id STACK1 template LONG_TEMPLATE
#
#
#
ztp set default-keystore DEFAULT_VALUES
ztp set default-template LONG_TEMPLATE
ztp set imagefile cat3k_caa-universalk9.SPA.03.06.06.E.152-2.E6.bin
ztp set image-supression 3600
ztp set delay-keystore 1000
#
#
#
#######################################################

DHCP FUNCTIONALITY

FreeZTP installs a DHCP service during the install. This DHCP service is used to serve IP addresses to switches with the appropriate options included.

FreeZTP commands can be used to fully configure the DHCP server. Scopes and options are created using the syntax ztp set dhcpd SCOPENAME <option> <value>.

FreeZTP automatically configures one or more scopes for you upon installation based on the interfaces detected in Linux and what IP addresses are on them. These scopes are needed to instruct the DHCP service to listen on those interfaces for DHCP requests. If you plan to deploy switches on the same VLAN as the FreeZTP server, then you can just add a few options to the auto-generated scope so it is ready to serve IP addresses. The installation instructions show how to do this.

After any configuration changes to the DHCP scopes, you will need to run the command ztp request dhcpd-commit to instruct ZTP to convert the scope settings, write them to the DHCP config file, and restart the DHCP service so the new settings take effect.

You can check on the status of the DHCP server using the command ztp show status

If at any point you have botched up your DHCP scopes and the DHCP service will not start back up, just delete all your scopes (using ztp clear dhcpd commands), and run the ztp request auto-dhcpd command. This will rerun the DHCP discovery and auto scope creation. Then you will just need to do a ztp request dhcpd-commit to commit the changes and restart the service.

It is possible to use an external DHCP server instead of the FreeZTP one, but you will have to manually configure option 150 (for TFTP service from FreeZTP's IP), and option 125 (to specify the IOS image discovery file). Option 150 will need to contain the IP address of the ZTP server. You can get some instructions on how to configure option 125 by running the command ztp request dhcp-option-125 cisco or ztp request dhcp-option-125 windows.


ADVANCED USAGE


UPGRADING

  1. Perform a ztp show config command and save the set commands displayed in a safe place (just in case)
  2. Download the code from the GitHub repo by using git clone https://github.com/PackeTsar/freeztp.git - If the "freeztp" folder already exists, you can use git to update the clone cd freeztp/; git pull
  3. Move to the freeztp folder created by git using the cd freeztp/ command
  4. (Optional) change to a different branch using the command git checkout vX.X.X
  5. Perform an upgrade of FreeZTP using the command python ztp.py upgrade
  6. Type in CONFIRM and hit ENTER to confirm you want to perform the upgrade
  7. Once the installer exits, you should run ztp show config and see your configuration from before
  8. Check that you are running the new version by issuing ztp version
  9. Perform a ztp service restart command to restart FreeZTP to use the new app version
    • NOTE: The FreeZTP service will continue running in the background throughout the upgrade process. It is not until you restart/stop the service that the new version and configuration will take effect.
  10. You may also want to log out of the shell and back in to activate any new auto-complete functions.

EXTERNAL KEYSTORES

FreeZTP v1.1.0 introduced the concept of external keystores. An external keystore is a data source which supplements or replaces the set keystore, set idarray, and set association commands in the native FreeZTP config. The only supported external data source, at this time, is CSV files. The CSV file format has some reserved and required headers which pass special information on to FreeZTP:

- `keystore_id` (Required) - This header/column is required in the CSV file as it defines the Keystore ID for ZTP to use to search for matching Real ID's
- `association` (Optional) - This header/column specifies which template will be used when the Keystore ID is matched. If it is left blank, FreeZTP will use the default template
- `idarray_xxx` (Optional) - Any header beginning with `idarray` will add the values in that column into an IDArray for the `keystore_id` specified in its row.

The easiest way to get started with external keystores is to use an example CSV file. Use the command ztp request keystore-csv-export <file_name> to generate one out of your current Keystore, IDArray, and Association data. You can then configure the external keystore; an example is provided below.

ztp set external-keystore MYCSVFILE type csv
ztp set external-keystore MYCSVFILE file '/home/user/mycsv.csv'

After you are finished, you can test the CSV file by converting it into native FreeZTP commands using the command ztp request external-keystore-test <store_name>. This test will translate the CSV and show you the equivalent native FreeZTP set commands which would configure all this data into FreeZTP. There is no need to enter the shown commands into ZTP, they are there just for your reference and to see if your CSV is formatted properly.

After your CSV is built and the external keystore is configured in the ZTP config, you will need to restart the service to let ZTP grab the CSV data and have it ready for any switches coming online.


INTEGRATIONS

FreeZTP v1.1.0 introduced some integration features with 3rd parties and was expanded in v1.5.0. Supported integrations at this time are Cisco Webex Teams (formerly Cisco Spark) and Microsoft Teams via Microsoft PowerAutomate (credit to @pschapman for this effort!). These integration options will allow FreeZTP to send messages to a chat room for provisioning notifications.

The easiest way to set up the integration is to use the command ztp request integration-setup <svc_name> (the <svc_name> is the name of the configuration object FreeZTP will use once the configuration is generated, its name doesn't matter). This command will start a wizard which will walk you through setting up the integration. You can also use the below configuration examples to set up an integration manually.

Cisco Webex Teams config

ztp set integration MY_SPARK_ROOM type spark
ztp set integration MY_SPARK_ROOM roomId R00m1dsTr1ng
ztp set integration MY_SPARK_ROOM api-key MyaP1k3y

Microsoft Teams/PowerAutomate config

ztp set integration PA type powerautomate
ztp set integration PA url https://prod-164.westus.logic.azure.com:443/<extended uri>

Once setup, you can send a test message to the integration destination using the command ztp request integration-test <svc_name>.

Note about PowerAutomate Integrations

FreeZTP v1.5.0 adds integration for Microsoft Power Automate via webhooks. Power Automate contains a trigger called "When a HTTP request is received", which generates an Azure webhook. Data sent from FreeZTP is JSON formatted text and includes a ZTP host identifier, message type (status), message text (HTML), generated config text, and a filename. Host ID and Status provide data for Power Automate conditions. Message text provides data to deliver to MS Teams. Config text and filename provide data for SharePoint storage.


USAGE TIPS

If you're looking for more powerful ways to use FreeZTP, check out the Usage Tips page where some of our contributors/maintainers have published advanced usage tips and how-tos.


VERSIONS

v1.0.1

Bug Fixes in V1.0.0 --> V1.0.1:

v1.1.0

Bug Fixes in V1.0.0 --> V1.1.0:

Added Features in V1.0.1 --> V1.1.0:

v1.2.0

Bug Fixes in V1.1.0 --> V1.2.0:

Added Features in V1.1.0 --> V1.2.0:

v1.3.0

Bug Fixes in V1.2.0 --> V1.3.0:

Added Features in V1.2.0 --> V1.3.0:

v1.3.1

Bug Fixes in V1.3.0 --> V1.3.1:

v1.4.0

Bug Fixes in V1.3.1 --> V1.4.0:

Added Features in V1.3.1 --> V1.4.0:

ztp set keystore GLOBAL ztp_ip_address 10.0.0.10
ztp set keystore GLOBAL somekey somevalue
#
#
ztp set global-keystore GLOBAL

v1.4.1

Bug Fixes in V1.4.0 --> V1.4.1:

v1.5.0

Added Features in V1.4.1 --> V1.5.0:

v1.5.1

Bug Fixes in V1.5.0 --> V1.5.1:


CONTRIBUTING

If you would like to help out by contributing code or reporting issues, please do!

Visit the GitHub page (https://github.com/packetsar/freeztp) and either report an issue or fork the project, commit some changes, and submit a pull request.