Search code examples
dockernexus

Customize Nexus UI using a Docker container


is there a way to customize the Nexus UI Nexus UI? I need to add some links to the imprint page of our organisation... I am using the docker-nexus3 image and I am trying to find the location of the frontend's *.html or *.js files there... However, I am not able to find them anywhere.

I've manually searched for the file and also used the "find" and "grep" command inside the container, e.g.

find / -name *.js
find / -name *.html

and

find / -xdev -type f -print0 | xargs -0 grep -H "label-1154"

Thanks!


Solution

  • I strongly discourage you to continue on that path. You won't find any of those js/html files as the only ones that do exist will be dynamically generated by the java application in a tmp directory when it starts and will be trashed on each reboot.

    Nexus has a branding capability that you can enable in Administration > System > Capabilities. Once done, you can click on it, then on the settings tab where you can enter content for a header and a footer. Both field accept html content.

    Note that this capability used to be available for the OpenSource Version but is now reserved to the Pro version. Meanwhile, I still have it enabled on all my open source instance and the groovy script we use to automate this does not seem to fire any errors.

    If you are interested in automating this, here is the groovy script to register in Nexus that you can find in an ansible role we use to install nexus (disclaimer: I'm the author).

    import groovy.json.JsonSlurper
    import org.sonatype.nexus.capability.CapabilityReference
    import org.sonatype.nexus.capability.CapabilityType
    import org.sonatype.nexus.internal.capability.DefaultCapabilityReference
    import org.sonatype.nexus.internal.capability.DefaultCapabilityRegistry
    
    parsed_args = new JsonSlurper().parseText(args)
    
    parsed_args.capability_properties['headerEnabled'] = parsed_args.capability_properties['headerEnabled'].toString()
    parsed_args.capability_properties['footerEnabled'] = parsed_args.capability_properties['footerEnabled'].toString()
    
    def capabilityRegistry = container.lookup(DefaultCapabilityRegistry.class.getName())
    def capabilityType = CapabilityType.capabilityType(parsed_args.capability_typeId)
    
    DefaultCapabilityReference existing = capabilityRegistry.all.find { CapabilityReference capabilityReference ->
        capabilityReference.context().descriptor().type() == capabilityType
    }
    
    if (existing) {
        log.info(parsed_args.typeId + ' capability updated to: {}',
                capabilityRegistry.update(existing.id(), Boolean.valueOf(parsed_args.get('capability_enabled', true)), existing.notes(), parsed_args.capability_properties).toString()
        )
    }
    else {
        log.info(parsed_args.typeId + ' capability created as: {}', capabilityRegistry.
                add(capabilityType, Boolean.valueOf(parsed_args.get('capability_enabled', true)), 'configured through api', parsed_args.capability_properties).toString()
        )
    }
    

    To enable branding with some content, here is an example json payload the script is expecting:

    {
        "capability_typeId": "rapture.branding",
        "capability_enabled": true,
        "capability_properties": {
            "footerHtml": "Your footer content",
            "headerHtml": "Your header content",
            "footerEnabled": true,
            "headerEnabled": true
    }
    

    With some extra work, it is possible to add the script and the configure payload to the image, then create an entry point script which will configure nexus with your defaults if not already initialized (on the same idea you can find in the official mysql or postgres images for example).

    The global workflow is:

    1. Check if data directory is empty else start nexus normally
    2. If empty set var to allow scripts in nexus
    3. Start nexus for provisioning only
    4. Capture generated admin password on disk
    5. copy the script through Nexus API
    6. call the script with the relevant payload
    7. optionally remove the script through API
    8. optionally disable scripting
    9. Restart nexus normally

    I have applied the above recipe to an image build we use on production which basically does the same thing but with much more scripts and a lot more data to pre-provision the instance (users, repositories, roles, permissions, content selectors....) if it starts from scratch. I unfortunately cannot share that entire work as is but below is a stripped down (not tested) version of the entrypoint for an example. Hope it can help.

    entrypoint.sh

    #!/usr/bin/bash
    set -e
    
    # Read or set default admin password
    NEXUS_ADMIN_PASSWORD=${NEXUS_ADMIN_PASSWORD:-changeme}
    
    # Set nexus REST api base url
    nexus_rest_url="http://localhost:8081/service/rest"
    
    call_api() {
      method=$1
      path=$2
      params=$3
      content_type=${4:-application/json}
      password="${5:-${NEXUS_ADMIN_PASSWORD}}"
      nexus_auth="admin:${password}"
      if [ ! -z "${NEXUS_ENABLE_DEBUG_PROVISIONNING+x}" ]; then
        echo curl -u "$nexus_auth" --write '\n%{http_code}\n' --fail --silent -H "accept: application/json" \
        -H "Content-Type: ${content_type}" -X ${method} "${nexus_rest_url}${path}" ${params:+-d "$params"}
      fi
      if result=$(curl -u "$nexus_auth" --write '\n%{http_code}\n' --fail --silent -H "accept: application/json" \
        -H "Content-Type: ${content_type}" -X ${method} "${nexus_rest_url}${path}" ${params:+-d "$params"}); then
        if [ ! -z NEXUS_ENABLE_DEBUG_PROVISIONNING ]; then
          echo "$result" | sed '$d'
        fi
      else
        >&2 echo "Failure: code=$result"
        exit 1
      fi
    }
    
    # Helper function to read the content of groovy script and workaround needed escapes for json
    load_script_content() {
      script_name="$1"
      sed -e ':a' -e 'N' -e '$!ba' -e 's/\\/\\\\/g' -e 's/\n/\\n /g' ${NEXUS_SCRIPTS}/${script_name}.groovy
    }
    
    # Helper function to upload new scripts in nexus
    upload_script() {
      script_name="$1"
      script_content=$(load_script_content $script_name)
      json_payload='{"name": "'"${script_name}"'",  "content": "'"${script_content}"'",  "type": "groovy"}'
      echo "Uploading script $script_name"
      call_api POST /v1/script "${json_payload}"
    }
    
    # Get a list of all nexus scripts to upload
    list_nexus_scripts() {
      script_list=$(find ${NEXUS_SCRIPTS} -name "*.groovy" -exec basename {} \;)
      echo ${script_list//.groovy/}
    }
    
    # We only try to initialize nexus if we were called with the default command
    if [ "$1" = 'start-nexus-repository-manager' ]; then
      # Check if the current data folder is empty
      # i.e we simply check if it contains the blobs directory
      # If it is empty, we start nexus manually and initialize
      # with the sent data
      if [ ! -d /nexus-data/blobs ]; then
        echo "Nexus data dir is empty. Starting initialization routines"
    
        echo "Make sure /etc directory exists in ${NEXUS_DATA}"
        mkdir -p ${NEXUS_DATA}/etc
        
        echo "Allowing nexus to register groovy scripts"
        echo "nexus.scripts.allowCreation=true" >> ${NEXUS_DATA}/etc/nexus.properties
    
        echo "starting nexus for the first time"
        $SONATYPE_DIR/nexus/bin/nexus start
    
        echo "Waiting for nexus to fully start and be available (max 2mn)"
        try_delay=5
        max_time=120
        try_counter=0
        while ! ([ -f ${NEXUS_DATA}/log/nexus.log ] && grep -q "Started Sonatype Nexus OSS" "${NEXUS_DATA}/log/nexus.log"); do
          (( try_counter = try_counter + 1 ))
          if (( try_counter * try_delay > max_time )); then
            echo -ne "\n"
            echo "Error: nexus did not finish starting after 2mn. Stopping. Dumping nexus log file for further debugging"
            cat "${NEXUS_DATA}/log/nexus.log"
            exit 1
          fi
          echo -n "."
          sleep $try_delay
        done
        echo -ne "\n"
        echo "Nexus started."
    
        # Get admin password from generated file
        NEXUS_ADMIN_PASSWORD="$(cat ${NEXUS_DATA}/admin.password)"
    
        # See the above functions. Set NEXUS_SCRIPT in your
        # Dockerfile and upload the scripts there
        echo "Uploading all defined scripts to nexus"
        for script in $(list_nexus_scripts); do
          upload_script $script
        done
    
        # Created from existing script call for your case
        echo Configure branding
        call_api POST /v1/script/create_repos_from_list/run @${NEXUS_SCRIPTS}/branding_payload.json
    
        # Set NEXUS_DELETE_PROVIONNING_SCRIPTS to a non empty value to delete
        if  [ ! -z "${NEXUS_DELETE_PROVIONNING_SCRIPTS+x}" ]; then
          echo "Deleting grooyy provisionning scripts in nexus"
          for script in $(list_nexus_scripts); do
            call_api DELETE "/v1/script/${script}"
          done
        fi
    
        # Set NEXUS_DELETE_PROVIONNING_SCRIPTS to a non empty value to disable
        if  [ ! -z "${NEXUS_DISABLE_GROOVY_AFTER_PROVISIONNING+x}" ]; then
          echo "Disabling further declaration of groovy scripts in nexus"
          sed -i 's/\(nexus\.scripts\.allowCreation=\).*/\1false/' ${NEXUS_DATA}/etc/nexus.properties
        fi
    
        echo "Restarting nexus after initial provisionning"
        $SONATYPE_DIR/nexus/bin/nexus stop
      fi
    fi
    
    exec "$@"