# Making a Pipeline

Assuming you have the container running, you should be able to set up a playbook. Before this, you should set up the link with Github. I don't remember exactly how I did that either, so I found a [guide](https://dev.to/logesh-sakthivel/jenkins-pipeline-script-from-scm-2k3k). Turns out it's easy to checkout, it was the part about the Repo needing credentials that was hard. I remember it took a little while to get the Github SSH keys all set up, but now that I have them it's trivial. Here is an old playbook I wrote before learning how to use Ansible to set up my first PXE server. Since this was before I figured out Ansible, I was just using Jenkins to automate my shell commands. Once I learned how to use Ansible, I abandoned this way of using Jenkins. I haven't ran this pipeline in a long time, but I believe it did work in the end.

<details id="bkmrk-jenkinsfile.old_pxe-"><summary>Jenkinsfile.old\_pxe</summary>

```bash
pipeline {
    agent any


    environment {
        DEBIAN_IP = 'debian_machine_ip'
        SSH_CREDENTIALS_ID = 'jenkins-ssh-key'
        PXE_AUTH = 'PXE_AUTH'
    }


    stages {


        stage('Install Packages') {
            steps {
                script {
                    sshagent([env.SSH_CREDENTIALS_ID]) {
                        sh """
                        ssh-keygen -f "/root/.ssh/known_hosts" -R "${params.host_ip}"
                        ssh -o StrictHostKeyChecking=no root@${params.host_ip} << EOF
                            echo "samba-common samba-common/workgroup string  WORKGROUP" | debconf-set-selections
                            echo "samba-common samba-common/dhcp boolean true" | debconf-set-selections
                            echo "samba-common samba-common/do_debconf boolean true" | debconf-set-selections
                            apt update --yes
                            apt upgrade --yes
                            apt install --yes isc-dhcp-server curl jq tftpd-hpa apache2 syslinux-common net-tools samba pwgen cifs-utils unzip
                        
                        """
                    }
                }
            }
        }


        stage('Configure DHCP Server') {
            steps {
                withCredentials([string(credentialsId: env.PXE_AUTH, variable: 'PXE_AUTH')]) {
                    sshagent([env.SSH_CREDENTIALS_ID]) {
                        sh """
                        ssh -o StrictHostKeyChecking=no root@${params.host_ip} << EOF
                            systemctl stop isc-dhcp-server
                            curl -o /etc/dhcp/dhcpd.conf -L https://$PXE_AUTH@mattifactory.com/dhcp/dhcpd.conf
                            curl -o /etc/default/isc-dhcp-server -L https://$PXE_AUTH@mattifactory.com/dhcp/isc-dhcp-server
                            systemctl start isc-dhcp-server
                        """
                    }
                }
            }
        }


        stage('Configure TFTP Server') {
            steps {
                withCredentials([string(credentialsId: env.PXE_AUTH, variable: 'PXE_AUTH')]) {
                    sshagent([env.SSH_CREDENTIALS_ID]) {
                        sh """
                        ssh -o StrictHostKeyChecking=no root@${params.host_ip} << EOF
                            systemctl stop tftpd-hpa
                            mkdir -p /srv/tftp
                            chown -R tftp:tftp /srv/tftp
                            chmod -R 777 /srv/tftp
                            curl -o /etc/default/tftpd-hpa -L https://$PXE_AUTH@mattifactory.com/dhcp/tftpd-hpa
                            systemctl start tftpd-hpa
                        """
                    }
                }
            }
        }


        stage('Configure HTTP Server') {
            steps {
                withCredentials([string(credentialsId: env.PXE_AUTH, variable: 'PXE_AUTH')]) {
                    sshagent([env.SSH_CREDENTIALS_ID]) {
                        sh """
                        ssh -o StrictHostKeyChecking=no root@${params.host_ip} << EOF
                            systemctl stop apache2
                            mkdir -p /var/www/html/debian-installer/amd64
                            cd /var/www/html/debian-installer/amd64
                            wget -q https://$PXE_AUTH@mattifactory.com/dhcp/netboot.tar.gz
                            tar -xzf netboot.tar.gz
                            systemctl start apache2
                        """
                    }
                }
            }
        }


        stage('Configure PXE Boot Configuration') {
            steps {
                withCredentials([string(credentialsId: env.PXE_AUTH, variable: 'PXE_AUTH')]) {
                    sshagent([env.SSH_CREDENTIALS_ID]) {
                        sh """
                        ssh -o StrictHostKeyChecking=no root@${params.host_ip} << EOF
                            cp /var/www/html/debian-installer/amd64/pxelinux.0 /srv/tftp/
                            cp /usr/lib/syslinux/modules/bios/* /srv/tftp/
                            cp -R /var/www/html/debian-installer/amd64/debian-installer /srv/tftp/
                            mkdir -p /srv/tftp/pxelinux.cfg
                            curl -o /srv/tftp/debian-installer/amd64/linux -L https://$PXE_AUTH@mattifactory.com/dhcp/linux
                            curl -o /srv/tftp/debian-installer/amd64/initrd.gz -L https://$PXE_AUTH@mattifactory.com/dhcp/initrd.gz
                            curl -o /srv/tftp/debian-installer/amd64/pxelinux.cfg/default -L https://$PXE_AUTH@mattifactory.com/dhcp/default
                            curl -o /srv/tftp/pxelinux.cfg/default -L https://$PXE_AUTH@mattifactory.com/dhcp/default
                            curl -o /srv/tftp/debian-installer/amd64/pxelinux.cfg/default -L https://$PXE_AUTH@mattifactory.com/dhcp/default
                            curl -o /srv/tftp/debian-installer/amd64/grub/grub.cfg -L https://$PXE_AUTH@mattifactory.com/dhcp/grub.cfg
                            curl -o /var/www/html/preseed.cfg -L https://$PXE_AUTH@mattifactory.com/dhcp/preseed.cfg
                            curl -o /srv/tftp/preseed.cfg -L https://$PXE_AUTH@mattifactory.com/dhcp/preseed.cfg
                        """
                    }
                }
            }
        }


        stage('Configure SMB & Hostname & Reboot') {
            steps {
                withCredentials([string(credentialsId: env.PXE_AUTH, variable: 'PXE_AUTH')]) {
                    sshagent([env.SSH_CREDENTIALS_ID]) {
                        sh """
                        ssh -o StrictHostKeyChecking=no root@${params.host_ip} << EOF
                            mkdir -p /media/share
                            chmod 777 /media/share 
                            systemctl stop smbd.service
                            curl -o /etc/samba/smb.conf -L https://$PXE_AUTH@mattifactory.com/smb/smb.conf
                            systemctl start smbd.service
                            echo cosmos-pxe > /etc/hostname
                            echo 127.0.0.1 cosmos-pxe >> /etc/hosts
                            sleep 2
                            reboot now

                        """
                    }
                }
            }
        }
    }
}
```

