Module Development Tips Thread

Module development is a step above developing inside the Ignition Designer.

In this thread we can add any tips and tricks we have learned along the way to make it a more approachable learning curve.

I have made a bunch of my posts into Wiki posts, so if you have the permissions, you can add/edit as needed.

1 Like

Package Names

IA Java has guidelines for the package names you build your modules under. They should be your company domain name reversed. Note the rules for "legalizing" a domain name.

http://domain.com
Becomes:
com.domain.modulename

After that should be a package name that reflects the scope for the target jar, if applicable:

com.domain.modulename.gateway
com.domain.modulename.client
com.domain.modulename.designer

Packages destined for multiple scopes usually are placed under
com.domain.modulename.common

Code Signing

When you are developing a module, you will not be able to run it outside of developer mode without signing it. Code signing is a process where you will use a cryptographic certificate similar to an SSL certificate to sign a module, which enables Ignition to verify that the module is what it claims to be in installation.
A code signing certificate is essentially an SSL or TLS certificate similar to what you need to install on a webserver to enable https:// traffic. The only difference is that when you generate the certificate signing request, you indicate that the certificate is to be used for code signing.

Enabling Developer Mode

In your ignition.conf, scroll down to the area with a number of entries similar to

wrapper.java.additional.1=
wrapper.java.additional.2=
wrapper.java.additional.3=
wrapper.java.additional.4=
wrapper.java.additional.5=
wrapper.java.additional.6=

Add the following entry:

wrapper.java.additional.7=-Dignition.allowunsignedmodules=true

This allows the gateway to load unsigned modules.
Use this at your own risk on non-production gateways for development purposes only.

When you need to migrate to signed code, you have two options available:

Self-Signed

Self signed code uses a certificate generated by you to sign the module. You can generate a code signing certificate yourself with minimal effort, and then sign your own modules ready for distribution.

CA Signed

CA signing uses a "CA" or Certificate Authority, who have proven security credentials, to prove that you are who you claim to be and that the code signed with your credentials is able to be proved to be from you, and has not been tampered with.

CA Signing is not necessary, but can appear to some people/organisations as more trustworthy and reliable.

CA Signing is expensive.

Once you have a ceritificate, download Keystore Explorer:

Create an empty JKS keystore and upload your certificate pair to this keystore.
Save the keystore then follow the guide here:

When you have run this command, you will end up with a signed module ready to install on any gateway necessary.

Trial Mode

You as the developer are expected to enable and disable your module functions based on the two hour trial period. This applies to free modules as well as paid.

To enable this there are functions like below to get the current license states:

gatewayContext.getLicenseManager().getPlatformLicenseState()

