Initial commit: EDGE Ignition gateway docker-compose build

Custom Dockerfile-based build for an Ignition Edge gateway with
pre-baked base gwbk, module registration, and admin password
provisioning, backed by MariaDB.
This commit is contained in:
2026-07-22 10:31:20 -05:00
commit 1911a4960f
15 changed files with 764 additions and 0 deletions

2
.env Normal file
View File

@@ -0,0 +1,2 @@
IGNITION_VERSION=8.3.7
GATEWAY_MAX_MEMORY=512

124
CHANGES.md Normal file
View File

@@ -0,0 +1,124 @@
# EDGE docker-compose build/runtime fixes — 2026-07-22
## Starting problem
`docker compose -f EDGE/docker-compose.yml up -d --build` failed. The custom
`gw-build/Dockerfile` (patterned after
https://github.com/thirdgen88/ignition-examples/tree/main/iiot) referenced four
helper scripts that did not exist anywhere in `gw-build/` or on disk:
- `retrieve-modules.sh`
- `register-module.sh`
- `register-password.sh`
- `docker-entrypoint-shim.sh`
Build cache masked this initially — earlier `RUN` layers that *used* these
scripts showed `CACHED` from a prior successful build, while the `COPY` steps
that needed the actual files on disk failed with "not found".
## What was checked
- Searched the whole filesystem (`~/git`, home directory) for the four
filenames — not found anywhere, including in the sibling `BAT/` repo (which
uses the stock Ignition image directly, no custom Dockerfile).
- Checked for any previously-built `edge-ignition` image, container, or volume
that might have the files baked in — none existed; this build has never
succeeded on this machine.
- User confirmed the reference source: the `iiot` example in
`thirdgen88/ignition-examples`, which contains all four scripts plus a
matching `Dockerfile`.
## Changes made
### 1. Copied the four missing scripts from the reference repo
Fetched from `thirdgen88/ignition-examples` (`iiot/gw-build/`) into
`EDGE/gw-build/`, marked executable (0755):
- `retrieve-modules.sh`
- `register-module.sh`
- `register-password.sh`
- `docker-entrypoint-shim.sh` — this one already existed locally, identical to
upstream, so no change was needed there.
Note: a stray `register-modules.sh` (plural) was found already sitting in
`gw-build/` — an exact duplicate of `retrieve-modules.sh`'s content under the
wrong filename, unreferenced by the Dockerfile. Left in place, not deleted
(flagged to user).
### 2. Dropped `mqttdistributor`/`mqttengine` from the build (user decision)
The reference `Dockerfile` defines `ARG` pairs
(`SUPPLEMENTAL_MQTTENGINE_DOWNLOAD_URL`/`_SHA256`,
`SUPPLEMENTAL_MQTTDISTRIBUTOR_DOWNLOAD_URL`/`_SHA256`) that our local
Dockerfile was missing, even though `docker-compose.yml` requested
`SUPPLEMENTAL_MODULES: "mqttdistributor mqttengine"`. User said they don't
want those modules, so instead of adding the missing ARGs, the module list in
`docker-compose.yml` was changed to:
```yaml
SUPPLEMENTAL_MODULES: ""
```
### 3. Fixed `retrieve-modules.sh` invocation for the empty-module case
`retrieve-modules.sh`'s `main()` is written to silently no-op when
`SUPPLEMENTAL_MODULES` is empty, but its own `getopts` argument-validation
block rejects `-m ""` before `main()` ever runs (`exit 1` with a usage
message). This is a bug in the vendored script itself, inherited from the
reference repo — not something introduced here.
Rather than edit the vendored script (to keep it matching upstream), the
Dockerfile's `RUN` line was changed to skip calling the script at all when
there are no modules to fetch:
```dockerfile
# before
RUN ./retrieve-modules.sh \
-m "${SUPPLEMENTAL_MODULES:-}"
# after
RUN if [ -n "${SUPPLEMENTAL_MODULES:-}" ]; then ./retrieve-modules.sh -m "${SUPPLEMENTAL_MODULES}"; fi
```
### 4. Fixed `BASE_GWBK_NAME` mismatch
`docker-compose.yml` passed `BASE_GWBK_NAME: gateway.gwbk` as a build arg, but
the actual gateway backup file in `gw-build/` is named `base.gwbk` (matching
the Dockerfile's own default `ARG BASE_GWBK_NAME="base.gwbk"`). This mismatch
didn't hard-fail the build immediately — it caused `unzip` inside the
module/password-registration `RUN` step to silently fail-and-fallback
("cannot find or open gateway.gwbk" / "skipping password registration"),
masking the real problem until the final `COPY --from=prep .../${BASE_GWBK_NAME}`
step errored with "not found".
Fixed by correcting the compose arg to match the real filename:
```yaml
BASE_GWBK_NAME: base.gwbk
```
This also means admin password registration now actually runs during the
build (previously it was silently skipped due to the filename mismatch).
### 5. Fixed a runtime crash loop after the image built successfully
Once the image built, the `EDGE` container crash-looped with:
```
ERROR: Gateway Public HTTP/HTTPS/Address must be specified together:
- HTTPS Port not specified or is invalid
```
The compose `command:` for the `ignition` service set `-a
gateway.localtest.me` (public address) and `-h 8088` (HTTP port) but no `-s`
(HTTPS port). Ignition's entrypoint requires all three (address, HTTP port,
HTTPS port) to be set together or none at all — this is just gateway
public-address metadata used for URL generation, not a request to actually
expose HTTPS. Fixed by adding the conventional Ignition default HTTPS port:
```yaml
command: >
-n FW-DST-SEN
-m 512
-a gateway.localtest.me
-h 8088
-s 8043
```
No compose port mapping was added for 8043 — it isn't published, only used
for the internal public-address config.
## Result
`docker compose -f EDGE/docker-compose.yml up -d --build` now completes
successfully. Both `EDGE` (Ignition gateway) and `EDGE-db` (MariaDB)
containers reach `healthy` status. Gateway is reachable at
`http://gateway.localtest.me:8088` (or `http://localhost:8088`).
## Open item for follow-up
- Confirm whether the stray `gw-build/register-modules.sh` (plural, unused
duplicate) should be deleted.

102
docker-compose.yml Normal file
View File

@@ -0,0 +1,102 @@
x-default-logging:
&default-logging
logging:
options:
max-size: '100m'
max-file: '5'
driver: json-file
x-ignition-opts:
&ignition-opts
<<: *default-logging
env_file: gw-init/gateway.env
secrets:
- gateway-admin-password
services:
ignition:
<<: *ignition-opts
build:
context: gw-build
dockerfile: Dockerfile
args:
IGNITION_VERSION: ${IGNITION_VERSION:-latest}
SUPPLEMENTAL_MODULES: ""
BASE_GWBK_NAME: base.gwbk
GATEWAY_ADMIN_USERNAME: admin
secrets:
# NOTE: changing a build secret will not bust the cache, run the build with `--no-cache` to force a rebuild
- gateway-admin-password
pull_policy: build
container_name: EDGE
ports:
- "8088:8088"
environment:
ACCEPT_IGNITION_EULA: "Y"
GATEWAY_ADMIN_USERNAME: admin
GATEWAY_ADMIN_PASSWORD: password
IGNITION_EDITION: edge
DISABLE_QUICKSTART: "true"
# Run the gateway as the HOST dev uid/gid (primebench pattern): started
# as root (user 0:0), the official entrypoint chowns the bind mounts to
# IGNITION_UID/GID and drops privileges — repo-mapped gateway files stay
# owned by you, so host-side edits need no chown dance (WSL2 included).
IGNITION_UID: "${IGNITION_UID:-1000}"
IGNITION_GID: "${IGNITION_GID:-1000}"
TZ: America/Chicago
user: "0:0"
# Runtime args: gateway name + 500 mb max JVM heap (8.3 uses the -m flag, not an env var)
command: >
-n FW-DST-SEN
-m 512
-a gateway.localtest.me
-h 8088
-s 8043
volumes:
- db-data-EDGE:/usr/local/bin/ignition/data
- ./gw-backup/gateway:/backup
depends_on:
db:
condition: service_healthy
restart: unless-stopped
networks:
- EDGE
db:
image: mariadb:11.8
container_name: EDGE-db
environment:
MARIADB_ROOT_PASSWORD: password
MARIADB_DATABASE: ignition
MARIADB_USER: ignition
MARIADB_PASSWORD: ignition
ports:
# localhost only, for inspecting with a SQL client
- "127.0.0.1:3306:3306"
volumes:
- db-data-EDGE:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
start_period: 15s
interval: 5s
timeout: 5s
retries: 12
restart: unless-stopped
networks:
- EDGE
secrets:
gateway-admin-password:
file: secrets/gateway-admin-password
db-ignition-password:
file: secrets/db-ignition-password
db-root-password:
file: secrets/db-root-password
networks:
EDGE:
volumes:
db-data-EDGE:

74
gw-build/Dockerfile Normal file
View File

@@ -0,0 +1,74 @@
# Draw from environment (see .env file) for Ignition version/tag to source from
ARG IGNITION_VERSION=${IGNITION_VERSION}
FROM inductiveautomation/ignition:${IGNITION_VERSION} as prep
# Switch to root user for base image updates
USER root
# Install some prerequisite packages
RUN apt-get update && apt-get install -y wget jq zip unzip sqlite3
ARG SUPPLEMENTAL_AWSINJECTOR_DOWNLOAD_URL="https://files.inductiveautomation.com/third-party/cirrus-link/4.0.24/AWS-Injector-signed.modl"
ARG SUPPLEMENTAL_AWSINJECTOR_DOWNLOAD_SHA256="5aa6ab27829e6dfa67026358553a3e5e5d1b8ec689e5ffaae3cdaddbc9a3c252"
ARG SUPPLEMENTAL_AZUREIOTINJECTOR_DOWNLOAD_URL="https://files.inductiveautomation.com/third-party/cirrus-link/4.0.24/Azure-Injector-signed.modl"
ARG SUPPLEMENTAL_AZUREIOTINJECTOR_DOWNLOAD_SHA256="90cee320d98fa9c1d8c0f3786586c8cd8dd256803e08ed3fcfc772acccdf3dbd"
ARG SUPPLEMENTAL_GCPINJECTOR_DOWNLOAD_URL="https://files.inductiveautomation.com/third-party/cirrus-link/4.0.24/Google-Cloud-Injector-signed.modl"
ARG SUPPLEMENTAL_GCPINJECTOR_DOWNLOAD_SHA256="72078b25fb3c6853d85a76c212fb63a8c1277707666b492f67fb91ea333775b8"
ARG SUPPLEMENTAL_MQTTTRANSMISSION_DOWNLOAD_URL="https://files.inductiveautomation.com/third-party/cirrus-link/4.0.24/MQTT-Transmission-signed.modl"
ARG SUPPLEMENTAL_MQTTTRANSMISSION_DOWNLOAD_SHA256="cb5d620513110c23b618989b56c5c2a67c45c36944a25d127586e8e303f0273a"
ARG SUPPLEMENTAL_MQTTTRANSMISSIONNIGHTLY_DOWNLOAD_URL="https://ignition-modules-nightly.s3.amazonaws.com/Ignition8/MQTT-Transmission-signed.modl"
ARG SUPPLEMENTAL_MQTTTRANSMISSIONNIGHTLY_DOWNLOAD_SHA256="notused"
ARG SUPPLEMENTAL_MODULES
# Set working directory for this prep image and ensure that exits from sub-shells bubble up and report an error
WORKDIR /root
SHELL [ "/usr/bin/env", "-S", "bash", "-euo", "pipefail", "-O", "inherit_errexit", "-c" ]
# Retrieve all targeted modules and verify their integrity
COPY --chmod=0755 retrieve-modules.sh .
RUN if [ -n "${SUPPLEMENTAL_MODULES:-}" ]; then ./retrieve-modules.sh -m "${SUPPLEMENTAL_MODULES}"; fi
# Set CERTIFICATES/EULAS acceptance in gateway backup config db
COPY *.gwbk ./
COPY --chmod=0755 register-module.sh register-password.sh ./
ARG GATEWAY_ADMIN_USERNAME="admin"
ARG BASE_GWBK_NAME="base.gwbk"
RUN --mount=type=secret,id=gateway-admin-password \
unzip -q "${BASE_GWBK_NAME}" db_backup_sqlite.idb && \
shopt -s nullglob; \
for module in *.modl; do \
./register-module.sh \
-f "${module}" \
-d db_backup_sqlite.idb; \
done; \
shopt -u nullglob && \
./register-password.sh \
-u "${GATEWAY_ADMIN_USERNAME}" \
-f /run/secrets/gateway-admin-password \
-d db_backup_sqlite.idb && \
zip -q -f "${BASE_GWBK_NAME}" db_backup_sqlite.idb || \
if [[ ${ZIP_EXIT_CODE:=$?} == 12 ]]; then \
echo "No changes to internal database needed during module registration."; \
else \
echo "Unknown error (${ZIP_EXIT_CODE}) encountered during re-packaging of config db, exiting." && \
exit ${ZIP_EXIT_CODE}; \
fi
# Final Image
FROM inductiveautomation/ignition:${IGNITION_VERSION} as final
ARG BASE_GWBK_NAME="base.gwbk"
ARG IGNITION_EDITION="edge"
# Embed modules and base gwbk from prep image as well as entrypoint shim
COPY --from=prep --chown=ignition:ignition /root/*.modl ${IGNITION_INSTALL_LOCATION}/user-lib/modules/
COPY --from=prep --chown=ignition:ignition /root/${BASE_GWBK_NAME} ${IGNITION_INSTALL_LOCATION}/base.gwbk
COPY --chmod=0755 --chown=root:root docker-entrypoint-shim.sh /usr/local/bin/
# Return to ignition user
USER ignition
# Set Ignition Edition default based on build argument
ENV IGNITION_EDITION="${IGNITION_EDITION}"
# Target the entrypoint shim for any custom logic prior to gateway launch
ENTRYPOINT [ "docker-entrypoint-shim.sh" ]

BIN
gw-build/base.gwbk Normal file

Binary file not shown.

View File

@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail
# Kick off the built-in entrypoint, with an in-built restore (-r <gwbk path>) directive
exec docker-entrypoint.sh -r base.gwbk "$@"

125
gw-build/register-module.sh Executable file
View File

@@ -0,0 +1,125 @@
#!/usr/bin/env bash
set -euo pipefail
shopt -s inherit_errexit
###############################################################################
# Performs auto-acceptance of EULA and import of certificates for third-party modules
###############################################################################
function main() {
if [ ! -f "${MODULE_LOCATION}" ]; then
echo ""
return 0 # Silently exit if there is no /modules path
elif [ ! -f "${DB_LOCATION}" ]; then
echo "WARNING: ${DB_FILE} not found, skipping module registration"
return 0
fi
register_module
}
###############################################################################
# Register the module with the target Config DB
###############################################################################
function register_module() {
local SQLITE3=( sqlite3 "${DB_LOCATION}" )
# Tie into db
local keytool module_sourcepath
module_basename=$(basename "${MODULE_LOCATION}")
module_sourcepath=${MODULE_LOCATION}
keytool=$(which keytool)
echo "Processing Module: ${module_basename}"
# Populate CERTIFICATES table
local cert_info subject_name thumbprint next_certificates_id thumbprint_already_exists
cert_info=$( unzip -qq -c "${module_sourcepath}" certificates.p7b | $keytool -printcert -v | head -n 9 )
thumbprint=$( echo "${cert_info}" | grep -A 2 "Certificate fingerprints" | grep SHA1 | cut -d : -f 2- | sed -e 's/\://g' | awk '{$1=$1;print tolower($0)}' )
subject_name=$( echo "${cert_info}" | grep -m 1 -Po '^Owner: CN=\K(.+?)(?=, (OU|O|L|ST|C)=)' | sed -e 's/"//g' )
echo " Thumbprint: ${thumbprint}"
echo " Subject Name: ${subject_name}"
next_certificates_id=$( "${SQLITE3[@]}" "SELECT COALESCE(MAX(CERTIFICATES_ID)+1,1) FROM CERTIFICATES" )
thumbprint_already_exists=$( "${SQLITE3[@]}" "SELECT 1 FROM CERTIFICATES WHERE lower(hex(THUMBPRINT)) = '${thumbprint}'" )
if [ "${thumbprint_already_exists}" != "1" ]; then
echo " Accepting Certificate as CERTIFICATES_ID=${next_certificates_id}"
"${SQLITE3[@]}" "INSERT INTO CERTIFICATES (CERTIFICATES_ID, THUMBPRINT, SUBJECTNAME) VALUES (${next_certificates_id}, x'${thumbprint}', '${subject_name}'); UPDATE SEQUENCES SET val=${next_certificates_id} WHERE name='CERTIFICATES_SEQ'"
else
echo " Thumbprint already found in CERTIFICATES table, skipping INSERT"
fi
# Populate EULAS table
local next_eulas_id license_crc32 module_id
local -i module_id_check
next_eulas_id=$( "${SQLITE3[@]}" "SELECT COALESCE(MAX(EULAS_ID)+1,1) FROM EULAS" )
license_filename=$( unzip -qq -c "${module_sourcepath}" module.xml | grep -oP '(?<=<license>).*(?=</license)' )
license_crc32=$( unzip -qq -c "${module_sourcepath}" "${license_filename}" | gzip -c | tail -c8 | od -t u4 -N 4 -A n | cut -c 2- )
module_id=$( unzip -qq -c "${module_sourcepath}" module.xml | grep -oP '(?<=<id>).*(?=</id)' )
module_id_check=$( "${SQLITE3[@]}" "SELECT CASE WHEN CRC=${license_crc32} THEN -1 ELSE 1 END FROM EULAS WHERE MODULEID='${module_id}'" )
if (( module_id_check == 1 )); then
echo " Removing previous EULAS entries for MODULEID='${module_id}'"
"${SQLITE3[@]}" "DELETE FROM EULAS WHERE MODULEID='${module_id}'"
fi
if (( module_id_check >= 0 )); then
echo " Accepting License on your behalf as EULAS_ID=${next_eulas_id}"
"${SQLITE3[@]}" "INSERT INTO EULAS (EULAS_ID, MODULEID, CRC) VALUES (${next_eulas_id}, '${module_id}', ${license_crc32}); UPDATE SEQUENCES SET val=${next_eulas_id} WHERE name='EULAS_SEQ'"
else
echo " License EULA already found in EULAS table, skipping INSERT"
fi
}
###############################################################################
# Outputs to stderr
###############################################################################
function debug() {
# shellcheck disable=SC2236
if [ ! -z ${verbose+x} ]; then
>&2 echo " DEBUG: $*"
fi
}
###############################################################################
# Print usage information
###############################################################################
function usage() {
>&2 echo "Usage: $0 -f <path/to/module> -d <path/to/db>"
}
# Argument Processing
while getopts ":hvf:d:" opt; do
case "$opt" in
v)
verbose=1
;;
f)
MODULE_LOCATION="${OPTARG}"
;;
d)
DB_LOCATION="${OPTARG}"
DB_FILE=$(basename "${DB_LOCATION}")
;;
h)
usage
exit 0
;;
\?)
usage
echo "Invalid option: -${OPTARG}" >&2
exit 1
;;
:)
usage
echo "Invalid option: -${OPTARG} requires an argument" >&2
exit 1
;;
esac
done
# shift positional args based on number consumed by getopts
shift $((OPTIND-1))
if [ -z "${MODULE_LOCATION:-}" ] || [ -z "${DB_LOCATION:-}" ]; then
usage
exit 1
fi
main

View File

@@ -0,0 +1,88 @@
#!/usr/bin/env bash
set -euo pipefail
shopt -s inherit_errexit
###############################################################################
# Retrieves third-party modules and verifies their checksums
###############################################################################
function main() {
if [ -z "${SUPPLEMENTAL_MODULES}" ]; then
return 0 # Silently exit if there are no supplemental modules to target
fi
retrieve_modules
}
###############################################################################
# Download the modules
###############################################################################
function retrieve_modules() {
IFS=', ' read -r -a module_install_key_arr <<< "${SUPPLEMENTAL_MODULES}"
for module_install_key in "${module_install_key_arr[@]}"; do
download_url_env="SUPPLEMENTAL_${module_install_key^^}_DOWNLOAD_URL"
download_sha256_env="SUPPLEMENTAL_${module_install_key^^}_DOWNLOAD_SHA256"
if [ -n "${!download_url_env:-}" ] && [ -n "${!download_sha256_env:-}" ]; then
download_basename=$(basename "${!download_url_env}")
wget --ca-certificate=/etc/ssl/certs/ca-certificates.crt --referer https://inductiveautomation.com/* "${!download_url_env}" && \
[[ "notused" == "${!download_sha256_env}" ]] || echo "${!download_sha256_env}" "${download_basename}" | sha256sum -c -
else
echo "Error finding specified module ${module_install_key} in build args, aborting..."
exit 1
fi
done
}
###############################################################################
# Outputs to stderr
###############################################################################
function debug() {
# shellcheck disable=SC2236
if [ ! -z ${verbose+x} ]; then
>&2 echo " DEBUG: $*"
fi
}
###############################################################################
# Print usage information
###############################################################################
function usage() {
>&2 echo "Usage: $0 -m \"space-separated modules list\""
>&2 echo " -m: space-separated list of module identifiers to download"
}
# Argument Processing
while getopts ":hvm:" opt; do
case "$opt" in
v)
verbose=1
;;
m)
SUPPLEMENTAL_MODULES="${OPTARG}"
;;
h)
usage
exit 0
;;
\?)
usage
echo "Invalid option: -${OPTARG}" >&2
exit 1
;;
:)
usage
echo "Invalid option: -${OPTARG} requires an argument" >&2
exit 1
;;
esac
done
# shift positional args based on number consumed by getopts
shift $((OPTIND-1))
# exit on missing required args
if [ -z "${SUPPLEMENTAL_MODULES:-}" ]; then
usage
exit 1
fi
main

148
gw-build/register-password.sh Executable file
View File

@@ -0,0 +1,148 @@
#!/usr/bin/env bash
set -euo pipefail
shopt -s inherit_errexit
# Global variables
declare -u AUTH_SALT
###############################################################################
# Update an Ignition SQLite Configuration DB with a baseline username/password
# ----------------------------------------------------------------------------
# ref: https://gist.github.com/thirdgen88/c4257bd4c47b6cc7194d1f5e7cbd6444
###############################################################################
function main() {
if [ ! -f "${SECRET_LOCATION}" ]; then
echo ""
return 0 # Silently exit if there is no secret at target path
elif [ ! -f "${DB_LOCATION}" ]; then
echo "WARNING: ${DB_FILE} not found, skipping password registration"
return 0
fi
register_password
}
###############################################################################
# Updates the target Config DB with the target username and salted pw hash
###############################################################################
function register_password() {
local SQLITE3=( sqlite3 "${DB_LOCATION}" ) password_hash password_input
echo "Registering Admin Password with Configuration DB"
# Generate Salted PW Hash
password_input="$(< "${SECRET_LOCATION}")"
if [[ "${password_input}" =~ ^\[[0-9A-F]{8,}][0-9a-f]{64}$ ]]; then
debug "Password is already hashed"
password_hash="${password_input}"
else
password_hash=$(generate_salted_hash "$(<"${SECRET_LOCATION}")")
fi
# Update INTERNALUSERTABLE
echo " Setting default admin user to USERNAME='${GATEWAY_ADMIN_USERNAME}' and PASSWORD='${password_hash}'"
"${SQLITE3[@]}" "UPDATE INTERNALUSERTABLE SET USERNAME='${GATEWAY_ADMIN_USERNAME}', PASSWORD='${password_hash}' WHERE PROFILEID=1 AND USERID=1"
}
###############################################################################
# Processes password input and translates to salted hash
###############################################################################
function generate_salted_hash() {
local auth_pwhash auth_pwsalthash auth_password password_input
password_input="${1}"
debug "auth_salt is ${AUTH_SALT}"
auth_pwhash=$(printf %s "${password_input}" | sha256sum - | cut -c -64)
debug "auth_pwhash is ${auth_pwhash}"
auth_pwsalthash=$(printf %s "${password_input}${AUTH_SALT}" | sha256sum - | cut -c -64)
debug "auth_pwsalthash is ${auth_pwsalthash}"
auth_password="[${AUTH_SALT}]${auth_pwsalthash}"
echo "${auth_password}"
}
###############################################################################
# Outputs to stderr
###############################################################################
function debug() {
# shellcheck disable=SC2236
if [ ! -z ${verbose+x} ]; then
>&2 echo " DEBUG: $*"
fi
}
###############################################################################
# Print usage information
###############################################################################
function usage() {
>&2 echo "Usage: $0 -u <string> -f <path/to/file> -d <path/to/db> [...]"
>&2 echo " -u <string> Gateway Admin Username"
>&2 echo " -f <path/to/file> Path to secret file containing password or salted hash"
>&2 echo " -d <path/to/db> Path to Ignition Configuration DB"
>&2 echo " -s <salt method> Salt method, either 'timestamp' or 'random' (default)"
}
# Argument Processing
while getopts ":hvu:f:d:s:" opt; do
case "$opt" in
v)
verbose=1
;;
u)
GATEWAY_ADMIN_USERNAME="${OPTARG}"
;;
f)
SECRET_LOCATION="${OPTARG}"
;;
d)
DB_LOCATION="${OPTARG}"
DB_FILE=$(basename "${DB_LOCATION}")
;;
s)
# Compute AUTH_SALT based on timestamp or random
case "${OPTARG}" in
timestamp)
AUTH_SALT=$(date +%s | sha256sum | head -c 8)
;;
random)
# no-op, default will be set below
;;
*)
usage
echo "Invalid salt method: ${OPTARG}" >&2
exit 1
;;
esac
;;
h)
usage
exit 0
;;
\?)
usage
echo "Invalid option: -${OPTARG}" >&2
exit 1
;;
:)
usage
echo "Invalid option: -${OPTARG} requires an argument" >&2
exit 1
;;
esac
done
# shift positional args based on number consumed by getopts
shift $((OPTIND-1))
# Check for required defaults
if [ -z "${GATEWAY_ADMIN_USERNAME:-}" ] || [ -z "${SECRET_LOCATION:-}" ] || [ -z "${DB_LOCATION:-}" ]; then
usage
exit 1
fi
# set defaults for unset optional args
if [[ -z ${AUTH_SALT+x} ]]; then
AUTH_SALT=$(od -An -v -t x1 -N 4 /dev/random | tr -d ' ')
fi
main

88
gw-build/retrieve-modules.sh Executable file
View File

@@ -0,0 +1,88 @@
#!/usr/bin/env bash
set -euo pipefail
shopt -s inherit_errexit
###############################################################################
# Retrieves third-party modules and verifies their checksums
###############################################################################
function main() {
if [ -z "${SUPPLEMENTAL_MODULES}" ]; then
return 0 # Silently exit if there are no supplemental modules to target
fi
retrieve_modules
}
###############################################################################
# Download the modules
###############################################################################
function retrieve_modules() {
IFS=', ' read -r -a module_install_key_arr <<< "${SUPPLEMENTAL_MODULES}"
for module_install_key in "${module_install_key_arr[@]}"; do
download_url_env="SUPPLEMENTAL_${module_install_key^^}_DOWNLOAD_URL"
download_sha256_env="SUPPLEMENTAL_${module_install_key^^}_DOWNLOAD_SHA256"
if [ -n "${!download_url_env:-}" ] && [ -n "${!download_sha256_env:-}" ]; then
download_basename=$(basename "${!download_url_env}")
wget --ca-certificate=/etc/ssl/certs/ca-certificates.crt --referer https://inductiveautomation.com/* "${!download_url_env}" && \
[[ "notused" == "${!download_sha256_env}" ]] || echo "${!download_sha256_env}" "${download_basename}" | sha256sum -c -
else
echo "Error finding specified module ${module_install_key} in build args, aborting..."
exit 1
fi
done
}
###############################################################################
# Outputs to stderr
###############################################################################
function debug() {
# shellcheck disable=SC2236
if [ ! -z ${verbose+x} ]; then
>&2 echo " DEBUG: $*"
fi
}
###############################################################################
# Print usage information
###############################################################################
function usage() {
>&2 echo "Usage: $0 -m \"space-separated modules list\""
>&2 echo " -m: space-separated list of module identifiers to download"
}
# Argument Processing
while getopts ":hvm:" opt; do
case "$opt" in
v)
verbose=1
;;
m)
SUPPLEMENTAL_MODULES="${OPTARG}"
;;
h)
usage
exit 0
;;
\?)
usage
echo "Invalid option: -${OPTARG}" >&2
exit 1
;;
:)
usage
echo "Invalid option: -${OPTARG} requires an argument" >&2
exit 1
;;
esac
done
# shift positional args based on number consumed by getopts
shift $((OPTIND-1))
# exit on missing required args
if [ -z "${SUPPLEMENTAL_MODULES:-}" ]; then
usage
exit 1
fi
main

5
gw-init/gateway.env Normal file
View File

@@ -0,0 +1,5 @@
ACCEPT_IGNITION_EULA=Y
# GATEWAY_ADMIN_USERNAME=admin
# GATEWAY_ADMIN_PASSWORD_FILE=/run/secrets/gateway-admin-password
TZ=America/Chicago
GATEWAY_MODULES_ENABLED=all

Binary file not shown.

View File

@@ -0,0 +1 @@
password

1
secrets/db-root-password Normal file
View File

@@ -0,0 +1 @@
password

View File

@@ -0,0 +1 @@
password