From e96b8db7c5ccebe01483bcee77f084fce381ec04 Mon Sep 17 00:00:00 2001 From: Dmitry Smirnov Date: Mon, 6 Jan 2025 16:34:16 +0200 Subject: [PATCH 01/13] symlink to supervisord config --- Dockerfile | 10 +++++----- lib/process_manager.sh | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6083e605..f45204cd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -103,11 +103,8 @@ RUN groupadd -g ${GID} ${USER} && \ useradd -l -m -u ${UID} -g ${GID} -s /bin/bash ${USER} # Create the Supervisor log directory and set permissions -RUN mkdir -p /var/log/supervisor && \ - chown -R ${USER}:${USER} /var/log/supervisor - -# Create a directory for Supervisor runtime files -RUN mkdir -p /var/run/supervisor && chown -R ${USER}:${USER} /var/run/supervisor +RUN mkdir -p /var/log/supervisor /var/run/supervisor /home/${USER}/etc && \ + chown -R ${USER}:${USER} /var/log/supervisor /var/run/supervisor /home/${USER}/etc # Prepare directories for the user and worker configuration RUN mkdir -p /etc/worker /home/${USER}/.cd/bin /home/${USER}/.cd/configs && \ @@ -137,6 +134,9 @@ COPY ./tests/tasks /usr/local/tests/tasks RUN chmod +x /usr/local/bin/entrypoint.sh /usr/local/tests/main.sh && \ chown -R ${UID}:${GID} /usr/local/lib /etc/worker /home/${USER}/etc /home/${USER}/.cd /usr/local/tests +# Create a symbolic link for the supervisord configuration file +RUN ln -sf /home/${USER}/etc/supervisord.conf /etc/supervisord.conf + # Switch to non-root user USER ${USER} diff --git a/lib/process_manager.sh b/lib/process_manager.sh index b4ca4404..a8ef047d 100644 --- a/lib/process_manager.sh +++ b/lib/process_manager.sh @@ -46,7 +46,7 @@ parse_service_info() { # Function to start Supervisor with the generated configuration start_supervisor() { echo "Starting Supervisor with the generated configuration..." - supervisord -c "$FINAL_CONFIG" + supervisord } # Function to configure and start the Supervisor From b593606dc54ab846bc3e748ce02993bd150b5c27 Mon Sep 17 00:00:00 2001 From: Dmitry Smirnov Date: Mon, 6 Jan 2025 16:52:06 +0200 Subject: [PATCH 02/13] entrypoint fix --- bin/entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/entrypoint.sh b/bin/entrypoint.sh index a5972ce3..0e555cee 100644 --- a/bin/entrypoint.sh +++ b/bin/entrypoint.sh @@ -53,7 +53,7 @@ wait_for_services() { # Main execution path if [ "$#" -gt 0 ]; then - log_info "Executing command:" "$*" + log_info "Executing command: $*" "$@" # Execute the provided command cmd_exit_status=$? handle_services $cmd_exit_status From f58be4217e4a97461856681c5796840c2bbc8207 Mon Sep 17 00:00:00 2001 From: Dmitry Smirnov Date: Tue, 7 Jan 2025 12:04:09 +0200 Subject: [PATCH 03/13] change service flag: enabled->ignore --- lib/process_manager.sh | 6 +++--- src/configs/services.yml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/process_manager.sh b/lib/process_manager.sh index a8ef047d..c93049ad 100644 --- a/lib/process_manager.sh +++ b/lib/process_manager.sh @@ -9,8 +9,8 @@ FINAL_CONFIG="/home/${USER}/etc/supervisord.conf" # Function to check for service configurations should_generate_config() { local enabled_services_count - # Extract enabled services into JSON format - services_yaml=$(yq e -o=json '.services[] | select(.enabled == true)' "$CONFIG_FILE") + # Extract services into JSON format + services_yaml=$(yq e -o=json '.services[] | select(.ignore != true)' "$CONFIG_FILE") # Count the number of items in the JSON array, trimming any newlines or spaces enabled_services_count=$(echo "$services_yaml" | jq -c '. | length' | tr -d '\n') @@ -61,7 +61,7 @@ configure_and_execute_services() { # Convert enabled services to JSON and process each. local services_yaml - services_yaml=$(yq e -o=json '.services[] | select(.enabled == true)' "$CONFIG_FILE" | jq -c .) + services_yaml=$(yq e -o=json '.services[] | select(.ignore != true)' "$CONFIG_FILE" | jq -c .) if [ -z "$services_yaml" ]; then echo "Failed to parse services from $CONFIG_FILE or no services defined." diff --git a/src/configs/services.yml b/src/configs/services.yml index 449385b1..4df92a62 100644 --- a/src/configs/services.yml +++ b/src/configs/services.yml @@ -3,7 +3,7 @@ kind: workerService version: udx.io/worker-v1/service services: - name: "app_service" - enabled: "false" + ignore: "true" command: "sh /usr/local/scripts/process_example.sh" autostart: "true" autorestart: "false" @@ -11,7 +11,7 @@ services: - "KEY1=value1" - "KEY2=value2" - name: "another_service" - enabled: "false" + ignore: "true" command: "sh /usr/local/scripts/process_example.sh" autostart: "true" autorestart: "true" From 275b3a3de13eb6c043ed9c1fc33ef123cf77ffb3 Mon Sep 17 00:00:00 2001 From: Dmitry Smirnov Date: Tue, 7 Jan 2025 12:25:59 +0200 Subject: [PATCH 04/13] initialized cli --- Dockerfile | 5 +++++ lib/cli.sh | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 lib/cli.sh diff --git a/Dockerfile b/Dockerfile index f45204cd..875ef636 100644 --- a/Dockerfile +++ b/Dockerfile @@ -117,6 +117,11 @@ RUN mkdir -p /etc/worker /home/${USER}/.cd/bin /home/${USER}/.cd/configs && \ # Switch to the user directory WORKDIR /home/${USER} +# Copy the CLI tool into the image +COPY lib/cli.sh /usr/local/bin/udx_worker_mgmt +RUN chmod +x /usr/local/bin/udx_worker_mgmt && \ + ln -s /usr/local/bin/udx_worker_mgmt /usr/local/bin/worker + # Copy built-in worker.yml to the container COPY ./src/configs /etc/worker COPY ./src/scripts /usr/local/scripts diff --git a/lib/cli.sh b/lib/cli.sh new file mode 100644 index 00000000..b8b05b84 --- /dev/null +++ b/lib/cli.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +# Function to list all services +list_services() { + supervisorctl status +} + +# Function to check the status of one or all services +check_status() { + if [ -z "$1" ]; then + supervisorctl status + else + supervisorctl status $1 + fi +} + +# Function to follow logs for a specific service +follow_logs() { + tail -f /var/log/supervisor/$1-*.log +} + +# Function to show supervisor configuration +show_config() { + cat /etc/supervisor/supervisord.conf +} + +# Function to start, stop, or restart a service +manage_service() { + supervisorctl $1 $2 +} + +# CLI Interface +case $1 in + list) + list_services + ;; + status) + check_status $2 + ;; + logs) + follow_logs $2 + ;; + config) + show_config + ;; + start|stop|restart) + manage_service $1 $2 + ;; + *) + echo "Usage: $0 {list|status [service_name]|logs |config|start |stop |restart }" + exit 1 + ;; +esac \ No newline at end of file From 7b3f1d581494d7613263c45b55bc88703721ed0d Mon Sep 17 00:00:00 2001 From: Dmitry Smirnov Date: Tue, 7 Jan 2025 12:53:01 +0200 Subject: [PATCH 05/13] shell fixes --- lib/cli.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/cli.sh b/lib/cli.sh index b8b05b84..6ceeb772 100644 --- a/lib/cli.sh +++ b/lib/cli.sh @@ -10,13 +10,13 @@ check_status() { if [ -z "$1" ]; then supervisorctl status else - supervisorctl status $1 + supervisorctl status "$1" fi } # Function to follow logs for a specific service follow_logs() { - tail -f /var/log/supervisor/$1-*.log + tail -f /var/log/supervisor/"$1"-*.log } # Function to show supervisor configuration @@ -26,7 +26,7 @@ show_config() { # Function to start, stop, or restart a service manage_service() { - supervisorctl $1 $2 + supervisorctl "$1" "$2" } # CLI Interface @@ -35,16 +35,16 @@ case $1 in list_services ;; status) - check_status $2 + check_status "$2" ;; logs) - follow_logs $2 + follow_logs "$2" ;; config) show_config ;; start|stop|restart) - manage_service $1 $2 + manage_service "$1" "$2" ;; *) echo "Usage: $0 {list|status [service_name]|logs |config|start |stop |restart }" From 6eded2713ce72d6d234d7db5dca5fdf3b5652545 Mon Sep 17 00:00:00 2001 From: Dmitry Smirnov Date: Tue, 7 Jan 2025 13:13:42 +0200 Subject: [PATCH 06/13] cli readme --- CLI.md | 25 ++++++++++++++++++++++++ lib/cli.sh | 57 ++++++++++++++++++++++++++++++++++++------------------ 2 files changed, 63 insertions(+), 19 deletions(-) create mode 100644 CLI.md diff --git a/CLI.md b/CLI.md new file mode 100644 index 00000000..faa0a7b8 --- /dev/null +++ b/CLI.md @@ -0,0 +1,25 @@ +### Commands + +* `worker service list` +Lists all available services. + +* `worker service status [service_name]` +Displays the status of a specified service or all services if no name is provided. + +* `worker service logs ` +Tails the output logs for a specific service. + +* `worker service errors ` +Tails the error logs for a specific service. + +* `worker service config` +Shows the services configuration settings. + +* `worker service start ` +Starts a specified service. + +* `worker service stop ` +Stops a specified service. + +* `worker service restart ` +Restarts a specified service. \ No newline at end of file diff --git a/lib/cli.sh b/lib/cli.sh index 6ceeb772..53971b64 100644 --- a/lib/cli.sh +++ b/lib/cli.sh @@ -16,12 +16,13 @@ check_status() { # Function to follow logs for a specific service follow_logs() { - tail -f /var/log/supervisor/"$1"-*.log + local type=${2:-out} # Use 'out' if $2 is unset or null. + tail -f /var/log/supervisor/"$1"."$type".log } # Function to show supervisor configuration show_config() { - cat /etc/supervisor/supervisord.conf + cat /etc/supervisord.conf } # Function to start, stop, or restart a service @@ -31,23 +32,41 @@ manage_service() { # CLI Interface case $1 in - list) - list_services - ;; - status) - check_status "$2" - ;; - logs) - follow_logs "$2" - ;; - config) - show_config - ;; - start|stop|restart) - manage_service "$1" "$2" - ;; + service) + shift # Shift the arguments to the left, so $2 becomes $1, $3 becomes $2, etc. + case $1 in + list) + list_services + ;; + status) + check_status "$2" + ;; + logs) + follow_logs "$2" + ;; + errors) + follow_logs "$2" "err" + ;; + config) + show_config + ;; + start) + manage_service start "$2" + ;; + stop) + manage_service stop "$2" + ;; + restart) + manage_service restart "$2" + ;; + *) + echo "Usage: $0 service {list|status [service_name]|logs |config|start |stop |restart }" + exit 1 + ;; + esac + ;; *) - echo "Usage: $0 {list|status [service_name]|logs |config|start |stop |restart }" + echo "Usage: $0 {service {list|status [service_name]|logs |config|start |stop |restart }}" exit 1 - ;; + ;; esac \ No newline at end of file From 745b4fb8b80f4732db754f30de6477a1e08f972b Mon Sep 17 00:00:00 2001 From: Dmitry Smirnov Date: Tue, 7 Jan 2025 14:16:33 +0200 Subject: [PATCH 07/13] configs docs --- lib/process_manager.sh | 20 +++++++++---- src/configs/{readme.md => _worker_config.md} | 0 src/configs/_worker_services.md | 31 ++++++++++++++++++++ src/configs/services.yml | 4 +-- 4 files changed, 48 insertions(+), 7 deletions(-) rename src/configs/{readme.md => _worker_config.md} (100%) create mode 100644 src/configs/_worker_services.md diff --git a/lib/process_manager.sh b/lib/process_manager.sh index c93049ad..b4511ac7 100644 --- a/lib/process_manager.sh +++ b/lib/process_manager.sh @@ -25,22 +25,32 @@ should_generate_config() { # Helper function to parse and process each service configuration parse_service_info() { local service_json="$1" - local name command autostart autorestart environment + local name command autostart autorestart envs name=$(echo "$service_json" | jq -r '.name') command=$(echo "$service_json" | jq -r '.command') - autostart=$(echo "$service_json" | jq -r '.autostart // "false"') + # Ensure 'ignore' is considered. If not present, default to "false" + ignore=$(echo "$service_json" | jq -r '.ignore // "false"') + # Use 'true' as default for 'autostart' if not specified + autostart=$(echo "$service_json" | jq -r '.autostart // "true"') + # Use 'false' as default for 'autorestart' if not specified autorestart=$(echo "$service_json" | jq -r '.autorestart // "false"') - environment=$(echo "$service_json" | jq -r '.environment // [] | join(",")') + # Ensure 'envs' defaults to an empty array if not specified + envs=$(echo "$service_json" | jq -r '.envs // [] | join(",")') + # Ignore the service if 'ignore' is set to "true" + if [[ "$ignore" == "true" ]]; then + return + fi + # Add an additional newline for better separation and readability - echo -e "\n" >> "$FINAL_CONFIG" # Adds two newlines to the end of the file + echo -e "\n" >> "$FINAL_CONFIG" sed "s|\${process_name}|$name|g; \ s|\${command}|$command|g; \ s|\${autostart}|$autostart|g; \ s|\${autorestart}|$autorestart|g; \ - s|\${envs}|$environment|g" "$PROGRAM_TEMPLATE_FILE" >> "$FINAL_CONFIG" + s|\${envs}|$envs|g" "$PROGRAM_TEMPLATE_FILE" >> "$FINAL_CONFIG" } # Function to start Supervisor with the generated configuration diff --git a/src/configs/readme.md b/src/configs/_worker_config.md similarity index 100% rename from src/configs/readme.md rename to src/configs/_worker_config.md diff --git a/src/configs/_worker_services.md b/src/configs/_worker_services.md new file mode 100644 index 00000000..116cc89f --- /dev/null +++ b/src/configs/_worker_services.md @@ -0,0 +1,31 @@ +## Services + +The `services.yml` file, located in the `src/configs` directory, is a critical component for configuring service behavior in the UDX Worker environment. This file follows the `workerService` kind specification, designed to manage and orchestrate services dynamically, based on the `udx.io/worker-v1/service` version schema. Below, we break down the structure and explain the significance of each field, including default values as defined in the `lib/process_manager.sh` script. + +```yaml +--- +kind: workerService +version: udx.io/worker-v1/service +services: + - name: "" + ignore: "" + command: "" + autostart: "" + autorestart: "" + envs: + - "=" +``` + +### Service Fields Explanation + +* `name`: Unique identifier for the service. This is used to reference and manage the service within the system. + +* `ignore`: Determines whether the service should be ignored. If set to "true", the service will not be managed by the worker. The default value is "false", which means the service is considered for management. + +* `command`: The command that the service will execute. This could be a shell script or any executable along with its arguments. + +* `autostart`: Indicates whether the service should start automatically when the worker starts. The default is "true", meaning the service will start automatically. + +* `autorestart`: Specifies whether the service should automatically restart if it stops. If "true", the service will restart according to the policy defined by the worker management system. The default value is "false", indicating the service will not restart automatically. + +* `envs`: An array of environment variables passed to the service in the format `KEY=value`. These variables are made available to the service at runtime. diff --git a/src/configs/services.yml b/src/configs/services.yml index 4df92a62..3b3b528f 100644 --- a/src/configs/services.yml +++ b/src/configs/services.yml @@ -7,7 +7,7 @@ services: command: "sh /usr/local/scripts/process_example.sh" autostart: "true" autorestart: "false" - environment: + envs: - "KEY1=value1" - "KEY2=value2" - name: "another_service" @@ -15,4 +15,4 @@ services: command: "sh /usr/local/scripts/process_example.sh" autostart: "true" autorestart: "true" - environment: [] + envs: [] From 297ffe571b0a288b8bfdce9d679573064cce3707 Mon Sep 17 00:00:00 2001 From: Dmitry Smirnov Date: Tue, 7 Jan 2025 14:21:04 +0200 Subject: [PATCH 08/13] servcices config error handling --- lib/process_manager.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/process_manager.sh b/lib/process_manager.sh index b4511ac7..286b91d5 100644 --- a/lib/process_manager.sh +++ b/lib/process_manager.sh @@ -42,6 +42,18 @@ parse_service_info() { if [[ "$ignore" == "true" ]]; then return fi + + # Check if 'name' is set, log error and return if not + if [ -z "$name" ]; then + echo "Error: 'name' not set for a service. Skipping..." + return + fi + + # Check if 'command' is set, log error and return if not + if [ -z "$command" ]; then + echo "Error: 'command' not set for service $name. Skipping..." + return + fi # Add an additional newline for better separation and readability echo -e "\n" >> "$FINAL_CONFIG" From ef31d3d2bb9bda14926e4bf3f894a212d5dc1668 Mon Sep 17 00:00:00 2001 From: Dmitry Smirnov Date: Tue, 7 Jan 2025 14:40:29 +0200 Subject: [PATCH 09/13] entrypoint update --- bin/entrypoint.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bin/entrypoint.sh b/bin/entrypoint.sh index 0e555cee..ebff5770 100644 --- a/bin/entrypoint.sh +++ b/bin/entrypoint.sh @@ -18,8 +18,7 @@ handle_services() { log_info "Tailing Supervisor logs to keep the container alive." tail -f /var/log/supervisor/supervisord.log else - log_warn "No services are active. Exiting after a short delay." - sleep 10 + log_warn "No services are active." exit "${cmd_exit_status:-0}" # Use 0 if cmd_exit_status is unset fi } @@ -51,9 +50,13 @@ wait_for_services() { return 1 } +# Main execution path # Main execution path if [ "$#" -gt 0 ]; then log_info "Executing command: $*" + if [ "$1" == "exit" ]; then + exit 0 + fi "$@" # Execute the provided command cmd_exit_status=$? handle_services $cmd_exit_status From 39a4845cefabc8d088c5e4f4fd806d1753cb85aa Mon Sep 17 00:00:00 2001 From: Dmitry Smirnov Date: Tue, 7 Jan 2025 15:04:09 +0200 Subject: [PATCH 10/13] cli error handling --- lib/cli.sh | 70 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 6 deletions(-) diff --git a/lib/cli.sh b/lib/cli.sh index 53971b64..0e65eff9 100644 --- a/lib/cli.sh +++ b/lib/cli.sh @@ -2,31 +2,89 @@ # Function to list all services list_services() { - supervisorctl status + # Capture the output of supervisorctl status + local services_status + services_status=$(supervisorctl status 2>&1) # Also capture stderr to handle error messages + + # Check if Supervisor is not running, not accessible, or if there are no managed services + if [[ -z "$services_status" ]] || echo "$services_status" | grep -Eq 'no such|ERROR'; then + echo "No services are currently managed." + exit 1 + fi + + echo "Listing all managed services:" + local i=1 + echo "$services_status" | while read -r line; do + echo "$i. $line" + ((i++)) + done } # Function to check the status of one or all services check_status() { + # Require a service name for this function if [ -z "$1" ]; then - supervisorctl status - else - supervisorctl status "$1" + echo "Error: No service name provided." + echo "Usage: $0 status " + exit 1 fi + + # Attempt to capture the status of the specific service, including errors + local service_status + service_status=$(supervisorctl status "$1" 2>&1) + + # Check if Supervisor is not running, not accessible, or if the service does not exist + if [[ -z "$service_status" ]] || echo "$service_status" | grep -Eq 'no such|ERROR'; then + echo "The service '$1' does not exist." + exit 1 + fi + + # Directly output the captured service status + echo "$service_status" } # Function to follow logs for a specific service follow_logs() { - local type=${2:-out} # Use 'out' if $2 is unset or null. - tail -f /var/log/supervisor/"$1"."$type".log + if [ -z "$1" ]; then + echo "Error: No service name provided." + echo "Usage: $0 logs " + exit 1 + fi + + local logfile="/var/log/supervisor/$1" + local type=${2:-out} # Default to 'out' if not specified + logfile="$logfile.$type.log" + + if [ ! -f "$logfile" ]; then + echo "Log file does not exist: $logfile" + exit 1 + fi + + tail -f "$logfile" } # Function to show supervisor configuration show_config() { + if [ ! -f "/etc/supervisord.conf" ]; then + echo "Configuration file is not generated since no services are managed." + exit 1 + fi cat /etc/supervisord.conf } # Function to start, stop, or restart a service manage_service() { + if [ -z "$2" ]; then + echo "Error: No service name provided." + echo "Usage: $0 $1 " + exit 1 + fi + + if [ ! -e "/var/run/supervisor/supervisord.sock" ]; then + echo "Error: Service doesn't exist." + exit 1 + fi + supervisorctl "$1" "$2" } From e53a7494c28545d33112ed62f3d4097c3e73b36e Mon Sep 17 00:00:00 2001 From: Dmitry Smirnov Date: Tue, 7 Jan 2025 15:11:07 +0200 Subject: [PATCH 11/13] tweak --- CLI.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CLI.md b/CLI.md index faa0a7b8..ef7c9dc2 100644 --- a/CLI.md +++ b/CLI.md @@ -3,8 +3,8 @@ * `worker service list` Lists all available services. -* `worker service status [service_name]` -Displays the status of a specified service or all services if no name is provided. +* `worker service status ` +Displays the status of a specified service. * `worker service logs ` Tails the output logs for a specific service. From d83d6b2030c7924b8dc40f99263ee8d527e05586 Mon Sep 17 00:00:00 2001 From: Dmitry Smirnov Date: Tue, 7 Jan 2025 17:34:43 +0200 Subject: [PATCH 12/13] prevent exit on entrypoint when no services enabled --- bin/entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/entrypoint.sh b/bin/entrypoint.sh index ebff5770..c3112f58 100644 --- a/bin/entrypoint.sh +++ b/bin/entrypoint.sh @@ -19,7 +19,7 @@ handle_services() { tail -f /var/log/supervisor/supervisord.log else log_warn "No services are active." - exit "${cmd_exit_status:-0}" # Use 0 if cmd_exit_status is unset + tail -f /dev/null fi } From f6d9ef084996a529831b409dd23a5156be7ddbfd Mon Sep 17 00:00:00 2001 From: Dmitry Smirnov Date: Tue, 7 Jan 2025 17:40:49 +0200 Subject: [PATCH 13/13] update doc --- src/configs/_worker_services.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/configs/_worker_services.md b/src/configs/_worker_services.md index 116cc89f..affcce88 100644 --- a/src/configs/_worker_services.md +++ b/src/configs/_worker_services.md @@ -29,3 +29,19 @@ services: * `autorestart`: Specifies whether the service should automatically restart if it stops. If "true", the service will restart according to the policy defined by the worker management system. The default value is "false", indicating the service will not restart automatically. * `envs`: An array of environment variables passed to the service in the format `KEY=value`. These variables are made available to the service at runtime. + +## Usage + +To use these configuration file, ensure that the `services.yml` file is correctly configured and placed in the appropriate directory (`/etc/worker`) within the container. + +### Volume Mount + +If you have a worker configuration outside of the worker image, you can mount it as a volume into the container: + +```shell +docker run -d --name udx-worker \ + --env-file .env \ + -v $(pwd)/my-tasks:/usr/src/app \ + -v $(pwd)/src/configs/services.yml:/etc/worker \ + usabilitydynamics/udx-worker:latest +``` \ No newline at end of file