(Don't use those.)

Your module hook will be notified at startup whether it is licensed and/or whether trial mode is active. And will be notified dynamically when/if the licensing state changes. Your module, if it cares, should capture this state in a gateway hook field that the rest of your module can interrogate. (Either make the field static with a static getter, or pass your hook instance into your module's infrastructure.)

Here is a fairly thorough Perspective Component Module focused repo I made that has a pretty wide span of tips and tools.

Helpful as a starter tutorial GitHub - keith-gamble/example-perspective-component-module: An Example Repository for building Ignition Perspective Component Modules

Digging in, if anyone finds gaps, has questions, etc. I am more than happy to add information and examples into its docs.

6 Likes

Monorepo for Multiple Modules

This repository is an example demonstrating multiple Ignition modules in the same repository, with shared library support.

Everyone's needs are different. But in our experience as a small team working on multiple related modules, the monorepo architecture has allowed us to move quickly while sharing internal resources in Embr.

1 Like

Powershell Script for Generating Signing Certificate and gradle.properties

It's not fantastic, but since I only run it a couple times per year I haven't felt the need to improve it. You will definitely need to adjust the Java install location.

This script takes:

  1. A DNS name.
  2. A number of years before expiration.

And generates (in a signature folder):

  1. A password protected signing certificate.
  2. Adds it to a password protected keystore.
  3. Creates a corresponding gradle.properties that can be used with IA's module creation plugin.
$CertStoreLocation = "cert:\CurrentUser\My"
$JavaHome = 'C:\Program Files\Java\jdk-18.0.1\bin'
$FolderName = "signature"

$FolderPath = "$PSScriptRoot\$FolderName"
New-Item $FolderPath -ItemType Directory

$InfoFile = ($FolderPath + "\keystoreinfo.txt")
$GradleProps = ($FolderPath + "\gradle.properties")

$DnsName = Read-Host "Certificate DNS Name"
$YearsBeforeExp = Read-Host "Years before expiration"

Add-Type -AssemblyName System.Web
$CertPassword = [System.Web.Security.Membership]::GeneratePassword(32,4)
$CertPasswordSecure = ConvertTo-SecureString $CertPassword –asplaintext –force 
$KeystorePassword = [System.Web.Security.Membership]::GeneratePassword(32,4)
$KeystoreSecure = ConvertTo-SecureString $CertPassword –asplaintext –force 

$RootFileName = $DnsName.Replace(' ','_')
$RootFilePath = "$FolderPath\$RootFilePath"

$KeystoreFileName = ($RootFileName + "_keystore.jks")
$KeystoreFilePath = "$FolderPath\$KeystoreFileName"

Write-Output "Creating new certificate with DnsName '$DnsName'..."
$Cert = New-SelfSignedCertificate -DnsName $DnsName -Type CodeSigning -CertStoreLocation $CertStoreLocation -NotAfter (Get-Date).AddYears($YearsBeforeExp)

$FileName = "$RootFileName.p7b"
$FilePath = "$FolderPath\$FileName"
Write-Output "Exporting certificate to '$FileName'..."
$Results = Export-Certificate -Cert $Cert -Type P7B -FilePath "$FilePath"

$FileName = "$RootFileName.pfx"
$FilePath = "$FolderPath\$FileName"
Write-Output "Exporting certificate to '$FileName'..."
$Results = Export-PfxCertificate -Cert $Cert -FilePath "$FilePath" -Password $CertPasswordSecure

Write-Output "Removing certificate from personal store..."
$Cert | Remove-Item

$FileName = "$RootFileName.pfx"
Write-Output "Converting '$FileName' to '$KeystoreFileName'..."
$FilePath = "$FolderPath\$FileName"
$Results = Start-Process -FilePath "$JavaHome\keytool.exe" -ArgumentList "-importkeystore -srckeystore .\$FolderName\$FileName -srcstoretype pkcs12  -destkeystore .\$FolderName\$KeystoreFileName -deststoretype JKS -srcstorepass $CertPassword -storepass $KeystorePassword -keypass $CertPassword" -NoNewWindow -PassThru -Wait

Write-Output "Reading key aliases..."
$Results = Start-Process -FilePath "$JavaHome\keytool.exe" -ArgumentList "-list -v -keystore .\$FolderName\$KeystoreFileName -storepass $KeystorePassword" -NoNewWindow -PassThru -RedirectStandardOutput $InfoFile -Wait
$AliasInfo = @(Get-Content -Path $InfoFile | Where-Object { $_.Contains("Alias") })

$AliasName = "ALIAS NOT FOUND"
$line = $AliasInfo[0]
$start = $line.IndexOf(":") + 2
$length = $line.Length - $start
$Alias = $line.Substring($start, $length)
$AliasName = $Alias.Trim()
Write-Output "Alias '$AliasName' found."
#Start-Process -FilePath "$JavaHome\keytool.exe" -ArgumentList "-changealias -alias $Alias -destalias signing_cert -keypass $CertPassword -keystore $RootFileName.jks -storepass $KeystorePassword" -Wait



Write-Output "Generating gradle.properties..."
$content = @"
ignition.signing.certFile= /$FolderName/$RootFileName.p7b
ignition.signing.certPassword= $CertPassword
ignition.signing.certAlias= $AliasName
ignition.signing.keystoreFile= /$FolderName/$KeystoreFileName
ignition.signing.keystorePassword= $KeystorePassword
"@

Add-Content $GradleProps $content
1 Like

Free Modules and Maker Modules
Making your module free and maker compatible is done by overriding these interface classes:

    @Override
    public boolean isMakerEditionCompatible() {
        return true;
    }

    @Override
    public boolean isFreeModule() {
        return true;
    }