├── .dockerignore ├── .github ├── actions │ └── publish_docker_image │ │ └── action.yml ├── dependabot.yml └── workflows │ ├── build_docker_image.yml │ └── erddap_version.yml ├── .gitignore ├── Dockerfile ├── README.md ├── datasets.d.sh ├── entrypoint.sh ├── examples ├── data │ └── trees.csv ├── datasets.d │ └── trees.csv.xml ├── docker-compose.yml └── rsyslog │ ├── rsyslog.conf │ └── rsyslog.d │ ├── 00-imports.conf │ ├── 10-tomcat.conf │ └── 20-erddap.conf ├── files └── setenv.sh ├── init.d └── 10-remove-hdf-nc-range-request-exclusion.sh ├── make-dataset-xml.sh ├── test └── datasets.d-inactive-files.sh └── update-server-xml.sh /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .gitignore 3 | README.md 4 | logs 5 | examples 6 | -------------------------------------------------------------------------------- /.github/actions/publish_docker_image/action.yml: -------------------------------------------------------------------------------- 1 | name: Publish Docker images 2 | description: Publish Docker images to Docker Hub 3 | inputs: 4 | DOCKER_USERNAME: 5 | description: Docker Hub username 6 | required: true 7 | DOCKER_TOKEN: 8 | description: Docker Hub token 9 | required: true 10 | runs: 11 | using: composite 12 | steps: 13 | - name: Docker meta 14 | id: meta 15 | uses: docker/metadata-action@v5 16 | with: 17 | images: | 18 | axiom/docker-erddap 19 | flavor: | 20 | suffix=-jdk21-openjdk,onlatest=true 21 | tags: | 22 | type=raw,value=latest,enable={{is_default_branch}} 23 | type=ref,event=tag 24 | 25 | - name: Login to Docker Hub 26 | uses: docker/login-action@v3 27 | with: 28 | username: ${{ inputs.DOCKER_USERNAME }} 29 | password: ${{ inputs.DOCKER_TOKEN }} 30 | 31 | - name: Push to Docker Hub 32 | uses: docker/build-push-action@v6 33 | with: 34 | push: true 35 | platforms: linux/amd64,linux/arm64/v8 36 | tags: ${{ steps.meta.outputs.tags }} 37 | labels: ${{ steps.meta.outputs.labels }} 38 | cache-from: type=gha 39 | cache-to: type=gha,mode=max 40 | 41 | - name: Update repo description 42 | uses: peter-evans/dockerhub-description@v4 43 | with: 44 | username: ${{ inputs.DOCKER_USERNAME }} 45 | password: ${{ inputs.DOCKER_TOKEN }} 46 | repository: axiom/docker-erddap 47 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: docker 4 | directory: "/" 5 | schedule: 6 | interval: weekly 7 | -------------------------------------------------------------------------------- /.github/workflows/build_docker_image.yml: -------------------------------------------------------------------------------- 1 | name: Build Docker images 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | push: 7 | branches: 8 | - main 9 | release: 10 | types: 11 | - published 12 | 13 | jobs: 14 | build: 15 | name: Build and test Docker Image 16 | runs-on: ubuntu-22.04 17 | timeout-minutes: 10 18 | 19 | steps: 20 | - name: Set up Docker Buildx 21 | uses: docker/setup-buildx-action@v3 22 | 23 | - name: Build image for testing 24 | uses: docker/build-push-action@v6 25 | with: 26 | push: false 27 | tags: docker-erddap-test 28 | cache-from: type=gha 29 | cache-to: type=gha,mode=max 30 | outputs: type=docker 31 | 32 | - name: Run Docker Image in Background 33 | run: docker run -d -p 8080:8080 docker-erddap-test 34 | 35 | - name: Check that ERDDAP Docker Image will return a 200 36 | uses: ifaxity/wait-on-action@v1 37 | timeout-minutes: 1 38 | with: 39 | resource: http://localhost:8080/erddap/index.html 40 | 41 | - name: Checkout custom actions 42 | uses: actions/checkout@v4 43 | with: 44 | sparse-checkout: | 45 | .github 46 | 47 | - name: Publish Docker images 48 | if: | 49 | ((github.event_name == 'release' && github.event.action == 'published') || github.ref == 'refs/heads/main') 50 | && github.repository == 'axiom-data-science/docker-erddap' 51 | uses: ./.github/actions/publish_docker_image 52 | with: 53 | DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} 54 | DOCKER_TOKEN: ${{ secrets.DOCKER_PASSWORD }} 55 | -------------------------------------------------------------------------------- /.github/workflows/erddap_version.yml: -------------------------------------------------------------------------------- 1 | name: Check ERDDAP Version 2 | 3 | on: 4 | schedule: 5 | - cron: "0 13 * * 1-5" # Check at 8 am on weekdays 6 | workflow_dispatch: 7 | # Once merged, under actions > Check ERDDAP Version there should be a 'Run Workflow' button which manually trigger a run 8 | 9 | 10 | 11 | jobs: 12 | version: 13 | name: Check the current ERDDAP version 14 | runs-on: ubuntu-22.04 15 | timeout-minutes: 5 16 | if: github.repository == 'axiom-data-science/docker-erddap' 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | 22 | - name: Get ERDDAP versions, and create an issue if it is out of date 23 | uses: actions/github-script@v3 24 | with: 25 | script: | 26 | const repo_name = "docker-erddap" 27 | const repo_owner = "axiom-data-science" 28 | const assign_user = "srstsavage" 29 | 30 | const fs = require("fs") 31 | 32 | const release = await github.repos.getLatestRelease({ 33 | owner: "ERDDAP", 34 | repo: "erddap" 35 | }) 36 | const tag = release.data.tag_name 37 | 38 | const dockerfile = fs.readFileSync(`${process.env.GITHUB_WORKSPACE}/Dockerfile`, "utf-8") 39 | const lines = dockerfile.split("\n") 40 | const docker_version_line = lines.find(line => line.includes("ERDDAP_VERSION")) 41 | const docker_version = `v${docker_version_line.split("=")[1]}` 42 | 43 | if (tag === docker_version) { 44 | console.log(`Latest Docker version (${docker_version}) matches the latest available ERDDAP/erddap release`) 45 | 46 | return 47 | } 48 | 49 | console.log(`Latest Docker version (${docker_version}) does not match the latest available ERDDAP/erddap release (${tag})`) 50 | 51 | const query = `query($owner: String!, $name: String!) { 52 | repository(owner: $owner, name: $name) { 53 | issues(first: 5, states: OPEN) { 54 | nodes { 55 | id 56 | title 57 | } 58 | } 59 | } 60 | }` 61 | 62 | const issue_label = "erddapversion" 63 | const issue_title = `Update ERDDAP to ${tag}` 64 | 65 | const variables = { 66 | owner: repo_owner, 67 | name: repo_name, 68 | label: issue_label 69 | } 70 | const result = await github.graphql(query, variables) 71 | const issues = result.repository.issues.nodes 72 | const issue_for_version = issues.find(issue => issue.title.includes(issue_title)) 73 | 74 | if (issue_for_version !== undefined) { 75 | console.log(`There is already an issue created for the latest version of ERDDAP`) 76 | return 77 | } 78 | 79 | console.log("Creating a new issue to update the ERDDAP version") 80 | 81 | const issue_body = ` 82 | ERDDAP has been updated to ${tag}! 83 | 84 | @${assign_user} Please update and test the version in the Dockerfile. 85 | ` 86 | 87 | const issue = await github.issues.create({ 88 | owner: repo_owner, 89 | repo: repo_name, 90 | title: issue_title, 91 | labels: [issue_label], 92 | body: issue_body, 93 | assignees: [ 94 | assign_user 95 | ] 96 | }) 97 | 98 | console.log(`New issue created at ${issue.data.html_url}`) 99 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | examples/erddapData 3 | examples/tomcatLogs 4 | examples/.env 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE=tomcat:10.1.26-jdk21-temurin-jammy 2 | #referencing a specific image digest pins our unidata tomcat-docker image to platform amd64 (good) 3 | ARG UNIDATA_TOMCAT_IMAGE=unidata/tomcat-docker:10-jdk17@sha256:af7d3fecec753cbd438f25881deeaf48b40ac1f105971d6f300252e104e39fb2 4 | FROM ${UNIDATA_TOMCAT_IMAGE} AS unidata-tomcat-image 5 | FROM ${BASE_IMAGE} 6 | 7 | #use approaches and hardened files from https://github.com/Unidata/tomcat-docker 8 | #note: we don't inherit directly from Unidata/tomcat-docker to allow more 9 | #flexibility in building images using different tomcat base images, architectures, etc 10 | RUN apt-get update && \ 11 | apt-get install -y --no-install-recommends \ 12 | gosu \ 13 | zip \ 14 | unzip \ 15 | && \ 16 | # Cleanup 17 | apt-get clean && \ 18 | rm -rf /var/lib/apt/lists/* && \ 19 | # Eliminate default web applications 20 | rm -rf ${CATALINA_HOME}/webapps/* && \ 21 | rm -rf ${CATALINA_HOME}/webapps.dist && \ 22 | # Obscuring server info 23 | cd ${CATALINA_HOME}/lib && \ 24 | mkdir -p org/apache/catalina/util/ && \ 25 | unzip -j catalina.jar org/apache/catalina/util/ServerInfo.properties \ 26 | -d org/apache/catalina/util/ && \ 27 | sed -i 's/server.info=.*/server.info=Apache Tomcat/g' \ 28 | org/apache/catalina/util/ServerInfo.properties && \ 29 | zip -ur catalina.jar \ 30 | org/apache/catalina/util/ServerInfo.properties && \ 31 | rm -rf org && cd ${CATALINA_HOME} && \ 32 | # Setting restrictive umask container-wide 33 | echo "session optional pam_umask.so" >> /etc/pam.d/common-session && \ 34 | sed -i 's/UMASK.*022/UMASK 007/g' /etc/login.defs 35 | 36 | # Security enhanced web.xml 37 | COPY --from=unidata-tomcat-image ${CATALINA_HOME}/conf/web.xml ${CATALINA_HOME}/conf/ 38 | 39 | # Security enhanced server.xml 40 | COPY --from=unidata-tomcat-image ${CATALINA_HOME}/conf/server.xml ${CATALINA_HOME}/conf/ 41 | 42 | ARG ERDDAP_VERSION=2.25.1 43 | ARG ERDDAP_CONTENT_VERSION=1.0.0 44 | ARG ERDDAP_WAR_URL="https://github.com/ERDDAP/erddap/releases/download/v${ERDDAP_VERSION}/erddap.war" 45 | ARG ERDDAP_CONTENT_URL="https://github.com/ERDDAP/erddapContent/archive/refs/tags/content${ERDDAP_CONTENT_VERSION}.zip" 46 | ENV ERDDAP_bigParentDirectory=/erddapData 47 | 48 | RUN apt-get update && apt-get install -y unzip xmlstarlet \ 49 | && if ! command -v gosu &> /dev/null; then apt-get install -y gosu; fi \ 50 | && rm -rf /var/lib/apt/lists/* 51 | 52 | ARG BUST_CACHE=1 53 | RUN \ 54 | mkdir -p /tmp/dl && \ 55 | curl -fSL "${ERDDAP_WAR_URL}" -o /tmp/dl/erddap.war && \ 56 | unzip /tmp/dl/erddap.war -d ${CATALINA_HOME}/webapps/erddap/ && \ 57 | curl -fSL "${ERDDAP_CONTENT_URL}" -o /tmp/dl/erddapContent.zip && \ 58 | unzip /tmp/dl/erddapContent.zip -d /tmp/dl/erddapContent && \ 59 | find /tmp/dl/erddapContent -type d -name content -exec cp -r "{}" ${CATALINA_HOME} \; && \ 60 | rm -rf /tmp/dl && \ 61 | sed -i 's##\n&#' ${CATALINA_HOME}/conf/context.xml && \ 62 | rm -rf /tmp/* /var/tmp/* && \ 63 | mkdir -p ${ERDDAP_bigParentDirectory} 64 | 65 | # Java options 66 | COPY files/setenv.sh ${CATALINA_HOME}/bin/setenv.sh 67 | 68 | # server.xml fixup 69 | COPY update-server-xml.sh /opt/update-server-xml.sh 70 | RUN /opt/update-server-xml.sh 71 | 72 | # Default configuration 73 | # Note: Make sure ERDDAP_flagKeyKey is set either in a runtime environment variable or in setup.xml 74 | # If a value is not set, a random value for ERDDAP_flagKeyKey will be generated at runtime. 75 | ENV ERDDAP_baseHttpsUrl="https://localhost:8443" \ 76 | ERDDAP_emailEverythingTo="nobody@example.com" \ 77 | ERDDAP_emailDailyReportsTo="nobody@example.com" \ 78 | ERDDAP_emailFromAddress="nothing@example.com" \ 79 | ERDDAP_emailUserName="" \ 80 | ERDDAP_emailPassword="" \ 81 | ERDDAP_emailProperties="" \ 82 | ERDDAP_emailSmtpHost="" \ 83 | ERDDAP_emailSmtpPort="" \ 84 | ERDDAP_adminInstitution="Axiom Docker Install" \ 85 | ERDDAP_adminInstitutionUrl="https://github.com/axiom-data-science/docker-erddap" \ 86 | ERDDAP_adminIndividualName="Axiom Docker Install" \ 87 | ERDDAP_adminPosition="Software Engineer" \ 88 | ERDDAP_adminPhone="555-555-5555" \ 89 | ERDDAP_adminAddress="123 Irrelevant St." \ 90 | ERDDAP_adminCity="Nowhere" \ 91 | ERDDAP_adminStateOrProvince="AK" \ 92 | ERDDAP_adminPostalCode="99504" \ 93 | ERDDAP_adminCountry="USA" \ 94 | ERDDAP_adminEmail="nobody@example.com" 95 | 96 | COPY entrypoint.sh datasets.d.sh / 97 | ENTRYPOINT ["/entrypoint.sh"] 98 | 99 | EXPOSE 8080 100 | CMD ["catalina.sh", "run"] 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ERDDAP on Docker 2 | 3 | A feature full Tomcat (SSL over APR, etc.) running [ERDDAP](http://coastwatch.pfeg.noaa.gov/erddap/index.html) 4 | 5 | Most recent versions: 6 | 7 | * `axiom/docker-erddap:latest-jdk21-openjdk` (2.25.1) 8 | * `axiom/docker-erddap:2.25.1-jdk21-openjdk` 9 | * `axiom/docker-erddap:2.24-jdk21-openjdk` 10 | * `axiom/docker-erddap:2.23-jdk17-openjdk` 11 | 12 | See all versions available [here](https://hub.docker.com/r/axiom/docker-erddap/tags). As always, consult the [ERDDAP Changes](https://coastwatch.pfeg.noaa.gov/erddap/download/changes.html) documentation before upgrading your server. 13 | 14 | Use any of the `latest-*` images with caution as they follow the upstream image, and is not as thoroughly tested as tagged images. 15 | 16 | [Dependabot](https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/keeping-your-dependencies-updated-automatically) is used to automatically make PRs to update the upstream image ([`.github/dependabot.yml`](.github/dependabot.yml)). 17 | 18 | ## Quickstart 19 | 20 | ```bash 21 | docker run -d -p 8080:8080 axiom/docker-erddap:latest-jdk21-openjdk 22 | ``` 23 | 24 | ## Running ERDDAP CLI Tools 25 | 26 | ### GenerateDatasetsXml 27 | 28 | ```bash 29 | docker run --rm -it \ 30 | -v $(pwd)/logs:/erddapData/logs \ 31 | --workdir /usr/local/tomcat/webapps/erddap/WEB-INF \ 32 | axiom/docker-erddap:latest \ 33 | bash GenerateDatasetsXml.sh -verbose 34 | ``` 35 | 36 | or, generate a basic dataset configuration without input for 37 | later customization 38 | 39 | ```bash 40 | ./make-dataset.xml /path/to/your.csv EDDTableFromAsciiFiles > /path/to/your-dataset.xml 41 | ``` 42 | 43 | ## Configuration 44 | 45 | ### Tomcat 46 | 47 | See [these instructions for configuring Tomcat](https://github.com/unidata/tomcat-docker) from the Tomcat image this image borrows from (`unidata/tomcat-docker`). 48 | 49 | ### CORS 50 | 51 | The [Tomcat configuration](https://github.com/unidata/tomcat-docker) used by this image enables the 52 | [Apache Tomcat CORS filter](https://tomcat.apache.org/tomcat-8.5-doc/config/filter.html#CORS_Filter) by 53 | default. To disable it (maybe you want to handle CORS uniformly in a proxying webserver?), set environment 54 | variable `DISABLE_CORS` to `1`. 55 | 56 | ### ERDDAP 57 | 58 | Any number of these options can be taken to configure your ERDDAP container instance to your liking. 59 | 60 | 1. Mount your own `content/erddap` directory: 61 | 62 | ```bash 63 | docker run \ 64 | -p 8080:8080 \ 65 | -v /path/to/your/erddap/directory:/usr/local/tomcat/content/erddap \ 66 | ... \ 67 | axiom/docker-erddap 68 | ``` 69 | 70 | Your content directory should contain a [setup.xml](http://coastwatch.pfeg.noaa.gov/erddap/download/setup.html#setup.xml) and [dataset.xml](http://coastwatch.pfeg.noaa.gov/erddap/download/setupDatasetsXml.html) file. It can also include CSS assets that you reference in your custom `setup.xml` file. 71 | 72 | If you just want to change [setup.xml](http://coastwatch.pfeg.noaa.gov/erddap/download/setup.html#setup.xml) and [dataset.xml](http://coastwatch.pfeg.noaa.gov/erddap/download/setupDatasetsXml.html), you can mount them individually: 73 | 74 | ```bash 75 | $ docker run \ 76 | -p 8080:8080 \ 77 | -v /path/to/your/setup.xml:/usr/local/tomcat/content/erddap/setup.xml \ 78 | -v /path/to/your/datasets.xml:/usr/local/tomcat/content/erddap/datasets.xml \ 79 | ... \ 80 | axiom/docker-erddap 81 | ``` 82 | 83 | **If you mount setup.xml file make sure to set `/erddapData/`** 84 | 85 | 2. Configure using environmental variables 86 | 87 | You can set environmental variables to configure ERDDAP's `setup.xml` since version 2.14. See the [ERDDAP documentation](https://coastwatch.pfeg.noaa.gov/erddap/download/setup.html#setupEnvironmentVariables) for details. This can be very useful so you don't need to mount a custom `setup.xml` file into your container. If taking this approach you should look into setting the following ERDDAP config options: 88 | 89 | * `ERDDAP_baseUrl` 90 | * `ERDDAP_baseHttpsUrl` 91 | * `ERDDAP_flagKeyKey` 92 | * `ERDDAP_emailEverythingTo` 93 | * `ERDDAP_emailFromAddress` 94 | * `ERDDAP_emailUserName` 95 | * `ERDDAP_emailPassword` 96 | * `ERDDAP_emailSmtpHost` 97 | * `ERDDAP_emailSmtpPort` 98 | * `ERDDAP_adminInstitution` 99 | * `ERDDAP_adminInstitutionUrl` 100 | * `ERDDAP_adminIndividualName` 101 | * `ERDDAP_adminPosition` 102 | * `ERDDAP_adminPhone` 103 | * `ERDDAP_adminAddress` 104 | * `ERDDAP_adminCity` 105 | * `ERDDAP_adminStateOrProvince` 106 | * `ERDDAP_adminPostalCode` 107 | * `ERDDAP_adminCountry` 108 | * `ERDDAP_adminEmail` 109 | 110 | For example: 111 | 112 | ```bash 113 | docker run \ 114 | -p 8080:8080 \ 115 | -e ERDDAP_baseURL="http://localhost:8080" \ 116 | -e ERDDAP_adminEmail="set_via_container_env@example.com" \ 117 | axiom/docker-erddap 118 | ``` 119 | 120 | **Depending on your container environment, it may pass in it's own environment variables relating to your resources. Potentially there could be a collision with the `ERDDAP_*` config variables if any of your resources start with ERDDAP.** 121 | 122 | 3. Configure using a shell script 123 | 124 | You can mount a file called `config.sh` to `${CATALINA_HOME}/bin/config.sh` that sets any ERDDAP configuration environmental variables you want to use. This is sourced in the container-provided `setenv.sh` file and and all variables will be exported to be used by ERDDAP for configuration. These will take precedence over environmental variable specified when running the container (see above). 125 | 126 | ```bash 127 | $ docker run \ 128 | -p 8080:8080 \ 129 | -e ERDDAP_adminEmail="overridden_by_config_file@example.com" \ 130 | -v /path/to/your/config.sh:/usr/local/tomcat/bin/config.sh \ 131 | ... \ 132 | axiom/docker-erddap 133 | ``` 134 | 135 | where `config.sh` contains any of the ERDDAP environmental configuration variables: 136 | 137 | ```sh 138 | ERDDAP_adminEmail="this_is_used@example.com" 139 | ``` 140 | 141 | You can set any number of configuration variables in the config.sh. 142 | 143 | ```bash 144 | ERDDAP_bigParentDirectory="/erddapData/" 145 | ERDDAP_baseUrl="http://localhost:8080" 146 | ERDDAP_baseHttpsUrl="https://localhost:8443" 147 | ERDDAP_flagKeyKey="73976bb0-9cd4-11e3-a5e2-0800200c9a66" 148 | 149 | ERDDAP_emailEverythingTo="nobody@example.com" 150 | ERDDAP_emailDailyReportTo="nobody@example.com" 151 | ERDDAP_emailFromAddress="nothing@example.com" 152 | ERDDAP_emailUserName="" 153 | ERDDAP_emailPassword="" 154 | ERDDAP_emailProperties="" 155 | ERDDAP_emailSmtpHost="" 156 | ERDDAP_emailSmtpPort="" 157 | 158 | ERDDAP_adminInstitution="Axiom Docker Install" 159 | ERDDAP_adminInstitutionUrl="https://github.com/axiom-data-science/docker-erddap" 160 | ERDDAP_adminIndividualName="Axiom Docker Install" 161 | ERDDAP_adminPosition="Software Engineer" 162 | ERDDAP_adminPhone="555-555-5555" 163 | ERDDAP_adminAddress="123 Irrelevant St." 164 | ERDDAP_adminCity="Nowhere" 165 | ERDDAP_adminStateOrProvince="AK" 166 | ERDDAP_adminPostalCode="99504" 167 | ERDDAP_adminCountry="USA" 168 | ERDDAP_adminEmail="nobody@example.com" 169 | ``` 170 | 171 | 4. Mount your own `bigParentDirectory`: 172 | 173 | ```bash 174 | docker run \ 175 | -p 8080:8080 \ 176 | -v /path/to/your/erddap/bigParentDirectory:/erddapData \ 177 | ... \ 178 | axiom/docker-erddap 179 | ``` 180 | 181 | This is **highly** recommended, or nothing will persist across container restarts (logs/cache/etc.) 182 | 183 | 5. Specify the amount of heap memory (using Java's `Xms` and `Xmx`) to be allocated: 184 | 185 | ``` bash 186 | docker run \ 187 | -p 8080:8080 \ 188 | --env ERDDAP_MEMORY=10G 189 | ... \ 190 | axiom/docker-erddap 191 | ``` 192 | 193 | You may also explicity set `ERDDAP_MIN_MEMORY` and `ERDDAP_MAX_MEMORY` value (these map to `Xms` and `Xmx` respectively), 194 | but generally the best practice is to set these to the same value to prevent costly heap resizing at runtime. 195 | 196 | ``` bash 197 | docker run \ 198 | -p 8080:8080 \ 199 | --env ERDDAP_MIN_MEMORY=8G --env ERDDAP_MAX_MEMORY=8G 200 | ... \ 201 | axiom/docker-erddap 202 | ``` 203 | 204 | Alternatively, you can set `ERDDAP_MAX_RAM_PERCENTAGE` set the maximum Java heap size to a percentage of the memory available to the container. This option sets the JVM option `-XX:MaxRAMPercentage`. For example, to limit the container's memory to 10GB and allow the Java heap size to use 90% of that amount: 205 | 206 | ``` bash 207 | docker run \ 208 | -p 8080:8080 \ 209 | --memory 10g \ 210 | --env ERDDAP_MAX_RAM_PERCENTAGE=90 \ 211 | ... \ 212 | axiom/docker-erddap 213 | ``` 214 | 215 | #### datasets.d mode - EXPERIMENTAL 216 | 217 | Typically ERDDAP is configured with a single `datasets.xml` configuration file describing all datasets to be served by ERDDAP, plus some global configuration options. This file is [described in detail in the official ERDDAP documentation](https://coastwatch.pfeg.noaa.gov/erddap/download/setupDatasetsXml.html). 218 | 219 | `docker-erddap` provides an alternative `datasets.d` mode, where `datasets.xml` `dataset` elements can be stored in separate files inside a `datasets.d` directory. At startup time, the `/datasets.d` directory is scanned for any files ending in `.xml`, and matching files are concatenated (sorted by file path inside `/datasets.d`) into a generated `datasets.xml` file (specifically, an empty `` element). 220 | 221 | In this mode, top level `datasets.xml` elements like ``, ``, etc can be configured using `ERDDAP_DATASET_*` environment variables. These behave much like the `ERDDAP_*` environment variables which affect `setup.xml` values (see the [ERDDAP docs](https://coastwatch.pfeg.noaa.gov/erddap/download/setup.html#setupEnvironmentVariables) for more details), but affect top level `datasets.xml` values instead. For example, to set the `standardLicense`: 222 | 223 | ```bash 224 | docker run -d -v $(pwd)/datasets.d:/datasets.d:ro \ 225 | -e ERDDAP_DATASETS_standardLicense="

