A Better Way to a Jenkins Service

The typical way to install a Jenkins service on a Windows system is to download the JNLP file and then execute the file with Java Web StartTM - Oracle: be quite afraid. This process manufactures an excess of anxiety and annoyance for the user due to dialog boxes and their controls appearing only upon successful completion of mysterious (magical) events. Upon failure all you can do is puzzle about how ye flask may not be got.

Thanks to me ye need not wonder, for I will show thou how to get ye flask. Executing javaws on the Jenkins JNLP file triggers several actions:

  • agent.jar is downloaded
  • WinSW.exe is downloaded
  • If the JavaWS window appears, the File menu appears within that window, and you click on Install as Windows Service in that menu, WinSW.exe is renamed to the name of the Jenkins service, which is then installed as a proper Windows service
  • The new service is started using the WinSW.exe as the service executable taking its config from a file generated by Jenkins (agent.jar I'm guessing)
  • The javaws process is terminated

At first you might reason as I did that since the actual Java command line is configured in the agent config file, that you can just run it directly in a PowerShell script and install that script as a service. However, Windows does not allow services to be defined by script files, only by platform-native executable files. One solution to this problem is represented by the popular NSSM. I find Jenkins's own Windows service wrapper to be much more elegant as it does not use an intermediary process. WinSW merely takes on the name of the service you want to run by executing the command line provided in its config file.

Here is the correct way to automate that whole process (guaranteed to get thou ye flask without any effort):

{%- set agent = {
    'service_name': 'JenkinsAgent',
    'service_long_name': 'Jenkins Agent (powered by WinSW)',
    'service_desc': 'This service runs an agent for Jenkins',
    'service_home': 'C:\\Jenkins',
    'winsw_uri': 'https://github.com/kohsuke/winsw/releases/download/winsw-v2.2.0/WinSW.NET4.exe',
    'winsw_sha512': 'f485dec639155528d804bbeddc69a0d9fe77c44444821a5b6cf557ec0b8f9932153a0411bab0524a47f133d2ee5bf3c03830f32b36c77b83cc7f0b1c5a108b33',
    'jar_sha512': '6lojuezjazurpmarwmzymthvo0aukur9fvgbiro8gdkkbv7t1ps2nhupdixjcqt1uhzqdzjswojkahulfqbeo5fepuc8ordezt5siugmelxncjwnmggmsvepjufomotk',
} %}


# Install {{ agent['service_name'] }}.jar
service-downloaded:
  file.managed:
    - name: {{ agent['service_home'] }}\\{{ agent['service_name'] }}.jar
    - source: https://jenkins.mycorp.com/jnlpJars/agent.jar
    - source_hash: {{ agent['jar_sha512'] }}
    - makedirs: True

# Setup {{ agent['service_name'] }}.xml
service-configured:
  file.managed:
    - name: {{ agent['service_home'] }}\\{{ agent['service_name'] }}.xml
    - contents: |
        <configuration>
          <id>{{ agent['service_name'] }}</id>
          <name>{{ agent['service_long_name'] }}</name>
          <description>{{ agent['service_desc'] }}</description>

          <executable>java.exe</executable>  <!-- Should be AdoptOpenJDK version -->
          <arguments>-Xrs -jar "{{ agent['service_home'] }}\\{{ agent['service_name'] }}.jar" -jnlpUrl https://jenkins.mycorp.com/computer/{{ grains['id'] }}/slave-agent.jnlp -secret {{ pillar[grains['id']]['jnlp_secret_key'] }}</arguments>

          <logmode>rotate</logmode>
          <onfailure action="restart" />
        </configuration>

# Download, install, and start {{ agent['service_name'] }}.exe
service-created:
  file.managed:
    - name: {{ agent['service_home'] }}\\{{ agent['service_name'] }}.exe
    - source: {{ agent['winsw_uri'] }}
    - source_hash: {{ agent['winsw_sha512'] }}
  module.run:
    - service.create:
      - name: {{ agent['service_name'] }}
      - bin_path: {{ agent['service_home'] }}\\{{ agent['service_name'] }}.exe
      - start_type: auto
      - account_name: '.\{{ pillar['robo_account']['name'] }}'
      - account_password: '{{ pillar['robo_account']['pass'] }}'
    - unless:  # Any changes to the data in this state will require a manual service reinstall because the requisite only checks for existence
      - 'if (Get-Service {{ agent['service_name'] }} -ErrorAction SilentlyContinue)
         {
             Write-Error "Do not execute because service {{ agent['service_name'] }} already exists"
         }'
  service.running:
    - name: {{ agent['service_name'] }}
    - enable: True
Posted: | Source