├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── example ├── multiple-process-image │ ├── Dockerfile │ ├── Makefile │ ├── environment │ │ ├── default.startup.yaml │ │ └── default.yaml │ └── service │ │ ├── nginx │ │ ├── install.sh │ │ ├── process.sh │ │ └── startup.sh │ │ └── php │ │ ├── config │ │ └── default │ │ ├── install.sh │ │ └── process.sh └── single-process-image │ ├── Dockerfile │ ├── Makefile │ ├── environment │ ├── default.startup.yaml │ └── default.yaml │ ├── service │ └── nginx │ │ ├── install.sh │ │ ├── process.sh │ │ └── startup.sh │ └── test-custom-env │ └── env.yaml ├── image ├── Dockerfile ├── build.sh ├── file │ ├── dpkg_nodoc │ └── dpkg_nolocales ├── service-available │ ├── :cron │ │ ├── download.sh │ │ ├── install.sh │ │ ├── process.sh │ │ └── startup.sh │ ├── :logrotate │ │ ├── assets │ │ │ └── config │ │ │ │ ├── logrotate.conf │ │ │ │ └── logrotate_syslogng │ │ ├── download.sh │ │ ├── install.sh │ │ └── startup.sh │ ├── :runit │ │ └── download.sh │ ├── :ssl-tools │ │ ├── assets │ │ │ ├── cfssl-default-env │ │ │ ├── default-ca │ │ │ │ ├── README.md │ │ │ │ ├── config │ │ │ │ │ ├── ca-config.json │ │ │ │ │ ├── ca-csr.json │ │ │ │ │ └── req-csr.json.tmpl │ │ │ │ ├── default-ca-key.pem │ │ │ │ ├── default-ca.csr │ │ │ │ └── default-ca.pem │ │ │ ├── default-env │ │ │ ├── jsonssl-default-env │ │ │ └── tool │ │ │ │ ├── cfssl-helper │ │ │ │ ├── jsonssl-helper │ │ │ │ ├── ssl-auto-renew │ │ │ │ └── ssl-helper │ │ ├── download.sh │ │ └── startup.sh │ └── :syslog-ng-core │ │ ├── assets │ │ └── config │ │ │ ├── syslog-ng.conf │ │ │ └── syslog_ng_default │ │ ├── download.sh │ │ ├── install.sh │ │ ├── process.sh │ │ └── startup.sh └── tool │ ├── add-multiple-process-stack │ ├── add-service-available │ ├── complex-bash-env │ ├── install-service │ ├── log-helper │ ├── run │ ├── setuser │ └── wait-process └── test ├── test.bats └── test_helper.bash /.gitignore: -------------------------------------------------------------------------------- 1 | /.twgit_features_subject 2 | /.twgit 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: bash 2 | 3 | services: 4 | - docker 5 | env: 6 | global: 7 | - NAME="osixia/light-baseimage" 8 | - VERSION="${TRAVIS_BRANCH}-dev" 9 | matrix: 10 | - TARGET_ARCH=amd64 QEMU_ARCH=x86_64 11 | - TARGET_ARCH=i386 QEMU_ARCH=i386 12 | - TARGET_ARCH=arm32v7 QEMU_ARCH=arm 13 | - TARGET_ARCH=arm64v8 QEMU_ARCH=aarch64 14 | 15 | addons: 16 | apt: 17 | # The docker manifest command was added in docker-ee version 18.x 18 | # So update our current installation and we also have to enable the experimental features. 19 | sources: 20 | - sourceline: "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" 21 | key_url: "https://download.docker.com/linux/ubuntu/gpg" 22 | packages: 23 | - docker-ce 24 | 25 | before_install: 26 | - docker --version 27 | - mkdir $HOME/.docker 28 | - 'echo "{" > $HOME/.docker/config.json' 29 | - 'echo " \"experimental\": \"enabled\"" >> $HOME/.docker/config.json' 30 | - 'echo "}" >> $HOME/.docker/config.json' 31 | - sudo service docker restart 32 | # To have `DOCKER_USER` and `DOCKER_PASS` 33 | # use `travis env set`. 34 | - echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin 35 | 36 | install: 37 | # For cross buidling our images 38 | # This is necessary because travis-ci.org has only x86_64 machines. 39 | # If travis-ci.org gets native arm builds, probably this step is not 40 | # necessary any more. 41 | - docker run --rm --privileged multiarch/qemu-user-static:register --reset 42 | # Bats is necessary for the UT 43 | - curl -o bats.tar.gz -SL https://github.com/bats-core/bats-core/archive/v1.1.0.tar.gz 44 | - mkdir bats-core && tar -xf bats.tar.gz -C bats-core --strip-components=1 45 | - cd bats-core/ 46 | - sudo ./install.sh /usr/local 47 | - cd .. 48 | 49 | before_script: 50 | # Injecting the necessary information and binaries for cross-compiling the images. 51 | # In native builds this information and binaries are not necessary and that is why 52 | # we are injecting them in the build scripts and we do not include them in the Dockerfiles 53 | - if [[ "${TARGET_ARCH}" != 'amd64' ]]; then 54 | sed -i "s/FROM debian/FROM ${TARGET_ARCH}\/debian/" image/Dockerfile; 55 | fi 56 | - if [[ "${TARGET_ARCH}" != 'amd64' ]]; then 57 | sed -i "/${TARGET_ARCH}\/debian/a COPY \ 58 | --from=multiarch/qemu-user-static:x86_64-${QEMU_ARCH} \ 59 | /usr/bin/qemu-${QEMU_ARCH}-static /usr/bin/" image/Dockerfile; 60 | fi 61 | - cat image/Dockerfile; 62 | # If this is a tag then change the VERSION variable to only have the 63 | # tag name and not also the commit hash. 64 | - if [ -n "$TRAVIS_TAG" ]; then 65 | VERSION=$(echo "${TRAVIS_TAG}" | sed -e 's/\(.*\)[-v]\(.*\)/\1\2/g'); 66 | fi 67 | - if [ "${TRAVIS_BRANCH}" == 'master' ]; then 68 | VERSION="stable"; 69 | fi 70 | # replace / with - in version 71 | - VERSION=$(echo "${VERSION}" | sed 's|/|-|g'); 72 | 73 | script: 74 | - make build-nocache NAME=${NAME} VERSION=${VERSION}-${TARGET_ARCH} 75 | # Run the test and if the test fails mark the build as failed. 76 | - make test NAME=${NAME} VERSION=${VERSION}-${TARGET_ARCH} 77 | 78 | before_deploy: 79 | - docker run -d --name test_image ${NAME}:${VERSION}-${TARGET_ARCH} sleep 10 80 | - sleep 5 81 | - sudo docker ps | grep -q test_image 82 | - make tag NAME=${NAME} VERSION=${VERSION}-${TARGET_ARCH} 83 | 84 | deploy: 85 | provider: script 86 | on: 87 | all_branches: true 88 | script: make push NAME=${NAME} VERSION=${VERSION}-${TARGET_ARCH} 89 | 90 | jobs: 91 | include: 92 | - stage: Manifest creation 93 | install: skip 94 | script: skip 95 | after_deploy: 96 | - echo "$DOCKER_PASS" | docker login -u "$DOCKER_USER" --password-stdin 97 | - docker manifest create ${NAME}:${VERSION} ${NAME}:${VERSION}-amd64 ${NAME}:${VERSION}-i386 ${NAME}:${VERSION}-arm32v7 ${NAME}:${VERSION}-arm64v8; 98 | docker manifest annotate ${NAME}:${VERSION} ${NAME}:${VERSION}-amd64 --os linux --arch amd64; 99 | docker manifest annotate ${NAME}:${VERSION} ${NAME}:${VERSION}-i386 --os linux --arch 386; 100 | docker manifest annotate ${NAME}:${VERSION} ${NAME}:${VERSION}-arm32v7 --os linux --arch arm --variant v7; 101 | docker manifest annotate ${NAME}:${VERSION} ${NAME}:${VERSION}-arm64v8 --os linux --arch arm64 --variant v8; 102 | 103 | # The latest tag is coming from the stable branch of the repo 104 | - if [ "${TRAVIS_BRANCH}" == 'master' ]; then 105 | docker manifest create ${NAME}:latest ${NAME}:${VERSION}-amd64 ${NAME}:${VERSION}-i386 ${NAME}:${VERSION}-arm32v7 ${NAME}:${VERSION}-arm64v8; 106 | docker manifest annotate ${NAME}:latest ${NAME}:${VERSION}-amd64 --os linux --arch amd64; 107 | docker manifest annotate ${NAME}:latest ${NAME}:${VERSION}-i386 --os linux --arch 386; 108 | docker manifest annotate ${NAME}:latest ${NAME}:${VERSION}-arm32v7 --os linux --arch arm --variant v7; 109 | docker manifest annotate ${NAME}:latest ${NAME}:${VERSION}-arm64v8 --os linux --arch arm64 --variant v8; 110 | fi 111 | 112 | - docker manifest push ${NAME}:${VERSION}; 113 | if [ "${TRAVIS_BRANCH}" == 'master' ]; then 114 | docker manifest push ${NAME}:latest; 115 | fi 116 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [1.3.3] - 2021-03-13 4 | ### Changed 5 | - Multiple log line message are now split and log line by line for a nicest display. 6 | 7 | ### Fixed 8 | - log-level broken pipe 9 | 10 | ## [1.3.2] - 2021-02-18 11 | ### Fixed 12 | - Remove -x bash flag on log-helper tool 13 | 14 | # [1.3.1] - 2021-01-24 15 | ### Fixed 16 | - Update expired default-ca #30 #29. Thanks to @heidemn 17 | 18 | ## [1.3.0] - 2020-11-21 19 | ### Changed 20 | - Add loglevel and datetime to log messages 21 | - Upgrade CFSSL version to 1.5.0 22 | 23 | ## [1.2.0] - 2020-06-15 24 | ### Added 25 | - Add multiarch support. Thanks to @ndanyluk and @anagno ! 26 | - Allow usage of additional hostnames in self signed certificate #19. Thanks to @Bobonium 27 | 28 | ### Changed 29 | - Use debian buster-slim as baseimage 30 | - Upgrade python script to python3 31 | - Upgrade CFSSL version to 1.4.1 32 | 33 | ### Fixed 34 | - Fix shellcheck errors and warnings on all scripts 35 | 36 | ## [1.1.2] - 2019-04-05 37 | ### Added 38 | - jsonssl add support for traefik >= v1.6 acme.json file 39 | 40 | ### Changed 41 | - "traefik" JSONSSL_PROFILE be becomes "traefik_up_to_v1_6" 42 | - "traefik" JSONSSL_PROFILE is now for traefik >= v1.6 acme.json file 43 | - Upgrade CFSSL version to 1.3.2 44 | - run: catch copy-service errors 45 | - KILL_PROCESS_TIMEOUT and KILL_ALL_PROCESSES_TIMEOUT to 30 seconds 46 | - make ssl-auto-renew cron log with /usr/bin/logger -t cron_ssl_auto_renew 47 | - syslog-ng config 48 | 49 | ### Fixed 50 | - my_init exits with 0 on SIGINT after runit is started 51 | - better sanitize_shenvname 52 | - exit status 53 | 54 | ## [1.1.1] - 2017-10-25 55 | ### Changed 56 | - chmod 444 logrotate config files 57 | 58 | ### Fixed 59 | - Fix jsonssl-helper get traefik ca certificate on alpine 60 | 61 | ## [1.1.0] - 2017-07-19 62 | ### Changed 63 | - Use debian stretch-slim as baseimage 64 | 65 | ## [1.0.0] - 2017-07-05 66 | ### Added 67 | - Run tool now use 2 environmen variable KILL_PROCESS_TIMEOUT and KILL_ALL_PROCESSES_TIMEOUT 68 | 69 | ### Changed 70 | - Default local to en_US.UTF-8 71 | 72 | ## [0.2.6] - 2016-11-06 73 | ### Added 74 | - Add to the 'run' tool option --dont-touch-etc-hosts Don't add in /etc/hosts a line with the container ip and $HOSTNAME environment variable value. 75 | 76 | ### Fixed 77 | - Fix wait-process script 78 | 79 | ## [0.2.5] - 2016-09-03 80 | ### Added 81 | - Add ssl-helper that allow certificate auto-renew and let choose 82 | certificate generator (cfssl-helper default, or jsonssl-helper) 83 | - Add jsonssl-helper that get certificates from a json file 84 | - Add to the 'run' tool options --run-only, --wait-first-startup, --wait-state, --cmd 85 | --keepalived becomes --keepalive-force, 86 | --keepalive now only keep alive container if all startup files and process 87 | exited without error. 88 | 89 | ### Changed 90 | - Upgrade cfssl 1.2.0 91 | - Change .yaml.startup and .json.startup files to .startup.yaml and .startup.json 92 | 93 | ### Fixed 94 | - Fix is_runit_installed check /usr/bin/sv instead of /sbin/runit #6 95 | - Fix logrotate config 96 | 97 | ## [0.2.4] - 2016-06-09 98 | ### Changed 99 | - Periodic update of debian baseimage and packages 100 | 101 | ## [0.2.3] - 2016-05-02 102 | ### Changed 103 | - Periodic update of debian baseimage and packages 104 | 105 | ## [0.2.2] - 2016-02-20 106 | ### Fixed 107 | - Fix --copy-service error if /container/run/service already exists 108 | - Fix /container/run/startup.sh file detection if no other startup files exists 109 | - Fix set_env_hostname_to_etc_hosts() on container restart 110 | 111 | ## [0.2.1] - 2016-01-25 112 | ### Added 113 | - Add cfssl as available service to generate ssl certs 114 | - Add tag #PYTHON2BASH and #JSON2BASH to convert env var to bash 115 | - Add multiple env file importation 116 | - Add setup only env file 117 | - Add json env file support 118 | - Rename my_init to run (delete previous run script) 119 | - Add run tool option --copy-service that copy /container/service to /container/run/service on startup 120 | - Add run tool option --loglevel (default : info) with possible values : none, error, warning, info, debug. 121 | - Add bash log-helper 122 | 123 | ### Changed 124 | - Container environment config directory /etc/container_environment moved to /container/environment 125 | - Container run environment is now saved in /container/run/environment 126 | - Container run environment bash export /etc/container_environment.sh moved to /container/run/environment.sh 127 | - Container state is now saved in /container/run/state 128 | - Container runit process directory /etc/service moved to /container/run/process 129 | - Container startup script directory /etc/my_init.d/ moved to /container/run/startup 130 | - Container final startup script /etc/rc.local moved to /container/run/startup.sh 131 | - Rename install-multiple-process-stack to add-multiple-process-stack 132 | - Rename install-service-available to add-service-available 133 | 134 | ### Removed 135 | - ssl-helper ssl-helper-openssl and ssl-helper-gnutls 136 | - Remove run tool option --quiet 137 | 138 | ## [0.2.0] - 2015-12-16 139 | ### Added 140 | - Makefile with build no cache 141 | 142 | ### Changed 143 | - Allow more easy image inheritance 144 | 145 | ### Fixed 146 | - Fix cron NUMBER OF HARD LINKS > 1 147 | 148 | 149 | ## [0.1.5] - 2015-11-20 150 | ### Fixed 151 | - Fix bug with host network 152 | 153 | ## [0.1.4] - 2015-11-19 154 | ### Added 155 | - Add run cmd arguments when it's a single process image 156 | 157 | ### Changed 158 | - Remove bash from command when it's a single process image 159 | 160 | ## [0.1.3] - 2015-11-06 161 | ### Added 162 | - Add hostname env variable to /etc/hosts 163 | to make the image more friendly with kubernetes again :) 164 | 165 | ## [0.1.2] - 2015-10-23 166 | ### Added 167 | - Load env.yaml file from /container/environment directory 168 | to make the image more friendly with kubernetes secrets :) 169 | 170 | ## [0.1.1] - 2015-08-18 171 | ### Added 172 | - Add python and PyYAML 173 | 174 | ### Fixed 175 | - Fix remove-service #1 176 | - Fix locales 177 | - Fix my_init 178 | 179 | ## 0.1.0 - 2015-07-23 180 | Initial release 181 | 182 | [1.3.3]: https://github.com/osixia/docker-light-baseimage/compare/v1.3.2...v1.3.3 183 | [1.3.2]: https://github.com/osixia/docker-light-baseimage/compare/v1.3.1...v1.3.2 184 | [1.3.1]: https://github.com/osixia/docker-light-baseimage/compare/v1.3.0...v1.3.1 185 | [1.3.0]: https://github.com/osixia/docker-light-baseimage/compare/v1.2.0...v1.3.0 186 | [1.2.0]: https://github.com/osixia/docker-light-baseimage/compare/v1.1.2...v1.2.0 187 | [1.1.2]: https://github.com/osixia/docker-light-baseimage/compare/v1.1.1...v1.1.2 188 | [1.1.1]: https://github.com/osixia/docker-light-baseimage/compare/v1.1.0...v1.1.1 189 | [1.1.0]: https://github.com/osixia/docker-light-baseimage/compare/v1.0.0...v1.1.0 190 | [1.0.0]: https://github.com/osixia/docker-light-baseimage/compare/v0.2.2...v1.0.0 191 | [0.2.6]: https://github.com/osixia/docker-light-baseimage/compare/v0.2.5...v0.2.6 192 | [0.2.5]: https://github.com/osixia/docker-light-baseimage/compare/v0.2.4...v0.2.5 193 | [0.2.4]: https://github.com/osixia/docker-light-baseimage/compare/v0.2.3...v0.2.4 194 | [0.2.3]: https://github.com/osixia/docker-light-baseimage/compare/v0.2.2...v0.2.3 195 | [0.2.2]: https://github.com/osixia/docker-light-baseimage/compare/v0.2.1...v0.2.2 196 | [0.2.1]: https://github.com/osixia/docker-light-baseimage/compare/v0.2.0...v0.2.1 197 | [0.2.0]: https://github.com/osixia/docker-light-baseimage/compare/v0.1.5...v0.2.0 198 | [0.1.5]: https://github.com/osixia/docker-light-baseimage/compare/v0.1.4...v0.1.5 199 | [0.1.4]: https://github.com/osixia/docker-light-baseimage/compare/v0.1.3...v0.1.4 200 | [0.1.3]: https://github.com/osixia/docker-light-baseimage/compare/v0.1.2...v0.1.3 201 | [0.1.2]: https://github.com/osixia/docker-light-baseimage/compare/v0.1.1...v0.1.2 202 | [0.1.1]: https://github.com/osixia/docker-light-baseimage/compare/v0.1.0...v0.1.1 203 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NAME = osixia/light-baseimage 2 | VERSION = 1.3.3 3 | 4 | .PHONY: build build-nocache test tag-latest push push-latest release git-tag-version 5 | 6 | build: 7 | docker build -f image/Dockerfile -t $(NAME):$(VERSION) --rm image 8 | 9 | build-nocache: 10 | docker build -f image/Dockerfile -t $(NAME):$(VERSION) --no-cache --rm image 11 | 12 | test: 13 | env NAME=$(NAME) VERSION=$(VERSION) bats test/test.bats 14 | 15 | tag: 16 | docker tag $(NAME):$(VERSION) $(NAME):$(VERSION) 17 | 18 | tag-latest: 19 | docker tag $(NAME):$(VERSION) $(NAME):latest 20 | 21 | push: 22 | docker push $(NAME):$(VERSION) 23 | 24 | push-latest: 25 | docker push $(NAME):latest 26 | 27 | release: build test tag-latest push push-latest 28 | 29 | git-tag-version: release 30 | git tag -a v$(VERSION) -m "v$(VERSION)" 31 | git push origin v$(VERSION) 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # osixia/light-baseimage 2 | 3 | [![Docker Pulls](https://img.shields.io/docker/pulls/osixia/light-baseimage.svg)][hub] 4 | [![Docker Stars](https://img.shields.io/docker/stars/osixia/light-baseimage.svg)][hub] 5 | [![](https://images.microbadger.com/badges/image/osixia/light-baseimage.svg)](http://microbadger.com/images/osixia/light-baseimage "Get your own image badge on microbadger.com") 6 | 7 | [hub]: https://hub.docker.com/r/osixia/light-baseimage/ 8 | 9 | Latest release: 1.3.3 [Changelog](CHANGELOG.md) 10 | | [Docker Hub](https://hub.docker.com/r/osixia/light-baseimage/)  11 | 12 | A **Debian 10 (Buster)** based docker image to build reliable image quickly. This image provide a simple opinionated solution to build multiple or single process image with minimum of layers and an optimized build. 13 | 14 | The aims of this image is to be used as a base for your own Docker images. It's base on the awesome work of: [phusion/baseimage-docker](https://github.com/phusion/baseimage-docker) 15 | 16 | Other base distribution are available: 17 | - [Alpine](https://github.com/osixia/docker-light-baseimage/tree/alpine) | [Docker Hub](https://hub.docker.com/r/osixia/alpine-light-baseimage/) | [![](https://images.microbadger.com/badges/image/osixia/alpine-light-baseimage.svg)](http://microbadger.com/images/osixia/alpine-light-baseimage "Get your own image badge on microbadger.com") 18 | 19 | Table of Contents 20 | - [osixia/light-baseimage](#osixialight-baseimage) 21 | - [Contributing](#contributing) 22 | - [Overview](#overview) 23 | - [Quick Start](#quick-start) 24 | - [Image directories structure](#image-directories-structure) 25 | - [Service directory structure](#service-directory-structure) 26 | - [Create a single process image](#create-a-single-process-image) 27 | - [Overview](#overview-1) 28 | - [Dockerfile](#dockerfile) 29 | - [Service files](#service-files) 30 | - [install.sh](#installsh) 31 | - [startup.sh](#startupsh) 32 | - [process.sh](#processsh) 33 | - [Environment files](#environment-files) 34 | - [default.yaml](#defaultyaml) 35 | - [default.startup.yaml](#defaultstartupyaml) 36 | - [Build and test](#build-and-test) 37 | - [Overriding default environment files at run time:](#overriding-default-environment-files-at-run-time) 38 | - [Create a multiple process image](#create-a-multiple-process-image) 39 | - [Overview](#overview-2) 40 | - [Dockerfile](#dockerfile-1) 41 | - [Service files](#service-files-1) 42 | - [install.sh](#installsh) 43 | - [process.sh](#processsh-1) 44 | - [Build and test](#build-and-test-1) 45 | - [Images Based On Light-Baseimage](#images-based-on-light-baseimage) 46 | - [Image Assets](#image-assets) 47 | - [Tools](#tools) 48 | - [Services available](#services-available) 49 | - [Advanced User Guide](#advanced-user-guide) 50 | - [Service available](#service-available) 51 | - [Fix docker mounted file problems](#fix-docker-mounted-file-problems) 52 | - [Distribution packages documentation and locales](#distribution-packages-documentation-and-locales) 53 | - [Mastering image tools](#mastering-image-tools) 54 | - [run](#run) 55 | - [Run command line options](#run-command-line-options) 56 | - [Run directory setup](#run-directory-setup) 57 | - [Startup files environment setup](#startup-files-environment-setup) 58 | - [Startup files execution](#startup-files-execution) 59 | - [Process execution](#process-execution) 60 | - [Single process image](#single-process-image) 61 | - [Multiple process image](#multiple-process-image) 62 | - [No process image](#no-process-image) 63 | - [Extra environment variables](#extra-environment-variables) 64 | - [log-helper](#log-helper) 65 | - [complex-bash-env](#complex-bash-env) 66 | - [Tests](#tests) 67 | - [Changelog](#changelog) 68 | 69 | ## Contributing 70 | 71 | If you find this image useful here's how you can help: 72 | 73 | - Send a pull request with your kickass new features and bug fixes 74 | - Help new users with [issues](https://github.com/osixia/docker-openldap/issues) they may encounter 75 | - Support the development of this image and star this repo! 76 | 77 | ## Overview 78 | 79 | This image takes all the advantages of [phusion/baseimage-docker](https://github.com/phusion/baseimage-docker) but makes programs optional which allow more lightweight images and single process images. It also define simple directory structure and files to quickly set how a program (here called service) is installed, setup and run. 80 | 81 | So major features are: 82 | - Greats building tools to minimize the image number of layers and optimize image build. 83 | - Simple way to install services and multiple process image stacks (runit, cron, syslog-ng-core and logrotate) if needed. 84 | - Getting environment variables from **.yaml** and **.json** files. 85 | - Special environment files **.startup.yaml** and **.startup.json** deleted after image startup files first execution to keep the image setup secret. 86 | 87 | 88 | ## Quick Start 89 | 90 | ### Image directories structure 91 | 92 | This image use four directories: 93 | 94 | - **/container/environment**: for environment files. 95 | - **/container/service**: for services to install, setup and run. 96 | - **/container/service-available**: for service that may be on demand downloaded, installed, setup and run. 97 | - **/container/tool**: for image tools. 98 | 99 | By the way at run time another directory is created: 100 | - **/container/run**: To store container run environment, state, startup files and process to run based on files in /container/environment and /container/service directories. 101 | 102 | But this will be dealt with in the following section. 103 | 104 | ### Service directory structure 105 | 106 | This section define a service directory that can be added in /container/service or /container/service-available. 107 | 108 | - **my-service**: root directory 109 | - **my-service/install.sh**: install script (not mandatory). 110 | - **my-service/startup.sh**: startup script to setup the service when the container start (not mandatory). 111 | - **my-service/process.sh**: process to run (not mandatory). 112 | - **my-service/finish.sh**: finish script run when the process script exit (not mandatory). 113 | - **my-service/...** add whatever you need! 114 | 115 | Ok that's pretty all to know to start building our first images! 116 | 117 | ### Create a single process image 118 | 119 | #### Overview 120 | For this example we are going to perform a basic nginx install. 121 | 122 | See complete example in: [example/single-process-image](example/single-process-image) 123 | 124 | First we create the directory structure of the image: 125 | 126 | - **single-process-image**: root directory 127 | - **single-process-image/service**: directory to store the nginx service. 128 | - **single-process-image/environment**: environment files directory. 129 | - **single-process-image/Dockerfile**: the Dockerfile to build this image. 130 | 131 | **service** and **environment** directories name are arbitrary and can be changed but make sure to adapt their name everywhere and especially in the Dockerfile. 132 | 133 | Let's now create the nginx service directory: 134 | 135 | - **single-process-image/service/nginx**: service root directory 136 | - **single-process-image/service/nginx/install.sh**: service installation script. 137 | - **single-process-image/service/nginx/startup.sh**: startup script to setup the service when the container start. 138 | - **single-process-image/service/nginx/process.sh**: process to run. 139 | 140 | 141 | #### Dockerfile 142 | 143 | In the Dockerfile we are going to: 144 | - Download nginx from apt-get. 145 | - Add the service directory to the image. 146 | - Install service and clean up. 147 | - Add the environment directory to the image. 148 | - Define ports exposed and volumes if needed. 149 | 150 | 151 | # Use osixia/light-baseimage 152 | # https://github.com/osixia/docker-light-baseimage 153 | FROM osixia/light-baseimage:1.3.3 154 | 155 | # Download nginx from apt-get and clean apt-get files 156 | RUN apt-get -y update \ 157 | && LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ 158 | nginx \ 159 | && apt-get clean \ 160 | && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 161 | 162 | # Add service directory to /container/service 163 | ADD service /container/service 164 | 165 | # Use baseimage install-service script 166 | # https://github.com/osixia/docker-light-baseimage/blob/stable/image/tool/install-service 167 | RUN /container/tool/install-service 168 | 169 | # Add default env directory 170 | ADD environment /container/environment/99-default 171 | 172 | # Set /var/www/ in a data volume 173 | VOLUME /var/www/ 174 | 175 | # Expose default http and https ports 176 | EXPOSE 80 443 177 | 178 | 179 | The Dockerfile contains directives to download nginx from apt-get but all the initial setup will take place in install.sh file (called by /container/tool/install-service tool) for a better build experience. The time consuming download task is decoupled from the initial setup to make great use of docker build cache. If install.sh file is changed the builder won't have to download again nginx, and will just run install scripts. 180 | 181 | #### Service files 182 | 183 | ##### install.sh 184 | 185 | This file must only contain directives for the service initial setup. Files download and apt-get command takes place in the Dockerfile for a better image building experience (see [Dockerfile](#dockerfile)). 186 | 187 | In this example, for the initial setup we just delete the default nginx debian index file and create a custom index.html: 188 | 189 | #!/bin/bash -e 190 | # this script is run during the image build 191 | 192 | rm -rf /var/www/html/index.nginx-debian.html 193 | echo "Hi!" > /var/www/html/index.html 194 | 195 | Make sure install.sh can be executed (chmod +x install.sh). 196 | 197 | Note: The install.sh script is run during the docker build so run time environment variables can't be used to customize the setup. This is done in the startup.sh file. 198 | 199 | 200 | ##### startup.sh 201 | 202 | This file is used to make process.sh ready to be run and customize the service setup based on run time environment. 203 | 204 | For example at run time we would like to introduce ourselves so we will use an environment variable WHO_AM_I set by command line with --env. So we add WHO_AM_I value to index.html file but we want to do that only on the first container start because on restart the index.html file will already contains our name: 205 | 206 | #!/bin/bash -e 207 | FIRST_START_DONE="${CONTAINER_STATE_DIR}/nginx-first-start-done" 208 | 209 | # container first start 210 | if [ ! -e "$FIRST_START_DONE" ]; then 211 | echo "I'm ${WHO_AM_I}." >> /var/www/html/index.html 212 | touch $FIRST_START_DONE 213 | fi 214 | 215 | exit 0 216 | 217 | Make sure startup.sh can be executed (chmod +x startup.sh). 218 | 219 | As you can see we use CONTAINER_STATE_DIR variable, it contains the directory where container state is saved, this variable is automatically set by run tool. Refer to the [Advanced User Guide](#extra-environment-variables) for more information. 220 | 221 | ##### process.sh 222 | 223 | This file define the command to run: 224 | 225 | #!/bin/bash -e 226 | exec /usr/sbin/nginx -g "daemon off;" 227 | 228 | Make sure process.sh can be executed (chmod +x process.sh). 229 | 230 | *Caution: The command executed must start a foreground process otherwise the container will immediately stops.* 231 | 232 | That why we run nginx with `-g "daemon off;"` 233 | 234 | That's it we have a single process image that run nginx! 235 | We could already build and test this image but two more minutes to take advantage of environment files! 236 | 237 | #### Environment files 238 | 239 | Let's create two files: 240 | - single-process-image/environment/default.yaml 241 | - single-process-image/environment/default.startup.yaml 242 | 243 | File name *default*.yaml and *default*.startup.yaml can be changed as you want. Also in this example we are going to use yaml files but json files works too. 244 | 245 | ##### default.yaml 246 | default.yaml file define variables that can be used at any time in the container environment: 247 | 248 | WHO_AM_I: We are Anonymous. We are Legion. We do not forgive. We do not forget. Expect us. 249 | 250 | ##### default.startup.yaml 251 | default.startup.yaml define variables that are only available during the container **first start** in **startup files**. 252 | \*.startup.yaml are deleted right after startup files are processed for the first time, 253 | then all variables they contains will not be available in the container environment. 254 | 255 | This helps to keep the container configuration secret. If you don't care all environment variables can be defined in **default.yaml** and everything will work fine. 256 | 257 | But for this tutorial we will add a variable to this file: 258 | 259 | FIRST_START_SETUP_ONLY_SECRET: The database password is KawaaahBounga 260 | 261 | And try to get its value in **startup.sh** script: 262 | 263 | #!/bin/bash -e 264 | FIRST_START_DONE="${CONTAINER_STATE_DIR}/nginx-first-start-done" 265 | 266 | # container first start 267 | if [ ! -e "$FIRST_START_DONE" ]; then 268 | echo ${WHO_AM_I} >> /var/www/html/index.html 269 | touch $FIRST_START_DONE 270 | fi 271 | 272 | echo "The secret is: $FIRST_START_SETUP_ONLY_SECRET" 273 | 274 | exit 0 275 | 276 | And in **process.sh** script: 277 | 278 | #!/bin/bash -e 279 | echo "The secret is: $FIRST_START_SETUP_ONLY_SECRET" 280 | exec /usr/sbin/nginx -g "daemon off;" 281 | 282 | Ok it's time for the show! 283 | 284 | #### Build and test 285 | 286 | Build the image: 287 | 288 | docker build -t example/single-process --rm . 289 | 290 | Start a new container: 291 | 292 | docker run -p 8080:80 example/single-process 293 | 294 | Inspect the output and you should see that the secret is present in startup script: 295 | > \*\*\* Running /container/run/startup/nginx... 296 | 297 | > The secret is: The database password is Baw0unga! 298 | 299 | And the secret is not defined in the process: 300 | > \*\*\* Remove file /container/environment/99-default/default.startup.yaml [...] 301 | 302 | > \*\*\* Running /container/run/process/nginx/run... 303 | 304 | > The secret is: 305 | 306 | Yes in this case it's not really useful to have a secret variable like this, but a concrete example can be found in [osixia/openldap](https://github.com/osixia/docker-openldap) image. 307 | The admin password is available in clear text during the container first start to create a new ldap database where it is saved encrypted. After that the admin password is not available in clear text in the container environment. 308 | 309 | Ok let's check our name now, go to [http://localhost:8080/](http://localhost:8080/) 310 | 311 | You should see: 312 | > Hi! We are Anonymous. We are Legion. We do not forgive. We do not forget. Expect us. 313 | 314 | And finally, let's say who we really are, stop the previous container (ctrl+c or ctrl+d) and start a new one: 315 | 316 | docker run --env WHO_AM_I="I'm Jon Snow, what?! i'm dead?" \ 317 | -p 8080:80 example/single-process 318 | 319 | Refresh [http://localhost:8080/](http://localhost:8080/) and you should see: 320 | > Hi! I'm Jon Snow, what?! i'm dead? 321 | 322 | 323 | ##### Overriding default environment files at run time: 324 | Let's create two new environment files: 325 | - single-process-image/test-custom-env/env.yaml 326 | - single-process-image/test-custom-env/env.startup.yaml 327 | 328 | env.yaml: 329 | 330 | WHO_AM_I: I'm bobby. 331 | 332 | env.startup.yaml: 333 | 334 | FIRST_START_SETUP_ONLY_SECRET: The database password is KawaaahB0unga!!! 335 | 336 | And we mount them at run time: 337 | 338 | docker run --volume $PWD/test-custom-env:/container/environment/01-custom \ 339 | -p 8080:80 example/single-process 340 | 341 | Take care to link your environment files folder to `/container/environment/XX-somedir` (with XX < 99 so they will be processed before default environment files) and not directly to `/container/environment` because this directory contains predefined baseimage environment files to fix container environment (INITRD, LANG, LANGUAGE and LC_CTYPE). 342 | 343 | In the output: 344 | > \*\*\* Running /container/run/startup/nginx... 345 | 346 | > The secret is: The database password is KawaaahB0unga!!! 347 | 348 | Refresh [http://localhost:8080/](http://localhost:8080/) and you should see: 349 | > Hi! I'm bobby. 350 | 351 | ### Create a multiple process image 352 | 353 | #### Overview 354 | 355 | This example takes back the single process image example and add php7.0-fpm to run php scripts. 356 | 357 | See complete example in: [example/multiple-process-image](example/multiple-process-image) 358 | 359 | Note: it would have been ♪ ~~harder~~, faster, better, stronger ♪ to extends the previous image but to make things easier we just copied files. 360 | 361 | So here the image directory structure: 362 | 363 | - **multiple-process-image**: root directory 364 | - **multiple-process-image/service**: directory to store the nginx and php7.0-fpm service. 365 | - **multiple-process-image/environment**: environment files directory. 366 | - **multiple-process-image/Dockerfile**: the Dockerfile to build this image. 367 | 368 | **service** and **environment** directories name are arbitrary and can be changed but make sure to adapt their name in the Dockerfile. 369 | 370 | Let's now create the nginx and php directories: 371 | 372 | - **multiple-process-image/service/nginx**: nginx root directory 373 | - **multiple-process-image/service/nginx/install.sh**: service installation script. 374 | - **multiple-process-image/service/nginx/startup.sh**: startup script to setup the service when the container start. 375 | - **multiple-process-image/service/nginx/process.sh**: process to run. 376 | 377 | - **multiple-process-image/service/php**: php root directory 378 | - **multiple-process-image/service/php/install.sh**: service installation script. 379 | - **multiple-process-image/service/php/process.sh**: process to run. 380 | - **multiple-process-image/service/php/config/default**: default nginx server config with 381 | 382 | #### Dockerfile 383 | 384 | In the Dockerfile we are going to: 385 | - Add the multiple process stack 386 | - Download nginx and php7.0-fpm from apt-get. 387 | - Add the service directory to the image. 388 | - Install service and clean up. 389 | - Add the environment directory to the image. 390 | - Define ports exposed and volumes if needed. 391 | 392 | 393 | # Use osixia/light-baseimage 394 | # https://github.com/osixia/docker-light-baseimage 395 | FROM osixia/light-baseimage:1.3.3 396 | 397 | # Install multiple process stack, nginx and php7.0-fpm and clean apt-get files 398 | # https://github.com/osixia/docker-light-baseimage/blob/stable/image/tool/add-multiple-process-stack 399 | RUN apt-get -y update \ 400 | && /container/tool/add-multiple-process-stack \ 401 | && LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ 402 | nginx \ 403 | php7.0-fpm \ 404 | && apt-get clean \ 405 | && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 406 | 407 | # Add service directory to /container/service 408 | ADD service /container/service 409 | 410 | # Use baseimage install-service script 411 | # https://github.com/osixia/docker-light-baseimage/blob/stable/image/tool/install-service 412 | RUN /container/tool/install-service 413 | 414 | # Add default env directory 415 | ADD environment /container/environment/99-default 416 | 417 | # Set /var/www/ in a data volume 418 | VOLUME /var/www/ 419 | 420 | # Expose default http and https ports 421 | EXPOSE 80 443 422 | 423 | 424 | The Dockerfile contains directives to download nginx and php7.0-fpm from apt-get but all the initial setup will take place in install.sh file (called by /container/tool/install-service tool) for a better build experience. The time consuming download task is decoupled from the initial setup to make great use of docker build cache. If an install.sh file is changed the builder will not have to download again nginx and php7.0-fpm add will just run install scripts. 425 | 426 | Maybe you already read that in the previous example ?Sorry. 427 | 428 | #### Service files 429 | 430 | Please refer to [single process image](#create-a-single-process-image) for the nginx service files description. Here just php service files are described. 431 | 432 | ##### install.sh 433 | 434 | This file must only contains directives for the service initial setup. Files download and apt-get command takes place in the Dockerfile for a better image building experience (see [Dockerfile](#dockerfile-1) ). 435 | 436 | In this example, for the initial setup we set some php default configuration, replace the default nginx server config and add phpinfo.php file: 437 | 438 | #!/bin/bash -e 439 | # this script is run during the image build 440 | 441 | # config 442 | sed -i -e "s/expose_php = On/expose_php = Off/g" /etc/php/7.0/fpm/php.ini 443 | sed -i -e "s/;cgi.fix_pathinfo=1/cgi.fix_pathinfo=0/g" /etc/php/7.0/fpm/php.ini 444 | sed -i -e "s/;listen.owner = www-data/listen.owner = www-data/g" /etc/php/7.0/fpm/php.ini 445 | sed -i -e "s/;listen.group = www-data/listen.group = www-data/g" /etc/php/7.0/fpm/php.ini 446 | 447 | # create php socket directory 448 | mkdir -p /run/php 449 | 450 | # replace default website with php service default website 451 | cp -f /container/service/php/config/default /etc/nginx/sites-available/default 452 | 453 | # create phpinfo.php 454 | echo " /var/www/html/phpinfo.php 455 | 456 | 457 | 458 | Make sure install.sh can be executed (chmod +x install.sh). 459 | 460 | ##### process.sh 461 | 462 | This file define the command to run: 463 | 464 | #!/bin/bash -e 465 | exec /usr/sbin/php-fpm7.0 --nodaemonize 466 | 467 | Make sure process.sh can be executed (chmod +x process.sh). 468 | 469 | *Caution: The command executed must start a foreground process otherwise runit (use to supervise multiple process images) will keep restarting php-fpm7.0.* 470 | 471 | That why we run php with `--nodaemonize"` 472 | 473 | ##### config/default 474 | nginx server configuration: 475 | 476 | server { 477 | listen 80 default_server; 478 | listen [::]:80 default_server; 479 | 480 | root /var/www/html; 481 | 482 | # Add index.php to the list if you are using PHP 483 | index index.html index.htm index.nginx-debian.html; 484 | 485 | server_name _; 486 | 487 | location / { 488 | # First attempt to serve request as file, then 489 | # as directory, then fall back to displaying a 404. 490 | try_files $uri $uri/ =404; 491 | } 492 | 493 | location ~ \.php$ { 494 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 495 | # With php fpm: 496 | fastcgi_pass unix:/run/php/php7.0-fpm.sock; 497 | fastcgi_index index.php; 498 | include fastcgi_params; 499 | include fastcgi.conf; 500 | } 501 | } 502 | 503 | That's it we have a multiple process image that run nginx and php! 504 | 505 | #### Build and test 506 | 507 | 508 | Build the image: 509 | 510 | docker build -t example/multiple-process --rm . 511 | 512 | Start a new container: 513 | 514 | docker run -p 8080:80 example/multiple-process 515 | 516 | Go to [http://localhost:8080/phpinfo.php](http://localhost:8080/phpinfo.php) 517 | 518 | > phpinfo should be printed 519 | 520 | So we have a container with two process supervised by runit running in our container! 521 | 522 | 523 | ## Images Based On Light-Baseimage 524 | 525 | Single process images: 526 | - [osixia/openldap](https://github.com/osixia/docker-openldap) 527 | - [osixia/keepalived](https://github.com/osixia/docker-keepalived) 528 | - [osixia/tinc](https://github.com/osixia/docker-tinc) 529 | - [osixia/registry-ldap-auth](https://github.com/osixia/docker-registry-ldap-auth) 530 | - [osixia/cfssl-multirootca](https://github.com/osixia/docker-cfssl-multirootca) 531 | - [osixia/backup](https://github.com/osixia/docker-backup) 532 | - [osixia/backup-manager](https://github.com/osixia/docker-backup-manager) 533 | - [osixia/mmc-agent](https://github.com/osixia/docker-mmc-agent) 534 | 535 | Multiple process images: 536 | - [osixia/openldap-backup](https://github.com/osixia/docker-openldap-backup) 537 | - [osixia/mariadb](https://github.com/osixia/docker-mariadb) 538 | - [osixia/wordpress](https://github.com/osixia/docker-wordpress) 539 | - [osixia/roundcube](https://github.com/osixia/docker-roundcube) 540 | - [osixia/piwik](https://github.com/osixia/docker-piwik) 541 | - [osixia/phpMyAdmin](https://github.com/osixia/docker-phpMyAdmin) 542 | - [osixia/phpLDAPadmin](https://github.com/osixia/docker-phpLDAPadmin) 543 | - [osixia/keepalived-confd](https://github.com/osixia/docker-keepalived-confd) 544 | - [osixia/tinc-etcd](https://github.com/osixia/docker-tinc-etcd) 545 | - [osixia/postfix-gateway-confd](https://github.com/osixia/docker-postfix-gateway-confd) 546 | - [osixia/mmc-mail](https://github.com/osixia/docker-mmc-mail) 547 | - [osixia/mmc-web](https://github.com/osixia/docker-mmc-web) 548 | 549 | Image adding light-baseimage tools to an existing image 550 | - [osixia/gitlab](https://github.com/osixia/docker-gitlab) 551 | 552 | Send me a message to add your image in this list. 553 | 554 | ## Image Assets 555 | 556 | ### Tools 557 | 558 | All container tools are available in `/container/tool` directory and are linked in `/sbin/` so they belong to the container PATH. 559 | 560 | 561 | | Filename | Description | 562 | | ---------------- | ------------------- | 563 | | run | The run tool is defined as the image ENTRYPOINT (see [Dockerfile](image/Dockerfile)). It set environment and run startup scripts and images process. More information in the [Advanced User Guide](#run). | 564 | | setuser | A tool for running a command as another user. Easier to use than su, has a smaller attack vector than sudo, and unlike chpst this tool sets $HOME correctly.| 565 | | log-helper | A simple bash tool to print message base on the log level. | 566 | |  add-service-available | A tool to download and add services in service-available directory to the regular service directory. | 567 | | add-multiple-process-stack | A tool to add the multiple process stack: runit, cron syslog-ng-core and logrotate. | 568 | | install-service | A tool that execute /container/service/install.sh and /container/service/\*/install.sh scripts. | 569 | |  complex-bash-env | A tool to iterate trough complex bash environment variables created by the run tool when a table or a list was set in environment files or in environment command line argument. | 570 | 571 | ### Services available 572 | 573 | | Name | Description | 574 | | ---------------- | ------------------- | 575 | | :runit | Replaces Debian's Upstart. Used for service supervision and management. Much easier to use than SysV init and supports restarting daemons when they crash. Much easier to use and more lightweight than Upstart.