</details>With that old example of how to use Jenkins without Ansible behind us, here is a simple Jenkisfile that first injects the Ansible SSH key into a new [NanoPi](https://www.friendlyelec.com/index.php?route=product/category&amp;path=69) Device and runs a super simple [playbook](https://jenkins.matt-cloud.com/job/Utility%20Playbooks/job/Pi%20Init/). Ansible requires an inventory file; assuming you're not actually keeping an Ansible inventory file, then you need to generate an inventory file. This is usually done by script ran in the Jenkinsfile. This **inventory.sh** script takes a list of IPs and creates an inventory file that **ansible-playbook** can read among other things. This is less important than what the Jenkinsfile shows; specifically this shows how to run commands with variables, how the SSH key works, and how to run a very simply Ansible playbook.

I recently updated the dynamic inventory generation script to have a few more options for passing user and group, as well as prohibiting lists of endpoints. This is because I want to open my Jenkins up a bit, and I don't want people running pipelines on my servers. It did make the script a lot more complicated.

<details id="bkmrk-jenkinsfile-pipeline"><summary>Jenkinsfile</summary>

```bash
pipeline {
    agent any
    
    // Define parameters
    parameters {
        string(name: 'host_ip', description: 'Target System Address')
    }

    environment {
        ANSIBLE_FORCE_COLOR = '1'
        jenkins_public_key = credentials('jenkins_public_key')        
    }

    options {
        ansiColor('xterm')
    }

    stages {

        stage('Inject Auth Key') {
            steps {
                script{
                    // clear ssh keys
                    echo "Target IP: ${params.host_ip}"
                    
                    sh """
                    ssh-keygen -f "/root/.ssh/known_hosts" -R "${params.host_ip}"
                    """
                    
                    sh """
                    echo Copy public key to pi home dir
                    sshpass -p 'pi' ssh -o StrictHostKeyChecking=no pi@${params.host_ip} "echo ${env.jenkins_public_key} > /home/pi/authorized_keys"
                    """

                    sh """
                    echo Make sure /root/.ssh exists
                    sshpass -p 'pi' ssh -o StrictHostKeyChecking=no pi@${params.host_ip} "echo pi | sudo -S mkdir -p /root/.ssh/"
                    """
                    
                    sh """
                    echo Move public key to root
                    sshpass -p 'pi' ssh -o StrictHostKeyChecking=no pi@${params.host_ip} "echo pi | sudo -S mv /home/pi/authorized_keys /root/.ssh/authorized_keys"
                    """
                    
                    sh """
                    echo Restrict permissions on file
                    sshpass -p 'pi' ssh -o StrictHostKeyChecking=no pi@${params.host_ip} "echo pi | sudo -S chmod -R 600 /root/.ssh/"
                    """
                    
                    sh """
                    echo Set owner to root
                    sshpass -p 'pi' ssh -o StrictHostKeyChecking=no pi@${params.host_ip} "echo pi | sudo -S chown -R root:root /root/.ssh/"
                    """
                }
            }
        }

        stage('Generate Inventory File') {
            steps {
                // Generate the dynamic inventory file
                sh """
                jenkins_group=\$(echo ${env.BUILD_USER_GROUPS} |  sed 's/,/\\n/g' | grep Jenkins | head -n 1)
                jenkins_user=\$(echo ${env.BUILD_USER})
                cd /var/jenkins_home/ansible
                chmod +x /var/jenkins_home/ansible/inventory/inventory.sh
                /var/jenkins_home/ansible/inventory/inventory.sh -s -g \$jenkins_group -u \$jenkins_user -i ${params.host_ip} 

                """
            }
        }

        stage('Ansible Check') {
            steps {
                sh """
                echo ${params.host_ip}
                hash=\$(echo -n ${params.host_ip} | md5sum | cut -c 1-8)
                inventory_file="/var/jenkins_home/ansible/.inv/inventory-\$hash.yml"

                cd /var/jenkins_home/ansible

                ansible-playbook -i \$inventory_file \
                    /var/jenkins_home/ansible/playbooks/pi-init.yaml --ssh-common-args='-o StrictHostKeyChecking=no' 
                
                """
            }
        }

    }


    post {
        always {
            // Remove dynamic Inventory file
            sh """
            hash=\$(echo -n "${params.host_ip}" | md5sum | cut -c 1-8)
            inventory_file="/var/jenkins_home/ansible/.inv/inventory-\$hash.yml"
            rm \$inventory_file
            
            """
        }
    }

}

```

</details><details id="bkmrk-pi-init.yaml-------n"><summary>pi-init.yaml</summary>

```yaml
---
- name: Ansible Test
  hosts: all
  become: yes

  # this is meant just as a tiny playbook to run after the public key is injected with jenkins
  tasks: 
  # Check System Architecture
  - name: Check CPU Arch
    shell: "dpkg --print-architecture"
    register: cpu_architecture_output

  - name: Display cpu_architecture_output variable
    debug: 
      msg: "{{ cpu_architecture_output.stdout_lines[0] }}"

...
```

</details><details id="bkmrk-inventory.sh-%23%21%2Fbin%2F"><summary>inventory.sh</summary>

```bash
#!/bin/bash

# Dynamic inventory generation script ansible

# Function to display usage
usage() {
    echo "Usage: $0 -i IP_LIST -u JENKINS_USER -g JENKINS_GROUP [-s] [-v]"
    echo "Options:"
    echo "  -i IP_LIST       Comma-separated list of IPs"
    echo "  -u JENKINS_USER  Jenkins user for SSH access"
    echo "  -g JENKINS_GROUP Jenkins group for SSH access"
    echo "  -s               Set variable to true if more than one IP is passed"
    echo "  -v               Display Ansible Version"
    exit 1
}

# Initialize variables with default values
skip=false
more_than_one=false
display_version=false

# Parse command line options
while getopts ":i:u:g:sv" opt; do
  case ${opt} in
    i ) # process option i
        IP_LIST=$OPTARG
        ;;
    u ) # process option u
        JENKINS_USER=$OPTARG
        ;;
    g ) # process option g
        JENKINS_GROUP=$OPTARG
        ;;
    s ) # process option s
        skip=true
        ;;
    v ) # process option v
        display_version=true
        ;;
    \? ) usage
        ;;
  esac
done
shift $((OPTIND -1))
# Check if all required options are provided
if [ -z "$IP_LIST" ] || [ -z "$JENKINS_USER" ] || [ -z "$JENKINS_GROUP" ]; then
    usage
fi

if $display_version; then
    echo "Showing ansible version"
    ansible --version
fi

# Generate an 8-character hash from the IP list
hash=$(echo -n "$IP_LIST" | md5sum | cut -c 1-8)
echo "IP List:"
echo $IP_LIST
echo $hash

# Define the inventory file path with the hash
inventory_file="/var/jenkins_home/ansible/.inv/inventory-$hash.yml"

if $skip; then
    IFS=',' read -ra IPS <<< "$IP_LIST"
    if [ ${#IPS[@]} -gt 1 ]; then
        more_than_one=true
    fi
fi

if $skip; then
    echo "Single host option set"
    if $more_than_one; then
        echo "IP list provided, inventory will be emptied"
        IP_LIST=""
    fi
fi

# Initialize the YAML inventory content
inventory_content="---
all:
  hosts:
"

# Loop through each IP in the comma-separated list
IFS=',' read -ra IPS <<< "$IP_LIST"
for IP in "${IPS[@]}"; do
    inventory_content+="    ${IP}:
      ansible_user: root
"
done

inventory_content+="  vars:
    ansible_connection: ssh
    ansible_ssh_private_key_file: /var/jenkins_home/jenkins_key
    ansible_python_interpreter: /usr/bin/python3
    jenkins_user: '${JENKINS_USER}'
    jenkins_group: '${JENKINS_GROUP}'
"

# Write the inventory content to the file
echo "$inventory_content" > $inventory_file

echo "Inventory file created at $inventory_file with the following content:"
cat $inventory_file

```

</details>[![image.png](https://kb.matt-cloud.com/uploads/images/gallery/2025-09/scaled-1680-/ONiimage.png)](https://kb.matt-cloud.com/uploads/images/gallery/2025-09/ONiimage.png)

This is how to configure this to pull from Github:

[![image.png](https://kb.matt-cloud.com/uploads/images/gallery/2025-09/scaled-1680-/oHIimage.png)](https://kb.matt-cloud.com/uploads/images/gallery/2025-09/oHIimage.png)  
[![image.png](https://kb.matt-cloud.com/uploads/images/gallery/2025-09/scaled-1680-/eQzimage.png)](https://kb.matt-cloud.com/uploads/images/gallery/2025-09/eQzimage.png)  
[![image.png](https://kb.matt-cloud.com/uploads/images/gallery/2025-09/scaled-1680-/A59image.png)](https://kb.matt-cloud.com/uploads/images/gallery/2025-09/A59image.png)