Use as you wish

" \ 226 | --name erddap 227 | axiom/docker-erddap 228 | ``` 229 | 230 | Note that in this mode, the `datasets.xml` file in the ERDDAP content directory (`/usr/local/tomcat/content/erddap`) is replaced by the generated `datasets.xml`. A backup of the original `datasets.xml` is created if one doesn't already exist. 231 | 232 | Consequently, when using `datasets.d` mode it is not necessary to mount the ERDDAP content directory at all. The contents of `datasets.d` provide all of the dataset configuration, and any top level `datasets.xml` configuration is performed through `ERDDAP_DATASETS_* env vars. 233 | 234 | For an example of running with `datasets.d` mode, see the docker-compose example in [examples](./examples). 235 | 236 | Generation of `datasets.xml` is handled in a script (`datasets.d.sh`) which prints to stdout and can be tested outside of `docker-erddap` initialization. 237 | 238 | Example: 239 | 240 | ```shell 241 | ERDDAP_DATASETS_cacheMinutes=20 ./datasets.d.sh -d examples/datasets.d 242 | ``` 243 | 244 | ##### Elegantly removing datasets in datasets.d mode 245 | 246 | ERDDAP has a [specific process](https://coastwatch.pfeg.noaa.gov/erddap/download/setupDatasetsXml.html#active) 247 | to remove a previously served dataset: 248 | 249 | * Edit the dataset's `datasets.xml` element and set the `active` attribute to `false`. 250 | * Allow ERDDAP to detect the inactive dataset on the next update (or set a reload flag detect the change immediately) 251 | * Once ERDDAP has removed the dataset, remove the dataset's `datasets.xml` element (or leave as-is with `active="false"`) 252 | 253 | Failure to follow this process will result in "orphan" datasets in the ERDDAP configuration. 254 | 255 | To allow `datasets.d` mode to automatically detect removed datasets (dataset ids in the running ERDDAP configuration but 256 | not present in the newly generated `datasets.xml`), you can set environment variable `DATASETSD_MARK_REMOVED_DATASETS_INACTIVE=1` 257 | or pass the `-i` flag to `./datasets.d.sh` when running manually. This behavior may become the default in the future. 258 | 259 | #### Initialization scripts (/init.d) - EXPERIMENTAL 260 | 261 | Additional configuration can be performed by placing executable files and/or shell scripts in `/init.d`. These executables will be run on every container start up, so they **must be idempotent**. This functionality is inspired by the postgres Docker image's [`/docker-entrypoint-initdb.d`](https://github.com/docker-library/docs/blob/master/postgres/README.md#initialization-scripts). 262 | 263 | Example: 264 | 265 | ```shell 266 | #remove .hdf and .nc files from range request exclusion 267 | mkdir -p init.d 268 | cat << 'EOF' > init.d/10-remove-hdf-nc-range-request-exclusion.sh 269 | sed -i 's/.hdf, .nc, //g' ${CATALINA_HOME}/webapps/erddap/WEB-INF/classes/gov/noaa/pfel/erddap/util/messages.xml 270 | EOF 271 | 272 | chmod +x init.d/10-remove-hdf-nc-range-request-exclusion.sh 273 | 274 | docker run -d -p 8080:8080 -v $(pwd)/init.d:/init.d:ro --name erddap axiom/docker-erddap 275 | ``` 276 | 277 | #### Log Consolidation - EXPERIMENTAL 278 | 279 | ERDDAP writes logs to a `logs/log.txt` file relative to ERDDAP's `bigParentDirectory`. The log format doesn't adhere to a standard logging format and isn't easily parsable. The logs also don't provide timestamps for when the logs messages were written. To enhance the logging experience when using this docker image you can run a sidecar `rsyslog` container that will: 280 | 281 | * Consolidate the log files from ERDDAP and Tomcat (both application and access) 282 | * Add a timestamp to the ERDDAP logs 283 | * Filter out some ERDDAP log "noise" (opinionated) 284 | * Send the consolidated and filtered log messages to `stdout` 285 | 286 | For an example of running with a sidecar `rsyslog` container, see the docker-compose example in [examples](./examples). The supporting `rsyslog` configuration files are located in [rsyslog](./examples/rsyslog). Please note that this requires both the ERDDAP `bigParentDirectory` and Tomcat's log directory to be bind mounted to the host from the ERDDAP container or managed in Docker named volumes mounted to both the ERDDAP and rsyslog containers. 287 | 288 | Example consolidated log: 289 | 290 | ```log 291 | erddap-rsyslogd_1 | [TOMCAT] 08-Jul-2022 04:44:14.004 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"] 292 | erddap-rsyslogd_1 | [TOMCAT] 08-Jul-2022 04:44:14.011 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in 3582 ms 293 | ... 294 | erddap-rsyslogd_1 | [ERDDAP] 2022-07-08T04:44:19Z Major LoadDatasets Time Series: MLD Datasets Loaded Requests (median times in ms) Number of Threads MB Open 295 | erddap-rsyslogd_1 | timestamp time nTry nFail nTotal nSuccess (median) nFail (median) memFail tooMany tomWait inotify other inUse Files 296 | erddap-rsyslogd_1 | ---------------------------- ----- ----------------- ------------------------------------------------ --------------------- ----- ----- 297 | erddap-rsyslogd_1 | 2022-07-08T04:44:18+00:00 1s 1 0 2 1 ( 8) 0 ( 0) 0 0 10 1 17 44 0% 298 | ... 299 | erddap-rsyslogd_1 | [ACCESS] 127.0.0.1 - - [08/Jul/2022:04:44:17 +0000] "GET /erddap/index.html HTTP/1.1" 200 25268 300 | erddap-rsyslogd_1 | [ACCESS] 127.0.0.1 - - [08/Jul/2022:04:44:27 +0000] "GET /erddap/index.html HTTP/1.1" 200 25268 301 | ``` 302 | -------------------------------------------------------------------------------- /datasets.d.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | #datasets.d datasets.xml generator 3 | #If /datasets.d exists, concatenate all *.xml files found in the 4 | #hierarchy into a generated datasets.xml file. Scan for environment 5 | #variables prefixed with ERDDAP_DATASETS_ and use the values to set 6 | #top level datasets.xml elements (e.g. cacheMinutes) 7 | #See ERDDAP docs at https://coastwatch.pfeg.noaa.gov/erddap/download/setupDatasetsXml.html 8 | #for a thorough description of the datasets.xml format. 9 | 10 | DATASETSD_DIR="${DATASETSD_DIR:-/datasets.d}" 11 | DATASETSD_OUTPUT_PATH="${DATASETSD_OUTPUT_PATH:-/usr/local/tomcat/content/erddap/datasets.xml}" 12 | DATASETSD_MARK_REMOVED_DATASETS_INACTIVE="${DATASETSD_MARK_REMOVED_DATASETS_INACTIVE:-0}" 13 | DATASETSD_WRITE_TO_OUTPUT_PATH="${DATASETSD_WRITE_TO_OUTPUT_PATH:-0}" 14 | DATASETSD_REFRESH_MISSING_DATASETS="${DATASETSD_REFRESH_MISSING_DATASETS:-0}" 15 | while getopts ":d:io:rw" opt; do 16 | case ${opt} in 17 | d ) 18 | DATASETSD_DIR="$OPTARG" 19 | ;; 20 | i ) 21 | DATASETSD_MARK_REMOVED_DATASETS_INACTIVE=1 22 | ;; 23 | o ) 24 | DATASETSD_OUTPUT_PATH="$OPTARG" 25 | ;; 26 | r ) 27 | DATASETSD_REFRESH_MISSING_DATASETS=1 28 | ;; 29 | w ) 30 | DATASETSD_WRITE_TO_OUTPUT_PATH=1 31 | ;; 32 | \? ) 33 | echo "Invalid option: $OPTARG" 1>&2 34 | ;; 35 | : ) 36 | echo "Invalid option: $OPTARG requires an argument" 1>&2 37 | ;; 38 | esac 39 | done 40 | shift $((OPTIND -1)) 41 | 42 | if [ ! -d "${DATASETSD_DIR}" ]; then 43 | echo "${DATASETSD_DIR} doesn't exist or isn't a directory, exiting" >&2 44 | exit 1 45 | fi 46 | 47 | #generate datasets.xml content from constituent datasets fragments in datasets.d directory 48 | #run dataset fragments through xmlstarlet to remove xml declarations and catch other problems 49 | DXML=$(echo "$(find ""${DATASETSD_DIR}"" -name '*.xml' -type f -print0 | sort -z | xargs -0 xmlstarlet edit --omit-decl)") 50 | 51 | #set top level datasets.xml config with ERDDAP_DATASETS_* env vars 52 | while read -r e; do 53 | k=$(echo "$e" | cut -d= -f1); 54 | v=$(echo "$e" | cut -d= -f2-); 55 | DXML=$(echo "$DXML" | xmlstarlet edit --inplace --subnode /erddapDatasets --type elem --name "$k" --value "$v") 56 | done < <(env | grep -oP '(?<=^ERDDAP_DATASETS_).*') 57 | 58 | #mark removed datasets as inactive 59 | if [ "$DATASETSD_MARK_REMOVED_DATASETS_INACTIVE" == "1" ] && [ -n "$DATASETSD_OUTPUT_PATH" ] && [ -f "$DATASETSD_OUTPUT_PATH" ]; then 60 | #previous version of the target datasets.xml file exists, 61 | #if any datasets there not marked inactive are missing from our generated file 62 | #add them as inactive datasets so ERDDAP can cleanly make them inactive 63 | #https://coastwatch.pfeg.noaa.gov/erddap/download/setupDatasetsXml.html#active 64 | while read -r inactive_dataset; do 65 | DXML=$(echo "$DXML" | xmlstarlet edit -P -s /erddapDatasets --type elem --name inactiveDataset -v "" \ 66 | -i //inactiveDataset -t attr -n "datasetID" -v "${inactive_dataset}" \ 67 | -i //inactiveDataset -t attr -n "active" -v "false" \ 68 | -r //inactiveDataset -v dataset) 69 | done < <(comm -13 <(echo "$DXML" | xmlstarlet select -t -v "//erddapDatasets/dataset/@datasetID" | sort) \ 70 | <(<"$DATASETSD_OUTPUT_PATH" xmlstarlet select -t -v "//erddapDatasets/dataset[not(@active='false')]/@datasetID" | sort)) 71 | true 72 | fi 73 | 74 | #empty edit for formatting 75 | DXML=$(echo "$DXML" | xmlstarlet edit --inplace) 76 | 77 | #write output to target file if one was provided and write to stdout flag was not provided 78 | if [ -n "$DATASETSD_OUTPUT_PATH" ] && [ "$DATASETSD_WRITE_TO_OUTPUT_PATH" == "1" ]; then 79 | echo "$DXML" > "$DATASETSD_OUTPUT_PATH" 80 | #set refresh flags for any datasetIDs in datasets.xml that are not in the running ERDDAP config if the refresh option was set 81 | if [ -n "$DATASETSD_REFRESH_MISSING_DATASETS" ] && [ "$DATASETSD_REFRESH_MISSING_DATASETS" == "1" ]; then 82 | comm -23 \ 83 | <(xmlstarlet select -t -v "/erddapDatasets/dataset/@datasetID" "$DATASETSD_OUTPUT_PATH" | sort) \ 84 | <(curl -sS "http://localhost:8080/erddap/tabledap/allDatasets.csv0?datasetID" | grep -v "^allDatasets$" | sort) \ 85 | | xargs -I{} touch /erddapData/flag/{} 86 | fi 87 | else 88 | echo "$DXML" 89 | fi 90 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # preferable to fire up Tomcat via start-tomcat.sh which will start Tomcat with 5 | # security manager, but inheriting containers can also start Tomcat via 6 | # catalina.sh 7 | 8 | if [ "$1" = 'start-tomcat.sh' ] || [ "$1" = 'catalina.sh' ]; then 9 | # generate random flagKeyKey if not set 10 | if [ -z "$ERDDAP_flagKeyKey" ] && grep "CHANGE THIS TO YOUR FAVORITE QUOTE" \ 11 | "${CATALINA_HOME}/content/erddap/setup.xml" &> /dev/null; then 12 | echo "flagKeyKey isn't properly set. Generating a random value." >&2 13 | export ERDDAP_flagKeyKey=$(cat /proc/sys/kernel/random/uuid) 14 | fi 15 | 16 | USER_ID=${TOMCAT_USER_ID:-1000} 17 | GROUP_ID=${TOMCAT_GROUP_ID:-1000} 18 | 19 | ### 20 | # Tomcat user 21 | ### 22 | # create group for GROUP_ID if one doesn't already exist 23 | if ! getent group $GROUP_ID &> /dev/null; then 24 | groupadd -r tomcat -g $GROUP_ID 25 | fi 26 | # create user for USER_ID if one doesn't already exist 27 | if ! getent passwd $USER_ID &> /dev/null; then 28 | useradd -u $USER_ID -g $GROUP_ID tomcat 29 | fi 30 | # alter USER_ID with nologin shell and CATALINA_HOME home directory 31 | usermod -d "${CATALINA_HOME}" -s /sbin/nologin $(id -u -n $USER_ID) 32 | 33 | ### 34 | # Change CATALINA_HOME ownership to tomcat user and tomcat group 35 | # Restrict permissions on conf 36 | ### 37 | 38 | chown -R $USER_ID:$GROUP_ID ${CATALINA_HOME} && find ${CATALINA_HOME}/conf \ 39 | -type d -exec chmod 755 {} \; -o -type f -exec chmod 400 {} \; 40 | chown -R $USER_ID:$GROUP_ID /erddapData 41 | sync 42 | 43 | ### 44 | # Deactivate CORS filter in web.xml if DISABLE_CORS=1 45 | # Useful if CORS is handled outside of Tomcat (e.g. in a proxying webserver like nginx) 46 | ### 47 | if [ "$DISABLE_CORS" == "1" ]; then 48 | echo "Deactivating Tomcat CORS filter" 49 | xmlstarlet edit --inplace --delete '//_:filter[./_:filter-name = "CorsFilter"]' \ 50 | --delete '//_:filter-mapping[./_:filter-name = "CorsFilter"]' "${CATALINA_HOME}/conf/web.xml" 51 | fi 52 | 53 | ### 54 | # Add datasets in /datasets.d to datasets.xml 55 | ### 56 | if [ -d "/datasets.d" ]; then 57 | echo "Creating datasets.xml from /datasets.d" 58 | ERDDAP_CONTENT_DIR="/usr/local/tomcat/content/erddap" 59 | DATASETS_XML="${ERDDAP_CONTENT_DIR}/datasets.xml" 60 | if [ -f "$DATASETS_XML" ]; then 61 | #datasets.xml exists, make sure we have a backup of it 62 | DATASETS_XML_MD5SUM=$(md5sum "$DATASETS_XML" | awk '{print $1}') 63 | if ! md5sum "${ERDDAP_CONTENT_DIR}/datasets.xml.*.bak" 2>/dev/null | grep -q "$DATASETS_XML_MD5SUM"; then 64 | #we don't have a backup of this version of datasets.xml yet, make one 65 | DATASETS_XML_BACKUP="${ERDDAP_CONTENT_DIR}"/datasets.xml.$(date -u +"%Y%m%dT%H%M%SZ").bak 66 | echo "Backing up "${DATASETS_XML}" to ${DATASETS_XML_BACKUP}" 67 | cp "$DATASETS_XML" "${DATASETS_XML_BACKUP}" 68 | fi 69 | fi 70 | /datasets.d.sh -o "$DATASETS_XML" -w 71 | fi 72 | 73 | ### 74 | # Run executables/shell scripts in /init.d on each container startup 75 | # Inspired by postgres' /docker-entrypoint-initdb.d 76 | # https://github.com/docker-library/docs/blob/master/postgres/README.md#initialization-scripts 77 | # https://github.com/docker-library/postgres/blob/master/docker-entrypoint.sh#L156 78 | ### 79 | if [ -d "/init.d" ]; then 80 | for f in /init.d/*; do 81 | if [ -x "$f" ]; then 82 | echo "Executing $f" 83 | "$f" 84 | elif [[ $f == *.sh ]]; then 85 | echo "Sourcing $f (not executable)" 86 | . "$f" 87 | fi 88 | done 89 | fi 90 | 91 | exec gosu $USER_ID "$@" 92 | fi 93 | 94 | exec "$@" 95 | -------------------------------------------------------------------------------- /examples/data/trees.csv: -------------------------------------------------------------------------------- 1 | "Index", "Girth (in)", "Height (ft)", "Volume(ft^3)" 2 | 1, 8.3, 70, 10.3 3 | 2, 8.6, 65, 10.3 4 | 3, 8.8, 63, 10.2 5 | 4, 10.5, 72, 16.4 6 | 5, 10.7, 81, 18.8 7 | 6, 10.8, 83, 19.7 8 | 7, 11.0, 66, 15.6 9 | 8, 11.0, 75, 18.2 10 | 9, 11.1, 80, 22.6 11 | 10, 11.2, 75, 19.9 12 | 11, 11.3, 79, 24.2 13 | 12, 11.4, 76, 21.0 14 | 13, 11.4, 76, 21.4 15 | 14, 11.7, 69, 21.3 16 | 15, 12.0, 75, 19.1 17 | 16, 12.9, 74, 22.2 18 | 17, 12.9, 85, 33.8 19 | 18, 13.3, 86, 27.4 20 | 19, 13.7, 71, 25.7 21 | 20, 13.8, 64, 24.9 22 | 21, 14.0, 78, 34.5 23 | 22, 14.2, 80, 31.7 24 | 23, 14.5, 74, 36.3 25 | 24, 16.0, 72, 38.3 26 | 25, 16.3, 77, 42.6 27 | 26, 17.3, 81, 55.4 28 | 27, 17.5, 82, 55.7 29 | 28, 17.9, 80, 58.3 30 | 29, 18.0, 80, 51.5 31 | 30, 18.0, 80, 51.0 32 | 31, 20.6, 87, 77.0 33 | 34 | -------------------------------------------------------------------------------- /examples/datasets.d/trees.csv.xml: -------------------------------------------------------------------------------- 1 | 2 | 10080 3 | 10000 4 | /data/ 5 | trees.csv 6 | false 7 | 8 | last 9 | 0 10 | ISO-8859-1 11 | 12 | 1 13 | 2 14 | 15 | 16 | false 17 | 18 | Other 19 | COARDS, CF-1.6, ACDD-1.3 20 | https://people.sc.fsu.edu/~jburkardt/data/csv/trees.csv 21 | ??? 22 | tree, data, ft^3, girth, height, index, local, source, volume, Volume_ft_3 23 | [standard] 24 | (local files) 25 | CF Standard Name Table v70 26 | Generated dataset for trees.csv 27 | trees.csv 28 | 29 | 30 | Index 31 | Index 32 | byte 33 | 34 | 127 35 | Unknown 36 | Index 37 | 38 | 39 | 40 | Girth (in) 41 | Girth 42 | float 43 | 44 | Unknown 45 | Girth (in) 46 | in 47 | 48 | 49 | 50 | Height (ft) 51 | Height 52 | byte 53 | 54 | 127 55 | Unknown 56 | Height (ft) 57 | ft 58 | 59 | 60 | 61 | Volume(ft^3) 62 | Volume 63 | float 64 | 65 | Unknown 66 | Volume(ft^3) 67 | ft^3 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /examples/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | erddap: 3 | build: .. 4 | ports: 5 | - "${ERDDAP_HTTP_PORT:-8888}:8080" 6 | environment: 7 | ERDDAP_baseUrl: "${ERDDAP_baseUrl:-http://localhost:8888}" 8 | ERDDAP_DATASETS_standardPrivacyPolicy: "

Any and all usage of this data is permitted.

" 9 | DATASETSD_MARK_REMOVED_DATASETS_INACTIVE: "1" 10 | DATASETSD_REFRESH_MISSING_DATASETS: "1" 11 | DISABLE_CORS: "1" 12 | volumes: 13 | - ./data:/data:ro 14 | - ./datasets.d:/datasets.d:ro 15 | - ./erddapData:/erddapData 16 | - ./tomcatLogs:/usr/local/tomcat/logs 17 | #healthcheck to check ERDDAP landing page. the check provides the added bonus 18 | #of triggering ERDDAP initialization before the first user visit 19 | healthcheck: 20 | test: ["CMD", "curl", "-f", "http://localhost:8080/erddap/index.html"] 21 | interval: 10s 22 | erddap-logs: 23 | image: gynter/rsyslog-relp:alpine 24 | volumes: 25 | - ./erddapData:/erddapData:ro 26 | - ./tomcatLogs:/usr/local/tomcat/logs:ro 27 | - ./rsyslog/rsyslog.conf:/etc/rsyslog.conf:ro 28 | - ./rsyslog/rsyslog.d:/etc/rsyslog.d:ro 29 | depends_on: 30 | - erddap 31 | -------------------------------------------------------------------------------- /examples/rsyslog/rsyslog.conf: -------------------------------------------------------------------------------- 1 | # 2 | # Docker RSyslog (https://github.com/gynter/docker-rsyslog) 3 | # 4 | # This configuration is designed to run securely on non-systemd (i.e Alpine) containers as UID/GID 1000. 5 | # It's a generic configuration file which can be extended via including extra configuration files from /etc/rsyslog.d/ 6 | # directory. 7 | # 8 | # See https://github.com/gynter/rsyslog-docker-compose-example for deployment example using Docker Compose. 9 | # 10 | # This configuration file uses advanced (RainerScript) and basic format. 11 | # See https://www.rsyslog.com/doc/v8-stable/configuration/conf_formats.html#which-format-should-i-use for more 12 | # information. Using obsolete legacy syntax is strongly discouraged! 13 | # See https://www.rsyslog.com/doc/master/rainerscript/ for more information. 14 | # 15 | # If you experience problems, see https://www.rsyslog.com/doc/troubleshoot.html 16 | 17 | #################################### 18 | #### Global directives #### 19 | #################################### 20 | 21 | # https://www.rsyslog.com/doc/v8-stable/rainerscript/global.html 22 | global( 23 | # Sets the directory that rsyslog uses for work files, e.g. imfile state or queue spool files. 24 | workDirectory="/srv/rsyslog" 25 | 26 | # Sets the rsyslogd process’ umask. If not specified, the system-provided default is used. The value given must always 27 | # be a 4-digit octal number, with the initial digit being zero. 28 | umask="0027" 29 | 30 | # If set to off (legacy default to remain compatible to sysklogd), the domain part from a name that is within the same 31 | # domain as the receiving system is stripped. If set to on, full names are always used. 32 | preserveFQDN="on" 33 | 34 | # Configures the maximum message size allowed for all inputs. Default is 8K. Anything above the maximum size will be 35 | # truncated. 36 | # Note: some modules provide separate parameters that allow overriding this setting (e.g., imrelp’s MaxDataSize 37 | # parameter). 38 | maxMessageSize="8K" 39 | 40 | # If set to “on”, rsyslogd can be terminated by pressing ctl-c. This is most useful for containers. If set to “off” 41 | # (the default), this is not possible. 42 | shutdown.enable.ctlc="on" 43 | 44 | # This controls whether slashes in the “programname” property (the static part of the tag) are permitted or not. By 45 | # default this is not permitted, but some Linux tools (including most importantly the journal) store slashes as part 46 | # of the program name inside the syslogtag. In those cases, the programname is truncated at the first slash. 47 | # 48 | # In other words, if the setting is off, a value of app/foo[1234] in the tag will result in a programname of app, and 49 | # if an application stores an absolute path name like /app/foo[1234], the programname property will be empty (“”). 50 | # If set to on, a syslogtag of /app/foo[1234] will result in a programname value of /app/foo and a syslogtag of 51 | # app/foo[1234] will result in a programname value of app/foo. 52 | parser.permitSlashInProgramName="on" 53 | 54 | # This parameter instructs rsyslogd to replace control characters during reception of the message. The intent is to 55 | # provide a way to stop non-printable messages from entering the syslog system as whole. If this option is turned on, 56 | # all control-characters are converted to a 3-digit octal number and be prefixed with the 57 | # parser.controlCharacterEscapePrefix character (being ‘#’ by default). For example, if the BEL character (ctrl-g) is 58 | # included in the message, it would be converted to ‘#007’. To be compatible to sysklogd, this option must be turned 59 | # on. 60 | # 61 | # Warning: 62 | # - turning on this option most probably destroys non-western character sets (like Japanese, Chinese and Korean); 63 | # - turning on this option destroys digital signatures if such exists inside the message; 64 | # - if turned on, the drop-cc, space-cc and escape-cc property replacer options do not work as expected because 65 | # control characters are already removed upon message reception. If you intend to use these property replacer 66 | # options, you must turn off parser.escapeControlCharactersOnReceive. 67 | parser.escapeControlCharactersOnReceive="off" 68 | 69 | # This parameter permits to prevent rsyslog from running when the configuration file is not clean. “Not Clean” means 70 | # there are errors or some other annoyances that rsyslogd reports on startup. Note that with the current code base it 71 | # is not always possible to differentiate between an real error and a warning-like condition. As such, the startup 72 | # will also prevented if warnings are present. I consider this a good thing in being “strict”, but I admit there also 73 | # currently is no other way of doing it. 74 | abortOnUncleanConfig="on" 75 | ) 76 | 77 | #################################### 78 | #### Modules #### 79 | #################################### 80 | 81 | # Provides module for writing messages to stdout. 82 | # https://www.rsyslog.com/doc/master/configuration/modules/omstdout.html 83 | module(load="omstdout") 84 | 85 | #################################### 86 | #### Rules #### 87 | #################################### 88 | 89 | # Basic syntax is used here. 90 | 91 | # Send own messages to Docker console. 92 | syslog.* :omstdout: 93 | 94 | # /END Basic syntax is used here. 95 | 96 | #################################### 97 | #### Other #### 98 | #################################### 99 | 100 | # Include all *.conf files in /etc/rsyslog.d/. 101 | # https://www.rsyslog.com/doc/v8-stable/rainerscript/include.html 102 | include( 103 | file="/etc/rsyslog.d/*.conf" 104 | mode="optional" 105 | ) 106 | -------------------------------------------------------------------------------- /examples/rsyslog/rsyslog.d/00-imports.conf: -------------------------------------------------------------------------------- 1 | module(load="imfile") 2 | -------------------------------------------------------------------------------- /examples/rsyslog/rsyslog.d/10-tomcat.conf: -------------------------------------------------------------------------------- 1 | input( 2 | type="imfile" 3 | File="/usr/local/tomcat/logs/*.log" 4 | Tag="tomcat" 5 | Ruleset="tomcatToStdOut" 6 | readMode="0" 7 | escapeLF="on" 8 | ) 9 | 10 | input( 11 | type="imfile" 12 | File="/usr/local/tomcat/logs/localhost_access_log*" 13 | Tag="access" 14 | Ruleset="tomcatToStdOut" 15 | ) 16 | 17 | template(name="tomcatStdOut" type="list") { 18 | constant(value="[") 19 | property(name="syslogtag" caseconversion="upper") 20 | constant(value="] ") 21 | property(name="msg" droplastlf="on" ) 22 | constant(value="\n") 23 | } 24 | 25 | ruleset(name="tomcatToStdOut") { 26 | action( 27 | type="omstdout" template="tomcatStdOut" 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /examples/rsyslog/rsyslog.d/20-erddap.conf: -------------------------------------------------------------------------------- 1 | input( 2 | type="imfile" 3 | File="/erddapData/logs/log.txt" 4 | Tag="erddap" 5 | Ruleset="erddapToStdOut" 6 | readMode="1" 7 | escapeLF="off" 8 | ) 9 | 10 | template(name="erddapStdOut" type="list") { 11 | constant(value="[") 12 | property(name="syslogtag" caseconversion="upper") 13 | constant(value="] ") 14 | property(name="timestamp" dateformat="year" date.inUTC="on") 15 | constant(value="-") 16 | property(name="timestamp" dateformat="month" date.inUTC="on") 17 | constant(value="-") 18 | property(name="timestamp" dateformat="day" date.inUTC="on") 19 | constant(value="T") 20 | property(name="timestamp" dateformat="hour" date.inUTC="on") 21 | constant(value=":") 22 | property(name="timestamp" dateformat="minute" date.inUTC="on") 23 | constant(value=":") 24 | property(name="timestamp" dateformat="second" date.inUTC="on") 25 | constant(value="Z ") 26 | property(name="msg" droplastlf="on" ) 27 | constant(value="\n") 28 | } 29 | 30 | ruleset(name="erddapToStdOut") { 31 | # Header/footer for log sections can be ignored 32 | if $msg startswith '\\\\\\\\****' then stop 33 | if $msg startswith '{{{' then stop 34 | if $msg startswith '}}}' then stop 35 | 36 | # Prevents the statistics on response from being logged since 37 | # this is better obtained from the ERDDAP status webpage 38 | if $msg contains 'Time Distribution' then stop 39 | if $msg contains 'Times Distribution' then stop 40 | if $msg contains 'TaskThread' then stop 41 | 42 | # Not sure what these mean and are noisy 43 | if $msg startswith 'Language (' then stop 44 | 45 | # The log messages explaining what each Thread is doing 46 | if $msg contains ' Thread[' then stop 47 | action( 48 | type="omstdout" template="erddapStdOut" 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /files/setenv.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ -f "${CATALINA_HOME}/bin/config.sh" ]; 4 | then 5 | set -o allexport 6 | source "${CATALINA_HOME}/bin/config.sh" 7 | set +o allexport 8 | fi 9 | 10 | ERDDAP_CONFIG=$(env | grep --regexp "^ERDDAP_.*$" | sort) 11 | if [ -n "$ERDDAP_CONFIG" ]; then 12 | echo -e "ERDDAP configured with:\n$ERDDAP_CONFIG" 13 | fi 14 | 15 | JAVA_MAJOR_VERSION=$(java -version 2>&1 | head -1 | cut -d'"' -f2 | sed '/^1\./s///' | cut -d'.' -f1) 16 | 17 | # JAVA_OPTS 18 | NORMAL="-server" 19 | 20 | # Memory 21 | if [ -n "$ERDDAP_MAX_RAM_PERCENTAGE" ]; then 22 | JVM_MEMORY_ARGS="-XX:MaxRAMPercentage=${ERDDAP_MAX_RAM_PERCENTAGE}" 23 | else 24 | ERDDAP_MEMORY="${ERDDAP_MEMORY:-4G}" 25 | JVM_MEMORY_ARGS="-Xms${ERDDAP_MIN_MEMORY:-${ERDDAP_MEMORY}} -Xmx${ERDDAP_MAX_MEMORY:-${ERDDAP_MEMORY}}" 26 | fi 27 | 28 | HEAP_DUMP="-XX:+HeapDumpOnOutOfMemoryError" 29 | HEADLESS="-Djava.awt.headless=true" 30 | 31 | EXTRAS=${JAVA_EXTRAS:-} 32 | if [ $JAVA_MAJOR_VERSION -lt 9 ]; then 33 | #these options are deprecated in java 9 and illegal in java 14+ 34 | EXTRAS="$EXTRAS -XX:+UseConcMarkSweepGC -XX:+CMSClassUnloadingEnabled" 35 | fi 36 | 37 | CONTENT_ROOT="-DerddapContentDirectory=$CATALINA_HOME/content/erddap" 38 | JNA_DIR="-Djna.tmpdir=/tmp/" 39 | FASTBOOT="-Djava.security.egd=file:/dev/./urandom" 40 | 41 | JAVA_OPTS="$JAVA_OPTS $NORMAL $JVM_MEMORY_ARGS $HEAP_DUMP $HEADLESS $EXTRAS $CONTENT_ROOT/ $JNA_DIR $FASTBOOT" 42 | echo "ERDDAP Running with: $JAVA_OPTS" 43 | -------------------------------------------------------------------------------- /init.d/10-remove-hdf-nc-range-request-exclusion.sh: -------------------------------------------------------------------------------- 1 | sed -i 's/.hdf, .nc, //g' ${CATALINA_HOME}/webapps/erddap/WEB-INF/classes/gov/noaa/pfel/erddap/util/messages.xml 2 | -------------------------------------------------------------------------------- /make-dataset-xml.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Used to non-interactively generate a datasets.xml dataset configuration for ERDDAP 4 | #using GenerateDatasetsXml.sh using all default values. The output 5 | #of this script should be used as a starting point and must be populated 6 | #with the correct values for the dataset 7 | # 8 | #Example usage: 9 | #./make-datasets-xml.sh /path/to/my.csv EDDTableFromAsciiFiles 10 | 11 | TARGET_FILE="$1" 12 | 13 | if [ ! -f "$TARGET_FILE" ]; then 14 | echo "File ""$TARGET_FILE"" doesn't exist" >&2 15 | exit 1 16 | fi 17 | 18 | DATASET_TYPE=${2:-"EDDTableFromAsciiFiles"} 19 | 20 | TARGET_FILENAME="$(basename ""$TARGET_FILE"")" 21 | 22 | docker run --rm -v $(realpath "$TARGET_FILE"):/data_directory/data_file \ 23 | --workdir /usr/local/tomcat/webapps/erddap/WEB-INF axiom/docker-erddap \ 24 | bash ./GenerateDatasetsXml.sh ${DATASET_TYPE} /data_directory data_file $(yes '""' | head -n 18) \ 25 | | sed -n '/^&2 11 | exit 1 12 | fi 13 | 14 | RELAXED_PATH_CHARS="[]|" 15 | RELAXED_QUERY_CHARS="[]:|{}^\`"<>" 16 | 17 | function set_attribute { 18 | ELEM="$1" 19 | ATTR="$2" 20 | VAL="$3" 21 | if [ -z "$(xmlstarlet sel -t -c "${ELEM}[@${ATTR}='${VAL}']" $SERVER_XML)" ]; then 22 | #xmlstarlet escapes special characters like & when writing values, and we 23 | #want the attributes to be exactly as we define them. insert replacement 24 | #target tokens instead, and then replace with sed. 25 | #ampersands are also special characters in sed, so replace with ~ first 26 | #and then replace again back to & 27 | TOKEN="__${ATTR}__" 28 | xmlstarlet edit --inplace -P -u "${ELEM}/@${ATTR}" -v "${TOKEN}" \ 29 | -i "${ELEM}[not(@${ATTR})]" -t attr -n "${ATTR}" -v "${TOKEN}" \ 30 | $SERVER_XML 31 | sed -i -e "s/${TOKEN}/$( echo $VAL | tr '&' '~')/" -e "s/~/\&/g" $SERVER_XML 32 | fi 33 | } 34 | 35 | #set Connector relaxedPathChars and relaxedQueryChars to allow DAP queries 36 | set_attribute /Server/Service/Connector relaxedPathChars "$RELAXED_PATH_CHARS" 37 | set_attribute /Server/Service/Connector relaxedQueryChars "$RELAXED_QUERY_CHARS" 38 | 39 | # Enable request attributes so that, when using a reverse proxy, the original 40 | # client ip is recorded in logs rather than the internal proxy ip 41 | set_attribute /Server/Service/Engine/Host/Valve requestAttributesEnable "true" 42 | 43 | #create RemoteIpValve if missing. this is needed so ERDDAP knows when its responding to https requests 44 | #end result should look like: 45 | # 49 | #https://stackoverflow.com/a/9172796/193435 50 | if [ -z "$(xmlstarlet sel -t -c "/Server/Service/Engine/Host/Valve[@className='org.apache.catalina.valves.RemoteIpValve']" $SERVER_XML)" ]; then 51 | xmlstarlet edit --inplace -P -s /Server/Service/Engine/Host -t elem -n RemoteIpValve -v "" \ 52 | -i //RemoteIpValve -t attr -n "className" -v "org.apache.catalina.valves.RemoteIpValve" \ 53 | -i //RemoteIpValve -t attr -n "remoteIpHeader" -v "X-Forwarded-For" \ 54 | -i //RemoteIpValve -t attr -n "protocolHeader" -v "X-Forwarded-Proto" \ 55 | -i //RemoteIpValve -t attr -n "protocolHeaderHttpsValue" -v "https" \ 56 | -r //RemoteIpValve -v Valve \ 57 | $SERVER_XML 58 | fi 59 | --------------------------------------------------------------------------------