*This service is part of the multiple-process-stack.*| 576 | | :cron | Cron daemon.

*This service is part of the multiple-process-stack.*| 577 | | :syslog-ng-core | Syslog daemon so that many services - including the kernel itself - can correctly log to /var/log/syslog. If no syslog daemon is running, a lot of important messages are silently swallowed.

Only listens locally. All syslog messages are forwarded to "docker logs".

*This service is part of the multiple-process-stack.* | 578 | | :logrotate | Rotates and compresses logs on a regular basis.

*This service is part of the multiple-process-stack.*| 579 | | :ssl-tools | Add CFSSL a CloudFlare PKI/TLS swiss army knife. It's a command line tool for signing, verifying, and bundling TLS certificates. Comes with cfssl-helper tool that make it docker friendly by taking command line parameters from environment variables.

Also add jsonssl-helper to get certificates from json files, parameters are set by environment variables. | 580 | 581 | 582 | ## Advanced User Guide 583 | 584 | ### Service available 585 | 586 | A service-available is basically a normal service expect that it is in the `service-available` directory and have a `download.sh` file. 587 | 588 | To add a service-available to the current image use the `add-service-available` tool. It will process the download.sh file of services given in argument and move them to the regular service directory (/container/service). 589 | 590 | After that the service-available will be process like regular services. 591 | 592 | Here simple Dockerfile example how to add a service-available to an image: 593 | 594 | # Use osixia/light-baseimage 595 | # https://github.com/osixia/docker-light-baseimage 596 | FROM osixia/light-baseimage:1.3.3 597 | 598 | # Add cfssl and cron service-available 599 | # https://github.com/osixia/docker-light-baseimage/blob/stable/image/tool/add-service-available 600 | # https://github.com/osixia/docker-light-baseimage/blob/stable/image/service-available/:ssl-tools/download.sh 601 | # https://github.com/osixia/docker-light-baseimage/blob/stable/image/service-available/:cron/download.sh 602 | RUN apt-get -y update \ 603 | && /container/tool/add-service-available :ssl-tools :cron \ 604 | && LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ 605 | nginx \ 606 | php7.0-fpm 607 | ... 608 | 609 | 610 | Note: Most of predefined service available start with a `:` to make sure they are installed before regular services (so they can be used by regular services). The install-service tool process services in /container/service in alphabetical order. 611 | 612 | To create a service-available just create a regular service, add a download.sh file to set how the needed content is downloaded and add it to /container/service-available directory. The download.sh script is not mandatory if nothing need to be downloaded. 613 | 614 | For example a simple image example that add service-available to this baseimage: [osixia/web-baseimage](https://github.com/osixia/docker-web-baseimage) 615 | 616 | 617 | ### Fix docker mounted file problems 618 | 619 | For some reasons you will probably have to mount custom files to your container. For example in the *mutliple process image example* you can customise the nginx config by mounting your custom config to "/container/service/php/config/default" : 620 | 621 | docker run -v /data/my-nginx-config:/container/service/php/config/default example/multiple-process 622 | 623 | In this case every thing should work fine, but if the startup script makes some `sed` replacement or change file owner and permissions this can results in "Device or resource busy" error. See [Docker documentation](https://docs.docker.com/v1.4/userguide/dockervolumes/#mount-a-host-file-as-a-data-volume). 624 | 625 | sed -i "s|listen 80|listen 8080|g" /container/service/php/config/default 626 | 627 | To prevent that king of error light-baseimage provide *--copy-service* command argument : 628 | 629 | docker run -v /data/my-nginx-config:/container/service/php/config/default example/multiple-process --copy-service 630 | 631 | On startup this will copy all /container/service directory to /container/run/service. 632 | 633 | 634 | At run time you can get the container service directory with `CONTAINER_SERVICE_DIR` environment variable. 635 | If *--copy-service* is used *CONTAINER_SERVICE_DIR=/container/run/service* otherwise *CONTAINER_SERVICE_DIR=/container/service* 636 | 637 | So to always apply sed on the correct file in the startup script the command becomes : 638 | 639 | sed -i "s|listen 80|listen 8080|g" ${CONTAINER_SERVICE_DIR}/php/config/default 640 | 641 | 642 | ### Distribution packages documentation and locales 643 | 644 | This image has a configuration to prevent documentation and locales to be installed from base distribution packages repositories to make it more lightweight as possible. If you need the doc and locales remove the following files : 645 | **/etc/dpkg/dpkg.cfg.d/01_nodoc** and **/etc/dpkg/dpkg.cfg.d/01_nolocales** 646 | 647 | 648 | ### Mastering image tools 649 | 650 | #### run 651 | 652 | The *run tool* is defined as the image ENTRYPOINT (see [Dockerfile](image/Dockerfile)). It's the core tool of this image. 653 | 654 | What it does: 655 | - Setup the run directory 656 | - Set the startup files environment 657 | - Run startup files 658 | - Set process environment 659 | - Run process 660 | 661 | ##### Run command line options 662 | 663 | *Run tool* takes several options, to list them: 664 | 665 | docker run osixia/light-baseimage:1.3.3 --help 666 | usage: run [-h] [-e] [-s] [-p] [-f] [-o {startup,process,finish}] 667 | [-c COMMAND [WHEN={startup,process,finish} ...]] [-k] 668 | [--wait-state FILENAME] [--wait-first-startup] [--keep-startup-env] 669 | [--copy-service] [--dont-touch-etc-hosts] [--keepalive] 670 | [--keepalive-force] [-l {none,error,warning,info,debug,trace}] 671 | [MAIN_COMMAND [MAIN_COMMAND ...]] 672 | 673 | Initialize the system. 674 | 675 | positional arguments: 676 | MAIN_COMMAND The main command to run, leave empty to only run 677 | container process. 678 | 679 | optional arguments: 680 | -h, --help show this help message and exit 681 | -e, --skip-env-files Skip getting environment values from environment 682 | file(s). 683 | -s, --skip-startup-files 684 | Skip running /container/run/startup/* and 685 | /container/run/startup.sh file(s). 686 | -p, --skip-process-files 687 | Skip running container process file(s). 688 | -f, --skip-finish-files 689 | Skip running container finish file(s). 690 | -o {startup,process,finish}, --run-only {startup,process,finish} 691 | Run only this file type and ignore others. 692 | -c COMMAND [WHEN={startup,process,finish} ...], --cmd COMMAND [WHEN={startup,process,finish} ...] 693 | Run this command before WHEN file(s). Default before 694 | startup file(s). 695 | -k, --no-kill-all-on-exit 696 | Don't kill all processes on the system upon exiting. 697 | --wait-state FILENAME 698 | Wait until the container state file exists in 699 | /container/run/state directory before starting. 700 | Usefull when 2 containers share /container/run 701 | directory via volume. 702 | --wait-first-startup Wait until the first startup is done before starting. 703 | Usefull when 2 containers share /container/run 704 | directory via volume. 705 | --keep-startup-env Don't remove ('.startup.yaml', '.startup.json') 706 | environment files after startup scripts. 707 | --copy-service Copy /container/service to /container/run/service. 708 | Help to fix docker mounted files problems. 709 | --dont-touch-etc-hosts 710 | Don't add in /etc/hosts a line with the container ip 711 | and $HOSTNAME environment variable value. 712 | --keepalive Keep alive container if all startup files and process 713 | exited without error. 714 | --keepalive-force Keep alive container in all circonstancies. 715 | -l {none,error,warning,info,debug,trace}, --loglevel {none,error,warning,info,debug,trace} 716 | Log level (default: info) 717 | 718 | Osixia! Light Baseimage: https://github.com/osixia/docker-light-baseimage 719 | 720 | 721 | ##### Run directory setup 722 | *Run tool* will create if they not exists the following directories: 723 | - /container/run/state 724 | - /container/run/environment 725 | - /container/run/startup 726 | - /container/run/process 727 | - /container/run/service 728 | 729 | At the container first start it will search in /container/service or /container/run/service (if --copy-service option is used) all image's services. 730 | 731 | In a service directory for example /container/service/my-service: 732 | - If a startup.sh file is found, the file is linked to /container/run/startup/my-service 733 | - If a process.sh file is found, the file is linked to /container/run/process/my-service/run 734 | 735 | ##### Startup files environment setup 736 | *Run tool* takes all file in /container/environment/* and import the variables values to the container environment. 737 | The container environment is then exported to /container/run/environment and in /container/run/environment.sh 738 | 739 | ##### Startup files execution 740 | *Run tool* iterate trough /container/run/startup/* directory in alphabetical order and run scripts. 741 | After each time *run tool* runs a startup script, it resets its own environment variables to the state in /container/run/environment, and re-dumps the new environment variables to /container/run/environment.sh 742 | 743 | After all startup script *run tool* run /container/run/startup.sh if exists. 744 | 745 | ##### Process environment setup 746 | *Run tool* delete all .startup.yaml and .startup.json in /container/environment/* and clear the previous run environment (/container/run/environment is removed) 747 | Then it takes all remaining file in /container/environment/* and import the variables values to the container environment. 748 | The container environment is then exported to /container/run/environment and in /container/run/environment.sh 749 | 750 | ##### Process execution 751 | 752 | ###### Single process image 753 | 754 | *Run tool* execute the unique /container/run/process/service-name/run file. 755 | 756 | If a main command is set for example: 757 | 758 | docker run -it osixia/openldap:1.4.0 bash 759 | 760 | *Run tool* will execute the single process and the main command. If the main command exits the container exits. This is useful to debug or image development purpose. 761 | 762 | ###### Multiple process image 763 | 764 | In a multiple process image *run tool* execute runit witch supervise /container/run/process directory and start all services automatically. Runit will also relaunched them if they failed. 765 | 766 | If a main command is set for example: 767 | 768 | docker run -it osixia/phpldapadmin:0.9.0 bash 769 | 770 | *run tool* will execute runit and the main command. If the main command exits the container exits. This is still useful to debug or image development purpose. 771 | 772 | ###### No process image 773 | If a main command is set *run tool* launch it otherwise bash is launched. 774 | Example: 775 | 776 | docker run -it osixia/light-baseimage:1.3.3 777 | 778 | 779 | ##### Extra environment variables 780 | 781 | *run tool* add 3 variables to the container environment: 782 | - **CONTAINER_STATE_DIR**: /container/run/state 783 | - **CONTAINER_SERVICE_DIR**: the container service directory. By default: /container/service but if the container is started with --copy-service option: /container/run/service 784 | - **CONTAINER_LOG_LEVEL**: log level set by --loglevel option defaults to: 3 (info) 785 | 786 | #### log-helper 787 | This tool is a simple utility based on the CONTAINER_LOG_LEVEL variable to print leveled log messages. 788 | 789 | For example if the log level is info: 790 | 791 | log-helper info hello 792 | 793 | will echo: 794 | > hello 795 | 796 | log-helper debug i'm bob 797 | 798 | will echo nothing. 799 | 800 | log-helper support piped input: 801 | 802 | echo "Heyyyyy" | log-helper info 803 | 804 | > Heyyyyy 805 | 806 | Log message functions usage: `log-helper error|warning|info|debug|trace message` 807 | 808 | You can also test the log level with the level function: 809 | 810 | log-helper level eq info && echo "log level is infos" 811 | 812 | for example this will echo "log level is trace" if log level is trace. 813 | 814 | Level `function usage: log-helper level eq|ne|gt|ge|lt|le none|error|warning|info|debug|trace` 815 | Help: [http://www.tldp.org/LDP/abs/html/comparison-ops.html](http://www.tldp.org/LDP/abs/html/comparison-ops.html) 816 | 817 | #### complex-bash-env 818 | With light-baseimage you can set bash environment variable from .yaml and .json files. 819 | But bash environment variables can't store complex objects such as table that can be defined in yaml or json files, that's why they are converted to "complex bash environment variables" and complex-bash-env tool help getting those variables values easily. 820 | 821 | For example the following yaml file: 822 | 823 | FRUITS: 824 | - orange 825 | - apple 826 | 827 | will produce this bash environment variables: 828 | 829 | FRUITS=#COMPLEX_BASH_ENV:TABLE: FRUITS_ROW_1 FRUITS_ROW_2 830 | FRUITS_ROW_1=orange 831 | FRUITS_ROW_2=apple 832 | 833 | (this is done by run tool) 834 | 835 | complex-bash-env make it easy to iterate trough this variable: 836 | 837 | for fruit in $(complex-bash-env iterate FRUITS) 838 | do 839 | echo ${!fruit} 840 | done 841 | 842 | A more complete example can be found [osixia/phpLDAPadmin](https://github.com/osixia/docker-phpLDAPadmin) image. 843 | 844 | Note this yaml definition: 845 | 846 | FRUITS: 847 | - orange 848 | - apple 849 | 850 | Can also be set by command line converted in python or json: 851 | 852 | docker run -it --env FRUITS="#PYTHON2BASH:['orange','apple']" osixia/light-baseimage:1.3.3 printenv 853 | docker run -it --env FRUITS="#JSON2BASH:[\"orange\",\"apple\"]" osixia/light-baseimage:1.3.3 printenv 854 | 855 | ### Tests 856 | 857 | We use **Bats** (Bash Automated Testing System) to test this image: 858 | 859 | > [https://github.com/bats-core/bats-core](https://github.com/bats-core/bats-core) 860 | 861 | Install Bats, and in this project directory run: 862 | 863 | make test 864 | 865 | ## Changelog 866 | 867 | Please refer to: [CHANGELOG.md](CHANGELOG.md) 868 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | | Version | Supported | 6 | | ------- | ------------------ | 7 | | >= 2.0.0 | :heavy_check_mark: | 8 | | < 2.0.0 | :x: | 9 | 10 | ## Reporting a Vulnerability 11 | 12 | If you discover a security vulnerability within this docker image, 13 | please send an email to security@osixia.net. 14 | 15 | For minor vulnerabilities feel free to add an issue here on github. 16 | 17 | Please include as many details as possible. 18 | -------------------------------------------------------------------------------- /example/multiple-process-image/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use osixia/light-baseimage 2 | # https://github.com/osixia/docker-light-baseimage 3 | FROM osixia/light-baseimage:1.3.3 4 | 5 | # Install multiple process stack, nginx and php7.0-fpm and clean apt-get files 6 | # https://github.com/osixia/docker-light-baseimage/blob/stable/image/tool/add-multiple-process-stack 7 | RUN apt-get -y update \ 8 | && /container/tool/add-multiple-process-stack \ 9 | && LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ 10 | nginx \ 11 | php7.0-fpm \ 12 | && apt-get clean \ 13 | && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 14 | 15 | # Add service directory to /container/service 16 | ADD service /container/service 17 | 18 | # Use baseimage install-service script 19 | # https://github.com/osixia/docker-light-baseimage/blob/stable/image/tool/install-service 20 | RUN /container/tool/install-service 21 | 22 | # Add default env directory 23 | ADD environment /container/environment/99-default 24 | 25 | # Set /var/www/ in a data volume 26 | VOLUME /var/www/ 27 | 28 | # Expose default http and https ports 29 | EXPOSE 80 443 30 | -------------------------------------------------------------------------------- /example/multiple-process-image/Makefile: -------------------------------------------------------------------------------- 1 | NAME = example/multiple-process 2 | 3 | .PHONY: build build-nocache 4 | 5 | build: 6 | docker build -t $(NAME) --rm . 7 | 8 | build-nocache: 9 | docker build -t $(NAME) --no-cache --rm . 10 | -------------------------------------------------------------------------------- /example/multiple-process-image/environment/default.startup.yaml: -------------------------------------------------------------------------------- 1 | # This is the default image startup configuration file 2 | # this file define environment variables used during the container **first start** in **startup files**. 3 | 4 | # This file is deleted right after startup files are processed for the first time, 5 | # after that all these values will not be available in the container environment. 6 | # This helps to keep your container configuration secret. 7 | # more information : https://github.com/osixia/docker-light-baseimage 8 | 9 | FIRST_START_SETUP_ONLY_SECRET: The bdd password is Baw0unga! 10 | -------------------------------------------------------------------------------- /example/multiple-process-image/environment/default.yaml: -------------------------------------------------------------------------------- 1 | # This is the default image configuration file 2 | # These values will persists in container environment. 3 | 4 | # All environment variables used after the container first start 5 | # must be defined here. 6 | # more information : https://github.com/osixia/docker-light-baseimage 7 | 8 | WHO_AM_I: We are Anonymous. We are Legion. We do not forgive. We do not forget. Expect us. 9 | -------------------------------------------------------------------------------- /example/multiple-process-image/service/nginx/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | # this script is run during the image build 3 | 4 | rm -rf /var/www/html/index.nginx-debian.html 5 | echo "Hi!" > /var/www/html/index.html 6 | -------------------------------------------------------------------------------- /example/multiple-process-image/service/nginx/process.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | echo "The secret is: $FIRST_START_SETUP_ONLY_SECRET" 3 | exec /usr/sbin/nginx -g "daemon off;" 4 | -------------------------------------------------------------------------------- /example/multiple-process-image/service/nginx/startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | FIRST_START_DONE="${CONTAINER_STATE_DIR}/nginx-first-start-done" 3 | 4 | # container first start 5 | if [ ! -e "$FIRST_START_DONE" ]; then 6 | echo ${WHO_AM_I} >> /var/www/html/index.html 7 | touch $FIRST_START_DONE 8 | fi 9 | 10 | echo "The secret is: $FIRST_START_SETUP_ONLY_SECRET" 11 | 12 | exit 0 13 | -------------------------------------------------------------------------------- /example/multiple-process-image/service/php/config/default: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default_server; 3 | listen [::]:80 default_server; 4 | 5 | root /var/www/html; 6 | 7 | # Add index.php to the list if you are using PHP 8 | index index.html index.htm index.nginx-debian.html; 9 | 10 | server_name _; 11 | 12 | location / { 13 | # First attempt to serve request as file, then 14 | # as directory, then fall back to displaying a 404. 15 | try_files $uri $uri/ =404; 16 | } 17 | 18 | location ~ \.php$ { 19 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 20 | # With php fpm: 21 | fastcgi_pass unix:/run/php/php7.0-fpm.sock; 22 | fastcgi_index index.php; 23 | include fastcgi_params; 24 | include fastcgi.conf; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /example/multiple-process-image/service/php/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | # this script is run during the image build 3 | 4 | # config 5 | sed -i -e "s/expose_php = On/expose_php = Off/g" /etc/php/7.0/fpm/php.ini 6 | sed -i -e "s/;cgi.fix_pathinfo=1/cgi.fix_pathinfo=0/g" /etc/php/7.0/fpm/php.ini 7 | sed -i -e "s/;listen.owner = www-data/listen.owner = www-data/g" /etc/php/7.0/fpm/php.ini 8 | sed -i -e "s/;listen.group = www-data/listen.group = www-data/g" /etc/php/7.0/fpm/php.ini 9 | 10 | # create php socket directory 11 | mkdir -p /run/php 12 | 13 | # replace default website with php service default website 14 | cp -f /container/service/php/config/default /etc/nginx/sites-available/default 15 | 16 | # create phpinfo.php 17 | echo " /var/www/html/phpinfo.php 18 | -------------------------------------------------------------------------------- /example/multiple-process-image/service/php/process.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | exec /usr/sbin/php-fpm7.0 --nodaemonize 3 | -------------------------------------------------------------------------------- /example/single-process-image/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use osixia/light-baseimage 2 | # https://github.com/osixia/docker-light-baseimage 3 | FROM osixia/light-baseimage:1.3.3 4 | 5 | # Download nginx from apt-get and clean apt-get files 6 | RUN apt-get -y update \ 7 | && LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ 8 | nginx \ 9 | && apt-get clean \ 10 | && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 11 | 12 | # Add service directory to /container/service 13 | ADD service /container/service 14 | 15 | # Use baseimage install-service script 16 | # https://github.com/osixia/docker-light-baseimage/blob/stable/image/tool/install-service 17 | RUN /container/tool/install-service 18 | 19 | # Add default env directory 20 | ADD environment /container/environment/99-default 21 | 22 | # Set /var/www/ in a data volume 23 | VOLUME /var/www/ 24 | 25 | # Expose default http and https ports 26 | EXPOSE 80 443 27 | -------------------------------------------------------------------------------- /example/single-process-image/Makefile: -------------------------------------------------------------------------------- 1 | NAME = example/single-process 2 | 3 | .PHONY: build build-nocache 4 | 5 | build: 6 | docker build -t $(NAME) --rm . 7 | 8 | build-nocache: 9 | docker build -t $(NAME) --no-cache --rm . 10 | -------------------------------------------------------------------------------- /example/single-process-image/environment/default.startup.yaml: -------------------------------------------------------------------------------- 1 | # This is the default image startup configuration file 2 | # this file define environment variables used during the container **first start** in **startup files**. 3 | 4 | # This file is deleted right after startup files are processed for the first time, 5 | # after that all these values will not be available in the container environment. 6 | # This helps to keep your container configuration secret. 7 | # more information : https://github.com/osixia/docker-light-baseimage 8 | 9 | FIRST_START_SETUP_ONLY_SECRET: The bdd password is Baw0unga! 10 | -------------------------------------------------------------------------------- /example/single-process-image/environment/default.yaml: -------------------------------------------------------------------------------- 1 | # This is the default image configuration file 2 | # These values will persists in container environment. 3 | 4 | # All environment variables used after the container first start 5 | # must be defined here. 6 | # more information : https://github.com/osixia/docker-light-baseimage 7 | 8 | WHO_AM_I: We are Anonymous. We are Legion. We do not forgive. We do not forget. Expect us. 9 | -------------------------------------------------------------------------------- /example/single-process-image/service/nginx/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | # this script is run during the image build 3 | 4 | rm -rf /var/www/html/index.nginx-debian.html 5 | echo "Hi!" > /var/www/html/index.html 6 | -------------------------------------------------------------------------------- /example/single-process-image/service/nginx/process.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | echo "The secret is: $FIRST_START_SETUP_ONLY_SECRET" 3 | exec /usr/sbin/nginx -g "daemon off;" 4 | -------------------------------------------------------------------------------- /example/single-process-image/service/nginx/startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | FIRST_START_DONE="${CONTAINER_STATE_DIR}/nginx-first-start-done" 3 | 4 | # container first start 5 | if [ ! -e "$FIRST_START_DONE" ]; then 6 | echo ${WHO_AM_I} >> /var/www/html/index.html 7 | touch $FIRST_START_DONE 8 | fi 9 | 10 | echo "The secret is: $FIRST_START_SETUP_ONLY_SECRET" 11 | 12 | exit 0 13 | -------------------------------------------------------------------------------- /example/single-process-image/test-custom-env/env.yaml: -------------------------------------------------------------------------------- 1 | WHO_AM_I: I'm bobby. 2 | -------------------------------------------------------------------------------- /image/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:buster-slim 2 | 3 | COPY . /container 4 | RUN /container/build.sh 5 | 6 | ENV LANG="en_US.UTF-8" \ 7 | LANGUAGE="en_US:en" \ 8 | LC_ALL="en_US.UTF-8" 9 | 10 | ENTRYPOINT ["/container/tool/run"] 11 | -------------------------------------------------------------------------------- /image/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -ex 2 | 3 | ## Add bash tools to /sbin 4 | ln -s /container/tool/* /sbin/ 5 | 6 | mkdir -p /container/service 7 | mkdir -p /container/environment /container/environment/startup 8 | chmod 700 /container/environment/ /container/environment/startup 9 | 10 | groupadd -g 8377 docker_env 11 | 12 | # dpkg options 13 | cp /container/file/dpkg_nodoc /etc/dpkg/dpkg.cfg.d/01_nodoc 14 | cp /container/file/dpkg_nolocales /etc/dpkg/dpkg.cfg.d/01_nolocales 15 | 16 | # General config 17 | export LC_ALL=C 18 | export DEBIAN_FRONTEND=noninteractive 19 | MINIMAL_APT_GET_INSTALL='apt-get install -y --no-install-recommends' 20 | 21 | ## Prevent initramfs updates from trying to run grub and lilo. 22 | ## https://journal.paul.querna.org/articles/2013/10/15/docker-ubuntu-on-rackspace/ 23 | ## http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=594189 24 | export INITRD=no 25 | printf no > /container/environment/INITRD 26 | 27 | apt-get update 28 | 29 | ## Fix some issues with APT packages. 30 | ## See https://github.com/dotcloud/docker/issues/1024 31 | dpkg-divert --local --rename --add /sbin/initctl 32 | ln -sf /bin/true /sbin/initctl 33 | 34 | ## Replace the 'ischroot' tool to make it always return true. 35 | ## Prevent initscripts updates from breaking /dev/shm. 36 | ## https://journal.paul.querna.org/articles/2013/10/15/docker-ubuntu-on-rackspace/ 37 | ## https://bugs.launchpad.net/launchpad/+bug/974584 38 | dpkg-divert --local --rename --add /usr/bin/ischroot 39 | ln -sf /bin/true /usr/bin/ischroot 40 | 41 | ## Install apt-utils. 42 | $MINIMAL_APT_GET_INSTALL apt-utils apt-transport-https ca-certificates locales procps dirmngr gnupg iproute2 python3-minimal python3-yaml 43 | 44 | ## Upgrade all packages. 45 | apt-get dist-upgrade -y --no-install-recommends -o Dpkg::Options::="--force-confold" 46 | 47 | # fix locale 48 | echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen 49 | locale-gen en_US 50 | update-locale LANG=en_US.UTF-8 LC_CTYPE=en_US.UTF-8 51 | 52 | printf en_US.UTF-8 > /container/environment/LANG 53 | printf en_US.UTF-8 > /container/environment/LANGUAGE 54 | printf en_US.UTF-8 > /container/environment/LC_CTYPE 55 | 56 | apt-get clean 57 | rm -rf /tmp/* /var/tmp/* 58 | rm -rf /var/lib/apt/lists/* 59 | 60 | # Remove useless files 61 | rm -rf /container/file 62 | rm -rf /container/build.sh /container/Dockerfile 63 | -------------------------------------------------------------------------------- /image/file/dpkg_nodoc: -------------------------------------------------------------------------------- 1 | path-exclude /usr/share/doc/* 2 | # we need to keep copyright files for legal reasons 3 | path-include /usr/share/doc/*/copyright 4 | path-exclude /usr/share/man/* 5 | path-exclude /usr/share/groff/* 6 | path-exclude /usr/share/info/* 7 | # lintian stuff is small, but really unnecessary 8 | path-exclude /usr/share/lintian/* 9 | path-exclude /usr/share/linda/* 10 | -------------------------------------------------------------------------------- /image/file/dpkg_nolocales: -------------------------------------------------------------------------------- 1 | path-exclude /usr/share/locale/* 2 | path-include /usr/share/locale/en* 3 | -------------------------------------------------------------------------------- /image/service-available/:cron/download.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | # download cron from apt-get 4 | LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends cron 5 | -------------------------------------------------------------------------------- /image/service-available/:cron/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | chmod 600 /etc/crontab 4 | 5 | # Fix https://github.com/phusion/baseimage-docker/issues/345 6 | sed -i 's/^\s*session\s\+required\s\+pam_loginuid.so/# &/' /etc/pam.d/cron 7 | 8 | ## Remove useless cron entries. 9 | # Checks for lost+found and scans for mtab. 10 | rm -f /etc/cron.daily/standard 11 | rm -f /etc/cron.daily/upstart 12 | rm -f /etc/cron.daily/dpkg 13 | rm -f /etc/cron.daily/password 14 | rm -f /etc/cron.weekly/fstrim 15 | -------------------------------------------------------------------------------- /image/service-available/:cron/process.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | log-helper level eq trace && set -x 3 | 4 | exec /usr/sbin/cron -f 5 | -------------------------------------------------------------------------------- /image/service-available/:cron/startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | log-helper level eq trace && set -x 3 | 4 | # prevent NUMBER OF HARD LINKS > 1 error 5 | # https://github.com/phusion/baseimage-docker/issues/198 6 | touch /etc/crontab /etc/cron.d /etc/cron.daily /etc/cron.hourly /etc/cron.monthly /etc/cron.weekly 7 | 8 | find /etc/cron.d/ -exec touch {} \; 9 | find /etc/cron.daily/ -exec touch {} \; 10 | find /etc/cron.hourly/ -exec touch {} \; 11 | find /etc/cron.monthly/ -exec touch {} \; 12 | find /etc/cron.weekly/ -exec touch {} \; 13 | -------------------------------------------------------------------------------- /image/service-available/:logrotate/assets/config/logrotate.conf: -------------------------------------------------------------------------------- 1 | # see "man logrotate" for details 2 | # rotate log files weekly 3 | weekly 4 | 5 | # use the syslog group by default, since this is the owning group 6 | # of /var/log/syslog. 7 | # su root syslog 8 | 9 | # keep 4 weeks worth of backlogs 10 | rotate 4 11 | 12 | # create new (empty) log files after rotating old ones 13 | create 14 | 15 | # uncomment this if you want your log files compressed 16 | #compress 17 | 18 | # packages drop log rotation information into this directory 19 | include /etc/logrotate.d 20 | 21 | # no packages own wtmp, or btmp -- we'll rotate them here 22 | /var/log/wtmp { 23 | missingok 24 | monthly 25 | create 0664 root utmp 26 | rotate 1 27 | } 28 | 29 | /var/log/btmp { 30 | missingok 31 | monthly 32 | create 0660 root utmp 33 | rotate 1 34 | } 35 | 36 | # system-specific logs may be configured here 37 | -------------------------------------------------------------------------------- /image/service-available/:logrotate/assets/config/logrotate_syslogng: -------------------------------------------------------------------------------- 1 | /var/log/syslog { 2 | rotate 7 3 | daily 4 | missingok 5 | notifempty 6 | delaycompress 7 | compress 8 | postrotate 9 | if [ -f /var/run/syslog-ng.pid ]; then 10 | kill -HUP `cat /var/run/syslog-ng.pid` 11 | fi 12 | endscript 13 | } 14 | 15 | /var/log/mail.info 16 | /var/log/mail.warn 17 | /var/log/mail.err 18 | /var/log/mail.log 19 | /var/log/daemon.log 20 | /var/log/kern.log 21 | /var/log/auth.log 22 | /var/log/user.log 23 | /var/log/lpr.log 24 | /var/log/cron.log 25 | /var/log/debug 26 | /var/log/messages { 27 | rotate 4 28 | weekly 29 | missingok 30 | notifempty 31 | compress 32 | delaycompress 33 | sharedscripts 34 | postrotate 35 | if [ -f /var/run/syslog-ng.pid ]; then 36 | kill -HUP `cat /var/run/syslog-ng.pid` 37 | fi 38 | endscript 39 | } -------------------------------------------------------------------------------- /image/service-available/:logrotate/download.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | # download logrotate from apt-get 4 | LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends logrotate 5 | -------------------------------------------------------------------------------- /image/service-available/:logrotate/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | rm -f /etc/logrotate.conf 4 | rm -f /etc/logrotate.d/syslog-ng 5 | -------------------------------------------------------------------------------- /image/service-available/:logrotate/startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | log-helper level eq trace && set -x 3 | ln -sf "${CONTAINER_SERVICE_DIR}/:logrotate/assets/config/logrotate.conf" /etc/logrotate.conf 4 | ln -sf "${CONTAINER_SERVICE_DIR}/:logrotate/assets/config/logrotate_syslogng" /etc/logrotate.d/syslog-ng 5 | 6 | chmod 444 -R "${CONTAINER_SERVICE_DIR}"/:logrotate/assets/config/* 7 | -------------------------------------------------------------------------------- /image/service-available/:runit/download.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | # download runit from apt-get 4 | LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends runit 5 | -------------------------------------------------------------------------------- /image/service-available/:ssl-tools/assets/cfssl-default-env: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Default CA config 4 | # 5 | CFSSL_DEFAULT_CACERT="${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/default-ca/default-ca.pem" 6 | CFSSL_DEFAULT_CA_KEY="${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/default-ca/default-ca-key.pem" 7 | CFSSL_DEFAULT_CA_CONFIG="${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/default-ca/config/ca-config.json" 8 | CFSSL_DEFAULT_CSR="${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/default-ca/config/req-csr.json.tmpl" 9 | 10 | # default csr file params 11 | CFSSL_DEFAULT_CA_CSR_CN=${CFSSL_DEFAULT_CA_CSR_CN:-${HOSTNAME}} 12 | 13 | CFSSL_DEFAULT_CA_CSR_KEY_ALGO=${CFSSL_DEFAULT_CA_CSR_KEY_ALGO:-"ecdsa"} 14 | CFSSL_DEFAULT_CA_CSR_KEY_SIZE=${CFSSL_DEFAULT_CA_CSR_KEY_SIZE:-384} 15 | 16 | CFSSL_DEFAULT_CA_CSR_ORGANIZATION=${CFSSL_DEFAULT_CA_CSR_ORGANIZATION:-"A1A Car Wash"} 17 | CFSSL_DEFAULT_CA_CSR_ORGANIZATION_UNIT=${CFSSL_DEFAULT_CA_CSR_ORGANIZATION_UNIT:-"Information Technology Dep."} 18 | CFSSL_DEFAULT_CA_CSR_LOCATION=${CFSSL_DEFAULT_CA_CSR_LOCATION:-"Albuquerque"} 19 | CFSSL_DEFAULT_CA_CSR_STATE=${CFSSL_DEFAULT_CA_CSR_STATE:-"New Mexico"} 20 | CFSSL_DEFAULT_CA_CSR_COUNTRY=${CFSSL_DEFAULT_CA_CSR_COUNTRY:-"US"} 21 | 22 | # 23 | # General CFSSL config 24 | # 25 | 26 | CFSSL_RETRY=${CFSSL_RETRY:-3} 27 | CFSSL_RETRY_DELAY=${CFSSL_RETRY_DELAY:-1} 28 | 29 | # remote config 30 | CFSSL_REMOTE=${CFSSL_REMOTE:-} 31 | CFSSL_REMOTE_HTTPS_CA_CERT=${CFSSL_REMOTE_HTTPS_CA_CERT:-} 32 | 33 | # local config 34 | CFSSL_CA_CERT=${CFSSL_CA_CERT:-${CFSSL_DEFAULT_CACERT}} 35 | CFSSL_CA_KEY=${CFSSL_CA_KEY:-${CFSSL_DEFAULT_CA_KEY}} 36 | 37 | # gencert 38 | CFSSL_CSR=${CFSSL_CSR:-${CFSSL_DEFAULT_CSR}} 39 | CFSSL_CSR_JSON=${CFSSL_CSR_JSON:-} 40 | CFSSL_CONFIG=${CFSSL_CONFIG:-${CFSSL_CA_CONFIG}} 41 | CFSSL_CONFIG_JSON=${CFSSL_CONFIG_JSON:-${CFSSL_CA_CONFIG_JSON}} 42 | CFSSL_HOSTNAME=${CFSSL_HOSTNAME:-${HOSTNAME}} 43 | CFSSL_PROFILE=${CFSSL_PROFILE:-} 44 | CFSSL_LABEL=${CFSSL_LABEL:-} 45 | -------------------------------------------------------------------------------- /image/service-available/:ssl-tools/assets/default-ca/README.md: -------------------------------------------------------------------------------- 1 | # How to generate the default CA: 2 | cfssl gencert -initca config/ca-csr.json | cfssljson -bare default-ca 3 | -------------------------------------------------------------------------------- /image/service-available/:ssl-tools/assets/default-ca/config/ca-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "signing": { 3 | "default": { 4 | "usages": [ 5 | "signing", 6 | "key encipherment", 7 | "server auth", 8 | "client auth" 9 | ], 10 | "expiry": "8760h" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /image/service-available/:ssl-tools/assets/default-ca/config/ca-csr.json: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "docker-light-baseimage", 3 | "key": { 4 | "algo": "ecdsa", 5 | "size": 384 6 | }, 7 | "names": [ 8 | { 9 | "O": "A1A Car Wash", 10 | "OU": "Information Technology Dep.", 11 | "L": "Albuquerque", 12 | "ST": "New Mexico", 13 | "C": "US" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /image/service-available/:ssl-tools/assets/default-ca/config/req-csr.json.tmpl: -------------------------------------------------------------------------------- 1 | { 2 | "CN": "{{ CFSSL_DEFAULT_CA_CSR_CN }}", 3 | "hosts": [ 4 | "{{ CFSSL_DEFAULT_CA_CSR_CN }}" 5 | ], 6 | "key": { 7 | "algo": "{{ CFSSL_DEFAULT_CA_CSR_KEY_ALGO }}", 8 | "size": {{ CFSSL_DEFAULT_CA_CSR_KEY_SIZE }} 9 | }, 10 | "names": [ 11 | { 12 | "O": "{{ CFSSL_DEFAULT_CA_CSR_ORGANIZATION }}", 13 | "OU": "{{ CFSSL_DEFAULT_CA_CSR_ORGANIZATION_UNIT }}", 14 | "L": "{{ CFSSL_DEFAULT_CA_CSR_LOCATION }}", 15 | "ST": "{{ CFSSL_DEFAULT_CA_CSR_STATE }}", 16 | "C": "{{ CFSSL_DEFAULT_CA_CSR_COUNTRY }}" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /image/service-available/:ssl-tools/assets/default-ca/default-ca-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MIGkAgEBBDABfvSnlC9AZQjMRTc5o/BcUQCoBkVN8y17VaezYR709tqPptcQ9fC9 3 | 4wtM1qDVho2gBwYFK4EEACKhZANiAATWvTsmK1cEzy4711tv5oRRTJkAGUhYsoKP 4 | YV6p8M/zQ8tGbkCrFBc0nnelFzbtXkIDB00rsFotk3W4El/KWs/sNkBs5tkFoUBZ 5 | HAPeqc01M40Gpw77qoFVIU1rJiNOFNk= 6 | -----END EC PRIVATE KEY----- 7 | -------------------------------------------------------------------------------- /image/service-available/:ssl-tools/assets/default-ca/default-ca.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIBkTCCARYCAQAwgZYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpOZXcgTWV4aWNv 3 | MRQwEgYDVQQHEwtBbGJ1cXVlcnF1ZTEVMBMGA1UEChMMQTFBIENhciBXYXNoMSQw 4 | IgYDVQQLExtJbmZvcm1hdGlvbiBUZWNobm9sb2d5IERlcC4xHzAdBgNVBAMTFmRv 5 | Y2tlci1saWdodC1iYXNlaW1hZ2UwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATWvTsm 6 | K1cEzy4711tv5oRRTJkAGUhYsoKPYV6p8M/zQ8tGbkCrFBc0nnelFzbtXkIDB00r 7 | sFotk3W4El/KWs/sNkBs5tkFoUBZHAPeqc01M40Gpw77qoFVIU1rJiNOFNmgADAK 8 | BggqhkjOPQQDAwNpADBmAjEApeMZVHllW2JJGkaFQ6DAJXTKvISiPwj8L41AeSJk 9 | LkrdH/eq6toM06sWkSCTdsJxAjEAlJKESvBJA3MZPmUGhG4AqZ70nHTvz0GJ1fsB 10 | T5TnyBv0ERmNCCQo3AaHJLkSqDfo 11 | -----END CERTIFICATE REQUEST----- 12 | -------------------------------------------------------------------------------- /image/service-available/:ssl-tools/assets/default-ca/default-ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICrjCCAjWgAwIBAgIUcun3KuyYiVryQCfOcWz7gNP0x/AwCgYIKoZIzj0EAwMw 3 | gZYxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpOZXcgTWV4aWNvMRQwEgYDVQQHEwtB 4 | bGJ1cXVlcnF1ZTEVMBMGA1UEChMMQTFBIENhciBXYXNoMSQwIgYDVQQLExtJbmZv 5 | cm1hdGlvbiBUZWNobm9sb2d5IERlcC4xHzAdBgNVBAMTFmRvY2tlci1saWdodC1i 6 | YXNlaW1hZ2UwHhcNMjEwMTE2MTE0MjAwWhcNMjYwMTE1MTE0MjAwWjCBljELMAkG 7 | A1UEBhMCVVMxEzARBgNVBAgTCk5ldyBNZXhpY28xFDASBgNVBAcTC0FsYnVxdWVy 8 | cXVlMRUwEwYDVQQKEwxBMUEgQ2FyIFdhc2gxJDAiBgNVBAsTG0luZm9ybWF0aW9u 9 | IFRlY2hub2xvZ3kgRGVwLjEfMB0GA1UEAxMWZG9ja2VyLWxpZ2h0LWJhc2VpbWFn 10 | ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABNa9OyYrVwTPLjvXW2/mhFFMmQAZSFiy 11 | go9hXqnwz/NDy0ZuQKsUFzSed6UXNu1eQgMHTSuwWi2TdbgSX8paz+w2QGzm2QWh 12 | QFkcA96pzTUzjQanDvuqgVUhTWsmI04U2aNCMEAwDgYDVR0PAQH/BAQDAgEGMA8G 13 | A1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNcSeGQ+1u3nsr2BcYY2jVecyBQlMAoG 14 | CCqGSM49BAMDA2cAMGQCMBHppmoY8E2fv0PIg8lR3Xq4bKNTH7cG3WEbR10NHPeJ 15 | NHtBrXWsnjAouXKFGS+1vgIwAVP1gZCPOTvChfTF8uOHW7RZ3UnC3xcJlGaOrC7s 16 | uElSBnLT7DIT3uBSxmIegHNH 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /image/service-available/:ssl-tools/assets/default-env: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | SSL_HELPER_TOOL=${SSL_HELPER_TOOL:-"cfssl-helper"} 3 | 4 | SSL_HELPER_AUTO_RENEW=${SSL_HELPER_AUTO_RENEW:-false} 5 | SSL_HELPER_AUTO_RENEW_CRON_EXP=${SSL_HELPER_AUTO_RENEW_CRON_EXP:-"0 0 * * *"} # every day at 00:00 6 | SSL_HELPER_AUTO_RENEW_SERVICES_IMPACTED=${SSL_HELPER_AUTO_RENEW_SERVICES_IMPACTED:-} 7 | SSL_HELPER_AUTO_RENEW_FROM_FILES=${SSL_HELPER_AUTO_RENEW_FROM_FILES:-false} 8 | SSL_HELPER_AUTO_RENEW_CERT_FROM_FILE=${SSL_HELPER_AUTO_RENEW_CERT_FROM_FILE:-} 9 | SSL_HELPER_AUTO_RENEW_KEY_FROM_FILE=${SSL_HELPER_AUTO_RENEW_KEY_FROM_FILE:-} 10 | SSL_HELPER_AUTO_RENEW_CA_CERT_FROM_FILE=${SSL_HELPER_AUTO_RENEW_CA_CERT_FROM_FILE:-} 11 | -------------------------------------------------------------------------------- /image/service-available/:ssl-tools/assets/jsonssl-default-env: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | JSONSSL_FILE_DEFAULT="${CONTAINER_SERVICE_DIR}/ssl-tools/assets/certs/certs.json" 3 | 4 | JSONSSL_FILE=${JSONSSL_FILE:-} # don't set default immediatly because we print a warning in jsonssl-helper 5 | JSONSSL_HOSTNAME=${JSONSSL_HOSTNAME:-${HOSTNAME}} 6 | JSONSSL_PROFILE=${JSONSSL_PROFILE:-} # traefik / traefik_up_to_v1_6 7 | 8 | JSONSSL_GET_CA_CERT_CMD=${JSONSSL_GET_CA_CERT_CMD:-} 9 | JSONSSL_GET_CERT_CMD=${JSONSSL_GET_CERT_CMD:-} 10 | JSONSSL_GET_KEY_CMD=${JSONSSL_GET_KEY_CMD:-} 11 | -------------------------------------------------------------------------------- /image/service-available/:ssl-tools/assets/tool/cfssl-helper: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | log-helper level eq trace && set -x 3 | 4 | # This tool helps to generate tls certificates with cfssl 5 | # It takes cfssl configuration from environment variable. 6 | # See cfssl-default-env file 7 | 8 | PREFIX=$1 9 | CERT_FILE=$2 10 | KEY_FILE=$3 11 | CA_FILE=$4 12 | 13 | log-helper debug "cfssl-helper is launched, everybody on the floor!" 14 | 15 | # before 0.2.5 retro compatibility, will be removed. 16 | mkdir -p "${CONTAINER_SERVICE_DIR}/:cfssl/assets/default-ca" 17 | ln -sf "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/default-ca/default-ca.pem" "${CONTAINER_SERVICE_DIR}/:cfssl/assets/default-ca/default-ca.pem" 18 | 19 | if [ -z "${PREFIX}" ] || [ -z "${CERT_FILE}" ] || [ -z "${KEY_FILE}" ] || [ -z "${CA_FILE}" ]; then 20 | log-helper error "Usage: cfssl-helper prefix cert_file key_file ca_file" 21 | exit 1 22 | fi 23 | 24 | if [ ! -e "${CERT_FILE}" ] && [ ! -e "${KEY_FILE}" ]; then 25 | 26 | log-helper info "No certificate file and certificate key provided, generate:" 27 | log-helper info "${CERT_FILE} and ${KEY_FILE}" 28 | 29 | LOG_LEVEL_PARAM="" 30 | 31 | case ${CONTAINER_LOG_LEVEL} in 32 | 0 ) 33 | LOG_LEVEL_PARAM="-loglevel 4";; 34 | 1 ) 35 | LOG_LEVEL_PARAM="-loglevel 3";; 36 | 2 ) 37 | LOG_LEVEL_PARAM="-loglevel 2";; 38 | 3 ) 39 | LOG_LEVEL_PARAM="-loglevel 1";; 40 | 4 ) 41 | LOG_LEVEL_PARAM="-loglevel 0";; 42 | 5 ) 43 | LOG_LEVEL_PARAM="-loglevel 0";; 44 | esac 45 | 46 | # set env vars 47 | PREFIX=${PREFIX^^} # uppercase 48 | 49 | # search for prefixed env var first 50 | 51 | # set prefix variable name 52 | # example : PREFIX_CFSSL_REMOTE='MARIADB_CFSSL_REMOTE' 53 | PREFIX_CFSSL_REMOTE=${PREFIX}_CFSSL_REMOTE 54 | PREFIX_CFSSL_REMOTE_HTTPS_CA_CERT=${PREFIX}_CFSSL_REMOTE_HTTPS_CA_CERT 55 | PREFIX_CFSSL_CA_CERT=${PREFIX}_CFSSL_CA_CERT 56 | PREFIX_CFSSL_CA_KEY=${PREFIX}_CFSSL_CA_KEY 57 | PREFIX_CFSSL_CSR=${PREFIX}_CFSSL_CSR 58 | PREFIX_CFSSL_CSR_JSON=${PREFIX}_CFSSL_CSR_JSON 59 | PREFIX_CFSSL_CONFIG=${PREFIX}_CFSSL_CONFIG 60 | PREFIX_CFSSL_CONFIG_JSON=${PREFIX}_CFSSL_CONFIG_JSON 61 | PREFIX_CFSSL_HOSTNAME=${PREFIX}_CFSSL_HOSTNAME 62 | PREFIX_CFSSL_PROFILE=${PREFIX}_CFSSL_PROFILE 63 | PREFIX_CFSSL_LABEL=${PREFIX}_CFSSL_LABEL 64 | PREFIX_CFSSL_RETRY=${PREFIX}_CFSSL_RETRY 65 | PREFIX_CFSSL_RETRY_DELAY=${PREFIX}_CFSSL_RETRY_DELAY 66 | 67 | # assign CFSSL_REMOTE=${!PREFIX_CFSSL_REMOTE} if value is not empty otherwise CFSSL_REMOTE=CFSSL_REMOTE 68 | CFSSL_REMOTE=${!PREFIX_CFSSL_REMOTE:-$CFSSL_REMOTE} 69 | CFSSL_REMOTE_HTTPS_CA_CERT=${!PREFIX_CFSSL_REMOTE_HTTPS_CA_CERT:-$CFSSL_REMOTE_HTTPS_CA_CERT} 70 | CFSSL_CA_CERT=${!PREFIX_CFSSL_CA_CERT:-$CFSSL_CA_CERT} 71 | CFSSL_CA_KEY=${!PREFIX_CFSSL_CA_KEY:-$CFSSL_CA_KEY} 72 | CFSSL_CSR=${!PREFIX_CFSSL_CSR:-$CFSSL_CSR} 73 | CFSSL_CSR_JSON=${!PREFIX_CFSSL_CSR_JSON:-$CFSSL_CSR_JSON} 74 | CFSSL_CONFIG=${!PREFIX_CFSSL_CONFIG:-$CFSSL_CONFIG} 75 | CFSSL_CONFIG_JSON=${!PREFIX_CFSSL_CONFIG_JSON:-$CFSSL_CONFIG_JSON} 76 | CFSSL_HOSTNAME=${!PREFIX_CFSSL_HOSTNAME:-$CFSSL_HOSTNAME} 77 | CFSSL_PROFILE=${!PREFIX_CFSSL_PROFILE:-$CFSSL_PROFILE} 78 | CFSSL_LABEL=${!PREFIX_CFSSL_LABEL:-$CFSSL_LABEL} 79 | CFSSL_RETRY=${!PREFIX_CFSSL_RETRY:-$CFSSL_RETRY} 80 | CFSSL_RETRY_DELAY=${!PREFIX_CFSSL_RETRY_DELAY:-$CFSSL_RETRY_DELAY} 81 | 82 | source "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/cfssl-default-env" 83 | 84 | # set csr file 85 | CSR_FILE="/tmp/csr-file" 86 | if [ -n "${CFSSL_CSR_JSON}" ]; then 87 | log-helper debug "use CFSSL_CSR_JSON value as csr file" 88 | echo "${CFSSL_CSR_JSON}" > "${CSR_FILE}" 89 | elif [ -n "${CFSSL_CSR}" ]; then 90 | log-helper debug "use ${CFSSL_CSR} as csr file" 91 | cp -f "${CFSSL_CSR}" "${CSR_FILE}" 92 | 93 | # it's the default csr 94 | if [ "${CFSSL_CSR}" = "${CFSSL_DEFAULT_CSR}" ]; then 95 | sed -i "s|{{ CFSSL_DEFAULT_CA_CSR_CN }}|${CFSSL_DEFAULT_CA_CSR_CN}|g" "${CSR_FILE}" 96 | sed -i "s|{{ CFSSL_DEFAULT_CA_CSR_KEY_ALGO }}|${CFSSL_DEFAULT_CA_CSR_KEY_ALGO}|g" "${CSR_FILE}" 97 | sed -i "s|{{ CFSSL_DEFAULT_CA_CSR_KEY_SIZE }}|${CFSSL_DEFAULT_CA_CSR_KEY_SIZE}|g" "${CSR_FILE}" 98 | sed -i "s|{{ CFSSL_CERT_ORGANIZATION_UNIT }}|${CFSSL_CERT_ORGANIZATION_UNIT}|g" "${CSR_FILE}" 99 | sed -i "s|{{ CFSSL_DEFAULT_CA_CSR_ORGANIZATION }}|${CFSSL_DEFAULT_CA_CSR_ORGANIZATION}|g" "${CSR_FILE}" 100 | sed -i "s|{{ CFSSL_DEFAULT_CA_CSR_ORGANIZATION_UNIT }}|${CFSSL_DEFAULT_CA_CSR_ORGANIZATION_UNIT}|g" "${CSR_FILE}" 101 | sed -i "s|{{ CFSSL_DEFAULT_CA_CSR_LOCATION }}|${CFSSL_DEFAULT_CA_CSR_LOCATION}|g" "${CSR_FILE}" 102 | sed -i "s|{{ CFSSL_DEFAULT_CA_CSR_STATE }}|${CFSSL_DEFAULT_CA_CSR_STATE}|g" "${CSR_FILE}" 103 | sed -i "s|{{ CFSSL_DEFAULT_CA_CSR_COUNTRY }}|${CFSSL_DEFAULT_CA_CSR_COUNTRY}|g" "${CSR_FILE}" 104 | fi 105 | else 106 | log-helper error "error: no csr file provided" 107 | log-helper error "CFSSL_CSR_JSON and CFSSL_CSR are empty" 108 | exit 1 109 | fi 110 | 111 | # generate cert 112 | CONFIG_FILE="/tmp/config-file" 113 | CERT_NAME="cert" 114 | 115 | REMOTE_PARAM="" 116 | CA_CERT_PARAM="" 117 | CA_KEY_PARAM="" 118 | CONFIG_PARAM="" 119 | HOSTNAME_PARAM="" 120 | PROFILE_PARAM="" 121 | LABEL_PARAM="" 122 | 123 | if [ -n "${CFSSL_REMOTE}" ]; then 124 | REMOTE_PARAM="-remote=${CFSSL_REMOTE}" 125 | 126 | # add remote https ca cert to known certificates if not empty 127 | if [ -n "${CFSSL_REMOTE_HTTPS_CA_CERT}" ]; then 128 | if [ -e "${CFSSL_REMOTE_HTTPS_CA_CERT}" ]; then 129 | [[ ! -d "/etc/ssl/certs/" ]] && mkdir -p /etc/ssl/certs/ 130 | cat "${CFSSL_REMOTE_HTTPS_CA_CERT}" >> /etc/ssl/certs/ca-certificates.crt 131 | else 132 | log-helper error "error: remote https ca cert file ${CFSSL_REMOTE_HTTPS_CA_CERT} not found" 133 | fi 134 | fi 135 | 136 | else 137 | 138 | # files path with : may cause issue with cfssl tools due to : 139 | # ReadBytes - https://github.com/cloudflare/cfssl/blob/master/helpers/helpers.go#L573 140 | # : is used to split env from file path 141 | # so we copy ca cert and key to tmp 142 | if [ -n "${CFSSL_CA_CERT}" ]; then 143 | 144 | CFSSL_CA_CERT_FILE="/tmp/ca-cert-file" 145 | cp -f "${CFSSL_CA_CERT}" "${CFSSL_CA_CERT_FILE}" 146 | chmod 644 "${CFSSL_CA_CERT_FILE}" 147 | 148 | CA_CERT_PARAM="-ca ${CFSSL_CA_CERT_FILE}" 149 | fi 150 | 151 | if [ -n "${CFSSL_CA_KEY}" ]; then 152 | 153 | CFSSL_CA_KEY_FILE="/tmp/ca-key-file" 154 | cp -f "${CFSSL_CA_KEY}" "${CFSSL_CA_KEY_FILE}" 155 | chmod 600 "${CFSSL_CA_CERT_FILE}" 156 | 157 | CA_KEY_PARAM="-ca-key ${CFSSL_CA_KEY_FILE}" 158 | fi 159 | 160 | fi 161 | 162 | if [ -n "${CFSSL_CONFIG_JSON}" ]; then 163 | log-helper debug "use CFSSL_CONFIG_JSON value as config file" 164 | echo "${CFSSL_CONFIG_JSON}" > "${CONFIG_FILE}" 165 | CONFIG_PARAM="-config ${CONFIG_FILE}" 166 | 167 | elif [ -n "${CFSSL_CONFIG}" ]; then 168 | log-helper debug "use ${CFSSL_CONFIG} as config file" 169 | cp -f "${CFSSL_CONFIG}" "${CONFIG_FILE}" 170 | CONFIG_PARAM="-config ${CONFIG_FILE}" 171 | fi 172 | 173 | if [ -n "$ADDITIONAL_HOSTNAMES" ]; then 174 | log-helper debug "additional hostnames found" 175 | CFSSL_HOSTNAME="${CFSSL_HOSTNAME},${ADDITIONAL_HOSTNAMES}" 176 | fi 177 | 178 | [[ -n "${CFSSL_HOSTNAME}" ]] && HOSTNAME_PARAM="-hostname ${CFSSL_HOSTNAME}" 179 | [[ -n "${CFSSL_PROFILE}" ]] && PROFILE_PARAM="-profile ${CFSSL_PROFILE}" 180 | [[ -n "${CFSSL_LABEL}" ]] && LABEL_PARAM="-label ${CFSSL_LABEL}" 181 | 182 | retry=0 183 | while [ $retry -lt "${CFSSL_RETRY}" ]; do 184 | log-helper debug "cfssl gencert ${LOG_LEVEL_PARAM} ${REMOTE_PARAM} ${CA_CERT_PARAM} ${CA_KEY_PARAM} ${CONFIG_PARAM} ${HOSTNAME_PARAM} ${PROFILE_PARAM} ${LABEL_PARAM} ${CSR_FILE} | cfssljson -bare /tmp/${CERT_NAME}" 185 | eval cfssl gencert "${LOG_LEVEL_PARAM}" "${REMOTE_PARAM}" "${CA_CERT_PARAM}" "${CA_KEY_PARAM}" "${CONFIG_PARAM}" "${HOSTNAME_PARAM}" "${PROFILE_PARAM}" "${LABEL_PARAM}" "${CSR_FILE}" | cfssljson -bare "/tmp/${CERT_NAME}" && break 186 | sleep "${CFSSL_RETRY_DELAY}" 187 | ((retry++)) 188 | done 189 | 190 | # move generated files 191 | [[ ! -e "/tmp/${CERT_NAME}.pem" ]] && exit 1 192 | log-helper debug "move /tmp/${CERT_NAME}.pem to ${CERT_FILE}" 193 | mv "/tmp/${CERT_NAME}.pem" "${CERT_FILE}" 194 | 195 | log-helper debug "move /tmp/${CERT_NAME}-key.pem to ${KEY_FILE}" 196 | mv "/tmp/${CERT_NAME}-key.pem" "${KEY_FILE}" 197 | 198 | # if ca file don't exists 199 | if [ ! -e "${CA_FILE}" ]; then 200 | 201 | if [ -n "${CFSSL_REMOTE}" ]; then 202 | log-helper debug "Get CA certificate from ${CFSSL_REMOTE}" 203 | 204 | retry=0 205 | while [ $retry -lt "${CFSSL_RETRY}" ]; do 206 | log-helper debug "cfssl info ${LOG_LEVEL_PARAM} ${REMOTE_PARAM} ${CONFIG_PARAM} ${PROFILE_PARAM} ${LABEL_PARAM}" 207 | eval cfssl info "${LOG_LEVEL_PARAM}" "${REMOTE_PARAM}" "${CONFIG_PARAM}" "${PROFILE_PARAM}" "${LABEL_PARAM}" | sed -e "s/.*certificate\":\"\(.*-----\)\".*/\1/g" | sed 's/\\n/\n/g' > "${CA_FILE}" && log-helper debug "CA certificate returned save as ${CA_FILE}" && break 208 | sleep "${CFSSL_RETRY_DELAY}" 209 | ((retry++)) 210 | done 211 | 212 | [[ ! -e "${CA_FILE}" ]] && exit 1 213 | 214 | elif [ -n "${CFSSL_CA_CERT}" ]; then 215 | log-helper info "Link ${CFSSL_CA_CERT} to ${CA_FILE}" 216 | ln -sf "${CFSSL_CA_CERT}" "${CA_FILE}" 217 | fi 218 | 219 | fi 220 | 221 | # delete tmp files 222 | rm -f /tmp/${CERT_NAME}.csr ${CONFIG_FILE} "${CSR_FILE}" 223 | [[ -e "${CFSSL_CA_CERT_FILE}" ]] && rm "${CFSSL_CA_CERT_FILE}" 224 | [[ -e "${CFSSL_CA_KEY_FILE}" ]] && rm "${CFSSL_CA_KEY_FILE}" 225 | 226 | log-helper debug "done :)" 227 | 228 | elif [ ! -e "${KEY_FILE}" ]; then 229 | log-helper error "Certificate file ${CERT_FILE} exists but not key file ${KEY_FILE}" 230 | exit 1 231 | elif [ ! -e "${CERT_FILE}" ]; then 232 | log-helper error "Key file ${KEY_FILE} exists but not certificate file ${CERT_FILE}" 233 | exit 1 234 | else 235 | log-helper debug "Files ${CERT_FILE} and ${KEY_FILE} exists, fix files permissions" 236 | chmod 644 "${CERT_FILE}" 237 | chmod 600 "${KEY_FILE}" 238 | fi 239 | -------------------------------------------------------------------------------- /image/service-available/:ssl-tools/assets/tool/jsonssl-helper: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | log-helper level eq trace && set -x 3 | 4 | # This tool helps get certificates from json files 5 | # like kubernetes secrets or traefik acme.json 6 | # It takes its configuration from environment variable. 7 | # See json-default-env file 8 | 9 | PREFIX=$1 10 | CERT_FILE=$2 11 | KEY_FILE=$3 12 | CA_FILE=$4 13 | 14 | log-helper debug "jsonssl-helper is launched, everybody on the floor!" 15 | 16 | if [ -z "${PREFIX}" ] || [ -z "${CERT_FILE}" ] || [ -z "${KEY_FILE}" ] || [ -z "${CA_FILE}" ]; then 17 | log-helper error "Usage: jsonssl-helper prefix cert_file key_file ca_file" 18 | exit 1 19 | fi 20 | 21 | if [ ! -e "${CERT_FILE}" ] && [ ! -e "${KEY_FILE}" ]; then 22 | 23 | # set env vars 24 | PREFIX=${PREFIX^^} # uppercase 25 | 26 | # search for prefixed env var first 27 | 28 | # set prefix variable name 29 | # example : PREFIX_JSONSSL_FILE='MARIADB_JSONSSL_FILE' 30 | PREFIX_JSONSSL_FILE=${PREFIX}_JSONSSL_FILE 31 | PREFIX_JSONSSL_HOSTNAME=${PREFIX}_JSONSSL_HOSTNAME 32 | 33 | PREFIX_JSONSSL_PROFILE=${PREFIX}_JSONSSL_PROFILE 34 | PREFIX_JSONSSL_GET_CA_CERT_CMD=${PREFIX}_JSONSSL_GET_CA_CERT_CMD 35 | PREFIX_JSONSSL_GET_CERT_CMD=${PREFIX}_JSONSSL_GET_CERT_CMD 36 | PREFIX_JSONSSL_GET_KEY_CMD=${PREFIX}_JSONSSL_GET_KEY_CMD 37 | 38 | # assign JSONSSL_FILE=${!PREFIX_JSONSSL_FILE} if value is not empty otherwise JSONSSL_FILE=JSONSSL_FILE 39 | JSONSSL_FILE=${!PREFIX_JSONSSL_FILE:-$JSONSSL_FILE} 40 | JSONSSL_HOSTNAME=${!PREFIX_JSONSSL_HOSTNAME:-$JSONSSL_HOSTNAME} 41 | 42 | JSONSSL_PROFILE=${!PREFIX_JSONSSL_PROFILE:-$JSONSSL_PROFILE} 43 | JSONSSL_GET_CA_CERT_CMD=${!PREFIX_JSONSSL_GET_CA_CERT_CMD:-$JSONSSL_GET_CA_CERT_CMD} 44 | JSONSSL_GET_CERT_CMD=${!PREFIX_JSONSSL_GET_CERT_CMD:-$JSONSSL_GET_CERT_CMD} 45 | JSONSSL_GET_KEY_CMD=${!PREFIX_JSONSSL_GET_KEY_CMD:-$JSONSSL_GET_KEY_CMD} 46 | 47 | source "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/jsonssl-default-env" 48 | 49 | if [ -z "${JSONSSL_FILE}" ]; then 50 | log-helper info "Variable JSONSSL_FILE is empty, set to default location:" 51 | log-helper info "JSONSSL_FILE=${JSONSSL_FILE_DEFAULT}" 52 | JSONSSL_FILE=${JSONSSL_FILE_DEFAULT} 53 | fi 54 | 55 | if [ ! -e "${JSONSSL_FILE}" ]; then 56 | log-helper error "JSONSSL_FILE file '${JSONSSL_FILE}' not found" 57 | exit 1 58 | fi 59 | 60 | # Json file profile, only traefik for now 61 | if [ "${JSONSSL_PROFILE,,}" = "traefik" ]; then 62 | # Let's Encrypt CA certificate is in cert file after the domain certificate. 63 | # So we took what's after the first cert. 64 | JSONSSL_GET_CA_CERT_CMD="awk '{if(found) print} /END CERTIFICATE/{found=1}' ${CERT_FILE}" 65 | 66 | JSONSSL_GET_CERT_CMD="cat ${JSONSSL_FILE} | jq -r '[.Certificates[]] | map(select(.Domain.Main == \"${JSONSSL_HOSTNAME}\")) | .[0].Certificate' | base64 -d" 67 | JSONSSL_GET_KEY_CMD="cat ${JSONSSL_FILE} | jq -r '[.Certificates[]] | map(select(.Domain.Main == \"${JSONSSL_HOSTNAME}\")) | .[0].Key' | base64 -d" 68 | elif [ "${JSONSSL_PROFILE,,}" = "traefik_up_to_v1_6" ]; then 69 | # Let's Encrypt CA certificate is in cert file after the domain certificate. 70 | # So we took what's after the first cert. 71 | JSONSSL_GET_CA_CERT_CMD="awk '{if(found) print} /END CERTIFICATE/{found=1}' ${CERT_FILE}" 72 | 73 | JSONSSL_GET_CERT_CMD="cat ${JSONSSL_FILE} | jq -r '[.[\"DomainsCertificate\"].Certs[].Certificate] | map(select(.Domain == \"${JSONSSL_HOSTNAME}\")) | .[0].Certificate' | base64 -d" 74 | JSONSSL_GET_KEY_CMD="cat ${JSONSSL_FILE} | jq -r '[.[\"DomainsCertificate\"].Certs[].Certificate] | map(select(.Domain == \"${JSONSSL_HOSTNAME}\")) | .[0].PrivateKey' | base64 -d" 75 | fi 76 | 77 | log-helper debug "Run JSONSSL_GET_CERT_CMD: ${JSONSSL_GET_CERT_CMD}" 78 | log-helper debug "put return in ${CERT_FILE}" 79 | eval "${JSONSSL_GET_CERT_CMD}" > "${CERT_FILE}" 80 | 81 | if [ ! -s "$CERT_FILE" ]; then 82 | log-helper error "Generated file '${CERT_FILE}' is empty" 83 | log-helper error "Set loglevel to debug for more information" 84 | exit 1 85 | fi 86 | 87 | log-helper debug "Run JSONSSL_GET_KEY_CMD: ${JSONSSL_GET_KEY_CMD}" 88 | log-helper debug "put return in ${KEY_FILE}" 89 | eval "$JSONSSL_GET_KEY_CMD" > "${KEY_FILE}" 90 | 91 | if [ ! -s "${KEY_FILE}" ]; then 92 | log-helper error "Generated file '${KEY_FILE}' is empty" 93 | log-helper error "Set loglevel to debug for more information" 94 | exit 1 95 | fi 96 | 97 | # if CA cert doesn't exist 98 | if [ ! -e "$CA_FILE" ]; then 99 | log-helper debug "Run JSONSSL_GET_CA_CERT_CMD: ${JSONSSL_GET_CA_CERT_CMD}" 100 | log-helper debug "put return in ${CA_FILE}" 101 | eval "$JSONSSL_GET_CA_CERT_CMD" > "${CA_FILE}" 102 | 103 | if [ ! -s "$CA_FILE" ]; then 104 | log-helper error "Generated file '${CA_FILE}' is empty" 105 | log-helper error "Set loglevel to debug for more information" 106 | exit 1 107 | fi 108 | fi 109 | 110 | log-helper debug "done :)" 111 | 112 | elif [ ! -e "${KEY_FILE}" ]; then 113 | log-helper error "Certificate file ${CERT_FILE} exists but not key file ${KEY_FILE}" 114 | exit 1 115 | elif [ ! -e "${CERT_FILE}" ]; then 116 | log-helper error "Key file ${KEY_FILE} exists but not certificate file ${CERT_FILE}" 117 | exit 1 118 | else 119 | log-helper debug "Files ${CERT_FILE} and ${KEY_FILE} exists, fix files permissions" 120 | chmod 644 "${CERT_FILE}" 121 | chmod 600 "${KEY_FILE}" 122 | fi 123 | -------------------------------------------------------------------------------- /image/service-available/:ssl-tools/assets/tool/ssl-auto-renew: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # This file aims to be called by a cron task 4 | # and not directly. See ssl-helper. 5 | 6 | source /container/run/environment.sh 7 | 8 | SSL_HELPER_TOOL=$1 9 | PREFIX=$2 10 | CERT_FILE=$3 11 | KEY_FILE=$4 12 | CA_FILE=$5 13 | IMPACTED_SERVICES=$6 14 | JSONSSL_FILE=$7 15 | FROM_FILES=$8 16 | CERT_FROM_FILE=$9 17 | KEY_FROM_FILE=${10} 18 | CA_CERT_FROM_FILE=${11} 19 | 20 | function stop_impacted_services() { 21 | # Stop impacted services 22 | if [ -n "${IMPACTED_SERVICES}" ]; then 23 | log-helper info "Services to stop: ${IMPACTED_SERVICES}" 24 | 25 | impacted_services_table=("${IMPACTED_SERVICES}") 26 | for service in "${impacted_services_table[@]}" 27 | do 28 | log-helper info "Stopping ${service}..." 29 | sv stop "/container/run/process/${service}" 30 | done 31 | 32 | log-helper info "All services are stopped" 33 | fi 34 | } 35 | 36 | function start_impacted_services() { 37 | # restart impacted services 38 | if [ -n "${IMPACTED_SERVICES}" ]; then 39 | 40 | impacted_services_table=("${IMPACTED_SERVICES}") 41 | for service in "${impacted_services_table[@]}" 42 | do 43 | log-helper info "Starting ${service}..." 44 | sv start "/container/run/process/${service}" 45 | done 46 | 47 | log-helper info "All services are started" 48 | fi 49 | } 50 | 51 | # renew from container files 52 | if [ "${FROM_FILES,,}" = "true" ]; then 53 | 54 | log-helper info "Check renew from files" 55 | renew=false 56 | 57 | # File previous md5 58 | CERT_PREVIOUS_MD5=$(cat "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5${CERT_FILE}.md5") || true 59 | KEY_PREVIOUS_MD5=$(cat "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5${KEY_FILE}.md5") || true 60 | CA_CERT_PREVIOUS_MD5=$(cat "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5${CA_FILE}.md5") || true 61 | 62 | # from file current md5 63 | FROM_CERT_MD5=$(md5sum "${CERT_FROM_FILE}" | awk '{ print $1 }') 64 | FROM_KEY_MD5=$(md5sum "${KEY_FROM_FILE}" | awk '{ print $1 }') 65 | FROM_CA_CERT_MD5=$(md5sum "${CA_CERT_FROM_FILE}" | awk '{ print $1 }') 66 | 67 | [[ "$CERT_PREVIOUS_MD5" != "$FROM_CERT_MD5" ]] && renew=true 68 | [[ "$KEY_PREVIOUS_MD5" != "$FROM_KEY_MD5" ]] && renew=true 69 | [[ "$CA_CERT_PREVIOUS_MD5" != "$FROM_CA_CERT_MD5" ]] && renew=true 70 | 71 | if ! $renew; then 72 | log-helper info "Certificate files are identicals" 73 | exit 0 74 | fi 75 | 76 | log-helper info "Certificate files are differents" 77 | 78 | stop_impacted_services 79 | 80 | if [ "${CERT_FROM_FILE}" != "${CERT_FILE}" ]; then 81 | log-helper info "Copy ${CERT_FROM_FILE} to ${CERT_FILE}" 82 | cp -f "${CERT_FROM_FILE}" "${CERT_FILE}" 83 | fi 84 | 85 | if [ "${KEY_FROM_FILE}" != "${KEY_FILE}" ]; then 86 | log-helper info "Copy ${KEY_FROM_FILE} to ${KEY_FILE}" 87 | cp -f "${KEY_FROM_FILE}" "${KEY_FILE}" 88 | fi 89 | 90 | if [ "${CA_CERT_FROM_FILE}" != "${CA_FILE}" ]; then 91 | log-helper info "Copy ${CA_CERT_FROM_FILE} to ${CA_FILE}" 92 | cp -f "${CA_CERT_FROM_FILE}" "${CA_FILE}" 93 | fi 94 | 95 | log-helper info "Update file md5 with new values" 96 | echo "${FROM_CERT_MD5}" > "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5${CERT_FILE}.md5" 97 | echo "${FROM_KEY_MD5}" > "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5${KEY_FILE}.md5" 98 | echo "${FROM_CA_CERT_MD5}" > "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5${CA_FILE}.md5" 99 | 100 | start_impacted_services 101 | 102 | # renew with cfssl or jsonssl 103 | else 104 | log-helper info "Check renew for cfssl or jsonssl" 105 | 106 | cert_ok=false 107 | ca_ok=false 108 | 109 | # the certificate will expired in the next day 110 | if openssl x509 -checkend 259200 -noout -in "${CERT_FILE}"; then 111 | log-helper info "The certificate '${CERT_FILE}' is ok for the next 3 days at least." 112 | cert_ok=true 113 | fi 114 | 115 | if openssl x509 -checkend 259200 -noout -in "${CA_FILE}"; then 116 | log-helper info "The CA certificate '${CA_FILE}' is ok for the next 3 days at least." 117 | ca_ok=true 118 | fi 119 | 120 | if [ "${SSL_HELPER_TOOL}" = "jsonssl-helper" ]; then 121 | log-helper info "Check if ${JSONSSL_FILE} has changed" 122 | JSONSSL_FILE_PREVIOUS_MD5=$(cat "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5${JSONSSL_FILE}.md5") || true 123 | JSONSSL_FILE_MD5=$(md5sum "${JSONSSL_FILE}" | awk '{ print $1 }') 124 | 125 | [[ "${JSONSSL_FILE_PREVIOUS_MD5}" != "${JSONSSL_FILE_MD5}" ]] && cert_ok=false 126 | fi 127 | 128 | if ${cert_ok} && ${ca_ok}; then 129 | log-helper info "Nothing to do :)" 130 | exit 0 131 | fi 132 | 133 | log-helper info "Auto-renew on the way!" 134 | 135 | stop_impacted_services 136 | 137 | log-helper info "Remove certificate files" 138 | rm -f "${CERT_FILE}" "${KEY_FILE}" "${CA_FILE}" 139 | 140 | log-helper info "Regenerate certificate with ${SSL_HELPER_TOOL}" 141 | ${SSL_HELPER_TOOL} "${PREFIX}" "${CERT_FILE}" "${KEY_FILE}" "${CA_FILE}" 142 | 143 | start_impacted_services 144 | 145 | if [ "${SSL_HELPER_TOOL}" = "jsonssl-helper" ]; then 146 | log-helper info "Update file md5 with new values" 147 | echo "${JSONSSL_FILE_MD5}" > "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5${JSONSSL_FILE}.md5" 148 | fi 149 | 150 | fi 151 | 152 | log-helper info "Auto-renew finished! Champagne!" 153 | -------------------------------------------------------------------------------- /image/service-available/:ssl-tools/assets/tool/ssl-helper: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | log-helper level eq trace && set -x 3 | 4 | # This tool helps to generate tls certificates with cfssl 5 | # or get certificates from a json file 6 | 7 | PREFIX=$1 8 | CERT_FILE=$2 9 | KEY_FILE=$3 10 | CA_FILE=$4 11 | 12 | log-helper debug "Hi! I'm ssl-helper, what button should i press ?" 13 | 14 | # set env vars 15 | PREFIX=${PREFIX^^} # uppercase 16 | 17 | PREFIX_SSL_HELPER_TOOL=${PREFIX}_SSL_HELPER_TOOL 18 | PREFIX_SSL_HELPER_AUTO_RENEW=${PREFIX}_SSL_HELPER_AUTO_RENEW 19 | PREFIX_SSL_HELPER_AUTO_RENEW_CRON_EXP=${PREFIX}_SSL_HELPER_AUTO_RENEW_CRON_EXP 20 | PREFIX_SSL_HELPER_AUTO_RENEW_SERVICES_IMPACTED=${PREFIX}_SSL_HELPER_AUTO_RENEW_SERVICES_IMPACTED 21 | PREFIX_SSL_HELPER_AUTO_RENEW_FROM_FILES=${PREFIX}_SSL_HELPER_AUTO_RENEW_FROM_FILES 22 | PREFIX_SSL_HELPER_AUTO_RENEW_CERT_FROM_FILE=${PREFIX}_SSL_HELPER_AUTO_RENEW_CERT_FROM_FILE 23 | PREFIX_SSL_HELPER_AUTO_RENEW_KEY_FROM_FILE=${PREFIX}_SSL_HELPER_AUTO_RENEW_KEY_FROM_FILE 24 | PREFIX_SSL_HELPER_AUTO_RENEW_CA_CERT_FROM_FILE=${PREFIX}_SSL_HELPER_AUTO_RENEW_CA_CERT_FROM_FILE 25 | 26 | SSL_HELPER_TOOL=${!PREFIX_SSL_HELPER_TOOL:-$SSL_HELPER_TOOL} 27 | SSL_HELPER_AUTO_RENEW=${!PREFIX_SSL_HELPER_AUTO_RENEW:-$SSL_HELPER_AUTO_RENEW} 28 | SSL_HELPER_AUTO_RENEW_CRON_EXP=${!PREFIX_SSL_HELPER_AUTO_RENEW_CRON_EXP:-$SSL_HELPER_AUTO_RENEW_CRON_EXP} 29 | SSL_HELPER_AUTO_RENEW_SERVICES_IMPACTED=${!PREFIX_SSL_HELPER_AUTO_RENEW_SERVICES_IMPACTED:-$SSL_HELPER_AUTO_RENEW_SERVICES_IMPACTED} 30 | SSL_HELPER_AUTO_RENEW_FROM_FILES=${!PREFIX_SSL_HELPER_AUTO_RENEW_FROM_FILES:-$SSL_HELPER_AUTO_RENEW_FROM_FILES} 31 | SSL_HELPER_AUTO_RENEW_CERT_FROM_FILE=${!PREFIX_SSL_HELPER_AUTO_RENEW_CERT_FROM_FILE:-$SSL_HELPER_AUTO_RENEW_CERT_FROM_FILE} 32 | SSL_HELPER_AUTO_RENEW_KEY_FROM_FILE=${!PREFIX_SSL_HELPER_AUTO_RENEW_KEY_FROM_FILE:-$SSL_HELPER_AUTO_RENEW_KEY_FROM_FILE} 33 | SSL_HELPER_AUTO_RENEW_CA_CERT_FROM_FILE=${!PREFIX_SSL_HELPER_AUTO_RENEW_CA_CERT_FROM_FILE:-$SSL_HELPER_AUTO_RENEW_CA_CERT_FROM_FILE} 34 | 35 | source "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/default-env" 36 | 37 | # call the certificate tool cfssl-helper (default) or jsonssl-helper 38 | ${SSL_HELPER_TOOL,,} "${PREFIX}" "${CERT_FILE}" "${KEY_FILE}" "${CA_FILE}" 39 | 40 | # auto-renew certificates just before it expired 41 | # or if source files have changed 42 | if [ "${SSL_HELPER_AUTO_RENEW,,}" = "true" ]; then 43 | 44 | # only for multiple process images (uses cron) 45 | if [ ! -e "/container/multiple_process_stack_added" ]; then 46 | log-helper error "auto-renew is available only with multiple process images" 47 | exit 1 48 | fi 49 | 50 | # if SSL_HELPER_AUTO_RENEW_FROM_FILES=true check certificate source files 51 | if [ "${SSL_HELPER_AUTO_RENEW_FROM_FILES,,}" = "true" ]; then 52 | 53 | [[ -z "${SSL_HELPER_AUTO_RENEW_CERT_FROM_FILE}" ]] && SSL_HELPER_AUTO_RENEW_CERT_FROM_FILE=${CERT_FILE} 54 | [[ -z "${SSL_HELPER_AUTO_RENEW_KEY_FROM_FILE}" ]] && SSL_HELPER_AUTO_RENEW_KEY_FROM_FILE=${KEY_FILE} 55 | [[ -z "${SSL_HELPER_AUTO_RENEW_CA_CERT_FROM_FILE}" ]] && SSL_HELPER_AUTO_RENEW_CA_CERT_FROM_FILE=${CA_FILE} 56 | 57 | if [ ! -e "${SSL_HELPER_AUTO_RENEW_CERT_FROM_FILE}" ] || [ ! -e "${SSL_HELPER_AUTO_RENEW_KEY_FROM_FILE}" ] || [ ! -e "${SSL_HELPER_AUTO_RENEW_CA_CERT_FROM_FILE}" ]; then 58 | log-helper error "with SSL_HELPER_AUTO_RENEW_FROM_FILES=true the following files must exists:" 59 | log-helper error "SSL_HELPER_AUTO_RENEW_CERT_FROM_FILE=${SSL_HELPER_AUTO_RENEW_CERT_FROM_FILE}" 60 | log-helper error "SSL_HELPER_AUTO_RENEW_KEY_FROM_FILE=${SSL_HELPER_AUTO_RENEW_KEY_FROM_FILE}" 61 | log-helper error "SSL_HELPER_AUTO_RENEW_CA_CERT_FROM_FILE=${SSL_HELPER_AUTO_RENEW_CA_CERT_FROM_FILE}" 62 | exit 1 63 | fi 64 | 65 | mkdir -p "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5$(dirname "${CERT_FILE}")" 66 | mkdir -p "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5$(dirname "${KEY_FILE}")" 67 | mkdir -p "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5$(dirname "${CA_FILE}")" 68 | 69 | # calculate certificates files md5 70 | md5sum "${CERT_FILE}" | awk '{ print $1 }' > "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5${CERT_FILE}.md5" 71 | md5sum "${KEY_FILE}" | awk '{ print $1 }' > "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5${KEY_FILE}.md5" 72 | md5sum "${CA_FILE}" | awk '{ print $1 }' > "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5${CA_FILE}.md5" 73 | 74 | fi 75 | 76 | if [ "${SSL_HELPER_TOOL,,}" = "jsonssl-helper" ]; then 77 | 78 | PREFIX_JSONSSL_FILE=${PREFIX}_JSONSSL_FILE 79 | JSONSSL_FILE=${!PREFIX_JSONSSL_FILE:-$JSONSSL_FILE} 80 | 81 | source "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/jsonssl-default-env" 82 | 83 | if [ -z "${JSONSSL_FILE}" ]; then 84 | JSONSSL_FILE=${JSONSSL_FILE_DEFAULT} 85 | fi 86 | 87 | # calculate jsonssl file md5 88 | mkdir -p "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5$(dirname "${JSONSSL_FILE}")" 89 | md5sum "${JSONSSL_FILE}" | awk '{ print $1 }' > "${CONTAINER_SERVICE_DIR}/:ssl-tools/assets/md5${JSONSSL_FILE}.md5" 90 | 91 | fi 92 | 93 | # add cron job 94 | echo "${SSL_HELPER_AUTO_RENEW_CRON_EXP} root /usr/sbin/ssl-auto-renew ${SSL_HELPER_TOOL,,} ${PREFIX} ${CERT_FILE} ${KEY_FILE} ${CA_FILE} \"${SSL_HELPER_AUTO_RENEW_SERVICES_IMPACTED}\" \"${JSONSSL_FILE}\" \"${SSL_HELPER_AUTO_RENEW_FROM_FILES}\" \"${SSL_HELPER_AUTO_RENEW_CERT_FROM_FILE}\" \"${SSL_HELPER_AUTO_RENEW_KEY_FROM_FILE}\" \"${SSL_HELPER_AUTO_RENEW_CA_CERT_FROM_FILE}\" 2>&1 | /usr/bin/logger -t cron_ssl_auto_renew" > "/etc/cron.d/${PREFIX}" 95 | chmod 600 "/etc/cron.d/${PREFIX}" 96 | 97 | # disable auto-renew if it was added 98 | elif [ -e "/etc/cron.d/${PREFIX}" ]; then 99 | rm -f "/etc/cron.d/${PREFIX}" 100 | fi 101 | -------------------------------------------------------------------------------- /image/service-available/:ssl-tools/download.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | UARCH=$(uname -m) 4 | echo "Architecture is ${UARCH}" 5 | 6 | case "${UARCH}" in 7 | 8 | "x86_64") 9 | HOST_ARCH="amd64" 10 | ;; 11 | 12 | "arm64" | "aarch64") 13 | HOST_ARCH="arm64" 14 | ;; 15 | 16 | "armv7l" | "armv6l" | "armhf") 17 | HOST_ARCH="arm" 18 | ;; 19 | 20 | "i386") 21 | HOST_ARCH="386" 22 | ;; 23 | 24 | *) 25 | echo "Architecture not supported. Exiting." 26 | exit 1 27 | ;; 28 | esac 29 | 30 | echo "Going to use ${HOST_ARCH} cfssl binaries" 31 | 32 | # download curl and ca-certificate from apt-get if needed 33 | to_install=() 34 | 35 | if [ "$(dpkg-query -W -f='${Status}' curl 2>/dev/null | grep -c "ok installed")" -eq 0 ]; then 36 | to_install+=("curl") 37 | fi 38 | 39 | if [ "$(dpkg-query -W -f='${Status}' ca-certificates 2>/dev/null | grep -c "ok installed")" -eq 0 ]; then 40 | to_install+=("ca-certificates") 41 | fi 42 | 43 | if [ ${#to_install[@]} -ne 0 ]; then 44 | LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends "${to_install[@]}" 45 | fi 46 | 47 | LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends openssl jq 48 | 49 | # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=923479 50 | if [[ "${HOST_ARCH}" == 'arm' ]]; then 51 | LC_ALL=C DEBIAN_FRONTEND=noninteractive c_rehash 52 | fi 53 | 54 | echo "Download cfssl ..." 55 | echo "curl -o /usr/sbin/cfssl -SL https://github.com/osixia/cfssl/releases/download/1.5.0/cfssl_linux-${HOST_ARCH}" 56 | curl -o /usr/sbin/cfssl -SL "https://github.com/osixia/cfssl/releases/download/1.5.0/cfssl_linux-${HOST_ARCH}" 57 | chmod 700 /usr/sbin/cfssl 58 | 59 | echo "Download cfssljson ..." 60 | echo "curl -o /usr/sbin/cfssljson -SL https://github.com/osixia/cfssl/releases/download/1.5.0/cfssljson_linux-${HOST_ARCH}" 61 | curl -o /usr/sbin/cfssljson -SL "https://github.com/osixia/cfssl/releases/download/1.5.0/cfssljson_linux-${HOST_ARCH}" 62 | chmod 700 /usr/sbin/cfssljson 63 | 64 | echo "Project sources: https://github.com/cloudflare/cfssl" 65 | 66 | # remove tools installed to download cfssl 67 | if [ ${#to_install[@]} -ne 0 ]; then 68 | apt-get remove -y --purge --auto-remove "${to_install[@]}" 69 | fi 70 | -------------------------------------------------------------------------------- /image/service-available/:ssl-tools/startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | log-helper level eq trace && set -x 3 | 4 | chmod 700 "${CONTAINER_SERVICE_DIR}"/:ssl-tools/assets/tool/* 5 | ln -sf "${CONTAINER_SERVICE_DIR}"/:ssl-tools/assets/tool/* /usr/sbin 6 | -------------------------------------------------------------------------------- /image/service-available/:syslog-ng-core/assets/config/syslog-ng.conf: -------------------------------------------------------------------------------- 1 | @version: 3.19 2 | @include "scl.conf" 3 | 4 | # Syslog-ng configuration file, compatible with default Debian syslogd 5 | # installation. 6 | 7 | # First, set some global options. 8 | options { chain_hostnames(off); flush_lines(0); use_dns(no); dns-cache(no); use_fqdn(no); 9 | owner("root"); group("adm"); perm(0640); stats_freq(0); 10 | bad_hostname("^gconfd$"); 11 | }; 12 | 13 | ######################## 14 | # Sources 15 | ######################## 16 | # This is the default behavior of sysklogd package 17 | # Logs may come from unix stream, but not from another machine. 18 | # 19 | source s_src { 20 | unix-dgram("/dev/log"); 21 | internal(); 22 | }; 23 | 24 | # If you wish to get logs from remote machine you should uncomment 25 | # this and comment the above source line. 26 | # 27 | #source s_net { tcp(ip(127.0.0.1) port(1000)); }; 28 | 29 | ######################## 30 | # Destinations 31 | ######################## 32 | # First some standard logfile 33 | # 34 | destination d_auth { file("/var/log/auth.log"); }; 35 | destination d_cron { file("/var/log/cron.log"); }; 36 | destination d_daemon { file("/var/log/daemon.log"); }; 37 | destination d_kern { file("/var/log/kern.log"); }; 38 | destination d_lpr { file("/var/log/lpr.log"); }; 39 | destination d_mail { file("/var/log/mail.log"); }; 40 | destination d_syslog { file("/var/log/syslog"); }; 41 | destination d_user { file("/var/log/user.log"); }; 42 | destination d_uucp { file("/var/log/uucp.log"); }; 43 | 44 | # This files are the log come from the mail subsystem. 45 | # 46 | destination d_mailinfo { file("/var/log/mail.info"); }; 47 | destination d_mailwarn { file("/var/log/mail.warn"); }; 48 | destination d_mailerr { file("/var/log/mail.err"); }; 49 | 50 | # Logging for INN news system 51 | # 52 | destination d_newscrit { file("/var/log/news/news.crit"); }; 53 | destination d_newserr { file("/var/log/news/news.err"); }; 54 | destination d_newsnotice { file("/var/log/news/news.notice"); }; 55 | 56 | # Some 'catch-all' logfiles. 57 | # 58 | destination d_debug { file("/var/log/debug"); }; 59 | destination d_error { file("/var/log/error"); }; 60 | destination d_messages { file("/var/log/messages"); }; 61 | 62 | # The named pipe /dev/xconsole is for the nsole' utility. To use it, 63 | # you must invoke nsole' with the -file' option: 64 | # 65 | # $ xconsole -file /dev/xconsole [...] 66 | # 67 | destination d_xconsole { pipe("/dev/xconsole"); }; 68 | 69 | # Send the messages to an other host 70 | # 71 | #destination d_net { tcp("127.0.0.1" port(1000) log_fifo_size(1000)); }; 72 | 73 | # Debian only 74 | destination d_ppp { file("/var/log/ppp.log"); }; 75 | 76 | # stdout for docker 77 | destination d_stdout { ##SYSLOG_OUTPUT_MODE_DEV_STDOUT##("/dev/stdout"); }; 78 | 79 | ######################## 80 | # Filters 81 | ######################## 82 | # Here's come the filter options. With this rules, we can set which 83 | # message go where. 84 | 85 | filter f_dbg { level(debug); }; 86 | filter f_info { level(info); }; 87 | filter f_notice { level(notice); }; 88 | filter f_warn { level(warn); }; 89 | filter f_err { level(err); }; 90 | filter f_crit { level(crit .. emerg); }; 91 | 92 | filter f_debug { level(debug) and not facility(auth, authpriv, news, mail); }; 93 | filter f_error { level(err .. emerg) ; }; 94 | filter f_messages { level(info,notice,warn) and 95 | not facility(auth,authpriv,cron,daemon,mail,news); }; 96 | 97 | filter f_auth { facility(auth, authpriv) and not filter(f_debug); }; 98 | filter f_cron { facility(cron) and not filter(f_debug); }; 99 | filter f_daemon { facility(daemon) and not filter(f_debug); }; 100 | filter f_kern { facility(kern) and not filter(f_debug); }; 101 | filter f_lpr { facility(lpr) and not filter(f_debug); }; 102 | filter f_local { facility(local0, local1, local3, local4, local5, 103 | local6, local7) and not filter(f_debug); }; 104 | filter f_mail { facility(mail) and not filter(f_debug); }; 105 | filter f_news { facility(news) and not filter(f_debug); }; 106 | filter f_syslog3 { not facility(auth, authpriv, mail) and not filter(f_debug); }; 107 | filter f_user { facility(user) and not filter(f_debug); }; 108 | filter f_uucp { facility(uucp) and not filter(f_debug); }; 109 | 110 | filter f_cnews { level(notice, err, crit) and facility(news); }; 111 | filter f_cother { level(debug, info, notice, warn) or facility(daemon, mail); }; 112 | 113 | filter f_ppp { facility(local2) and not filter(f_debug); }; 114 | filter f_console { level(warn .. emerg); }; 115 | 116 | ######################## 117 | # Log paths 118 | ######################## 119 | log { source(s_src); filter(f_auth); destination(d_auth); }; 120 | log { source(s_src); filter(f_cron); destination(d_cron); }; 121 | log { source(s_src); filter(f_daemon); destination(d_daemon); }; 122 | log { source(s_src); filter(f_kern); destination(d_kern); }; 123 | log { source(s_src); filter(f_lpr); destination(d_lpr); }; 124 | log { source(s_src); filter(f_syslog3); destination(d_syslog); destination(d_stdout); }; 125 | log { source(s_src); filter(f_user); destination(d_user); }; 126 | log { source(s_src); filter(f_uucp); destination(d_uucp); }; 127 | 128 | log { source(s_src); filter(f_mail); destination(d_mail); }; 129 | #log { source(s_src); filter(f_mail); filter(f_info); destination(d_mailinfo); }; 130 | #log { source(s_src); filter(f_mail); filter(f_warn); destination(d_mailwarn); }; 131 | #log { source(s_src); filter(f_mail); filter(f_err); destination(d_mailerr); }; 132 | 133 | log { source(s_src); filter(f_news); filter(f_crit); destination(d_newscrit); }; 134 | log { source(s_src); filter(f_news); filter(f_err); destination(d_newserr); }; 135 | log { source(s_src); filter(f_news); filter(f_notice); destination(d_newsnotice); }; 136 | #log { source(s_src); filter(f_cnews); destination(d_console_all); }; 137 | #log { source(s_src); filter(f_cother); destination(d_console_all); }; 138 | 139 | #log { source(s_src); filter(f_ppp); destination(d_ppp); }; 140 | 141 | log { source(s_src); filter(f_debug); destination(d_debug); }; 142 | log { source(s_src); filter(f_error); destination(d_error); }; 143 | log { source(s_src); filter(f_messages); destination(d_messages); }; 144 | 145 | # All messages send to a remote site 146 | # 147 | #log { source(s_src); destination(d_net); }; 148 | 149 | ### 150 | # Include all config files in /etc/syslog-ng/conf.d/ 151 | ### 152 | @include "/etc/syslog-ng/conf.d/*.conf" 153 | -------------------------------------------------------------------------------- /image/service-available/:syslog-ng-core/assets/config/syslog_ng_default: -------------------------------------------------------------------------------- 1 | # If a variable is not set here, then the corresponding 2 | # parameter will not be changed. 3 | # If a variables is set, then every invocation of 4 | # syslog-ng's init script will set them using dmesg. 5 | 6 | # log level of messages which should go to console 7 | # see syslog(3) for details 8 | # 9 | #CONSOLE_LOG_LEVEL=1 10 | 11 | # Command line options to syslog-ng 12 | SYSLOGNG_OPTS="--no-caps" 13 | -------------------------------------------------------------------------------- /image/service-available/:syslog-ng-core/download.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | # download syslog-ng-core from apt-get 4 | LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends syslog-ng-core 5 | -------------------------------------------------------------------------------- /image/service-available/:syslog-ng-core/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | mkdir -p /var/lib/syslog-ng 4 | rm -f /etc/default/syslog-ng 5 | 6 | touch /var/log/syslog 7 | chmod u=rw,g=r,o= /var/log/syslog 8 | rm -f /etc/syslog-ng/syslog-ng.conf 9 | -------------------------------------------------------------------------------- /image/service-available/:syslog-ng-core/process.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | log-helper level eq trace && set -x 3 | 4 | PIDFILE="/var/run/syslog-ng.pid" 5 | SYSLOGNG_OPTS="" 6 | 7 | [ -r /etc/default/syslog-ng ] && . /etc/default/syslog-ng 8 | 9 | exec /usr/sbin/syslog-ng --pidfile "$PIDFILE" -F $SYSLOGNG_OPTS 10 | -------------------------------------------------------------------------------- /image/service-available/:syslog-ng-core/startup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | log-helper level eq trace && set -x 3 | 4 | ln -sf "${CONTAINER_SERVICE_DIR}/:syslog-ng-core/assets/config/syslog_ng_default" /etc/default/syslog-ng 5 | ln -sf "${CONTAINER_SERVICE_DIR}/:syslog-ng-core/assets/config/syslog-ng.conf" /etc/syslog-ng/syslog-ng.conf 6 | 7 | # If /dev/log is either a named pipe or it was placed there accidentally, 8 | # e.g. because of the issue documented at https://github.com/phusion/baseimage-docker/pull/25, 9 | # then we remove it. 10 | if [ ! -S /dev/log ]; then rm -f /dev/log; fi 11 | if [ ! -S /var/lib/syslog-ng/syslog-ng.ctl ]; then rm -f /var/lib/syslog-ng/syslog-ng.ctl; fi 12 | 13 | # determine output mode on /dev/stdout because of the issue documented at https://github.com/phusion/baseimage-docker/issues/468 14 | if [ -p /dev/stdout ]; then 15 | sed -i 's/##SYSLOG_OUTPUT_MODE_DEV_STDOUT##/pipe/' /etc/syslog-ng/syslog-ng.conf 16 | else 17 | sed -i 's/##SYSLOG_OUTPUT_MODE_DEV_STDOUT##/file/' /etc/syslog-ng/syslog-ng.conf 18 | fi 19 | 20 | # If /var/log is writable by another user logrotate will fail 21 | /bin/chown root:root /var/log 22 | /bin/chmod 0755 /var/log 23 | -------------------------------------------------------------------------------- /image/tool/add-multiple-process-stack: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | echo "Install the multiple process stack: runit, syslog-ng-core, logrotate and cron" 3 | /container/tool/add-service-available :runit :syslog-ng-core :logrotate :cron 4 | touch /container/multiple_process_stack_added 5 | -------------------------------------------------------------------------------- /image/tool/add-service-available: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | # Usage : 4 | # RUN /container/tool/add-service-available [service1] [service2] ... 5 | 6 | SERVICE_DIR="/container/service" 7 | SERVICE_AVAILABLE_DIR="/container/service-available" 8 | DOWNLOAD_FILENAME="download.sh" 9 | 10 | for i in "$@" 11 | do 12 | 13 | echo "add-service-available: ${i}" 14 | if [ -d "${SERVICE_AVAILABLE_DIR}/${i}" ]; then 15 | 16 | if [ -f "${SERVICE_AVAILABLE_DIR}/${i}/${DOWNLOAD_FILENAME}" ]; then 17 | echo "run ${SERVICE_AVAILABLE_DIR}/${i}/${DOWNLOAD_FILENAME}" 18 | ${SERVICE_AVAILABLE_DIR}/"${i}"/"${DOWNLOAD_FILENAME}" 19 | echo "remove ${SERVICE_AVAILABLE_DIR}/${i}/${DOWNLOAD_FILENAME}" 20 | rm -f "${SERVICE_AVAILABLE_DIR}/${i}/${DOWNLOAD_FILENAME}" 21 | fi 22 | 23 | echo "move ${SERVICE_AVAILABLE_DIR}/${i} to ${SERVICE_DIR}/${i}" 24 | mv "${SERVICE_AVAILABLE_DIR}/${i}" "${SERVICE_DIR}/${i}" 25 | 26 | else 27 | echo "service-available: ${i} not found in ${SERVICE_AVAILABLE_DIR}/${i}" 28 | exit 1 29 | fi 30 | done 31 | -------------------------------------------------------------------------------- /image/tool/complex-bash-env: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | call=$1 4 | 5 | function iterate() { 6 | local env_var_name=$1 7 | local env_var=${!env_var_name} 8 | 9 | if [ "$(complex-bash-env isTable "$env_var")" = true ]; then 10 | complex-bash-env stripTablePrefix "${env_var}" 11 | else 12 | echo "${env_var_name}" 13 | fi 14 | } 15 | 16 | function isTable() { 17 | local env_var=$1 18 | if [ "$(echo "${env_var}" | grep "#COMPLEX_BASH_ENV:TABLE:" -c )" -eq 1 ]; then 19 | echo true 20 | else 21 | echo false 22 | fi 23 | } 24 | 25 | function isRow() { 26 | local env_var=$1 27 | if [ "$(echo "${env_var}" | grep "#COMPLEX_BASH_ENV:ROW:" -c )" -eq 1 ]; then 28 | echo true 29 | else 30 | echo false 31 | fi 32 | } 33 | 34 | function getRowKey() { 35 | local env_var=$1 36 | local row_key_var_name 37 | row_key_var_name=$(complex-bash-env getRowKeyVarName "$env_var") 38 | echo "${!row_key_var_name}" 39 | } 40 | 41 | function getRowValue() { 42 | local env_var=$1 43 | local row_value_var_name 44 | row_value_var_name=$(complex-bash-env getRowValueVarName "$env_var") 45 | echo "${!row_value_var_name}" 46 | } 47 | 48 | function getRowKeyVarName() { 49 | local env_var=$1 50 | local row=($(complex-bash-env getRow "$env_var")) 51 | echo "${row[0]}" 52 | } 53 | 54 | function getRowValueVarName() { 55 | local env_var=$1 56 | local row=($(complex-bash-env getRow "$env_var")) 57 | echo "${row[1]}" 58 | } 59 | 60 | function getRow() { 61 | local env_var 62 | env_var=$1 63 | if [ "$(complex-bash-env isRow "$env_var")" = true ]; then 64 | local env_var 65 | env_var=$(complex-bash-env stripRowPrefix "$env_var") 66 | echo "${env_var}" 67 | else 68 | echo "$env_var is not a complex bash env row" 69 | exit 1 70 | fi 71 | } 72 | 73 | function stripTablePrefix() { 74 | local env_var=$1 75 | stripPrefix "$env_var" "#COMPLEX_BASH_ENV:TABLE:" 76 | } 77 | 78 | function stripRowPrefix() { 79 | local env_var=$1 80 | stripPrefix "$env_var" "#COMPLEX_BASH_ENV:ROW:" 81 | } 82 | 83 | function stripPrefix() { 84 | local env_var=$1 85 | local prefix=$2 86 | local r=${env_var#$prefix} 87 | echo "${r}" 88 | } 89 | 90 | shift 91 | $call "$@" 92 | -------------------------------------------------------------------------------- /image/tool/install-service: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 -u 2 | import os, os.path, subprocess 3 | 4 | SERVICE_DIR = "/container/service" 5 | INSTALL_FILENAME = "install.sh" 6 | PROCESS_FILENAME = "process.sh" 7 | nb_process = 0 8 | 9 | print("install-service") 10 | # Auto run global install script if available 11 | if os.path.isfile(SERVICE_DIR + os.sep + INSTALL_FILENAME): 12 | print(("run " + SERVICE_DIR + os.sep + INSTALL_FILENAME)) 13 | subprocess.call([SERVICE_DIR + os.sep + INSTALL_FILENAME],shell=True) 14 | 15 | print(("remove " + SERVICE_DIR + os.sep + INSTALL_FILENAME + "\n")) 16 | os.remove(SERVICE_DIR + os.sep + INSTALL_FILENAME) 17 | 18 | # Process install script of services in /container/service 19 | for service in sorted(os.listdir(SERVICE_DIR)): 20 | 21 | if os.path.isfile(SERVICE_DIR + os.sep + service + os.sep + INSTALL_FILENAME): 22 | print(("run " + SERVICE_DIR + os.sep + service + os.sep + INSTALL_FILENAME)) 23 | subprocess.call([SERVICE_DIR + os.sep + service + os.sep + INSTALL_FILENAME],shell=True) 24 | 25 | print(("remove " + SERVICE_DIR + os.sep + service + os.sep + INSTALL_FILENAME)) 26 | os.remove(SERVICE_DIR + os.sep + service + os.sep + INSTALL_FILENAME) 27 | 28 | if os.path.isfile(SERVICE_DIR + os.sep + service + os.sep + PROCESS_FILENAME): 29 | nb_process += 1 30 | 31 | 32 | print((str(nb_process) + " process found.")) 33 | 34 | # Multiple process image 35 | if nb_process > 1: 36 | if not os.path.exists("/container/multiple_process_stack_added"): 37 | print("This image has multiple process.") 38 | subprocess.call(["apt-get update"],shell=True) 39 | subprocess.call(["/container/tool/add-multiple-process-stack"],shell=True) 40 | print("For better image build process consider adding:") 41 | print("\"/container/tool/add-multiple-process-stack\" after an apt-get update in your Dockerfile.") 42 | -------------------------------------------------------------------------------- /image/tool/log-helper: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | # log helper base on environment variable CONTAINER_LOG_LEVEL 4 | # CONTAINER_LOG_LEVEL environment variable is set by run tool based on --log-level argument (info by default) 5 | # or you can set it directly with docker --env argument 6 | 7 | # Usage example: log-helper info CONTAINER_LOG_LEVEL is info or more 8 | # the message "CONTAINER_LOG_LEVEL is info or more" will be printed only if log level is info, debug or trace 9 | 10 | LOG_LEVEL_NONE=0 11 | LOG_LEVEL_ERROR=1 12 | LOG_LEVEL_WARNING=2 13 | LOG_LEVEL_INFO=3 14 | LOG_LEVEL_DEBUG=4 15 | LOG_LEVEL_TRACE=5 16 | 17 | # default log level if CONTAINER_LOG_LEVEL is not set -> info 18 | log_level=${CONTAINER_LOG_LEVEL:-${LOG_LEVEL_INFO}} 19 | 20 | call=$1 # function to call (error, warning, info, debug, trace, level) 21 | if [[ ! "$call" =~ ^(error|warning|info|debug|trace|level)$ ]]; then 22 | echo "Error: Function $call not found" 23 | echo "Allowed functions are: error, warning, info, debug, trace, level" 24 | echo "usage example: log-helper info hello !" 25 | exit 1 26 | fi 27 | 28 | 29 | echo_msg="" # message to print if required log level is set 30 | echo_param="" # echo command parameters 31 | 32 | function print_log(){ 33 | local level_txt=$1 34 | local message=$2 35 | local date=$(date +"%Y-%m-%d %T") 36 | 37 | readarray -t messages <<<"$message" 38 | 39 | for m in "${messages[@]}"; do 40 | echo "*** ${level_txt} | ${date} | ${m}" 41 | done 42 | } 43 | 44 | function error() { 45 | 46 | # getEchoParams no matter what level it is to not break pipes 47 | getEchoParams $@ 48 | 49 | if [ $log_level -ge 1 ]; then 50 | echo $echo_param "$(print_log " ERROR " "$echo_msg")" 51 | fi 52 | } 53 | 54 | function warning() { 55 | 56 | # getEchoParams no matter what level it is to not break pipes 57 | getEchoParams $@ 58 | 59 | if [ $log_level -ge 2 ]; then 60 | echo $echo_param "$(print_log "WARNING" "$echo_msg")" 61 | fi 62 | } 63 | 64 | function info() { 65 | 66 | # getEchoParams no matter what level it is to not break pipes 67 | getEchoParams $@ 68 | 69 | if [ $log_level -ge 3 ]; then 70 | echo $echo_param "$(print_log " INFO " "$echo_msg")" 71 | fi 72 | } 73 | 74 | function debug() { 75 | 76 | # getEchoParams no matter what level it is to not break pipes 77 | getEchoParams $@ 78 | 79 | if [ $log_level -ge 4 ]; then 80 | echo $echo_param "$(print_log " DEBUG " "$echo_msg")" 81 | fi 82 | } 83 | 84 | function trace() { 85 | 86 | # getEchoParams no matter what level it is to not break pipes 87 | getEchoParams $@ 88 | 89 | if [ $log_level -ge 5 ]; then 90 | echo $echo_param "$(print_log " TRACE " "$echo_msg")" 91 | fi 92 | } 93 | 94 | function getMsgFromStdin() { 95 | if [ -z "$2" ]; then 96 | echo_msg=$(cat) 97 | fi 98 | } 99 | 100 | function getEchoParams() { 101 | 102 | echo_msg="$@" 103 | 104 | if [[ "$1" =~ ^(-e|-n|-E)$ ]]; then 105 | echo_param=$1 106 | echo_msg=${echo_msg#$1 } 107 | fi 108 | 109 | # read from pipe if echo_msg is empty 110 | [[ -n "$echo_msg" ]] || getMsgFromStdin 111 | } 112 | 113 | function level() { 114 | 115 | local operator=$1 116 | local loglevel_str=$2 117 | local loglevel_str=${loglevel_str^^} # uppercase 118 | 119 | if [[ ! "$operator" =~ ^(eq|ne|gt|ge|lt|le)$ ]]; then 120 | echo "Error: Operator $operator not allowed" 121 | echo "Allowed operators are: eq, ne, gt, ge, lt, le" 122 | echo "Help: http://www.tldp.org/LDP/abs/html/comparison-ops.html" 123 | exit 1 124 | fi 125 | 126 | if [ -z "$loglevel_str" ]; then 127 | echo "Error: No log level provided" 128 | echo "Allowed log level are: none, error, warning, info, debug, trace" 129 | echo "usage example: log-helper level eq info" 130 | exit 1 131 | fi 132 | 133 | local log_level_var=LOG_LEVEL_$loglevel_str 134 | 135 | if [ $log_level -$operator ${!log_level_var} ]; then 136 | exit 0 137 | else 138 | exit 1 139 | fi 140 | } 141 | 142 | shift 143 | $call "$@" 144 | -------------------------------------------------------------------------------- /image/tool/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 -u 2 | # -*- coding: utf-8 -*- 3 | 4 | import os, os.path, sys, stat, signal, errno, argparse, time, json, re, yaml, ast, socket, shutil, pwd, grp 5 | from datetime import datetime 6 | 7 | KILL_PROCESS_TIMEOUT = int(os.environ.get('KILL_PROCESS_TIMEOUT', 30)) 8 | KILL_ALL_PROCESSES_TIMEOUT = int(os.environ.get('KILL_ALL_PROCESSES_TIMEOUT', 30)) 9 | 10 | LOG_LEVEL_NONE = 0 11 | LOG_LEVEL_ERROR = 1 12 | LOG_LEVEL_WARNING = 2 13 | LOG_LEVEL_INFO = 3 14 | LOG_LEVEL_DEBUG = 4 15 | LOG_LEVEL_TRACE = 5 16 | 17 | SHENV_NAME_WHITELIST_REGEX = re.compile('\W') 18 | 19 | log_level = None 20 | 21 | environ_backup = dict(os.environ) 22 | terminated_child_processes = {} 23 | 24 | IMPORT_STARTUP_FILENAME="startup.sh" 25 | IMPORT_PROCESS_FILENAME="process.sh" 26 | IMPORT_FINISH_FILENAME="finish.sh" 27 | 28 | IMPORT_ENVIRONMENT_DIR="/container/environment" 29 | IMPORT_FIRST_STARTUP_ENVIRONMENT_DIR="/container/environment/startup" 30 | 31 | ENV_FILES_YAML_EXTENSIONS = ('.yaml', '.startup.yaml') 32 | ENV_FILES_JSON_EXTENSIONS = ('.json', '.startup.json') 33 | ENV_FILES_STARTUP_EXTENSIONS = ('.startup.yaml', '.startup.json') 34 | 35 | IMPORT_SERVICE_DIR="/container/service" 36 | 37 | RUN_DIR="/container/run" 38 | RUN_STATE_DIR = RUN_DIR + "/state" 39 | RUN_ENVIRONMENT_DIR = RUN_DIR + "/environment" 40 | RUN_ENVIRONMENT_FILE_EXPORT = RUN_DIR + "/environment.sh" 41 | RUN_STARTUP_DIR = RUN_DIR + "/startup" 42 | RUN_STARTUP_FINAL_FILE = RUN_DIR + "/startup.sh" 43 | RUN_PROCESS_DIR = RUN_DIR + "/process" 44 | RUN_SERVICE_DIR = RUN_DIR + "/service" 45 | 46 | ENVIRONMENT_LOG_LEVEL_KEY = 'CONTAINER_LOG_LEVEL' 47 | ENVIRONMENT_SERVICE_DIR_KEY = 'CONTAINER_SERVICE_DIR' 48 | ENVIRONMENT_STATE_DIR_KEY = 'CONTAINER_STATE_DIR' 49 | 50 | class AlarmException(Exception): 51 | pass 52 | 53 | def write_log(level, message): 54 | now = datetime.now() 55 | for line in message.splitlines(): 56 | sys.stderr.write("*** %s | %s | %s\n" % (level, now.strftime("%Y-%m-%d %H:%M:%S"), line)) 57 | 58 | def error(message): 59 | if log_level >= LOG_LEVEL_ERROR: 60 | write_log(" ERROR ", message) 61 | 62 | def warning(message): 63 | if log_level >= LOG_LEVEL_WARNING: 64 | write_log("WARNING", message) 65 | 66 | def info(message): 67 | if log_level >= LOG_LEVEL_INFO: 68 | write_log(" INFO ", message) 69 | 70 | def debug(message): 71 | if log_level >= LOG_LEVEL_DEBUG: 72 | write_log(" DEBUG ", message) 73 | 74 | def trace(message): 75 | if log_level >= LOG_LEVEL_TRACE: 76 | write_log(" TRACE ", message) 77 | 78 | def debug_env_dump(): 79 | debug("------------ Environment dump ------------") 80 | for name, value in list(os.environ.items()): 81 | debug(name + " = " + value) 82 | debug("------------------------------------------") 83 | 84 | def ignore_signals_and_raise_keyboard_interrupt(signame): 85 | signal.signal(signal.SIGTERM, signal.SIG_IGN) 86 | signal.signal(signal.SIGINT, signal.SIG_IGN) 87 | raise KeyboardInterrupt(signame) 88 | 89 | def raise_alarm_exception(): 90 | raise AlarmException('Alarm') 91 | 92 | def listdir(path): 93 | try: 94 | result = os.stat(path) 95 | except OSError: 96 | return [] 97 | if stat.S_ISDIR(result.st_mode): 98 | return sorted(os.listdir(path)) 99 | else: 100 | return [] 101 | 102 | def is_exe(path): 103 | try: 104 | return os.path.isfile(path) and os.access(path, os.X_OK) 105 | except OSError: 106 | return False 107 | 108 | def xstr(s): 109 | if s is None: 110 | return '' 111 | return str(s) 112 | 113 | def set_env_hostname_to_etc_hosts(): 114 | try: 115 | if "HOSTNAME" in os.environ: 116 | socket_hostname = socket.gethostname() 117 | 118 | if os.environ["HOSTNAME"] != socket_hostname: 119 | ip_address = socket.gethostbyname(socket_hostname) 120 | with open("/etc/hosts", "a") as myfile: 121 | myfile.write(ip_address+" "+os.environ["HOSTNAME"]+"\n") 122 | except: 123 | warning("set_env_hostname_to_etc_hosts: failed at some point...") 124 | 125 | def python_dict_to_bash_envvar(name, python_dict): 126 | 127 | for value in python_dict: 128 | python_to_bash_envvar(name+"_KEY", value) 129 | python_to_bash_envvar(name+"_VALUE", python_dict.get(value)) 130 | 131 | values = "#COMPLEX_BASH_ENV:ROW: "+name+"_KEY "+name+"_VALUE" 132 | os.environ[name] = xstr(values) 133 | trace("python2bash : set : " + name + " = "+ os.environ[name]) 134 | 135 | def python_list_to_bash_envvar(name, python_list): 136 | 137 | values="#COMPLEX_BASH_ENV:TABLE:" 138 | 139 | i=1 140 | for value in python_list: 141 | child_name = name + "_ROW_" + str(i) 142 | values += " " + child_name 143 | python_to_bash_envvar(child_name, value) 144 | i = i +1 145 | 146 | os.environ[name] = xstr(values) 147 | trace("python2bash : set : " + name + " = "+ os.environ[name]) 148 | 149 | def python_to_bash_envvar(name, value): 150 | 151 | try: 152 | value = ast.literal_eval(value) 153 | except: 154 | pass 155 | 156 | if isinstance(value, list): 157 | python_list_to_bash_envvar(name,value) 158 | 159 | elif isinstance(value, dict): 160 | python_dict_to_bash_envvar(name,value) 161 | 162 | else: 163 | os.environ[name] = xstr(value) 164 | trace("python2bash : set : " + name + " = "+ os.environ[name]) 165 | 166 | def decode_python_envvars(): 167 | _environ = dict(os.environ) 168 | for name, value in list(_environ.items()): 169 | if value.startswith("#PYTHON2BASH:") : 170 | value = value.replace("#PYTHON2BASH:","",1) 171 | python_to_bash_envvar(name, value) 172 | 173 | def decode_json_envvars(): 174 | _environ = dict(os.environ) 175 | for name, value in list(_environ.items()): 176 | if value.startswith("#JSON2BASH:") : 177 | value = value.replace("#JSON2BASH:","",1) 178 | try: 179 | value = json.loads(value) 180 | python_to_bash_envvar(name,value) 181 | except: 182 | os.environ[name] = xstr(value) 183 | warning("failed to parse : " + xstr(value)) 184 | trace("set : " + name + " = "+ os.environ[name]) 185 | 186 | def decode_envvars(): 187 | decode_json_envvars() 188 | decode_python_envvars() 189 | 190 | def generic_import_envvars(path, override_existing_environment): 191 | if not os.path.exists(path): 192 | trace("generic_import_envvars "+ path+ " don't exists") 193 | return 194 | new_env = {} 195 | for envfile in listdir(path): 196 | filePath = path + os.sep + envfile 197 | if os.path.isfile(filePath) and "." not in envfile: 198 | name = os.path.basename(envfile) 199 | with open(filePath, "r") as f: 200 | # Text files often end with a trailing newline, which we 201 | # don't want to include in the env variable value. See 202 | # https://github.com/phusion/baseimage-docker/pull/49 203 | value = re.sub('\n\Z', '', f.read()) 204 | new_env[name] = value 205 | trace("import " + name + " from " + filePath) 206 | 207 | for name, value in list(new_env.items()): 208 | if override_existing_environment or name not in os.environ: 209 | os.environ[name] = value 210 | trace("set : " + name + " = "+ os.environ[name]) 211 | else: 212 | debug("ignore : " + name + " = " + xstr(value) + " (keep " + name + " = " + os.environ[name] + " )") 213 | 214 | def import_run_envvars(): 215 | clear_environ() 216 | generic_import_envvars(RUN_ENVIRONMENT_DIR, True) 217 | 218 | def import_envvars(): 219 | generic_import_envvars(IMPORT_ENVIRONMENT_DIR, False) 220 | generic_import_envvars(IMPORT_FIRST_STARTUP_ENVIRONMENT_DIR, False) 221 | 222 | def export_run_envvars(to_dir = True): 223 | if to_dir and not os.path.exists(RUN_ENVIRONMENT_DIR): 224 | warning("export_run_envvars: "+RUN_ENVIRONMENT_DIR+" don't exists") 225 | return 226 | shell_dump = "" 227 | for name, value in list(os.environ.items()): 228 | if name in ['USER', 'GROUP', 'UID', 'GID', 'SHELL']: 229 | continue 230 | if to_dir: 231 | with open(RUN_ENVIRONMENT_DIR + os.sep + name, "w") as f: 232 | f.write(value) 233 | trace("export " + name + " to " + RUN_ENVIRONMENT_DIR + os.sep + name) 234 | shell_dump += "export " + sanitize_shenvname(name) + "=" + shquote(value) + "\n" 235 | 236 | with open(RUN_ENVIRONMENT_FILE_EXPORT, "w") as f: 237 | f.write(shell_dump) 238 | trace("export "+RUN_ENVIRONMENT_FILE_EXPORT) 239 | 240 | def create_run_envvars(): 241 | set_dir_env() 242 | set_log_level_env() 243 | import_envvars() 244 | import_env_files() 245 | decode_envvars() 246 | export_run_envvars() 247 | 248 | def clear_run_envvars(): 249 | try: 250 | shutil.rmtree(RUN_ENVIRONMENT_DIR) 251 | os.makedirs(RUN_ENVIRONMENT_DIR) 252 | os.chmod(RUN_ENVIRONMENT_DIR, 700) 253 | except: 254 | warning("clear_run_envvars: failed at some point...") 255 | 256 | def print_env_files_order(file_extensions): 257 | 258 | if not os.path.exists(IMPORT_ENVIRONMENT_DIR): 259 | warning("print_env_files_order "+IMPORT_ENVIRONMENT_DIR+" don't exists") 260 | return 261 | 262 | to_print = 'Caution: previously defined variables will not be overriden.\n' 263 | 264 | file_found = False 265 | for subdir, _, files in sorted(os.walk(IMPORT_ENVIRONMENT_DIR)): 266 | for file in files: 267 | filepath = subdir + os.sep + file 268 | if filepath.endswith(file_extensions): 269 | file_found = True 270 | filepath = subdir + os.sep + file 271 | to_print += filepath + '\n' 272 | 273 | if file_found: 274 | if log_level < LOG_LEVEL_DEBUG: 275 | to_print+='\nTo see how this files are processed and environment variables values,\n' 276 | to_print+='run this container with \'--loglevel debug\'' 277 | 278 | info('Environment files will be proccessed in this order : \n' + to_print) 279 | 280 | def import_env_files(): 281 | 282 | if not os.path.exists(IMPORT_ENVIRONMENT_DIR): 283 | warning("import_env_files: "+IMPORT_ENVIRONMENT_DIR+" don't exists") 284 | return 285 | 286 | file_extensions = ENV_FILES_YAML_EXTENSIONS + ENV_FILES_JSON_EXTENSIONS 287 | print_env_files_order(file_extensions) 288 | 289 | for subdir, _, files in sorted(os.walk(IMPORT_ENVIRONMENT_DIR)): 290 | for file in files: 291 | if file.endswith(file_extensions): 292 | filepath = subdir + os.sep + file 293 | 294 | try: 295 | with open(filepath, "r") as f: 296 | 297 | debug("process environment file : " + filepath) 298 | 299 | if file.endswith(ENV_FILES_YAML_EXTENSIONS): 300 | env_vars = yaml.load(f) 301 | 302 | elif file.endswith(ENV_FILES_JSON_EXTENSIONS): 303 | env_vars = json.load(f) 304 | 305 | for name, value in list(env_vars.items()): 306 | if not name in os.environ: 307 | if isinstance(value, list) or isinstance(value, dict): 308 | os.environ[name] = '#PYTHON2BASH:' + xstr(value) 309 | else: 310 | os.environ[name] = xstr(value) 311 | trace("set : " + name + " = "+ os.environ[name]) 312 | else: 313 | debug("ignore : " + name + " = " + xstr(value) + " (keep " + name + " = " + os.environ[name] + " )") 314 | except: 315 | warning('failed to parse: ' + filepath) 316 | 317 | def remove_startup_env_files(): 318 | 319 | if os.path.isdir(IMPORT_FIRST_STARTUP_ENVIRONMENT_DIR): 320 | try: 321 | shutil.rmtree(IMPORT_FIRST_STARTUP_ENVIRONMENT_DIR) 322 | except: 323 | warning("remove_startup_env_files: failed to remove "+IMPORT_FIRST_STARTUP_ENVIRONMENT_DIR) 324 | 325 | if not os.path.exists(IMPORT_ENVIRONMENT_DIR): 326 | warning("remove_startup_env_files: "+IMPORT_ENVIRONMENT_DIR+" don't exists") 327 | return 328 | 329 | for subdir, _, files in sorted(os.walk(IMPORT_ENVIRONMENT_DIR)): 330 | for file in files: 331 | filepath = subdir + os.sep + file 332 | if filepath.endswith(ENV_FILES_STARTUP_EXTENSIONS): 333 | try: 334 | os.remove(filepath) 335 | info("Remove file "+filepath) 336 | except: 337 | warning("remove_startup_env_files: failed to remove "+filepath) 338 | 339 | def restore_environ(): 340 | clear_environ() 341 | trace("Restore initial environment") 342 | os.environ.update(environ_backup) 343 | 344 | def clear_environ(): 345 | trace("Clear existing environment") 346 | os.environ.clear() 347 | 348 | def set_startup_scripts_env(): 349 | debug("Set environment for startup files") 350 | clear_run_envvars() # clear previous environment 351 | create_run_envvars() # create run envvars with all env files 352 | 353 | def set_process_env(keep_startup_env = False): 354 | debug("Set environment for container process") 355 | if not keep_startup_env: 356 | remove_startup_env_files() 357 | clear_run_envvars() 358 | 359 | restore_environ() 360 | create_run_envvars() # recreate env var without startup env files 361 | 362 | def setup_run_directories(args): 363 | 364 | directories = (RUN_PROCESS_DIR, RUN_STARTUP_DIR, RUN_STATE_DIR, RUN_ENVIRONMENT_DIR) 365 | for directory in directories: 366 | if not os.path.exists(directory): 367 | os.makedirs(directory) 368 | 369 | if directory == RUN_ENVIRONMENT_DIR: 370 | os.chmod(directory, 700) 371 | 372 | if not os.path.exists(RUN_ENVIRONMENT_FILE_EXPORT): 373 | open(RUN_ENVIRONMENT_FILE_EXPORT, 'a').close() 374 | os.chmod(RUN_ENVIRONMENT_FILE_EXPORT, 640) 375 | uid = pwd.getpwnam("root").pw_uid 376 | gid = grp.getgrnam("docker_env").gr_gid 377 | os.chown(RUN_ENVIRONMENT_FILE_EXPORT, uid, gid) 378 | 379 | if state_is_first_start(): 380 | 381 | if args.copy_service: 382 | copy_service_to_run_dir() 383 | 384 | set_dir_env() 385 | 386 | base_path = os.environ[ENVIRONMENT_SERVICE_DIR_KEY] 387 | nb_service = len(listdir(base_path)) 388 | 389 | if nb_service > 0 : 390 | info("Search service in " + ENVIRONMENT_SERVICE_DIR_KEY + " = "+base_path+" :") 391 | for d in listdir(base_path): 392 | d_path = base_path + os.sep + d 393 | if os.path.isdir(d_path): 394 | if is_exe(d_path + os.sep + IMPORT_STARTUP_FILENAME): 395 | info('link ' + d_path + os.sep + IMPORT_STARTUP_FILENAME + ' to ' + RUN_STARTUP_DIR + os.sep + d) 396 | try: 397 | os.symlink(d_path + os.sep + IMPORT_STARTUP_FILENAME, RUN_STARTUP_DIR + os.sep + d) 398 | except OSError as detail: 399 | warning('failed to link ' + d_path + os.sep + IMPORT_STARTUP_FILENAME + ' to ' + RUN_STARTUP_DIR + os.sep + d + ': ' + xstr(detail)) 400 | 401 | if is_exe(d_path + os.sep + IMPORT_PROCESS_FILENAME): 402 | info('link ' + d_path + os.sep + IMPORT_PROCESS_FILENAME + ' to ' + RUN_PROCESS_DIR + os.sep + d + os.sep + 'run') 403 | 404 | if not os.path.exists(RUN_PROCESS_DIR + os.sep + d): 405 | os.makedirs(RUN_PROCESS_DIR + os.sep + d) 406 | else: 407 | warning('directory ' + RUN_PROCESS_DIR + os.sep + d + ' already exists') 408 | 409 | try: 410 | os.symlink(d_path + os.sep + IMPORT_PROCESS_FILENAME, RUN_PROCESS_DIR + os.sep + d + os.sep + 'run') 411 | except OSError as detail: 412 | warning('failed to link ' + d_path + os.sep + IMPORT_PROCESS_FILENAME + ' to ' + RUN_PROCESS_DIR + os.sep + d + os.sep + 'run : ' + xstr(detail)) 413 | 414 | if not args.skip_finish_files and is_exe(d_path + os.sep + IMPORT_FINISH_FILENAME): 415 | info('link ' + d_path + os.sep + IMPORT_FINISH_FILENAME + ' to ' + RUN_PROCESS_DIR + os.sep + d + os.sep + 'finish') 416 | 417 | if not os.path.exists(RUN_PROCESS_DIR + os.sep + d): 418 | os.makedirs(RUN_PROCESS_DIR + os.sep + d) 419 | 420 | try: 421 | os.symlink(d_path + os.sep + IMPORT_FINISH_FILENAME, RUN_PROCESS_DIR + os.sep + d + os.sep + 'finish') 422 | except OSError as detail: 423 | warning('failed to link ' + d_path + os.sep + IMPORT_FINISH_FILENAME + ' to ' + RUN_PROCESS_DIR + os.sep + d + os.sep + 'finish : ' + xstr(detail)) 424 | 425 | def set_dir_env(): 426 | if state_is_service_copied_to_run_dir(): 427 | os.environ[ENVIRONMENT_SERVICE_DIR_KEY] = RUN_SERVICE_DIR 428 | else: 429 | os.environ[ENVIRONMENT_SERVICE_DIR_KEY] = IMPORT_SERVICE_DIR 430 | trace("set : " + ENVIRONMENT_SERVICE_DIR_KEY + " = " + os.environ[ENVIRONMENT_SERVICE_DIR_KEY]) 431 | 432 | os.environ[ENVIRONMENT_STATE_DIR_KEY] = RUN_STATE_DIR 433 | trace("set : " + ENVIRONMENT_STATE_DIR_KEY + " = " + os.environ[ENVIRONMENT_STATE_DIR_KEY]) 434 | 435 | def set_log_level_env(): 436 | os.environ[ENVIRONMENT_LOG_LEVEL_KEY] = xstr(log_level) 437 | trace("set : "+ENVIRONMENT_LOG_LEVEL_KEY+" = " + os.environ[ENVIRONMENT_LOG_LEVEL_KEY]) 438 | 439 | def copy_service_to_run_dir(): 440 | 441 | if os.path.exists(RUN_SERVICE_DIR): 442 | warning("Copy "+IMPORT_SERVICE_DIR+" to "+RUN_SERVICE_DIR + " ignored") 443 | warning(RUN_SERVICE_DIR + " already exists") 444 | return 445 | 446 | info("Copy "+IMPORT_SERVICE_DIR+" to "+RUN_SERVICE_DIR) 447 | 448 | try: 449 | shutil.copytree(IMPORT_SERVICE_DIR, RUN_SERVICE_DIR) 450 | except shutil.Error as e: 451 | warning(e) 452 | 453 | state_set_service_copied_to_run_dir() 454 | 455 | def state_set_service_copied_to_run_dir(): 456 | open(RUN_STATE_DIR+"/service-copied-to-run-dir", 'a').close() 457 | 458 | def state_is_service_copied_to_run_dir(): 459 | return os.path.exists(RUN_STATE_DIR+'/service-copied-to-run-dir') 460 | 461 | def state_set_first_startup_done(): 462 | open(RUN_STATE_DIR+"/first-startup-done", 'a').close() 463 | 464 | def state_is_first_start(): 465 | return os.path.exists(RUN_STATE_DIR+'/first-startup-done') == False 466 | 467 | def state_set_startup_done(): 468 | open(RUN_STATE_DIR+"/startup-done", 'a').close() 469 | 470 | def state_reset_startup_done(): 471 | try: 472 | os.remove(RUN_STATE_DIR+"/startup-done") 473 | except OSError: 474 | pass 475 | 476 | def is_multiple_process_container(): 477 | return len(listdir(RUN_PROCESS_DIR)) > 1 478 | 479 | def is_single_process_container(): 480 | return len(listdir(RUN_PROCESS_DIR)) == 1 481 | 482 | def get_container_process(): 483 | for p in listdir(RUN_PROCESS_DIR): 484 | return RUN_PROCESS_DIR + os.sep + p + os.sep + 'run' 485 | 486 | def is_runit_installed(): 487 | return os.path.exists('/usr/bin/sv') 488 | 489 | _find_unsafe = re.compile(r'[^\w@%+=:,./-]').search 490 | 491 | def shquote(s): 492 | """Return a shell-escaped version of the string *s*.""" 493 | if not s: 494 | return "''" 495 | if _find_unsafe(s) is None: 496 | return s 497 | 498 | # use single quotes, and put single quotes into double quotes 499 | # the string $'b is then quoted as '$'"'"'b' 500 | return "'" + s.replace("'", "'\"'\"'") + "'" 501 | 502 | def sanitize_shenvname(s): 503 | return re.sub(SHENV_NAME_WHITELIST_REGEX, "_", s) 504 | 505 | # Waits for the child process with the given PID, while at the same time 506 | # reaping any other child processes that have exited (e.g. adopted child 507 | # processes that have terminated). 508 | def waitpid_reap_other_children(pid): 509 | global terminated_child_processes 510 | 511 | status = terminated_child_processes.get(pid) 512 | if status: 513 | # A previous call to waitpid_reap_other_children(), 514 | # with an argument not equal to the current argument, 515 | # already waited for this process. Return the status 516 | # that was obtained back then. 517 | del terminated_child_processes[pid] 518 | return status 519 | 520 | done = False 521 | status = None 522 | while not done: 523 | try: 524 | # https://github.com/phusion/baseimage-docker/issues/151#issuecomment-92660569 525 | this_pid, status = os.waitpid(pid, os.WNOHANG) 526 | if this_pid == 0: 527 | this_pid, status = os.waitpid(-1, 0) 528 | if this_pid == pid: 529 | done = True 530 | else: 531 | # Save status for later. 532 | terminated_child_processes[this_pid] = status 533 | except OSError as e: 534 | if e.errno == errno.ECHILD or e.errno == errno.ESRCH: 535 | return None 536 | else: 537 | raise 538 | return status 539 | 540 | def stop_child_process(name, pid, signo = signal.SIGTERM, time_limit = KILL_PROCESS_TIMEOUT): 541 | info("Shutting down %s (PID %d)..." % (name, pid)) 542 | try: 543 | os.kill(pid, signo) 544 | except OSError: 545 | pass 546 | signal.alarm(time_limit) 547 | try: 548 | try: 549 | waitpid_reap_other_children(pid) 550 | except OSError: 551 | pass 552 | except AlarmException: 553 | warning("%s (PID %d) did not shut down in time. Forcing it to exit." % (name, pid)) 554 | try: 555 | os.kill(pid, signal.SIGKILL) 556 | except OSError: 557 | pass 558 | try: 559 | waitpid_reap_other_children(pid) 560 | except OSError: 561 | pass 562 | finally: 563 | signal.alarm(0) 564 | 565 | def run_command_killable(command): 566 | status = None 567 | debug_env_dump() 568 | pid = os.spawnvp(os.P_NOWAIT, command[0], command) 569 | try: 570 | status = waitpid_reap_other_children(pid) 571 | except BaseException: 572 | warning("An error occurred. Aborting.") 573 | stop_child_process(command[0], pid) 574 | raise 575 | if status != 0: 576 | if status is None: 577 | error("%s exited with unknown status\n" % command[0]) 578 | else: 579 | error("%s failed with status %d\n" % (command[0], os.WEXITSTATUS(status))) 580 | sys.exit(1) 581 | 582 | def run_command_killable_and_import_run_envvars(command): 583 | run_command_killable(command) 584 | import_run_envvars() 585 | export_run_envvars(False) 586 | 587 | def kill_all_processes(time_limit): 588 | info("Killing all processes...") 589 | try: 590 | os.kill(-1, signal.SIGTERM) 591 | except OSError: 592 | pass 593 | signal.alarm(time_limit) 594 | try: 595 | # Wait until no more child processes exist. 596 | done = False 597 | while not done: 598 | try: 599 | os.waitpid(-1, 0) 600 | except OSError as e: 601 | if e.errno == errno.ECHILD: 602 | done = True 603 | else: 604 | raise 605 | except AlarmException: 606 | warning("Not all processes have exited in time. Forcing them to exit.") 607 | try: 608 | os.kill(-1, signal.SIGKILL) 609 | except OSError: 610 | pass 611 | finally: 612 | signal.alarm(0) 613 | 614 | def container_had_startup_script(): 615 | return (len(listdir(RUN_STARTUP_DIR)) > 0 or is_exe(RUN_STARTUP_FINAL_FILE)) 616 | 617 | def run_startup_files(args): 618 | 619 | # Run /container/run/startup/* 620 | for name in listdir(RUN_STARTUP_DIR): 621 | filename = RUN_STARTUP_DIR + os.sep + name 622 | if is_exe(filename): 623 | info("Running %s..." % filename) 624 | run_command_killable_and_import_run_envvars([filename]) 625 | 626 | # Run /container/run/startup.sh. 627 | if is_exe(RUN_STARTUP_FINAL_FILE): 628 | info("Running "+RUN_STARTUP_FINAL_FILE+"...") 629 | run_command_killable_and_import_run_envvars([RUN_STARTUP_FINAL_FILE]) 630 | 631 | def wait_for_process_or_interrupt(pid): 632 | status = waitpid_reap_other_children(pid) 633 | return (True, status) 634 | 635 | def run_process(args, background_process_name, background_process_command): 636 | background_process_pid = run_background_process(background_process_name,background_process_command) 637 | background_process_exited = False 638 | exit_status = None 639 | 640 | if len(args.main_command) == 0: 641 | background_process_exited, exit_status = wait_background_process(background_process_name, background_process_pid) 642 | else: 643 | exit_status = run_foreground_process(args.main_command) 644 | 645 | return background_process_pid, background_process_exited, exit_status 646 | 647 | def run_background_process(name, command): 648 | info("Running "+ name +"...") 649 | pid = os.spawnvp(os.P_NOWAIT, command[0], command) 650 | debug("%s started as PID %d" % (name, pid)) 651 | return pid 652 | 653 | def wait_background_process(name, pid): 654 | exit_code = None 655 | exit_status = None 656 | process_exited = False 657 | 658 | process_exited, exit_code = wait_for_process_or_interrupt(pid) 659 | if process_exited: 660 | if exit_code is None: 661 | info(name + " exited with unknown status") 662 | exit_status = 1 663 | else: 664 | exit_status = os.WEXITSTATUS(exit_code) 665 | info("%s exited with status %d" % (name, exit_status)) 666 | return (process_exited, exit_status) 667 | 668 | def run_foreground_process(command): 669 | exit_code = None 670 | exit_status = None 671 | 672 | info("Running %s..." % " ".join(command)) 673 | pid = os.spawnvp(os.P_NOWAIT, command[0], command) 674 | try: 675 | exit_code = waitpid_reap_other_children(pid) 676 | if exit_code is None: 677 | info("%s exited with unknown status." % command[0]) 678 | exit_status = 1 679 | else: 680 | exit_status = os.WEXITSTATUS(exit_code) 681 | info("%s exited with status %d." % (command[0], exit_status)) 682 | except KeyboardInterrupt: 683 | stop_child_process(command[0], pid) 684 | raise 685 | except BaseException: 686 | error("An error occurred. Aborting.") 687 | stop_child_process(command[0], pid) 688 | raise 689 | 690 | return exit_status 691 | 692 | def shutdown_runit_services(): 693 | debug("Begin shutting down runit services...") 694 | os.system("/usr/bin/sv -w %d force-stop %s/* > /dev/null" % (KILL_PROCESS_TIMEOUT, RUN_PROCESS_DIR)) 695 | 696 | def wait_for_runit_services(): 697 | debug("Waiting for runit services to exit...") 698 | done = False 699 | while not done: 700 | done = os.system("/usr/bin/sv status "+RUN_PROCESS_DIR+"/* | grep -q '^run:'") != 0 701 | if not done: 702 | time.sleep(0.1) 703 | shutdown_runit_services() 704 | 705 | def run_multiple_process_container(args): 706 | if not is_runit_installed(): 707 | error("Error: runit is not installed and this is a multiple process container.") 708 | return 709 | 710 | background_process_exited=False 711 | background_process_pid=None 712 | 713 | try: 714 | runit_command=["/usr/bin/runsvdir", "-P", RUN_PROCESS_DIR] 715 | background_process_pid, background_process_exited, exit_status = run_process(args, "runit daemon", runit_command) 716 | 717 | sys.exit(exit_status) 718 | finally: 719 | shutdown_runit_services() 720 | if not background_process_exited: 721 | stop_child_process("runit daemon", background_process_pid) 722 | wait_for_runit_services() 723 | 724 | def run_single_process_container(args): 725 | background_process_exited=False 726 | background_process_pid=None 727 | 728 | try: 729 | container_process=get_container_process() 730 | background_process_pid, background_process_exited, exit_status = run_process(args, container_process, [container_process]) 731 | 732 | sys.exit(exit_status) 733 | finally: 734 | if not background_process_exited: 735 | stop_child_process(container_process, background_process_pid) 736 | 737 | def run_no_process_container(args): 738 | if len(args.main_command) == 0: 739 | args.main_command=['bash'] # run bash by default 740 | 741 | exit_status = run_foreground_process(args.main_command) 742 | sys.exit(exit_status) 743 | 744 | def run_finish_files(): 745 | 746 | # iterate process dir to find finish files 747 | for name in listdir(RUN_PROCESS_DIR): 748 | filename = RUN_PROCESS_DIR + os.sep + name + os.sep + "finish" 749 | if is_exe(filename): 750 | info("Running %s..." % filename) 751 | run_command_killable_and_import_run_envvars([filename]) 752 | 753 | def wait_states(states): 754 | for state in states: 755 | filename = RUN_STATE_DIR + os.sep + state 756 | info("Wait state: " + state) 757 | 758 | while not os.path.exists(filename): 759 | time.sleep(0.1) 760 | debug("Check file " + filename) 761 | pass 762 | debug("Check file " + filename + " [Ok]") 763 | 764 | def run_cmds(args, when): 765 | debug("Run commands before " + when + "...") 766 | if len(args.cmds) > 0: 767 | 768 | for cmd in args.cmds: 769 | if (len(cmd) > 1 and cmd[1] == when) or (len(cmd) == 1 and when == "startup"): 770 | info("Running '"+cmd[0]+"'...") 771 | run_command_killable_and_import_run_envvars(cmd[0].split()) 772 | 773 | def main(args): 774 | 775 | info(ENVIRONMENT_LOG_LEVEL_KEY + " = " + xstr(log_level) + " (" + log_level_switcher_inv.get(log_level) + ")") 776 | state_reset_startup_done() 777 | 778 | if args.set_env_hostname_to_etc_hosts: 779 | set_env_hostname_to_etc_hosts() 780 | 781 | wait_states(args.wait_states) 782 | setup_run_directories(args) 783 | 784 | if not args.skip_env_files: 785 | set_startup_scripts_env() 786 | 787 | run_cmds(args,"startup") 788 | 789 | if not args.skip_startup_files and container_had_startup_script(): 790 | run_startup_files(args) 791 | 792 | state_set_startup_done() 793 | state_set_first_startup_done() 794 | 795 | if not args.skip_env_files: 796 | set_process_env(args.keep_startup_env) 797 | 798 | run_cmds(args,"process") 799 | 800 | debug_env_dump() 801 | 802 | if is_single_process_container() and not args.skip_process_files: 803 | run_single_process_container(args) 804 | 805 | elif is_multiple_process_container() and not args.skip_process_files: 806 | run_multiple_process_container(args) 807 | 808 | else: 809 | run_no_process_container(args) 810 | 811 | # Parse options. 812 | parser = argparse.ArgumentParser(description = 'Initialize the system.', epilog='Osixia! Light Baseimage: https://github.com/osixia/docker-light-baseimage') 813 | parser.add_argument('main_command', metavar = 'MAIN_COMMAND', type = str, nargs = '*', 814 | help = 'The main command to run, leave empty to only run container process.') 815 | parser.add_argument('-e', '--skip-env-files', dest = 'skip_env_files', 816 | action = 'store_const', const = True, default = False, 817 | help = 'Skip getting environment values from environment file(s).') 818 | parser.add_argument('-s', '--skip-startup-files', dest = 'skip_startup_files', 819 | action = 'store_const', const = True, default = False, 820 | help = 'Skip running '+RUN_STARTUP_DIR+'/* and '+RUN_STARTUP_FINAL_FILE + ' file(s).') 821 | parser.add_argument('-p', '--skip-process-files', dest = 'skip_process_files', 822 | action = 'store_const', const = True, default = False, 823 | help = 'Skip running container process file(s).') 824 | parser.add_argument('-f', '--skip-finish-files', dest = 'skip_finish_files', 825 | action = 'store_const', const = True, default = False, 826 | help = 'Skip running container finish file(s).') 827 | parser.add_argument('-o', '--run-only', type=str, choices=["startup","process","finish"], dest = 'run_only', default = None, 828 | help = 'Run only this file type and ignore others.') 829 | parser.add_argument('-c', '--cmd', metavar=('COMMAND', 'WHEN={startup,process,finish}'), dest = 'cmds', type = str, 830 | action = 'append', default = [], nargs = "+", 831 | help = 'Run COMMAND before WHEN file(s). Default before startup file(s).') 832 | parser.add_argument('-k', '--no-kill-all-on-exit', dest = 'kill_all_on_exit', 833 | action = 'store_const', const = False, default = True, 834 | help = 'Don\'t kill all processes on the system upon exiting.') 835 | parser.add_argument('--wait-state', metavar = 'FILENAME', dest = 'wait_states', type = str, 836 | action = 'append', default=[], 837 | help = 'Wait until the container FILENAME file exists in '+RUN_STATE_DIR+' directory before starting. Usefull when 2 containers share '+RUN_DIR+' directory via volume.') 838 | parser.add_argument('--wait-first-startup', dest = 'wait_first_startup', 839 | action = 'store_const', const = True, default = False, 840 | help = 'Wait until the first startup is done before starting. Usefull when 2 containers share '+RUN_DIR+' directory via volume.') 841 | parser.add_argument('--keep-startup-env', dest = 'keep_startup_env', 842 | action = 'store_const', const = True, default = False, 843 | help = 'Don\'t remove ' + xstr(ENV_FILES_STARTUP_EXTENSIONS) + ' environment files after startup scripts.') 844 | parser.add_argument('--copy-service', dest = 'copy_service', 845 | action = 'store_const', const = True, default = False, 846 | help = 'Copy '+IMPORT_SERVICE_DIR+' to '+RUN_SERVICE_DIR+'. Help to fix docker mounted files problems.') 847 | parser.add_argument('--dont-touch-etc-hosts', dest = 'set_env_hostname_to_etc_hosts', 848 | action = 'store_const', const = False, default = True, 849 | help = 'Don\'t add in /etc/hosts a line with the container ip and $HOSTNAME environment variable value.') 850 | parser.add_argument('--keepalive', dest = 'keepalive', 851 | action = 'store_const', const = True, default = False, 852 | help = 'Keep alive container if all startup files and process exited without error.') 853 | parser.add_argument('--keepalive-force', dest = 'keepalive_force', 854 | action = 'store_const', const = True, default = False, 855 | help = 'Keep alive container in all circonstancies.') 856 | parser.add_argument('-l', '--loglevel', type=str, choices=["none","error","warning","info","debug","trace"], dest = 'log_level', default = "info", 857 | help = 'Log level (default: info)') 858 | 859 | args = parser.parse_args() 860 | 861 | log_level_switcher = {"none": LOG_LEVEL_NONE,"error": LOG_LEVEL_ERROR,"warning": LOG_LEVEL_WARNING,"info": LOG_LEVEL_INFO,"debug": LOG_LEVEL_DEBUG, "trace": LOG_LEVEL_TRACE} 862 | log_level_switcher_inv = {LOG_LEVEL_NONE: "none",LOG_LEVEL_ERROR:"error",LOG_LEVEL_WARNING:"warning",LOG_LEVEL_INFO:"info",LOG_LEVEL_DEBUG:"debug",LOG_LEVEL_TRACE:"trace"} 863 | log_level = log_level_switcher.get(args.log_level) 864 | 865 | # Run only arg 866 | if args.run_only != None: 867 | if args.run_only == "startup" and args.skip_startup_files: 868 | error("Error: When '--run-only startup' is set '--skip-startup-files' can't be set.") 869 | sys.exit(1) 870 | elif args.run_only == "process" and args.skip_startup_files: 871 | error("Error: When '--run-only process' is set '--skip-process-files' can't be set.") 872 | sys.exit(1) 873 | elif args.run_only == "finish" and args.skip_startup_files: 874 | error("Error: When '--run-only finish' is set '--skip-finish-files' can't be set.") 875 | sys.exit(1) 876 | 877 | if args.run_only == "startup": 878 | args.skip_process_files = True 879 | args.skip_finish_files = True 880 | elif args.run_only == "process": 881 | args.skip_startup_files = True 882 | args.skip_finish_files = True 883 | elif args.run_only == "finish": 884 | args.skip_startup_files = True 885 | args.skip_process_files = True 886 | 887 | # wait for startup args 888 | if args.wait_first_startup: 889 | args.wait_states.insert(0, 'first-startup-done') 890 | 891 | # Run main function. 892 | signal.signal(signal.SIGTERM, lambda signum, frame: ignore_signals_and_raise_keyboard_interrupt('SIGTERM')) 893 | signal.signal(signal.SIGINT, lambda signum, frame: ignore_signals_and_raise_keyboard_interrupt('SIGINT')) 894 | signal.signal(signal.SIGALRM, lambda signum, frame: raise_alarm_exception()) 895 | 896 | exit_code = 0 897 | 898 | try: 899 | main(args) 900 | 901 | except SystemExit as err: 902 | exit_code = err.code 903 | if args.keepalive and err.code == 0: 904 | try: 905 | info("All process have exited without error, keep container alive...") 906 | while True: 907 | time.sleep(60) 908 | pass 909 | except: 910 | error("Keep alive process ended.") 911 | 912 | except KeyboardInterrupt: 913 | warning("Init system aborted.") 914 | exit(2) 915 | 916 | finally: 917 | 918 | run_cmds(args,"finish") 919 | 920 | # for multiple process images finish script are run by runit 921 | if not args.skip_finish_files and not is_multiple_process_container(): 922 | run_finish_files() 923 | 924 | if args.keepalive_force: 925 | try: 926 | info("All process have exited, keep container alive...") 927 | while True: 928 | time.sleep(60) 929 | pass 930 | except: 931 | error("Keep alive process ended.") 932 | 933 | if args.kill_all_on_exit: 934 | kill_all_processes(KILL_ALL_PROCESSES_TIMEOUT) 935 | 936 | exit(exit_code) 937 | -------------------------------------------------------------------------------- /image/tool/setuser: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | ''' 4 | Copyright (c) 2013-2015 Phusion Holding B.V. 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | ''' 24 | 25 | import sys 26 | import os 27 | import pwd 28 | 29 | 30 | def abort(message): 31 | sys.stderr.write("setuser: %s\n" % message) 32 | sys.exit(1) 33 | 34 | 35 | def main(): 36 | ''' 37 | A simple alternative to sudo that executes a command as a user by setting 38 | the user ID and user parameters to those described by the system and then 39 | using execvp(3) to execute the command without the necessity of a TTY 40 | ''' 41 | 42 | username = sys.argv[1] 43 | try: 44 | user = pwd.getpwnam(username) 45 | except KeyError: 46 | abort("user %s not found" % username) 47 | os.initgroups(username, user.pw_gid) 48 | os.setgid(user.pw_gid) 49 | os.setuid(user.pw_uid) 50 | os.environ['USER'] = username 51 | os.environ['HOME'] = user.pw_dir 52 | os.environ['UID'] = str(user.pw_uid) 53 | try: 54 | os.execvp(sys.argv[2], sys.argv[2:]) 55 | except OSError as e: 56 | abort("cannot execute %s: %s" % (sys.argv[2], str(e))) 57 | 58 | if __name__ == '__main__': 59 | 60 | if len(sys.argv) < 3: 61 | sys.stderr.write("Usage: /sbin/setuser USERNAME COMMAND [args..]\n") 62 | sys.exit(1) 63 | 64 | main() 65 | -------------------------------------------------------------------------------- /image/tool/wait-process: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | # wait startup to finish 4 | log-helper debug "Waits until startup is complete..." 5 | while ! test -f /container/run/state/startup-done 6 | do 7 | sleep 0.5 8 | done 9 | 10 | for process in "$@" 11 | do 12 | # wait service 13 | log-helper debug "Waits for process ${process} to be started..." 14 | while ! pgrep -c "${process}" > /dev/null 15 | do 16 | sleep 0.5 17 | done 18 | done 19 | -------------------------------------------------------------------------------- /test/test.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | load test_helper 3 | 4 | @test "image build" { 5 | 6 | run build_image 7 | [ "$status" -eq 0 ] 8 | 9 | } 10 | -------------------------------------------------------------------------------- /test/test_helper.bash: -------------------------------------------------------------------------------- 1 | setup() { 2 | IMAGE_NAME="$NAME:$VERSION" 3 | } 4 | 5 | # function relative to the current container / image 6 | build_image() { 7 | #disable outputs 8 | docker build -t $IMAGE_NAME $BATS_TEST_DIRNAME/../image &> /dev/null 9 | } 10 | 11 | run_image() { 12 | CONTAINER_ID=$(docker run $@ -d $IMAGE_NAME) 13 | CONTAINER_IP=$(get_container_ip_by_cid $CONTAINER_ID) 14 | } 15 | 16 | start_container() { 17 | start_containers_by_cid $CONTAINER_ID 18 | } 19 | 20 | stop_container() { 21 | stop_containers_by_cid $CONTAINER_ID 22 | } 23 | 24 | remove_container() { 25 | remove_containers_by_cid $CONTAINER_ID 26 | } 27 | 28 | clear_container() { 29 | stop_containers_by_cid $CONTAINER_ID 30 | remove_containers_by_cid $CONTAINER_ID 31 | } 32 | 33 | wait_process() { 34 | wait_process_by_cid $CONTAINER_ID $@ 35 | } 36 | 37 | # generic functions 38 | get_container_ip_by_cid() { 39 | local IP=$(docker inspect -f "{{ .NetworkSettings.IPAddress }}" $1) 40 | echo "$IP" 41 | } 42 | 43 | start_containers_by_cid() { 44 | for cid in "$@" 45 | do 46 | #disable outputs 47 | docker start $cid &> /dev/null 48 | done 49 | } 50 | 51 | stop_containers_by_cid() { 52 | for cid in "$@" 53 | do 54 | #disable outputs 55 | docker stop $cid &> /dev/null 56 | done 57 | } 58 | 59 | remove_containers_by_cid() { 60 | for cid in "$@" 61 | do 62 | #disable outputs 63 | docker rm $cid &> /dev/null 64 | done 65 | } 66 | 67 | clear_containers_by_cid() { 68 | stop_containers_by_cid $@ 69 | remove_containers_by_cid $@ 70 | } 71 | 72 | wait_process_by_cid() { 73 | cid=$1 74 | docker exec $cid /container/tool/wait-process ${@:2} 75 | } 76 | --------------------------------------------------------------------------------