├── .github
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── config.yml
├── dependabot.yml
└── workflows
│ └── main.yml
├── CHANGELOG.md
├── Dockerfile
├── LICENSE
├── README.md
├── examples
└── docker-compose.yml
└── install
├── etc
├── cont-init.d
│ └── 10-cloudflare-companion
└── services.available
│ └── 10-cloudflare-companion
│ └── run
└── usr
└── sbin
└── cloudflare-companion
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [tiredofit]
2 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: If something isn't working right..
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | ### Summary
11 |
12 |
13 |
14 |
15 | ### Steps to reproduce
16 |
17 |
18 |
19 |
20 | ### What is the expected *correct* behavior?
21 |
22 |
23 |
24 |
25 | ### Relevant logs and/or screenshots
26 |
27 |
28 |
29 | ### Environment
30 |
31 |
32 | - Image version / tag:
33 | - Host OS:
34 |
35 |
36 | Any logs | docker-compose.yml
37 |
38 |
39 |
40 |
41 | ### Possible fixes
42 |
43 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea or feature
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | ---
11 | name: Feature Request
12 | about: Suggest an idea for this project
13 |
14 | ---
15 |
16 | **Description of the feature**
17 |
18 |
19 | **Benftits of feature**
20 |
21 |
22 | **Additional context**
23 |
24 |
--------------------------------------------------------------------------------
/.github/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | # Maintain dependencies for GitHub Actions
4 | - package-ecosystem: "github-actions"
5 | directory: "/"
6 | schedule:
7 | interval: "daily"
8 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | ### Application Level Image CI
2 | ### Dave Conroy
3 |
4 | name: 'build'
5 |
6 | on:
7 | push:
8 | paths:
9 | - '**'
10 | - '!README.md'
11 | jobs:
12 | docker:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - name: Checkout
16 | uses: actions/checkout@v3
17 |
18 | - name: Prepare
19 | id: prep
20 | run: |
21 | DOCKER_IMAGE=${GITHUB_REPOSITORY/docker-/}
22 | set -x
23 | if [[ $GITHUB_REF == refs/heads/* ]]; then
24 | if [[ $GITHUB_REF == refs/heads/*/* ]] ; then
25 | BRANCH="${DOCKER_IMAGE}:$(echo $GITHUB_REF | sed "s|refs/heads/||g" | sed "s|/|-|g")"
26 | else
27 | BRANCH=${GITHUB_REF#refs/heads/}
28 | fi
29 |
30 | case ${BRANCH} in
31 | "main" | "master" )
32 | BRANCHTAG="${DOCKER_IMAGE}:latest"
33 | ;;
34 | "develop" )
35 | BRANCHTAG="${DOCKER_IMAGE}:develop"
36 | ;;
37 | * )
38 | if [ -n "${{ secrets.LATEST }}" ] ; then
39 | if [ "${BRANCHTAG}" = "${{ secrets.LATEST }}" ]; then
40 | BRANCHTAG="${DOCKER_IMAGE}:${BRANCH},${DOCKER_IMAGE}:${BRANCH}-latest,${DOCKER_IMAGE}:latest"
41 | else
42 | BRANCHTAG="${DOCKER_IMAGE}:${BRANCH},${DOCKER_IMAGE}:${BRANCH}-latest"
43 | fi
44 | else
45 | BRANCHTAG="${DOCKER_IMAGE}:${BRANCH},${DOCKER_IMAGE}:${BRANCH}-latest"
46 | fi
47 | ;;
48 | esac
49 | fi
50 |
51 |
52 | if [[ $GITHUB_REF == refs/tags/* ]]; then
53 | GITTAG="${DOCKER_IMAGE}:$(echo $GITHUB_REF | sed 's|refs/tags/||g')"
54 | fi
55 |
56 | if [ -n "${BRANCHTAG}" ] && [ -n "${GITTAG}" ]; then
57 | TAGS=${BRANCHTAG},${GITTAG}
58 | else
59 | TAGS="${BRANCHTAG}${GITTAG}"
60 | fi
61 |
62 | echo ::set-output name=tags::${TAGS}
63 | echo ::set-output name=docker_image::${DOCKER_IMAGE}
64 |
65 | - name: Set up QEMU
66 | uses: docker/setup-qemu-action@v2
67 | with:
68 | platforms: all
69 |
70 | - name: Set up Docker Buildx
71 | id: buildx
72 | uses: docker/setup-buildx-action@v2
73 |
74 | - name: Login to DockerHub
75 | if: github.event_name != 'pull_request'
76 | uses: docker/login-action@v2
77 | with:
78 | username: ${{ secrets.DOCKER_USERNAME }}
79 | password: ${{ secrets.DOCKER_PASSWORD }}
80 |
81 | - name: Label
82 | id: Label
83 | run: |
84 | if [ -f "Dockerfile" ] ; then
85 | sed -i "/FROM .*/a LABEL tiredofit.image.git_repository=\"https://github.com/${GITHUB_REPOSITORY}\"" Dockerfile
86 | sed -i "/FROM .*/a LABEL tiredofit.image.git_commit=\"${GITHUB_SHA}\"" Dockerfile
87 | sed -i "/FROM .*/a LABEL tiredofit.image.git_committed_by=\"${GITHUB_ACTOR}\"" Dockerfile
88 | sed -i "/FROM .*/a LABEL tiredofit.image.image_build_date=\"$(date +'%Y-%m-%d %H:%M:%S')\"" Dockerfile
89 | if [ -f "CHANGELOG.md" ] ; then
90 | sed -i "/FROM .*/a LABEL tiredofit.image.git_changelog_version=\"$(head -n1 ./CHANGELOG.md | awk '{print $2}')\"" Dockerfile
91 | mkdir -p install/assets/.changelogs ; cp CHANGELOG.md install/assets/.changelogs/${GITHUB_REPOSITORY/\//_}.md
92 | fi
93 |
94 | if [[ $GITHUB_REF == refs/tags/* ]]; then
95 | sed -i "/FROM .*/a LABEL tiredofit.image.git_tag=\"${GITHUB_REF#refs/tags/v}\"" Dockerfile
96 | fi
97 |
98 | if [[ $GITHUB_REF == refs/heads/* ]]; then
99 | sed -i "/FROM .*/a LABEL tiredofit.image.git_branch=\"${GITHUB_REF#refs/heads/}\"" Dockerfile
100 | fi
101 | fi
102 |
103 | - name: Build
104 | uses: docker/build-push-action@v3
105 | with:
106 | builder: ${{ steps.buildx.outputs.name }}
107 | context: .
108 | file: ./Dockerfile
109 | platforms: linux/amd64,linux/arm/v7,linux/arm64
110 | push: true
111 | tags: ${{ steps.prep.outputs.tags }}
112 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 4.1.3 2022-05-24
2 |
3 | ### Added
4 | - Alpine 3.16 base
5 |
6 |
7 | ## 4.1.2 2021-11-24
8 |
9 | ### Added
10 | - Alpine 3.15 base
11 |
12 |
13 | ## 4.1.1 2021-07-25
14 |
15 | ### Added
16 | - Alpine 3.14
17 |
18 |
19 | ## 4.1.0 2021-05-03
20 |
21 | ### Added
22 | - Add Rust build dependency
23 |
24 |
25 | ## 4.0.1 2021-01-14
26 |
27 | ### Changed
28 | - Alpine 3.13 Base
29 |
30 |
31 | ## 4.0.0 2020-08-29
32 |
33 | ### Added
34 | - Docker Secrets Support
35 | - Docker Swarm Support
36 | - Refresh Entries if they already exist in Cloudflare
37 | - Scoped API Token support
38 | - Support more than 20 Domains
39 | - Lookup DNS Zone if not provided
40 | - Debug Mode - Set CONTAINER_LOG_LEVEL=DEBUG for verbosity
41 | - Pure Python 3 Implementation (initial credit: bugficks@github)
42 |
43 |
44 | ## 3.1.2 2020-07-09
45 |
46 | ### Added
47 | - Alpine 3.12 base
48 |
49 |
50 | ## 3.1.1 2020-07-09
51 |
52 | ### Changed
53 | - Bugfix for variables enforcement
54 | - Removed requirement to have CF_EMAIL as it can be blank in specific cases
55 |
56 |
57 | ## 3.1.0 2020-06-09
58 |
59 | ### Added
60 | - Alpine 3.11
61 | - Update to support tiredofit/alpine 5.0.0 base image
62 |
63 |
64 | ## 3.0 2019-05-01 Dave Conroy
65 |
66 | * Added DEFAULT_TTL env Var
67 | * Added DOCKER_ENTRYPOINT env Var
68 | * Cleaned up Output
69 | * Alpine 3.9
70 |
71 | ## 2.1 2017-12-01 Dave Conroy
72 |
73 | * Base bump to Alpine 3.7
74 |
75 | ## 2.0 2017-09-15 Dave Conroy
76 |
77 | * Updated to support Multiple Zones by means of adding additional DOMAIN Environment Variables
78 |
79 | ## 1.1 2017-09-13 Dave Conroy
80 |
81 | * Minor Permissions and Script Fix
82 |
83 | ## 1.0 2017-09-03 Dave Conroy
84 |
85 | * Initial Release
86 | * Python 2
87 | * Alpine 3.6
88 |
89 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM docker.io/tiredofit/alpine:3.16
2 | LABEL maintainer="Dave Conroy (github.com/tiredofit)"
3 |
4 | ### Set Environment Variables
5 | ENV ENABLE_CRON=FALSE \
6 | CONTAINER_ENABLE_MESSAGING=FALSE \
7 | IMAGE_NAME="tiredofit/nginx-proxy-cloudflare-companion" \
8 | IMAGE_REPO_URL="https://github.com/tiredofit/docker-nginx-proxy-cloudflare-companion/"
9 |
10 | ### Dependencies
11 | RUN set -x && \
12 | apk add -t .npcc-build-deps \
13 | cargo \
14 | gcc \
15 | libffi-dev \
16 | musl-dev \
17 | openssl-dev \
18 | py-pip \
19 | py3-setuptools \
20 | py3-wheel \
21 | python3-dev \
22 | && \
23 | \
24 | apk add -t .npcc-run-deps \
25 | py3-beautifulsoup4 \
26 | py3-certifi \
27 | py3-chardet \
28 | py3-idna \
29 | py3-openssl \
30 | py3-requests \
31 | py3-soupsieve \
32 | py3-urllib3 \
33 | py3-websocket-client \
34 | py3-yaml \
35 | python3 \
36 | && \
37 | \
38 | pip install \
39 | cloudflare==2.19.* \
40 | get-docker-secret \
41 | docker[tls] \
42 | && \
43 | \
44 | ### Cleanup
45 | apk del .npcc-build-deps && \
46 | rm -rf /root/.cache /root/.cargo && \
47 | rm -rf /var/cache/apk/*
48 |
49 | ### Add Files
50 | ADD install /
51 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2021 Dave Conroy
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # github.com/tiredofit/docker-nginx-proxy-cloudflare-companion
2 |
3 | [](https://github.com/tiredofit/docker-nginx-proxy-cloudflare-companion/releases/latest)
4 | [](https://github.com/tiredofit/docker-nginx-proxy-cloudflare-companion/actions?query=workflow%3Abuild)
5 | [](https://hub.docker.com/r/tiredofit/nginx-proxy-cloudflare-companion/)
6 | [](https://hub.docker.com/r/tiredofit/nginx-proxy-cloudflare-companion/)
7 | [](https://github.com/sponsors/tiredofit)
8 | [](https://www.paypal.me/tiredofit)
9 |
10 | * * *
11 | ## About
12 |
13 | This builds a Docker image to automatically update Cloudflare DNS records upon container start. A time saver if you are regularly moving containers around to different systems. This will allow you to set multiple zone's you wish to update.
14 |
15 | ## Maintainer
16 |
17 | - [Dave Conroy](http://github/tiredofit/)
18 |
19 | ## Table of Contents
20 |
21 | - [Introduction](#introduction)
22 | - [Authors](#authors)
23 | - [Table of Contents](#table-of-contents)
24 | - [Prerequisites](#prerequisites)
25 | - [Installation](#installation)
26 | - [Quick Start](#quick-start)
27 | - [Configuration](#configuration)
28 | - [Volumes](#volumes)
29 | - [Environment Variables](#environment-variables)
30 | - [Docker Secrets](#docker-secrets)
31 | - [Maintenance](#maintenance)
32 | - [Shell Access](#shell-access)
33 | - [References](#references)
34 |
35 | ## Prerequisites and Assumptions
36 | * Assumes you are using Nginx as a reverse proxy:
37 | * [Nginx-Proxy](https://github.com/jwilder/nginx-proxy)
38 |
39 | ## Installation
40 | ### Build from Source
41 | Clone this repository and build the image with `docker build -t (imagename) .`
42 |
43 | ### Prebuilt Images
44 | Builds of the image are available on [Docker Hub](https://hub.docker.com/r/tiredofit/traefik-cloudflare-companion) and is the recommended method of installation.
45 |
46 | ```bash
47 | docker pull tiredofit/traefik-cloudflare-companion:(imagetag)
48 | ```
49 | The following image tags are available along with their tagged release based on what's written in the [Changelog](CHANGELOG.md):
50 |
51 | | Container OS | Tag |
52 | | ------------ | --------- |
53 | | Alpine | `:latest` |
54 |
55 | #### Multi Architecture
56 | Images are built primarily for `amd64` architecture, and may also include builds for `arm/v6`, `arm/v7`, `arm64` and others. These variants are all unsupported. Consider [sponsoring](https://github.com/sponsors/tiredofit) my work so that I can work with various hardware. To see if this image supports multiple architecures, type `docker manifest (image):(tag)`
57 |
58 | ## Configuration
59 |
60 | ### Quick Start
61 |
62 | * The quickest way to get started is using [docker-compose](https://docs.docker.com/compose/). See the examples folder for a working [docker-compose.yml](examples/docker-compose.yml) that can be modified for development or production use.
63 |
64 | * Set various [environment variables](#environment-variables) to understand the capabilities of this image.
65 |
66 | Upon startup the image looks for a label containing `traefik.frontend.rule` (version 1) or `Host*` (version2) from your running containers of either updates Cloudflare with a CNAME record of your `TARGET_DOMAIN`. Previous versions of this container used to only update one Zone, however with the additional of the `DOMAIN` environment variables it now parses the containers variables and updates the appropriate zone.
67 |
68 | For those wishing to assign multiple CNAMEs to a container use the following format:
69 | ### Volumes
70 | | File | Description |
71 | | ---------------------- | ------------------------------------------------------------------------ |
72 | | `/var/run/docker.sock` | You must have access to the docker socket in order to utilize this image |
73 |
74 | ### Environment Variables
75 |
76 | #### Base Images used
77 |
78 | This image relies on an [Alpine Linux](https://hub.docker.com/r/tiredofit/alpine) or [Debian Linux](https://hub.docker.com/r/tiredofit/debian) base image that relies on an [init system](https://github.com/just-containers/s6-overlay) for added capabilities. Outgoing SMTP capabilities are handlded via `msmtp`. Individual container performance monitoring is performed by [zabbix-agent](https://zabbix.org). Additional tools include: `bash`,`curl`,`less`,`logrotate`, `nano`,`vim`.
79 |
80 | Be sure to view the following repositories to understand all the customizable options:
81 |
82 | | Image | Description |
83 | | ------------------------------------------------------ | -------------------------------------- |
84 | | [OS Base](https://github.com/tiredofit/docker-alpine/) | Customized Image based on Alpine Linux |
85 |
86 |
87 | | Parameter | Description | Default |
88 | | ------------------- | --------------------------------------------------------------------------------------- | ---------------------------- |
89 | | `DOCKER_ENTRYPOINT` | Docker Entrypoint default (local mode) | `unix://var/run/docker.sock` |
90 | | `DOCKER_HOST` | (optional) If using tcp connection e.g. `tcp://111.222.111.32:2376` | |
91 | | `DOCKER_CERT_PATH` | (optional) If using tcp connection with TLS - Certificate location e.g. `/docker-certs` | |
92 | | `DOCKER_TLS_VERIFY` | (optional) If using tcp conneciton to socket Verify TLS | `1` |
93 | | `REFRESH_ENTRIES` | If record exists, update entry with new values `TRUE` or `FALSE` | `TRUE` |
94 | | `SWARM_MODE` | Enable Docker Swarm Mode `TRUE` or `FALSE` | `FALSE` |
95 | | `CF_EMAIL` | Email address tied to Cloudflare Account - Leave Blank for Scoped API | |
96 | | `CF_TOKEN` | API Token for the Domain | |
97 | | `DEFAULT_TTL` | TTL to apply to records | `1` |
98 | | `TARGET_DOMAIN` | Destination Host to forward records to e.g. `host.example.com` | |
99 | | `DOMAIN1` | Domain 1 you wish to update records for. | |
100 | | `DOMAIN1_ZONE_ID` | Domain 1 Zone ID from Cloudflare | |
101 | | `DOMAIN1_PROXIED` | Domain 1 True or False if proxied | |
102 | | `DOMAIN2` | (optional Domain 2 you wish to update records for.) | |
103 | | `DOMAIN2_ZONE_ID` | Domain 2 Zone ID from Cloudflare | |
104 | | `DOMAIN2_PROXIED` | Domain 1 True or False if proxied | |
105 | | `DOMAIN3....` | And so on.. | |
106 |
107 | ### Docker Secrets
108 |
109 | `CF_EMAIL` and `CF_TOKEN` support Docker Secrets
110 | Name your secrets either CF_EMAIL and CF_TOKEN or cf_email and cf_token.
111 |
112 |
113 | * * *
114 | ## Maintenance
115 | ### Shell Access
116 |
117 | For debugging and maintenance purposes you may want access the containers shell.
118 |
119 | ```bash
120 | docker exec -it (whatever your container name is e.g. traefik-cloudflare-companion) bash
121 | ```
122 |
123 | ## Support
124 |
125 | These images were built to serve a specific need in a production environment and gradually have had more functionality added based on requests from the community.
126 | ### Usage
127 | - The [Discussions board](../../discussions) is a great place for working with the community on tips and tricks of using this image.
128 | - Consider [sponsoring me](https://github.com/sponsors/tiredofit) personalized support.
129 | ### Bugfixes
130 | - Please, submit a [Bug Report](issues/new) if something isn't working as expected. I'll do my best to issue a fix in short order.
131 |
132 | ### Feature Requests
133 | - Feel free to submit a feature request, however there is no guarantee that it will be added, or at what timeline.
134 | - Consider [sponsoring me](https://github.com/sponsors/tiredofit) regarding development of features.
135 |
136 | ### Updates
137 | - Best effort to track upstream changes, More priority if I am actively using the image in a production environment.
138 | - Consider [sponsoring me](https://github.com/sponsors/tiredofit) for up to date releases.
139 |
140 | ## License
141 | MIT. See [LICENSE](LICENSE) for more details.
142 |
143 | ## References
144 |
145 | * https://www.cloudflare.com
146 | * https://github.com/tiredofit/docker-traefik-cloudflare-companion
147 | * https://github.com/code5-lab/dns-flare
148 |
--------------------------------------------------------------------------------
/examples/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: '2'
2 |
3 | services:
4 | nginx-proxy:
5 | image: jwilder/nginx-proxy
6 | container_name: nginx-proxy
7 | ports:
8 | - 80:80
9 | - 443:443
10 | volumes:
11 | - ./conf/vhost.d:/etc/nginx/vhost.d
12 | - ./conf/html:/usr/share/nginx/html
13 | - ./conf/certs:/etc/nginx/certs:ro
14 | - ./logs:/var/log/nginx
15 | - /var/run/docker.sock:/tmp/docker.sock:ro
16 | networks:
17 | - proxy-tier
18 | labels:
19 | - "com.github.jrcs.letsencrypt_nginx_proxy_companion.nginx_proxy=true"
20 | restart: always
21 |
22 | letsencrypt-companion:
23 | image: jrcs/letsencrypt-nginx-proxy-companion
24 | container_name: letsencrypt-companion
25 | volumes_from:
26 | - nginx-proxy
27 | volumes:
28 | - /var/run/docker.sock:/var/run/docker.sock:ro
29 | - ./conf/certs:/etc/nginx/certs:rw
30 | restart: always
31 |
32 | cloudflare-companion:
33 | image: tiredofit/nginx-proxy-cloudflare-companion
34 | container_name: cloudflare-companion
35 | volumes_from:
36 | - nginx-proxy
37 | volumes:
38 | - /var/run/docker.sock:/var/run/docker.sock:ro
39 | environment:
40 | - CF_EMAIL=noemail@example.org
41 | - CF_TOKEN=1234
42 | - TARGET_DOMAIN=host.example.org
43 | - DOMAIN1=example.org
44 | - DOMAIN1_ZONE_ID=1234567890
45 | restart: always
46 |
47 | networks:
48 | proxy-tier:
49 | external:
50 | name: nginx-proxy
51 |
--------------------------------------------------------------------------------
/install/etc/cont-init.d/10-cloudflare-companion:
--------------------------------------------------------------------------------
1 | #!/command/with-contenv bash
2 |
3 | source /assets/functions/00-container
4 | prepare_service single
5 | PROCESS_NAME="nginx-proxy-cloudflare-companion"
6 |
7 | ### Sanity Test
8 | sanity_var TARGET_DOMAIN "Target Domain"
9 | sanity_var DOMAIN1 "Domain 1"
10 | sanity_var DOMAIN1_ZONE_ID "Domain 1 Zone ID"
11 |
12 | liftoff
13 |
--------------------------------------------------------------------------------
/install/etc/services.available/10-cloudflare-companion/run:
--------------------------------------------------------------------------------
1 | #!/command/with-contenv bash
2 |
3 | source /assets/functions/00-container
4 | PROCESS_NAME="nginx-proxy-cloudflare-companion"
5 |
6 | check_container_initialized
7 | check_service_initialized init
8 | liftoff
9 |
10 | print_start "Starting Nginx Proxy Cloudflare Companion"
11 | exec python3 -u /usr/sbin/cloudflare-companion
12 |
--------------------------------------------------------------------------------
/install/usr/sbin/cloudflare-companion:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | from __future__ import print_function
4 | from datetime import datetime
5 | from get_docker_secret import get_docker_secret
6 | import CloudFlare
7 | import docker
8 | import json
9 | import os
10 | import re
11 |
12 | DEFAULT_TTL = os.environ.get('DEFAULT_TTL', 1)
13 | SWARM_MODE = os.environ.get('SWARM_MODE', "FALSE")
14 | REFRESH_ENTRIES = os.environ.get('REFRESH_ENTRIES', "FALSE" )
15 | CONTAINER_LOG_LEVEL = os.environ.get('CONTAINER_LOG_LEVEL', "INFO")
16 |
17 | def init_doms_from_env():
18 | RX_DOMS = re.compile('^DOMAIN[0-9]+$', re.IGNORECASE)
19 |
20 | doms = list()
21 | for k in os.environ:
22 | if not RX_DOMS.match(k):
23 | continue
24 |
25 | name = os.environ[k]
26 | try:
27 | dom = {
28 | 'name': name,
29 | 'proxied': os.environ.get("{}_PROXIED".format(k), "FALSE").upper() == "TRUE",
30 | 'zone_id': os.environ["{}_ZONE_ID".format(k)],
31 | 'ttl': os.environ.get("{}_TTL".format(k), DEFAULT_TTL),
32 | }
33 |
34 | doms.append(dom)
35 |
36 | except KeyError as e:
37 | print("*** ERROR: {} is not set!".format(e))
38 |
39 | return doms
40 |
41 |
42 | def point_domain(name, doms):
43 | for dom in doms:
44 | if name.find(dom['name']) >= 0:
45 | records = cf.zones.dns_records.get(dom['zone_id'], params={u'name':name})
46 | data = {
47 | u'type': u'CNAME',
48 | u'name': name,
49 | u'content': target_domain,
50 | u'ttl': int(dom['ttl']),
51 | u'proxied': dom['proxied']
52 | }
53 | if REFRESH_ENTRIES is True :
54 | try:
55 | if len(records) == 0:
56 | r = cf.zones.dns_records.post(dom['zone_id'], data=data)
57 | print ("[info] Created new record:", name, "to point to", target_domain)
58 | else:
59 | for record in records:
60 | cf.zones.dns_records.put(dom['zone_id'], record["id"], data=data)
61 | print ("[info] Updated existing record:", name, "to point to", target_domain)
62 | except CloudFlare.exceptions.CloudFlareAPIError as e:
63 | pass
64 | else:
65 | try:
66 | r = cf.zones.dns_records.post(dom['zone_id'], data=data)
67 | print ("[info] Created new record:", name, "to point to", target_domain)
68 |
69 | except CloudFlare.exceptions.CloudFlareAPIError as e:
70 | print('** %s - %d %s' % (name, e, e))
71 |
72 |
73 | def check_container_nginx(c, doms):
74 | if CONTAINER_LOG_LEVEL == "DEBUG" :
75 | print ("[debug] Called check_container_nginx for:", c)
76 | cont_id = c.attrs.get(u'Id')
77 | for prop in c.attrs.get(u'Config').get(u'Env'):
78 | if u'VIRTUAL_HOST' in prop or u'DNS_NAME' in prop:
79 | value = prop.split("=")[1].strip()
80 | if ',' in value:
81 | for v in value.split(","):
82 | print("[info] Found Container ID:",cont_id,"with Multi-Hostname",v)
83 | point_domain(v, doms)
84 | else:
85 | print("info Found ContainerID :",cont_id,"with Hostname", value)
86 | point_domain(value, doms)
87 |
88 |
89 | def check_service_nginx(s, doms):
90 | if CONTAINER_LOG_LEVEL == "DEBUG" :
91 | print ("[debug] Called check_service_nginx for:", s)
92 | cont_id = s
93 | s = client.services.get(s)
94 | for prop in s.attrs.get(u'Spec').get(u'TaskTemplate').get(u'ContainerSpec').get(u'Env'):
95 | if u'VIRTUAL_HOST' in prop or u'DNS_NAME' in prop:
96 | value = prop.split("=")[1].strip()
97 | if ',' in value:
98 | for v in value.split(","):
99 | print("[info] Found Container ID:",cont_id,"with Multi-Hostname",v)
100 | point_domain(v, doms)
101 | else:
102 | print("info Found ContainerID :",cont_id,"with Hostname", value)
103 | point_domain(value, doms)
104 | else: pass
105 |
106 |
107 | def init(doms):
108 | if CONTAINER_LOG_LEVEL == "DEBUG" :
109 | print ("[debug] Starting Initialization Routines")
110 |
111 | for c in client.containers.list():
112 | if CONTAINER_LOG_LEVEL == "DEBUG" :
113 | print ("[debug] Container List Discovery Loop")
114 |
115 | check_container_nginx(c, doms)
116 |
117 | if SWARM_MODE :
118 | if CONTAINER_LOG_LEVEL == "DEBUG" :
119 | print ("[debug] Service List Discovery Loop")
120 | for s in api.services():
121 | full_serv_id = s["ID"]
122 | short_serv_id = full_serv_id[:10]
123 | serv_id = ""
124 | cont = str(api.containers(quiet=1, filters={'label': 'com.docker.swarm.service.id=' + full_serv_id}))
125 | full_cont_id = cont[9:-4]
126 | short_cont_id = full_cont_id[:10]
127 | cont_id = ""
128 | check_service_nginx(full_serv_id, doms)
129 |
130 | try:
131 | email = get_docker_secret('CF_EMAIL', autocast_name=False, getenv=True)
132 | token = get_docker_secret('CF_TOKEN', autocast_name=False, getenv=True)
133 | target_domain= os.environ['TARGET_DOMAIN']
134 | domain= os.environ['DOMAIN1']
135 | except KeyError as e:
136 | exit("ERROR: {} not defined".format(e))
137 |
138 | if CONTAINER_LOG_LEVEL.lower() == "debug":
139 | CONTAINER_LOG_LEVEL = "DEBUG"
140 |
141 | if REFRESH_ENTRIES.lower() == "true":
142 | REFRESH_ENTRIES = True
143 | elif REFRESH_ENTRIES.lower() == "false":
144 | REFRESH_ENTRIES = False
145 |
146 | if SWARM_MODE.lower() == "true":
147 | SWARM_MODE = True
148 | elif SWARM_MODE.lower() == "false":
149 | SWARM_MODE = False
150 |
151 |
152 | if CONTAINER_LOG_LEVEL == "DEBUG" :
153 | print ("[debug] Swarm Mode:", SWARM_MODE)
154 | print ("[debug] Refresh Entries:", REFRESH_ENTRIES)
155 | print ("[debug] Default TTL:", DEFAULT_TTL)
156 | if not email :
157 | print ("[debug] API Mode: Scoped")
158 | cf = CloudFlare.CloudFlare(debug=True, token=token)
159 | else:
160 | print ("[debug] API Mode: Global")
161 | cf = CloudFlare.CloudFlare(debug=True, email=email, token=token)
162 | else :
163 | if not email :
164 | cf = CloudFlare.CloudFlare(token=token)
165 | else:
166 | cf = CloudFlare.CloudFlare(email=email, token=token)
167 |
168 |
169 | client= docker.from_env()
170 | api= docker.APIClient()
171 | doms= init_doms_from_env()
172 |
173 | init(doms)
174 |
175 | if CONTAINER_LOG_LEVEL == "DEBUG" :
176 | print ("[debug] Starting event watch routines")
177 |
178 | t= datetime.now().strftime("%s")
179 |
180 | if CONTAINER_LOG_LEVEL == "DEBUG" :
181 | print ("[debug] Time:", t)
182 |
183 |
184 | for event in client.events(since=t, filters={'Type': 'service', 'Action': u'update', 'status': u'start'}, decode=True):
185 |
186 | if event.get(u'status') == u'start' :
187 | try:
188 | check_container_nginx(client.containers.get(event.get(u'id')), doms)
189 | if SWARM_MODE :
190 | check_service_nginx(client.services.get(event.get(u'id')), doms)
191 |
192 | except docker.errors.NotFound as e:
193 | pass
194 |
195 | if SWARM_MODE :
196 | if event.get(u'Action') == u'update' :
197 | try:
198 | node_id = event.get(u'Actor').get(u'ID')
199 | if CONTAINER_LOG_LEVEL == "DEBUG" :
200 | print ("[debug] Detected Update on node:", node_id)
201 | check_service_nginx(node_id , doms)
202 |
203 | except docker.errors.NotFound as e:
204 | pass
205 |
206 |
--------------------------------------------------------------------------------