Compare commits

...

15 Commits
master ... cn

Author SHA1 Message Date
apple
47538330f8 Merge branch 'master' into cn
Some checks failed
dependency-updates / prepare (push) Has been cancelled
dependency-updates / update / ${{ matrix.name }} (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
* master: (546 commits)
  chore(deps): bump gunicorn in /tests/apps/dockerfile-release
  chore(deps): bump github.com/traefik/traefik/v2
  chore(deps): bump gunicorn from 26.0.0 to 26.1.0 in /tests/apps/multi
  chore(deps): bump gunicorn in /tests/apps/python-flask
  chore(deps): bump pygments from 2.20.0 to 2.21.0 in /docs/_build
  chore: bump herokuish to 0.11.16
  chore: bump go modules
  chore(deps): bump golang.org/x/crypto in /plugins/common
  chore(deps): bump sqlparse in /tests/apps/dockerfile-release
  chore(deps): bump helm.sh/helm/v3 in /plugins/scheduler-k3s
  chore(deps): bump golang in /tests/apps/zombies-dockerfile-no-tini
  chore(deps): bump golang in /tests/apps/go-fail-postdeploy
  chore(deps): bump golang in /tests/apps/go-fail-predeploy
  chore(deps): bump golang from 1.26.5 to 1.26.6 in /tests/apps/gogrpc
  chore(deps): bump golang in /tests/apps/zombies-dockerfile-tini
  chore(deps): bump google.golang.org/protobuf in /tests/apps/gogrpc
  fix: retire cron containers past their active deadline
  Release 0.38.27
  fix: report traefik dns-provider env vars as global keys
  fix: do not require a local image for k3s deploys
  ...

# Conflicts:
#	common.mk
#	contrib/dependencies.json
2026-08-22 17:40:33 +08:00
apple
89e359ae7d 更新 letsencrypt
Some checks failed
CodeQL / Analyze (go) (push) Has been cancelled
2026-04-25 14:14:39 +08:00
apple
96812e7ed4 Merge branch 'master' into cn
* master:
  chore(deps): bump github.com/fluxcd/pkg/kustomize
  chore(deps-dev): bump heroku/heroku-buildpack-php in /tests/apps/php
  chore(deps): bump github.com/traefik/traefik/v2
  chore(deps): bump timberio/vector in /plugins/logs
  chore(deps): bump traefik in /plugins/traefik-vhosts
  chore(deps): bump click from 8.3.2 to 8.3.3 in /docs/_build
  chore(deps): bump k8s.io/kubectl in /plugins/scheduler-k3s
  chore(deps): bump k8s.io/client-go in /plugins/scheduler-k3s
  chore(deps): update markdown requirement in /docs/_build
  chore(deps): bump ruby in /tests/apps/dockerfile-entrypoint
  chore(deps): bump psycopg2-binary in /tests/apps/dockerfile-release
  chore(deps): bump k8s.io/kubernetes in /plugins/scheduler-k3s
  chore(deps): bump github.com/go-openapi/jsonpointer
  chore: label test app dependency updates as type: tests
2026-04-25 14:13:20 +08:00
apple
72de4581be Merge branch 'master' into cn
Some checks failed
CodeQL / Analyze (go) (push) Failing after 1m9s
* master: (35 commits)
  Release 0.37.9
  chore(deps): bump github.com/go-acme/lego/v4 in /plugins/scheduler-k3s
  chore(deps): bump github.com/moby/spdystream in /plugins/scheduler-k3s
  chore(deps): bump github.com/go-openapi/jsonpointer
  chore(deps): bump k8s.io/api in /plugins/scheduler-k3s
  Release 0.37.8
  chore(deps): bump k8s.io/apimachinery in /plugins/scheduler-k3s
  chore(deps): bump k8s.io/kubernetes in /plugins/scheduler-k3s
  chore(deps): bump packaging from 26.0 to 26.1 in /docs/_build
  chore(deps): bump github.com/fluxcd/pkg/kustomize
  chore(deps): bump zipp from 3.23.0 to 3.23.1 in /docs/_build
  fix(deps): pin controller-runtime to v0.22.4 for keda compatibility
  chore(deps): bump github.com/cert-manager/cert-manager
  chore: bump dependencies in tests/apps/php
  chore: upgrade traefik from v2.11.41 to v2.11.42
  chore: bump go modules
  chore(deps): bump mvdan.cc/sh/v3 from 3.13.0 to 3.13.1 in /plugins/cron
  chore: bump dependencies in tests/apps/multi
  chore: bump go modules
  chore: bump go modules
  ...
2026-04-20 13:59:23 +08:00
apple
bf4bd0e3ea Merge branch 'master' into cn
* master: (68 commits)
  chore(deps): bump werkzeug in /tests/apps/python-flask
  chore(deps): bump github.com/go-jose/go-jose/v4
  chore(deps): bump rack from 3.2.5 to 3.2.6 in /tests/apps/ruby
  chore(deps): bump pymdown-extensions in /docs/_build
  Add application/graphql-response+json to nginx gzip_types
  chore(deps): bump google.golang.org/grpc in /tests/apps/gogrpc
  chore(deps): bump pygments from 2.19.2 to 2.20.0 in /docs/_build
  chore(deps): bump golang.org/x/crypto in /plugins/common
  chore: bump go modules
  chore(deps): bump github.com/fatih/color in /plugins/common
  chore(deps): bump brace-expansion in /tests/apps/multi
  chore(deps): bump gunicorn in /tests/apps/python-flask
  chore(deps): bump path-to-regexp in /tests/apps/checks-root
  chore(deps): bump gunicorn in /tests/apps/dockerfile-release
  chore(deps): bump traefik in /plugins/traefik-vhosts
  chore(deps): bump gunicorn from 25.2.0 to 25.3.0 in /tests/apps/multi
  chore(deps): bump werkzeug in /tests/apps/python-flask
  chore(deps): bump picomatch from 2.3.1 to 2.3.2 in /tests/apps/multi
  chore(deps): bump gunicorn in /tests/apps/dockerfile-release
  chore(deps): bump djangorestframework in /tests/apps/dockerfile-release
  ...
2026-04-08 10:14:03 +08:00
apple
28f76dd30c Merge branch 'master' into cn
* master: (427 commits)
  chore(deps): bump gunicorn from 25.0.2 to 25.1.0 in /tests/apps/multi
  chore(deps): bump flask from 3.1.2 to 3.1.3 in /tests/apps/multi
  chore(deps): bump python in /docs/_build
  chore(deps): bump google.golang.org/grpc in /tests/apps/gogrpc
  chore(deps): bump qs from 6.14.1 to 6.14.2 in /tests/apps/checks-root
  chore(deps): bump rack from 3.2.4 to 3.2.5 in /tests/apps/ruby
  fix: call correct function for limiting letsencrypt to certain domains
  chore(deps): bump gunicorn in /tests/apps/python-flask
  chore(deps): bump whitenoise in /tests/apps/dockerfile-release
  chore(deps): bump phusion/baseimage from noble-1.0.2 to noble-1.0.3
  chore(deps): bump actions/upload-artifact from 6 to 7
  chore(deps): bump actions/download-artifact from 7 to 8
  chore(deps): bump github.com/traefik/traefik/v2
  chore(deps): bump gunicorn in /tests/apps/dockerfile-release
  chore(deps): bump flask from 3.1.2 to 3.1.3 in /tests/apps/python-flask
  chore(deps): bump traefik from 3.6.7 to 3.6.9 in /plugins/traefik-vhosts
  chore(deps): bump mkdocs-material from 9.7.1 to 9.7.3 in /docs/_build
  chore(deps): bump byjg/easy-haproxy in /plugins/haproxy-vhosts
  chore(deps): bump dj-database-url in /tests/apps/dockerfile-release
  chore(deps): bump werkzeug in /tests/apps/python-flask
  ...

# Conflicts:
#	common.mk
#	contrib/dependencies.json
2026-03-02 16:41:58 +08:00
apple
807271359d 删除插件 2025-10-30 10:45:43 +08:00
apple
479287c3e6 删除mysql 2025-10-30 10:37:23 +08:00
apple
809a271859 添加 mysql redis postgres 插件 2025-10-27 13:17:17 +08:00
apple
a16eae23ce Merge commit 'c7cbebece5c0b03e9777cd8b6228659c340cc6dc' into cn 2025-10-16 16:56:54 +08:00
apple
46b20246db 添加 ssl 插件 2025-10-11 17:50:48 +08:00
apple
6f31504a29 修改docker 安装方式 2025-10-09 13:11:15 +08:00
apple
f0e2fd35bd 忽略 wget ssl 检查 2025-10-09 11:50:32 +08:00
apple
69eee39904 Merge branch 'master' into cn
# Conflicts:
#	common.mk
#	contrib/dependencies.json
2025-10-09 11:40:06 +08:00
root
ba8e522ff8 修改成国内可以部署 2025-01-23 14:39:15 +08:00
42 changed files with 1682 additions and 33 deletions

View File

@@ -11,7 +11,7 @@ PROCFILE_UTIL_URL ?= $(shell jq -r --arg name procfile-util --arg arch $(TARGET
SIGIL_URL ?= $(shell jq -r --arg name gliderlabs-sigil --arg arch $(TARGETARCH) '.predependencies[] | select(.name == $$name) | .urls[$$arch]' contrib/dependencies.json)
SSHCOMMAND_URL ?= $(shell jq -r --arg name sshcommand --arg arch $(TARGETARCH) '.dependencies[] | select(.name == $$name) | .urls[$$arch]' contrib/dependencies.json)
STACK_URL ?= https://github.com/gliderlabs/herokuish.git
PREBUILT_STACK_URL ?= gliderlabs/herokuish:latest-24
PREBUILT_STACK_URL ?= ccr.ccs.tencentyun.com/miaogai/herokuish:latest-24
DOKKU_LIB_ROOT ?= /var/lib/dokku
PLUGINS_PATH ?= ${DOKKU_LIB_ROOT}/plugins
CORE_PLUGINS_PATH ?= ${DOKKU_LIB_ROOT}/core-plugins
@@ -190,7 +190,8 @@ docker:
grep -i -E "^docker" /etc/group || groupadd docker
usermod -aG docker dokku
ifndef CI
wget -nv -O - https://get.docker.com/ | sh
sudo apt install docker.io
#wget --no-check-certificate -nv -O - https://get.docker.com/ | sh
ifdef DOCKER_VERSION
apt-get -qq -y --no-install-recommends install docker-engine=${DOCKER_VERSION} || (apt-cache madison docker-engine ; exit 1)
endif

View File

@@ -1,7 +1,7 @@
GO_ARGS ?=
GO_PLUGIN_MAKE_TARGET ?= build
GO_REPO_ROOT := /go/src/github.com/dokku/dokku
BUILD_IMAGE := golang:1.26.2
BUILD_IMAGE := hub.diyla.com/golang:1.26.2
GO_BUILD_CACHE ?= /tmp/dokku-go-build-cache
GO_MOD_CACHE ?= /tmp/dokku-go-mod-mod
GO_ROOT_MOUNT ?= $$PWD/../..:$(GO_REPO_ROOT)
@@ -19,6 +19,7 @@ build-in-docker: clean
-v $(GO_MOD_CACHE):/go/pkg/mod \
-e PLUGIN_NAME=$(PLUGIN_NAME) \
-e GO111MODULE=on \
-e GOPROXY=https://goproxy.cn,direct \
-w $(GO_REPO_ROOT)/plugins/$(PLUGIN_NAME) \
$(BUILD_IMAGE) \
bash -c "GO_ARGS='$(GO_ARGS)' CGO_ENABLED=0 GOOS=linux GOARCH=$(GOARCH) GOWORK=off make -j4 $(GO_PLUGIN_MAKE_TARGET)" || exit $$?

View File

@@ -4,56 +4,56 @@
"name": "docker-container-healthchecker",
"version": "0.16.0",
"urls": {
"amd64": "https://github.com/dokku/docker-container-healthchecker/releases/download/v0.16.0/docker-container-healthchecker-linux-amd64",
"arm64": "https://github.com/dokku/docker-container-healthchecker/releases/download/v0.16.0/docker-container-healthchecker-linux-arm64"
"amd64": "https://hub.diyla.com/https://github.com/dokku/docker-container-healthchecker/releases/download/v0.16.0/docker-container-healthchecker-linux-amd64",
"arm64": "https://hub.diyla.com/https://github.com/dokku/docker-container-healthchecker/releases/download/v0.16.0/docker-container-healthchecker-linux-arm64"
}
},
{
"name": "docker-image-labeler",
"version": "0.10.0",
"urls": {
"amd64": "https://github.com/dokku/docker-image-labeler/releases/download/v0.10.0/docker-image-labeler-linux-amd64",
"arm64": "https://github.com/dokku/docker-image-labeler/releases/download/v0.10.0/docker-image-labeler-linux-arm64"
"amd64": "https://hub.diyla.com/https://github.com/dokku/docker-image-labeler/releases/download/v0.10.0/docker-image-labeler-linux-amd64",
"arm64": "https://hub.diyla.com/https://github.com/dokku/docker-image-labeler/releases/download/v0.10.0/docker-image-labeler-linux-arm64"
}
},
{
"name": "lambda-builder",
"version": "0.9.4",
"urls": {
"amd64": "https://github.com/dokku/lambda-builder/releases/download/v0.9.4/lambda-builder-linux-amd64",
"arm64": "https://github.com/dokku/lambda-builder/releases/download/v0.9.4/lambda-builder-linux-arm64"
"amd64": "https://hub.diyla.com/https://github.com/dokku/lambda-builder/releases/download/v0.9.4/lambda-builder-linux-amd64",
"arm64": "https://hub.diyla.com/https://github.com/dokku/lambda-builder/releases/download/v0.9.4/lambda-builder-linux-arm64"
}
},
{
"name": "netrc",
"version": "0.11.1",
"urls": {
"amd64": "https://github.com/dokku/netrc/releases/download/v0.11.1/netrc-linux-amd64",
"arm64": "https://github.com/dokku/netrc/releases/download/v0.11.1/netrc-linux-arm64"
"amd64": "https://hub.diyla.com/https://github.com/dokku/netrc/releases/download/v0.11.1/netrc-linux-amd64",
"arm64": "https://hub.diyla.com/https://github.com/dokku/netrc/releases/download/v0.11.1/netrc-linux-arm64"
}
},
{
"name": "pack",
"version": "0.40.9",
"urls": {
"amd64": "https://github.com/buildpacks/pack/releases/download/v0.40.9/pack-v0.40.9-linux.tgz",
"arm64": "https://github.com/buildpacks/pack/releases/download/v0.40.9/pack-v0.40.9-linux-arm64.tgz"
"amd64": "https://hub.diyla.com/https://github.com/buildpacks/pack/releases/download/v0.40.9/pack-v0.40.9-linux.tgz",
"arm64": "https://hub.diyla.com/https://github.com/buildpacks/pack/releases/download/v0.40.9/pack-v0.40.9-linux-arm64.tgz"
}
},
{
"name": "procfile-util",
"version": "0.20.8",
"urls": {
"amd64": "https://github.com/dokku/procfile-util/releases/download/v0.20.8/procfile-util-linux-amd64",
"arm64": "https://github.com/dokku/procfile-util/releases/download/v0.20.8/procfile-util-linux-arm64"
"amd64": "https://hub.diyla.com/https://github.com/dokku/procfile-util/releases/download/v0.20.8/procfile-util-linux-amd64",
"arm64": "https://hub.diyla.com/https://github.com/dokku/procfile-util/releases/download/v0.20.8/procfile-util-linux-arm64"
}
},
{
"name": "sshcommand",
"version": "0.20.2",
"urls": {
"amd64": "https://github.com/dokku/sshcommand/releases/download/v0.20.2/sshcommand",
"arm64": "https://github.com/dokku/sshcommand/releases/download/v0.20.2/sshcommand"
"amd64": "https://hub.diyla.com/https://github.com/dokku/sshcommand/releases/download/v0.20.2/sshcommand",
"arm64": "https://hub.diyla.com/https://github.com/dokku/sshcommand/releases/download/v0.20.2/sshcommand"
}
}
],
@@ -62,16 +62,16 @@
"name": "gliderlabs-sigil",
"version": "0.12.1",
"urls": {
"amd64": "https://github.com/gliderlabs/sigil/releases/download/v0.12.1/sigil-linux-amd64",
"arm64": "https://github.com/gliderlabs/sigil/releases/download/v0.12.1/sigil-linux-arm64"
"amd64": "https://hub.diyla.com/https://github.com/gliderlabs/sigil/releases/download/v0.12.1/sigil-linux-amd64",
"arm64": "https://hub.diyla.com/https://github.com/gliderlabs/sigil/releases/download/v0.12.1/sigil-linux-arm64"
}
},
{
"name": "plugn",
"version": "0.17.1",
"urls": {
"amd64": "https://github.com/dokku/plugn/releases/download/v0.17.1/plugn-linux-amd64",
"arm64": "https://github.com/dokku/plugn/releases/download/v0.17.1/plugn-linux-arm64"
"amd64": "https://hub.diyla.com/https://github.com/dokku/plugn/releases/download/v0.17.1/plugn-linux-amd64",
"arm64": "https://hub.diyla.com/https://github.com/dokku/plugn/releases/download/v0.17.1/plugn-linux-arm64"
}
}
],
@@ -80,24 +80,24 @@
"name": "dokku-event-listener",
"version": "0.20.1",
"urls": {
"amd64": "https://github.com/dokku/dokku-event-listener/releases/download/v0.20.1/dokku-event-listener-linux-amd64",
"arm64": "https://github.com/dokku/dokku-event-listener/releases/download/v0.20.1/dokku-event-listener-linux-arm64"
"amd64": "https://hub.diyla.com/https://github.com/dokku/dokku-event-listener/releases/download/v0.20.1/dokku-event-listener-linux-amd64",
"arm64": "https://hub.diyla.com/https://github.com/dokku/dokku-event-listener/releases/download/v0.20.1/dokku-event-listener-linux-arm64"
}
},
{
"name": "dokku-update",
"version": "0.10.0",
"urls": {
"amd64": "https://github.com/dokku/dokku-update/releases/download/v0.10.0/dokku-update",
"arm64": "https://github.com/dokku/dokku-update/releases/download/v0.10.0/dokku-update"
"amd64": "https://hub.diyla.com/https://github.com/dokku/dokku-update/releases/download/v0.10.0/dokku-update",
"arm64": "https://hub.diyla.com/https://github.com/dokku/dokku-update/releases/download/v0.10.0/dokku-update"
}
},
{
"name": "herokuish",
"version": "0.11.16",
"urls": {
"amd64": "https://github.com/gliderlabs/herokuish/releases/download/v0.11.16/herokuish_0.11.16_linux_x86_64.tgz",
"arm64": "https://github.com/gliderlabs/herokuish/releases/download/v0.11.16/herokuish_0.11.16_linux_x86_64.tgz"
"amd64": "https://hub.diyla.com/https://github.com/gliderlabs/herokuish/releases/download/v0.11.16/herokuish_0.11.16_linux_x86_64.tgz",
"arm64": "https://hub.diyla.com/https://github.com/gliderlabs/herokuish/releases/download/v0.11.16/herokuish_0.11.16_linux_x86_64.tgz"
}
}
]

4
dokku
View File

@@ -39,8 +39,8 @@ DOKKU_DISTRO=$(
# configuration can ever override the DOCKER_BIN value
export DOCKER_BIN=${DOCKER_BIN:="docker"}
export DOKKU_IMAGE=${DOKKU_IMAGE:="gliderlabs/herokuish:latest-24"}
export DOKKU_CNB_BUILDER=${DOKKU_CNB_BUILDER:="heroku/builder:24"}
export DOKKU_IMAGE=${DOKKU_IMAGE:="ccr.ccs.tencentyun.com/miaogai/herokuish:latest-24"}
export DOKKU_CNB_BUILDER=${DOKKU_CNB_BUILDER:="ccr.ccs.tencentyun.com/miaogai/heroku:builder24"}
export DOKKU_LIB_ROOT=${DOKKU_LIB_PATH:="/var/lib/dokku"}
export PLUGIN_PATH=${PLUGIN_PATH:="$DOKKU_LIB_ROOT/plugins"}

View File

@@ -0,0 +1,9 @@
# http://EditorConfig.org
root = true
[*]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2

View File

@@ -0,0 +1,12 @@
---
version: 2
updates:
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "daily"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: daily
open-pull-requests-limit: 10

View File

@@ -0,0 +1,52 @@
---
name: "bump-version"
# yamllint disable-line rule:truthy
on:
workflow_dispatch:
inputs:
bump_type:
description: "Bump type"
default: "patch"
required: true
type: choice
options:
- patch
- minor
- major
env:
GITHUB_ACCESS_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }}
jobs:
bump-version:
name: bump-version
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v6.0.2
with:
fetch-depth: 0
token: ${{ env.GITHUB_ACCESS_TOKEN }}
- name: Get Latest Tag
id: latest-tag
run: |
echo GIT_LATEST_TAG="$(git describe --tags "$(git rev-list --tags --max-count=1)")" >>"$GITHUB_OUTPUT"
- name: Compute Next Tag
id: next-tag
uses: docker://ghcr.io/dokku/semver-generator:latest
with:
bump: ${{ github.event.inputs.bump_type }}
input: ${{ steps.latest-tag.outputs.GIT_LATEST_TAG }}
- name: Create and Push Tag
run: |
git config --global user.name 'Dokku Bot'
git config --global user.email no-reply@dokku.com
git tag "$GIT_NEXT_TAG"
git push origin "$GIT_NEXT_TAG"
env:
GIT_NEXT_TAG: ${{ steps.next-tag.outputs.version }}

View File

@@ -0,0 +1,20 @@
---
name: "tagged-release"
# yamllint disable-line rule:truthy
on:
push:
tags:
- "*"
jobs:
tagged-release:
name: tagged-release
runs-on: ubuntu-24.04
steps:
- name: Release
uses: softprops/action-gh-release@v2.6.1
with:
generate_release_notes: true
make_latest: "true"

10
plugins/letsencrypt/.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
#### joe made this: http://goel.io/joe
#####=== Vim ===#####
[._]*.s[a-w][a-z]
[._]s[a-w][a-z]
*.un~
Session.vim
.netrwhist
*~

View File

@@ -0,0 +1 @@
FROM goacme/lego:v4.33.0

View File

@@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2015 Stefan Seemayer
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,217 @@
# dokku-letsencrypt
dokku-letsencrypt is the official plugin for [dokku][dokku] that gives the ability to automatically retrieve and install TLS certificates from [letsencrypt.org](https://letsencrypt.org). During ACME validation, your app will stay available at any time.
> By running this plugin, you agree to the Let's Encrypt Subscriber Agreement automatically (because prompting you whether you agree might break running the plugin as part of a cronjob).
>
> If you like Let's Encrypt, please consider [donating to Let's Encrypt](https://letsencrypt.org/donate).
## Installation
```shell
sudo dokku plugin:install https://github.com/dokku/dokku-letsencrypt.git
sudo dokku letsencrypt:cron-job --add # <- To enable auto-renew
```
### Upgrading from previous versions
```shell
sudo dokku plugin:update letsencrypt
```
## Commands
```
$ dokku letsencrypt:help
letsencrypt:active <app> Verify if letsencrypt is active for an app
letsencrypt:auto-renew Auto-renew all apps secured by letsencrypt if renewal is necessary
letsencrypt:auto-renew <app> Auto-renew app if renewal is necessary
letsencrypt:cleanup <app> Cleanup stale certificates and configurations
letsencrypt:cron-job <--add|--remove> Add or remove an auto-renewal cronjob
letsencrypt:disable <app> Disable letsencrypt for an app
letsencrypt:enable <app> Enable or renew letsencrypt for an app
letsencrypt:list List letsencrypt-secured apps with certificate expiry
letsencrypt:revoke <app> Revoke letsencrypt certificate for app
```
## Usage
> If using this plugin with Cloudflare:
>
> - The domain dns should be setup in "Proxied" mode
> - SSL/TLS mode must be in "Full" mode
> - Using letsencrypt in "Flexible" mode will cause Cloudflare to detect your server as down
> - Using "Full" mode will require disabling SSL/TLS in cloudflare in order to renew the certificate.
>
> If using "Flexible" SSL/TLS mode, avoid using this plugin.
>
> See these two links for more details:
>
> - https://community.cloudflare.com/t/lets-encrypt-ssl-cannot-renew-with-cloudflare/257666
> - https://support.cloudflare.com/hc/en-us/articles/214820528-Validating-a-Let-s-Encrypt-Certificate-on-a-Site-Already-Active-on-Cloudflare
The app which is obtaining a letsencrypt certificate must already be deployed and accessible over the internet (i.e. in the browser) in order to add letsencrypt to your app. This plugin will fail to apply for an app that has otherwise only been created.
Obtain a Let's encrypt TLS certificate for app `myapp` (you can also run this command to renew the certificate):
```
$ dokku letsencrypt:set myapp email your@email.tld
-----> Setting email to your@email.tld
$ dokku letsencrypt:enable myapp
=====> Let's Encrypt myapp...
-----> Updating letsencrypt docker image...
latest: Pulling from dokku/letsencrypt
Digest: sha256:20f2a619795c1a3252db6508f77d6d3648ad5b336e67caaf801126367dbdfa22
Status: Image is up to date for dokku/letsencrypt:latest
done
-----> Enabling letsencrypt proxy for myapp...
-----> Getting letsencrypt certificate for myapp...
- Domain 'myapp.mydomain.com'
[ removed various log messages for brevity ]
-----> Certificate retrieved successfully.
-----> Symlinking let's encrypt certificates
-----> Configuring SSL for myapp.mydomain.com...(using /var/lib/dokku/plugins/available/nginx-vhosts/templates/nginx.ssl.conf.template)
-----> Creating https nginx.conf
-----> Running nginx-pre-reload
Reloading nginx
-----> Disabling letsencrypt proxy for myapp...
done
```
Once the certificate is installed, you can use the `certs:*` built-in commands to edit and query your certificate.
You could also use the following command to set an email address for global. So you don't need to type the email address for different application.
```shell
dokku letsencrypt:set --global email your@email.tld
```
## Automatic certificate renewal
To enable the automatic renewal of certificates, a cronjob needs to be defined for
the `dokku` user which will run daily and renew any certificates that are due to
be renewed.
This can be done using the following command:
```shell
dokku letsencrypt:cron-job --add
```
## Configuration
`dokku-letsencrypt` uses the [Dokku environment variable manager](https://dokku.com/docs/configuration/environment-variables/) for all configuration. The important environment variables are:
Variable | Default | Description
---------------------|-------------------|-------------------------------------------------------------------------
`dns-provider` | (none) | The name of a [valid lego dns-provider](https://go-acme.github.io/lego/dns/)
`email` | (none) | **REQUIRED:** E-mail address to use for registering with Let's Encrypt.
`graceperiod` | 2592000 (30 days) | Time in seconds left on a certificate before it should get renewed
`lego-docker-args` | (none) | Extra arguments to pass via `docker run`. See the [lego CLI documentation](https://go-acme.github.io/lego/usage/cli/) for available options.
`server` | default | Which ACME server to use. Can be 'default', 'staging' or a URL
You can set a setting using `dokku letsencrypt:set $APP $SETTING_NAME $SETTING_VALUE`. When looking for a setting, the plugin will first look if it was defined for the current app and fall back to settings defined by `--global`.
> Note: See "DNS-01 Challenge" for more information on configuration a dns-provider for DNS-01 based challenges and wildcard support.
## Redirecting from HTTP to HTTPS
Dokku's default nginx template will automatically redirect HTTP requests to HTTPS when a certificate is present.
You can [customize the nginx template](https://dokku.com/docs/networking/proxies/nginx/) if you want different behaviour.
## Design
`dokku-letsencrypt` gets around having to disable your web server using the following workflow:
1. Temporarily add a reverse proxy for the `/.well-known/` path of your app to `https://127.0.0.1:$ACMEPORT`
2. Run [the acme/lego Let's Encrypt client](https://github.com/go-acme/lego) in a [Docker container](https://hub.docker.com/r/goacme/lego/) binding to `$ACMEPORT` to complete the ACME challenge and retrieve the TLS certificates
3. Install the TLS certificates
4. Remove the reverse proxy and reload nginx
For a more in-depth explanation, see [this blog post](https://blog.semicolonsoftware.de/securing-dokku-with-lets-encrypt-tls-certificates/)
## Dockerfile and Image-based Deploys
When securing Dockerfile and Image-based deploys with dokku-letsencrypt, be aware of the [proxy mechanism for dokku 0.6+](https://dokku.com/docs/networking/port-management/#dockerfile).
For Dockerfile deploys - as well as those via `git:from-image` - Dokku will determine which ports a container exposes (using `EXPOSE`) and will proxy them on the same port numbers on the host. If the Dockerfile exposes another port than 443, then HTTPS port 443 **needs to be manually configured** using the `dokku ports:*` commands in order for certificate validation and browsing to the app via HTTPS to work.
A full workflow for creating a new Dockerfile/Image-based deployment (assuming the app is listening/exposed on port 5555) with `dokku-letsencrypt` would be:
1. Create a new app `myapp` in dokku and push to the `dokku@myhost.com` remote.
2. On the dokku host, use `dokku letsencrypt:enable myapp` to retrieve HTTPS certificates.
3. On the dokku host, use `dokku ports:add myapp https:443:5555` to proxy HTTPS port 443 to port 5555 on the Docker image
After these steps, the output of `dokku ports:report myapp` should look like this:
```
=====> myapp ports information
Ports map: https:443:5555
Ports map detected: https:5555:5555
```
Replace the container port (`5555` in the above example) with the port your app is listening on.
## Dealing with rate limit
Be aware that Let's Encrypt is subject to [rate limiting](https://letsencrypt.org/docs/rate-limits/). The limit about the number of certificates you can add on a domain per week is a concern for dokku because of the default domain added to your new applications, named like `<app>.<dokku-domain>`: using `dokku-letsencrypt` on all your applications would create a certificate for each application subdomain on `<dokku-domain>`.
As a workaround, if you want to encrypt many applications, make sure to add a proper domain for each one and remove their default domain before running `dokku-letsencrypt`. For example, if your dokku domain is `dokku.example.com` and you want to encrypt your `foo` app:
```sh
dokku domains:add foo foo.com
dokku domains:remove foo foo.dokku.example.com
dokku letsencrypt:enable foo
```
While playing around with this plugin, you might want to switch to the let's encrypt staging server by running `dokku letsencrypt:set myapp server staging` to enjoy much higher rate limits and switching back to the real server by running `dokku letsencrypt:set myapp server` once you are ready.
## Generating a Cert for multiple domains
Your [default dokku app](https://dokku.com/docs/networking/proxies/nginx/?h=default+site#default-site) is accessible under the root domain too. So if you have an application `00-default` that is running under `00-default.mydomain.com` it is accessible under `mydomain.com` too. Now if you enable letsencrypt for your `00-default` application, it is not accessible anymore on `mydomain.com`. You can add the root domain to your dokku domains by typing:
```shell
dokku domains:add 00-default mydomain.com
dokku letsencrypt:enable 00-default
```
## DNS-01 Challenge
> Functionality sponsored by [Orca Scan Ltd](https://orcascan.com/).
In order to provide a Letsencrypt certificate for a wildcard domain, a DNS-01 challenge must be used. To configure, the `dns-provider` property must be set to a [supported Lego provider](https://go-acme.github.io/lego/dns/). Additionally, the environment variables used by the DNS provider must be set as letsencrypt properties with the prefix `dns-provider-`. Both global and app-specific properties are supported.
> Warning: Before using a DNS-based challenge, ensure all DNS records - including wildcard records - are pointing at your server.
```shell
# set the provider to namecheap
dokku letsencrypt:set --global dns-provider namecheap
# set the properties necessary for namecheap usage
dokku letsencrypt:set --global dns-provider-NAMECHEAP_API_USER user
dokku letsencrypt:set --global dns-provider-NAMECHEAP_API_KEY key
```
Due to limitations in how certain DNS providers work, environment variables _must not_ use the `_FILE` based method for referring to values in files.
Please see the Lego documentation for your DNS provider for more information on what configuration is necessary to utilize DNS-01 challenges.
## Conditional enabling
`dokku letsencrypt:enable <app>` enables letsencrypt for an application or renews the certificate. This may lead to hitting rate limits with letsencrypt.
To avoid renewals, for example in a continuous deployment scenario, you could first check if letsencrypt has already been enabled for the app:
```shell
dokku letsencrypt:active <app> || dokku letsencrypt:enable <app>
```
## License
This plugin is released under the MIT license. See the file [LICENSE](LICENSE).
[dokku]: https://github.com/dokku/dokku

View File

@@ -0,0 +1,323 @@
#!/usr/bin/env bash
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions"
source "$PLUGIN_AVAILABLE_PATH/letsencrypt/internal-functions"
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-letsencrypt-active() {
declare desc="Verify if letsencrypt is active for an app"
declare cmd="letsencrypt:active"
[[ "$1" == "$cmd" ]] && shift 1
# Support --app/$DOKKU_APP_NAME flag by reordering args into "$cmd $DOKKU_APP_NAME $@"
[[ -n "$DOKKU_APP_NAME" ]] && set -- $DOKKU_APP_NAME $@
declare APP="$1"
verify_app_name "$APP"
fn-letsencrypt-is-active "$APP"
}
cmd-letsencrypt-auto-renew() {
declare desc="auto-renew certificates if necessary"
declare cmd="letsencrypt:auto-renew"
[[ "$1" == "$cmd" ]] && shift 1
# Support --app/$DOKKU_APP_NAME flag by reordering args into "$cmd $DOKKU_APP_NAME $@"
[[ -n "$DOKKU_APP_NAME" ]] && set -- $DOKKU_APP_NAME $@
declare APP="$1"
local expiry grace_period
if [ -z "$APP" ]; then
dokku_log_info2 "Auto-renewing all apps..."
local EXIT_CODE=0
# For all apps, sorted by ascending time left until renewal.
# This way, we'll prioritize apps that need to be renewed soon
# if we should hit a rate limit along the way.
# Store the list in a temporary file to avoid subshell issues with pipelines
local temp_file=$(mktemp)
fn-letsencrypt-list-apps-with-expiry | sort -nk5 > "$temp_file"
while IFS=$'\t' read -r -a appExpiry; do
if [[ ${appExpiry[4]} -lt 0 ]]; then
dokku_log_info1 "${appExpiry[0]} needs renewal"
if ! dokku letsencrypt:enable "${appExpiry[0]}"; then
EXIT_CODE=1
fi
else
days_left=$(fn-letsencrypt-format-timediff "${appExpiry[4]}")
dokku_log_verbose "${appExpiry[0]} still has $days_left days left before renewal"
fi
done < "$temp_file"
rm -f "$temp_file"
dokku_log_info2 "Finished auto-renewal"
if [[ "$EXIT_CODE" != 0 ]]; then
dokku_log_fail "One or more apps failed to have their certificates renewed"
fi
else
verify_app_name "$APP"
if [[ "$(fn-letsencrypt-is-active "$APP")" != "true" ]]; then
dokku_log_info1 "Letsencrypt not enabled for ${APP}"
return
fi
expiry=$(fn-letsencrypt-expiration "$APP")
grace_period=$(fn-letsencrypt-computed-graceperiod "$APP")
local time_to_renewal=$((expiry - grace_period - $(date +%s)))
if [[ $time_to_renewal -lt 0 ]]; then
dokku_log_info2 "Auto-renew ${APP}..."
dokku letsencrypt:enable "$APP"
else
days_left=$(fn-letsencrypt-format-timediff $time_to_renewal)
dokku_log_verbose "$APP still has $days_left left before renewal"
fi
fi
}
cmd-letsencrypt-cleanup() {
declare desc="clean up unused certificate directories"
declare cmd="letsencrypt:cleanup"
[[ "$1" == "$cmd" ]] && shift 1
local certdir_basename current_config
# Support --app/$DOKKU_APP_NAME flag by reordering args into "$cmd $DOKKU_APP_NAME $@"
[[ -n "$DOKKU_APP_NAME" ]] && set -- $DOKKU_APP_NAME $@
declare APP="$1"
verify_app_name "$APP"
local app_root="$DOKKU_ROOT/$APP"
local le_root="$app_root/letsencrypt"
current_config="$(basename "$(readlink "$le_root/certs/current")")"
if [ -z "$current_config" ] || [[ ! -d "$le_root/certs/$current_config" ]]; then
dokku_log_warn "Cannot resolve the 'current' certificate directory!"
return 1
fi
dokku_log_info2 "Cleaning up stale certificate directories for $APP"
dokku_log_info1 " - current config hash $current_config"
for certdir in $le_root/certs/*; do
certdir_basename="$(basename "$certdir")"
if [[ "$certdir_basename" == "current" ]] || [[ "$certdir_basename" == "$current_config" ]]; then continue; fi
dokku_log_info1 " - stale directory $certdir_basename"
rm -rf "$le_root/certs/$certdir_basename"
done
}
cmd-letsencrypt-cron-job() {
declare desc="Add or remove a cron job that periodically calls auto-renew"
declare cmd="letsencrypt:cron-job"
[[ "$1" == "$cmd" ]] && shift 1
declare FLAG="$1"
if [[ "$FLAG" == "--add" ]]; then
fn-letsencrypt-cron-job-add
elif [[ "$FLAG" == "--remove" ]]; then
fn-letsencrypt-cron-job-remove
else
dokku_log_verbose "Specify --add or --remove to modify the cron-job"
fi
}
cmd-letsencrypt-disable() {
declare desc="Disable letsencrypt for an app"
declare cmd="letsencrypt:disable"
[[ "$1" == "$cmd" ]] && shift 1
# Support --app/$DOKKU_APP_NAME flag by reordering args into "$cmd $DOKKU_APP_NAME $@"
[[ -n "$DOKKU_APP_NAME" ]] && set -- $DOKKU_APP_NAME $@
declare APP="$1"
verify_app_name "$APP"
dokku_log_info1 "Disabling letsencrypt for app"
local le_root="$DOKKU_ROOT/$APP/letsencrypt"
local APP_SSL_PATH="$DOKKU_ROOT/$APP/tls"
dokku_log_verbose "Removing letsencrypt files for $APP"
rm -rf "$le_root"
dokku_log_verbose "Removing SSL endpoint from $APP"
rm -rf "$APP_SSL_PATH"
plugn trigger post-certs-remove "$APP"
plugn trigger post-domains-update "$APP"
dokku_log_info1 "Done"
}
cmd-letsencrypt-enable() {
declare desc="Enable or renew letsencrypt for an app"
declare cmd="letsencrypt:enable"
[[ "$1" == "$cmd" ]] && shift 1
# Support --app/$DOKKU_APP_NAME flag by reordering args into "$cmd $DOKKU_APP_NAME $@"
[[ -n "$DOKKU_APP_NAME" ]] && set -- $DOKKU_APP_NAME $@
declare APP="$1"
if [[ "$APP" == "--all" ]]; then
for app in $(dokku_apps); do
fn-letsencrypt-enable "$app"
done
else
fn-letsencrypt-enable "$APP"
fi
}
cmd-letsencrypt-list() {
declare desc="list letsencrypt-secured apps and certificate expiries"
declare cmd="letsencrypt:list"
[[ "$1" == "$cmd" ]] && shift 1
dokku_col_log_info1_quiet "App name" "Certificate Expiry" "Time before expiry" "Time before renewal"
fn-letsencrypt-list-apps-with-expiry \
| sort -nk2 \
| while IFS=$'\t' read -r -a appExpiry; do
expire_date=$(date -d "@${appExpiry[1]}" +"%F %T")
expire_time=$(fn-letsencrypt-format-timediff "${appExpiry[3]}")
renew_time=$(fn-letsencrypt-format-timediff "${appExpiry[4]}")
dokku_col_log_msg "${appExpiry[0]}" "${expire_date}" "${expire_time}" "${renew_time}"
done
}
cmd-letsencrypt-report() {
declare desc="displays a letsencrypt report for one or more apps"
declare cmd="letsencrypt:report"
[[ "$1" == "$cmd" ]] && shift 1
declare APP="$1" INFO_FLAG="$2"
local INSTALLED_APPS
INSTALLED_APPS=$(dokku_apps)
if [[ -n "$APP" ]] && [[ "$APP" == --* ]]; then
INFO_FLAG="$APP"
APP=""
fi
if [[ -z "$APP" ]] && [[ -z "$INFO_FLAG" ]]; then
INFO_FLAG="true"
fi
if [[ -z "$APP" ]]; then
for app in $INSTALLED_APPS; do
cmd-letsencrypt-report-single "$app" "$INFO_FLAG" | tee || true
done
else
cmd-letsencrypt-report-single "$APP" "$INFO_FLAG"
fi
}
cmd-letsencrypt-report-single() {
declare APP="$1" INFO_FLAG="$2"
if [[ "$INFO_FLAG" == "true" ]]; then
INFO_FLAG=""
fi
verify_app_name "$APP"
local flag_map=(
"--letsencrypt-active: $(fn-letsencrypt-is-active "$APP")"
"--letsencrypt-autorenew: $(fn-letsencrypt-is-autorenew-enabled "$APP")"
"--letsencrypt-computed-dns-provider: $(fn-letsencrypt-computed-dns-provider "$APP")"
"--letsencrypt-global-dns-provider: $(fn-letsencrypt-global-dns-provider)"
"--letsencrypt-dns-provider: $(fn-letsencrypt-dns-provider "$APP")"
"--letsencrypt-computed-email: $(fn-letsencrypt-computed-email "$APP")"
"--letsencrypt-global-email: $(fn-letsencrypt-global-email)"
"--letsencrypt-email: $(fn-letsencrypt-email "$APP")"
"--letsencrypt-expiration: $(fn-letsencrypt-expiration "$APP")"
"--letsencrypt-computed-graceperiod: $(fn-letsencrypt-computed-graceperiod "$APP")"
"--letsencrypt-global-graceperiod: $(fn-letsencrypt-global-graceperiod)"
"--letsencrypt-graceperiod: $(fn-letsencrypt-graceperiod "$APP")"
"--letsencrypt-computed-lego-docker-args: $(fn-letsencrypt-computed-lego-docker-args "$APP")"
"--letsencrypt-global-lego-docker-args: $(fn-letsencrypt-global-lego-docker-args)"
"--letsencrypt-lego-docker-args: $(fn-letsencrypt-lego-docker-args "$APP")"
"--letsencrypt-computed-server: $(fn-letsencrypt-computed-server "$APP")"
"--letsencrypt-global-server: $(fn-letsencrypt-global-server)"
"--letsencrypt-server: $(fn-letsencrypt-server "$APP")"
)
if [[ -z "$INFO_FLAG" ]]; then
dokku_log_info2_quiet "${APP} letsencrypt information"
for flag in "${flag_map[@]}"; do
key="$(echo "${flag#--}" | cut -f1 -d' ' | tr - ' ')"
dokku_log_verbose "$(printf "%-30s %-25s" "${key^}" "${flag#*: }")"
done
else
local match=false
local value_exists=false
for flag in "${flag_map[@]}"; do
valid_flags="${valid_flags} $(echo "$flag" | cut -d':' -f1)"
if [[ "$flag" == "${INFO_FLAG}:"* ]]; then
value=${flag#*: }
size="${#value}"
if [[ "$size" -ne 0 ]]; then
echo "$value" && match=true && value_exists=true
else
match=true
fi
fi
done
[[ "$match" == "true" ]] || dokku_log_fail "Invalid flag passed, valid flags:${valid_flags}"
[[ "$value_exists" == "true" ]] || dokku_log_fail "not deployed"
fi
}
cmd-letsencrypt-revoke() {
declare desc="Revoke a certificate"
declare cmd="letsencrypt:revoke"
[[ "$1" == "$cmd" ]] && shift 1
# Support --app/$DOKKU_APP_NAME flag by reordering args into "$cmd $DOKKU_APP_NAME $@"
[[ -n "$DOKKU_APP_NAME" ]] && set -- $DOKKU_APP_NAME $@
declare APP="$1"
verify_app_name "$APP"
dokku_log_info2 "Revoke letsencrypt certificate from ${APP}..."
fn-letsencrypt-check-email "$APP"
fn-letsencrypt-acme-revoke "$APP" || true
dokku_log_info1 "Done"
}
cmd-letsencrypt-set() {
declare desc="set or clear a letsencrypt property for an app"
declare cmd="letsencrypt:set"
[[ "$1" == "$cmd" ]] && shift 1
declare APP="$1" KEY="$2" VALUE="$3"
local VALID_KEYS=("dns-provider" "email" "graceperiod" "server" "lego-docker-args")
[[ "$APP" == "--global" ]] || verify_app_name "$APP"
[[ -z "$KEY" ]] && dokku_log_fail "No key specified"
if ! fn-in-array "$KEY" "${VALID_KEYS[@]}" && [[ "$KEY" != dns-provider-* ]]; then
dokku_log_fail "Invalid key specified, valid keys include: dns-provider, dns-provider-*, email, graceperiod, server, lego-docker-args"
fi
if [[ -n "$VALUE" ]]; then
dokku_log_info2_quiet "Setting ${KEY} to ${VALUE}"
fn-plugin-property-write "letsencrypt" "$APP" "$KEY" "$VALUE"
else
dokku_log_info2_quiet "Unsetting ${KEY}"
if [[ "$KEY" == "rev-env-var" ]]; then
fn-plugin-property-write "letsencrypt" "$APP" "$KEY" "$VALUE"
else
fn-plugin-property-delete "letsencrypt" "$APP" "$KEY"
if [[ "$KEY" == "enabled" ]]; then
fn-plugin-property-destroy "letsencrypt" "$APP"
fi
fi
fi
}

16
plugins/letsencrypt/commands Executable file
View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
[[ " help letsencrypt:help " == *" $1 "* ]] || exit "$DOKKU_NOT_IMPLEMENTED_EXIT"
source "$PLUGIN_AVAILABLE_PATH/letsencrypt/help-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
case "$1" in
help | letsencrypt:help)
cmd-letsencrypt-help "$@"
;;
*)
exit "$DOKKU_NOT_IMPLEMENTED_EXIT"
;;
esac

15
plugins/letsencrypt/config Executable file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
export LETSENCRYPT_IMAGE=${LETSENCRYPT_IMAGE:="$(awk -F '[ :]' '{print $2}' "${_DIR}/Dockerfile")"}
export LETSENCRYPT_IMAGE_VERSION=${LETSENCRYPT_IMAGE_VERSION:="$(awk -F '[ :]' '{print $3}' "${_DIR}/Dockerfile")"}
export PLUGIN_DISABLE_PULL=${LETSENCRYPT_DISABLE_PULL:=}
export PLUGIN_DISABLE_PULL_VARIABLE="LETSENCRYPT_DISABLE_PULL"
export PLUGIN_IMAGE=$LETSENCRYPT_IMAGE
export PLUGIN_IMAGE_VERSION=$LETSENCRYPT_IMAGE_VERSION
export LETSENCRYPT_CRON_CMD="$PLUGIN_AVAILABLE_PATH/letsencrypt/cron-job"
export LETSENCRYPT_CRON_JOB="@daily $LETSENCRYPT_CRON_CMD"

View File

@@ -0,0 +1,9 @@
#!/usr/bin/env bash
trigger-letsencrypt-cron-entries() {
if [[ -f "${DOKKU_LIB_ROOT}/data/letsencrypt/autorenew" ]]; then
echo "24 6 * * *;dokku letsencrypt:auto-renew;/var/log/dokku/letsencrypt.log"
fi
}
trigger-letsencrypt-cron-entries "$@"

4
plugins/letsencrypt/cron-job Executable file
View File

@@ -0,0 +1,4 @@
#!/usr/bin/env bash
PATH=$PATH:/usr/local/bin
dokku letsencrypt:auto-renew &>>/var/log/dokku/letsencrypt.log

View File

@@ -0,0 +1,41 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-letsencrypt-help() {
declare desc="help command"
declare CMD="$1"
local plugin_name="letsencrypt"
local plugin_description="Manage the letsencrypt integration"
if [[ "$CMD" == "${plugin_name}:help" ]]; then
echo -e "Usage: dokku ${plugin_name}[:COMMAND]"
echo ''
echo "$plugin_description"
echo ''
echo 'Additional commands:'
fn-help-content | sort | column -c2 -t -s,
elif [[ $(ps -o command= $PPID) == *"--all"* ]]; then
fn-help-content
else
cat <<help_desc
$plugin_name, $plugin_description
help_desc
fi
}
fn-help-content() {
declare desc="return help content"
cat <<help_content
letsencrypt:active <app>, Verify if letsencrypt is active for an app
letsencrypt:auto-renew [<app>], Auto-renew app if renewal is necessary
letsencrypt:cleanup <app>, Remove stale certificate directories for app
letsencrypt:cron-job [--add --remove], Add or remove a cron job that periodically calls auto-renew.
letsencrypt:disable <app>, Disable letsencrypt for an app
letsencrypt:enable <app>, Enable or renew letsencrypt for an app
letsencrypt:help, Display letsencrypt help
letsencrypt:list, List letsencrypt-secured apps with certificate expiry times
letsencrypt:revoke <app>, Revoke letsencrypt certificate for app
letsencrypt:set <app> <property> (<value>), Set or clear a letsencrypt property for an app
help_content
}

92
plugins/letsencrypt/install Executable file
View File

@@ -0,0 +1,92 @@
#!/usr/bin/env bash
source "$PLUGIN_AVAILABLE_PATH/config/functions"
source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions"
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
fn-letsencrypt-migrate-properties() {
declare desc="migrates deprecated config variables to property counterpart"
local value
value=$(plugn trigger config-get-global "DOKKU_LETSENCRYPT_EMAIL" || true)
if [[ -n "$value" ]]; then
dokku_log_info1 "Migrating deprecated global DOKKU_LETSENCRYPT_EMAIL to letsencrypt email property."
fn-plugin-property-write "letsencrypt" "--global" "email" "$value"
DOKKU_QUIET_OUTPUT=1 config_unset --global DOKKU_LETSENCRYPT_EMAIL || true
fi
value=$(plugn trigger config-get-global "DOKKU_LETSENCRYPT_GRACEPERIOD" || true)
if [[ -n "$value" ]]; then
dokku_log_info1 "Migrating deprecated global DOKKU_LETSENCRYPT_GRACEPERIOD to letsencrypt graceperiod property."
fn-plugin-property-write "letsencrypt" "--global" "graceperiod" "$value"
DOKKU_QUIET_OUTPUT=1 config_unset --global DOKKU_LETSENCRYPT_GRACEPERIOD || true
fi
value=$(plugn trigger config-get-global "DOKKU_LETSENCRYPT_ARGS" || true)
if [[ -n "$value" ]]; then
dokku_log_info1 "Migrating deprecated global DOKKU_LETSENCRYPT_ARGS to letsencrypt lego-docker-args property."
fn-plugin-property-write "letsencrypt" "--global" "lego-docker-args" "$value"
DOKKU_QUIET_OUTPUT=1 config_unset --global DOKKU_LETSENCRYPT_ARGS || true
fi
value=$(plugn trigger config-get-global "DOKKU_LETSENCRYPT_SERVER" || true)
if [[ -n "$value" ]]; then
dokku_log_info1 "Migrating deprecated global DOKKU_LETSENCRYPT_SERVER to letsencrypt server property."
fn-plugin-property-write "letsencrypt" "--global" "server" "$value"
DOKKU_QUIET_OUTPUT=1 config_unset --global DOKKU_LETSENCRYPT_SERVER || true
fi
for app in $(dokku_apps "false"); do
value="$(plugn trigger config-get "$app" DOKKU_LETSENCRYPT_EMAIL || true)"
if [[ -n "$value" ]]; then
dokku_log_info1 "Migrating deprecated DOKKU_LETSENCRYPT_EMAIL to letsencrypt email property for $app."
fn-plugin-property-write "letsencrypt" "$app" "email" "$value"
DOKKU_QUIET_OUTPUT=1 config_unset --no-restart "$app" "DOKKU_LETSENCRYPT_EMAIL" || true
fi
value="$(plugn trigger config-get "$app" DOKKU_LETSENCRYPT_GRACEPERIOD || true)"
if [[ -n "$value" ]]; then
dokku_log_info1 "Migrating deprecated DOKKU_LETSENCRYPT_GRACEPERIOD to letsencrypt graceperiod property for $app."
fn-plugin-property-write "letsencrypt" "$app" "graceperiod" "$value"
DOKKU_QUIET_OUTPUT=1 config_unset --no-restart "$app" "DOKKU_LETSENCRYPT_GRACEPERIOD" || true
fi
value="$(plugn trigger config-get "$app" DOKKU_LETSENCRYPT_ARGS || true)"
if [[ -n "$value" ]]; then
dokku_log_info1 "Migrating deprecated DOKKU_LETSENCRYPT_ARGS to letsencrypt lego-docker-args property for $app."
fn-plugin-property-write "letsencrypt" "$app" "lego-docker-args" "$value"
DOKKU_QUIET_OUTPUT=1 config_unset --no-restart "$app" "DOKKU_LETSENCRYPT_ARGS" || true
fi
value="$(plugn trigger config-get "$app" DOKKU_LETSENCRYPT_SERVER || true)"
if [[ -n "$value" ]]; then
dokku_log_info1 "Migrating deprecated DOKKU_LETSENCRYPT_SERVER to letsencrypt server property for $app."
fn-plugin-property-write "letsencrypt" "$app" "server" "$value"
DOKKU_QUIET_OUTPUT=1 config_unset --no-restart "$app" "DOKKU_LETSENCRYPT_SERVER" || true
fi
done
}
plugin-install() {
pull-docker-image() {
declare IMAGE="$1"
if [[ "$PLUGIN_DISABLE_PULL" == "true" ]]; then
echo " ! ${PLUGIN_DISABLE_PULL_VARIABLE} environment variable detected. Not running pull command." 1>&2
echo " ! docker pull ${IMAGE}" 1>&2
return
fi
if [[ "$(docker images -q "${IMAGE}" 2>/dev/null)" == "" ]]; then
docker pull "${IMAGE}"
fi
}
pull-docker-image "${PLUGIN_IMAGE}:${PLUGIN_IMAGE_VERSION}"
mkdir -p "${DOKKU_LIB_ROOT}/data/letsencrypt"
chown -R "${DOKKU_SYSTEM_USER}:${DOKKU_SYSTEM_GROUP}" "${DOKKU_LIB_ROOT}/data/letsencrypt"
fn-plugin-property-setup "letsencrypt"
fn-letsencrypt-migrate-properties
}
plugin-install "$@"

View File

@@ -0,0 +1,639 @@
#!/usr/bin/env bash
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
source "$PLUGIN_CORE_AVAILABLE_PATH/certs/functions"
source "$PLUGIN_CORE_AVAILABLE_PATH/domains/functions"
source "$PLUGIN_CORE_AVAILABLE_PATH/nginx-vhosts/functions"
if [[ -f "$PLUGIN_CORE_AVAILABLE_PATH/nginx-vhosts/internal-functions" ]]; then
source "$PLUGIN_CORE_AVAILABLE_PATH/nginx-vhosts/internal-functions"
fi
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
fn-letsencrypt-acme-execute-challenge() {
declare desc="perform actual ACME validation procedure"
declare APP="$1"
local FAKE_NGINX_CONF=false
local challenge_mode config_dir
if [[ ! -f "$DOKKU_ROOT/$APP/nginx.conf" ]]; then
FAKE_NGINX_CONF=true
fi
fn-letsencrypt-create-root "$APP"
challenge_mode="HTTP-01"
dns_provider="$(fn-letsencrypt-computed-dns-provider "$APP")"
if [[ -n "$dns_provider" ]]; then
challenge_mode="DNS-01"
fi
dokku_log_info1 "Getting letsencrypt certificate for ${APP} via ${challenge_mode}"
# read arguments from appropriate config file into the config array
config_dirs="$(fn-letsencrypt-configure-and-get-dir "$APP")"
host_config_dir="$(echo "$config_dirs" | cut -d: -f1)"
container_config_dir="$(echo "$config_dirs" | cut -d: -f2)"
read -r -a config <"$container_config_dir/config"
# run letsencrypt as a docker container using "certonly" mode
# port 80 of the standalone webserver will be forwarded by the proxy
set +e
export DOKKU_UID=$(id -u)
export DOKKU_GID=$(id -g)
mkdir -p "$DOKKU_LIB_ROOT/data/letsencrypt/$APP"
docker run --rm \
--env-file "$host_config_dir/docker.env" \
--user $DOKKU_UID:$DOKKU_GID \
-v "$host_config_dir:/certs" \
-v "$DOKKU_LIB_ROOT/data/letsencrypt/$APP:/webroot" \
"${PLUGIN_IMAGE}:${PLUGIN_IMAGE_VERSION}" \
"${config[@]}" run | sed "s/^/ /"
local exit_code=$?
set -e
if [[ "$FAKE_NGINX_CONF" == "true" ]]; then
rm "$DOKKU_ROOT/$APP/nginx.conf"
fi
if [[ $exit_code != 0 ]]; then
dokku_log_info1 "Certificate retrieval failed!"
return $exit_code
fi
# got certificate
dokku_log_info1 "Certificate retrieved successfully."
fn-letsencrypt-symlink-certs "$APP" "$container_config_dir"
plugn trigger proxy-build-config "$APP"
}
fn-letsencrypt-acme-revoke() {
declare desc="perform actual certificate revocation"
local APP="$1"
fn-letsencrypt-create-root "$APP"
challenge_mode="HTTP-01"
dns_provider="$(fn-letsencrypt-computed-dns-provider "$APP")"
if [[ -n "$dns_provider" ]]; then
challenge_mode="DNS-01"
fi
dokku_log_info1 "Revoking letsencrypt certificate for ${APP} via ${challenge_mode}"
local acme_port="$(plugn trigger ports-get-available)"
if [[ -z "$acme_port" ]]; then
acme_port="$(get_available_port)"
fi
# read arguments from appropriate config file into the config array
config_dirs="$(fn-letsencrypt-configure-and-get-dir "$APP")"
host_config_dir="$(echo "$config_dirs" | cut -d: -f1)"
container_config_dir="$(echo "$config_dirs" | cut -d: -f2)"
read -r -a config <"$container_config_dir/config"
# run letsencrypt as a docker container using "certonly" mode
# port 80 of the standalone webserver will be forwarded by the proxy
set +e
export DOKKU_UID=$(id -u)
export DOKKU_GID=$(id -g)
mkdir -p "$DOKKU_LIB_ROOT/data/letsencrypt/$APP"
docker run --rm \
--env-file "$host_config_dir/docker.env" \
--user $DOKKU_UID:$DOKKU_GID \
-p "$acme_port:$acme_port" \
-v "$host_config_dir:/certs" \
-v "$DOKKU_LIB_ROOT/data/letsencrypt/$APP:/webroot" \
"${PLUGIN_IMAGE}:${PLUGIN_IMAGE_VERSION}" \
"${config[@]}" revoke | sed "s/^/ /"
local exit_code=$?
set -e
# handle return codes
if [[ $exit_code == 0 ]]; then
# certificate revoked
dokku_log_info1 "Certificate revoked successfully."
else
# error - don't try to link certificates
dokku_log_info1 "Certificate revocation failed (code $simple_result)!"
return
fi
local domain="$(get_app_domains "$APP" | xargs | awk '{print $1}')"
# removing the certificate will automatically reconfigure nginx
if [[ -z $DOKKU_APP_NAME ]]; then
dokku certs:remove "$APP"
else
dokku certs:remove
fi
}
fn-letsencrypt-acme-proxy-disable() {
declare desc="disable ACME proxy for an app"
local APP="$1"
local app_root="$DOKKU_ROOT/$APP"
local app_config_dir="$app_root/nginx.conf.d"
dokku_log_info1 "Disabling ACME proxy for $APP..."
[[ -f "$app_config_dir/letsencrypt.conf" ]] && rm "$app_config_dir/letsencrypt.conf"
restart_nginx | sed "s/^/ /"
}
fn-letsencrypt-acme-proxy-enable() {
declare desc="enable ACME proxy for an app"
local APP="$1"
local app_root="$DOKKU_ROOT/$APP"
local app_config_dir="$app_root/nginx.conf.d"
dokku_log_info1 "Enabling ACME proxy for ${APP}..."
# ensure the nginx.conf.d directory exists
[[ -d "$app_config_dir" ]] || mkdir "$app_config_dir"
# generate letsencrypt config
sigil -f "$PLUGIN_AVAILABLE_PATH/letsencrypt/templates/letsencrypt.conf.sigil" \
APP="$APP" \
DOKKU_LIB_ROOT="$DOKKU_LIB_ROOT" \
>"$app_config_dir/letsencrypt.conf"
restart_nginx | sed "s/^/ /"
}
fn-letsencrypt-computed-dns-provider() {
declare desc="get configured dns provider"
declare APP="$1"
value="$(fn-letsencrypt-dns-provider "$APP")"
if [[ -z "$value" ]]; then
value="$(fn-letsencrypt-global-dns-provider)"
fi
echo "$value"
}
fn-letsencrypt-global-dns-provider() {
declare desc="get configured dns provider"
fn-plugin-property-get-default "letsencrypt" "--global" "dns-provider" ""
}
fn-letsencrypt-dns-provider() {
declare desc="get configured dns provider"
declare APP="$1"
fn-plugin-property-get-default "letsencrypt" "$APP" "dns-provider" ""
}
fn-letsencrypt-computed-lego-docker-args() {
declare desc="get configured lego docker args"
declare APP="$1"
value="$(fn-letsencrypt-lego-docker-args "$APP")"
if [[ -z "$value" ]]; then
value="$(fn-letsencrypt-global-lego-docker-args)"
fi
echo "$value"
}
fn-letsencrypt-global-lego-docker-args() {
declare desc="get configured lego docker args"
fn-plugin-property-get-default "letsencrypt" "--global" "lego-docker-args" ""
}
fn-letsencrypt-lego-docker-args() {
declare desc="get configured lego docker args"
declare APP="$1"
fn-plugin-property-get-default "letsencrypt" "$APP" "lego-docker-args" ""
}
fn-letsencrypt-check-email() {
declare desc="Check if an e-mail address is provided globally or for the app"
declare APP="$1"
# check we have a valid e-mail address
if [[ -z "$(fn-letsencrypt-computed-email "$APP")" ]]; then
dokku_log_warn "ERROR: Cannot request a certificate without an e-mail address!"
dokku_log_warn " please provide your e-mail address using"
dokku_log_warn " dokku letsencrypt:set $APP email <e-mail>"
return 1
fi
}
fn-letsencrypt-configure-and-get-dir() {
declare desc="assemble lego command line arguments and create a config hash directory for them"
declare APP="$1"
local config config_dir config_hash dns_provider domain_args domains email extra_args cert_timeout key server value
local app_root="$DOKKU_ROOT/$APP"
local le_root="$app_root/letsencrypt"
mkdir -p "$DOKKU_ROOT/$APP/letsencrypt/account"
# build up a string of all certificate-controlling configuration settings.
# this will be used to determine the folder name for the account key and certificates
# get the selected ACME server
server="$(fn-letsencrypt-computed-server "$APP")"
# construct domain arguments
domains="$(get_app_domains "$APP")"
domain_args=''
for domain in $domains; do
dokku_log_verbose " - Domain '$domain'" >&2
domain_args="$domain_args --domains $domain"
done
# lego --cert.timeout is in seconds and defaults to 30
cert_timeout=30
email="$(fn-letsencrypt-computed-email "$APP")"
extra_args="$(fn-letsencrypt-computed-lego-docker-args "$APP")"
config="--pem --accept-tos --cert.timeout $cert_timeout --path /certs --server $server --email $email $extra_args $domain_args"
dns_provider="$(fn-letsencrypt-computed-dns-provider "$APP")"
if [[ -n "$dns_provider" ]]; then
config="--dns $dns_provider $config"
else
config="--http --http.webroot /webroot $config"
fi
config_hash=$(echo "$config" | sha1sum | awk '{print $1}')
config_dir="$le_root/certs/$config_hash"
mkdir -p "$config_dir"
rm -f "$config_dir/docker.env"
touch "$config_dir/docker.env"
if [[ -n "$dns_provider" ]]; then
fn-plugin-property-get-all "letsencrypt" "--global" | while read -r line; do
[[ -n "$line" ]] || continue
key="$(cut -d" " -f1 <<<"$line")"
if [[ "$key" == dns-provider-* ]]; then
value="$(cut -d" " -f2 <<<"$line")"
echo "${key#"dns-provider-"}=$value" >>"$config_dir/docker.env"
fi
done
fn-plugin-property-get-all "letsencrypt" "$APP" | while read -r line; do
[[ -n "$line" ]] || continue
key="$(cut -d" " -f1 <<<"$line")"
if [[ "$key" == dns-provider-* ]]; then
value="$(cut -d" " -f2 <<<"$line")"
echo "${key#"dns-provider-"}=$value" >>"$config_dir/docker.env"
fi
done
fi
# ensure the permissions are set correctly on anything that may expose api keys
chmod 0755 "$config_dir/docker.env"
# store config settings
echo "$config" >"$config_dir/config"
# send both host and container path
# to respect mapped DOKKU_ROOT when running in a container
echo "$DOKKU_HOST_ROOT/$APP/letsencrypt/certs/$config_hash:$config_dir"
}
fn-letsencrypt-cron-job-enabled() {
declare desc="Check if the cron plugin is available"
if [[ ! -f "$PLUGIN_AVAILABLE_PATH/cron/cron-write" ]]; then
return 1
fi
}
fn-letsencrypt-cron-job-add() {
declare desc="Add auto-renew cronjob to dokku user's crontab"
touch "${DOKKU_LIB_ROOT}/data/letsencrypt/autorenew"
if fn-letsencrypt-cron-job-enabled; then
plugn trigger cron-write
else
crons="$(crontab -l || true)"
crons="$(grep -v -F "$LETSENCRYPT_CRON_CMD" <<<"$crons")"
printf "%s\n%s\n" "$crons" "$LETSENCRYPT_CRON_JOB" | crontab -
fi
dokku_log_info1 "Added cron job to dokku's crontab."
}
fn-letsencrypt-cron-job-remove() {
declare desc="Remove auto-renew cronjob from dokku user's crontab"
rm -f "${DOKKU_LIB_ROOT}/data/letsencrypt/autorenew"
if fn-letsencrypt-cron-job-enabled; then
plugn trigger cron-write
else
crons="$(crontab -l || true)"
crons="$(grep -v -F "$LETSENCRYPT_CRON_CMD" <<<"$crons")"
printf "%s" "$crons" | crontab -
fi
dokku_log_info1 "Removed cron job from dokku's crontab."
}
fn-letsencrypt-create-root() {
declare desc="Ensure the let's encrypt root directory exists"
declare APP="$1"
local NGINX_ACCESS_LOG_FORMAT NGINX_ACCESS_LOG_PATH NGINX_ERROR_LOG_PATH
local app_root="$DOKKU_ROOT/$APP"
local le_root="$app_root/letsencrypt"
mkdir -p "$le_root"
if [[ ! -f "$DOKKU_ROOT/$APP/nginx.conf" ]]; then
dokku_log_info1 "Setting temporary site"
NGINX_ACCESS_LOG_FORMAT="$(fn-nginx-access-log-format "$APP")"
NGINX_ACCESS_LOG_PATH="$(fn-nginx-access-log-path "$APP")"
NGINX_ERROR_LOG_PATH="$(fn-nginx-error-log-path "$APP")"
if [[ -z "$NGINX_ACCESS_LOG_FORMAT" ]]; then
NGINX_ACCESS_LOG_FORMAT="$(fn-nginx-computed-access-log-format "$APP")"
fi
if [[ -z "$NGINX_ACCESS_LOG_PATH" ]]; then
NGINX_ACCESS_LOG_PATH="$(fn-nginx-computed-access-log-path "$APP")"
fi
if [[ -z "$NGINX_ERROR_LOG_PATH" ]]; then
NGINX_ERROR_LOG_PATH="$(fn-nginx-computed-error-log-path "$APP")"
fi
sigil -f "$PLUGIN_AVAILABLE_PATH/letsencrypt/templates/default-nginx.conf.sigil" \
DOMAINS="$(get_app_domains "$APP" | xargs)" DOKKU_ROOT="$DOKKU_ROOT" APP="$APP" \
NGINX_ACCESS_LOG_FORMAT="$NGINX_ACCESS_LOG_FORMAT" NGINX_ACCESS_LOG_PATH="$NGINX_ACCESS_LOG_PATH" \
NGINX_ERROR_LOG_PATH="$NGINX_ERROR_LOG_PATH" \
>"$DOKKU_ROOT/$APP/nginx.conf"
restart_nginx | sed "s/^/ /"
fi
}
fn-letsencrypt-is-autorenew-enabled() {
declare desc="check if autorenew is enabled"
local enabled=false
if [[ -f "${DOKKU_LIB_ROOT}/data/letsencrypt/autorenew" ]]; then
enabled=true
fi
echo "$enabled"
}
fn-letsencrypt-computed-email() {
declare desc="get configured email address"
declare APP="$1"
value="$(fn-letsencrypt-email "$APP")"
if [[ -z "$value" ]]; then
value="$(fn-letsencrypt-global-email)"
fi
echo "$value"
}
fn-letsencrypt-global-email() {
declare desc="get configured email address"
fn-plugin-property-get-default "letsencrypt" "--global" "email" ""
}
fn-letsencrypt-email() {
declare desc="get configured email address"
declare APP="$1"
fn-plugin-property-get-default "letsencrypt" "$APP" "email" ""
}
fn-letsencrypt-enable() {
declare APP="$1"
local EXIT_CODE=0
local domain
verify_app_name "$APP"
domain="$(get_app_domains "$APP" | xargs | awk '{print $1}')"
if [[ -z "$domain" ]]; then
dokku_log_warn "No domains detected for $APP"
return 1
fi
dokku_log_info2 "Enabling letsencrypt for $APP"
fn-letsencrypt-check-email "$APP"
fn-letsencrypt-acme-proxy-enable "$APP"
fn-letsencrypt-acme-execute-challenge "$APP" || EXIT_CODE=$? # remove ACME proxy even if this fails
fn-letsencrypt-acme-proxy-disable "$APP"
if [[ "$EXIT_CODE" == 0 ]]; then
dokku_log_info1 "Done"
return
fi
dokku_log_warn "Failed to setup letsencrypt"
DOKKU_FAIL_EXIT_CODE="$EXIT_CODE" dokku_log_fail "Check log output for further information on failure"
}
fn-letsencrypt-expiration() {
declare desc="prints expiration time"
declare APP="$1"
if [[ -f "$DOKKU_ROOT/$APP/tls/server.letsencrypt.crt" ]]; then
date -u -d "$(openssl x509 -in "$DOKKU_ROOT/$APP/tls/server.letsencrypt.crt" -enddate -noout | sed -e "s/^notAfter=//")" "+%s"
else
date -u -d "$(openssl x509 -in "$DOKKU_ROOT/$APP/tls/server.crt" -enddate -noout | sed -e "s/^notAfter=//")" "+%s"
fi
}
fn-letsencrypt-computed-graceperiod() {
declare desc="get configured graceperiod"
declare APP="$1"
local value
value="$(fn-letsencrypt-graceperiod "$APP")"
if [[ -z "$value" ]]; then
value="$(fn-letsencrypt-global-graceperiod)"
fi
if [[ -z "$value" ]]; then
value="$((60 * 60 * 24 * 30))"
fi
echo "$value"
}
fn-letsencrypt-global-graceperiod() {
declare desc="get configured graceperiod"
fn-plugin-property-get-default "letsencrypt" "--global" "graceperiod" ""
}
fn-letsencrypt-graceperiod() {
declare desc="get configured graceperiod"
declare APP="$1"
fn-plugin-property-get-default "letsencrypt" "$APP" "graceperiod" ""
}
fn-letsencrypt-format-timediff() {
declare desc="format a time difference in seconds into a human-readable string"
local td="$1"
local negative_td=0
if [ "$td" -lt 0 ]; then
negative_td=1
td=$((-td))
fi
local days=$((td / (24 * 60 * 60)))
td=$((td % (24 * 60 * 60)))
local hours=$((td / (60 * 60)))
td=$((td % (60 * 60)))
local minutes=$((td / 60))
local secs=$((td % 60))
local res=""
if [ $days -gt 0 ]; then
res="${days}d, "
fi
if [ $hours -gt 0 ]; then
res="${res}${hours}h, "
fi
if [ $minutes -gt 0 ]; then
res="${res}${minutes}m, "
fi
if [ $secs -gt 0 ]; then
res="${res}${secs}s, "
fi
# remove trailing comma
res="$(echo "$res" | sed -re 's/, ?$//g')"
if [[ $negative_td == 1 ]]; then
res="${res} ago"
fi
echo "$res"
}
fn-letsencrypt-is-active() {
declare desc="outputs true if active, false otherwise"
declare APP=$1
local domain
# check if SSL is enabled on per-app level
if ! is_ssl_enabled "$APP"; then
echo "false"
return
fi
domain="$(get_app_domains "$APP" | xargs | awk '{print $1}')"
# check if certificate is identical to the current let's encrypt certificate by comparing SHA1 hashes
local cert_sha1
if [[ -f "$DOKKU_ROOT/$APP/tls/server.letsencrypt.crt" ]]; then
cert_sha1=$( (cat "$DOKKU_ROOT/$APP/tls/server.letsencrypt.crt" 2>/dev/null) | sha1sum || echo "not_found")
else
cert_sha1=$( (cat "$DOKKU_ROOT/$APP/tls/server.crt" 2>/dev/null) | sha1sum || echo "not_found")
fi
local le_sha1="not_found"
local fileSafeDomain
fileSafeDomain="${domain/\*/_}" # wildcards are using *.example.com which have certificates named _.example.com
if [[ -f "$DOKKU_ROOT/$APP/letsencrypt/certs/current/certificates/$fileSafeDomain.pem" ]]; then
le_sha1=$( (cat "$DOKKU_ROOT/$APP/letsencrypt/certs/current/certificates/$fileSafeDomain.crt" 2>/dev/null) | sha1sum || echo "not_found")
elif [[ -f "$DOKKU_ROOT/$APP/letsencrypt/certs/current/fullchain.pem" ]]; then
le_sha1=$( (cat "$DOKKU_ROOT/$APP/letsencrypt/certs/current/fullchain.pem" 2>/dev/null) | sha1sum || echo "not_found")
fi
if [[ "$cert_sha1" != "$le_sha1" ]]; then
echo "false"
return
fi
echo "true"
}
fn-letsencrypt-list-apps-with-expiry() {
declare desc="list all letsencrypt-secured apps together with their expiry date"
# prints a tab-separated list of
# * app name
# * expiry dates as UNIX timestamp (seconds since epoch)
# * selected renewal grace period (in seconds)
# * time left on certificate (in seconds)
# * time until renewal (in seconds)
for APP in $(dokku_apps); do
if [[ "$APP" == "=====>" ]] || [[ "$APP" == "My" ]] || [[ "$APP" == "Apps" ]]; then continue; fi
if [[ "$(fn-letsencrypt-is-active "$APP")" == "true" ]]; then
local expiry=$(fn-letsencrypt-expiration "$APP")
local grace_period="$(fn-letsencrypt-computed-graceperiod "$APP")"
local time_to_expiry=$((expiry - $(date +%s)))
local time_to_renewal=$((expiry - grace_period - $(date +%s)))
echo -e "$APP\t$expiry\t$grace_period\t$time_to_expiry\t$time_to_renewal"
fi
done
}
fn-letsencrypt-computed-server() {
declare desc="get configured server"
declare APP="$1"
value="$(fn-letsencrypt-server "$APP")"
if [[ -z "$value" ]]; then
value="$(fn-letsencrypt-global-server)"
fi
if [[ -z "$value" ]] || [[ "$value" == "default" ]]; then
value="https://acme-v02.api.letsencrypt.org/directory"
elif [[ "$value" == "staging" ]]; then
value="https://acme-staging-v02.api.letsencrypt.org/directory"
fi
echo "$value"
}
fn-letsencrypt-global-server() {
declare desc="get configured server"
fn-plugin-property-get-default "letsencrypt" "--global" "server" ""
}
fn-letsencrypt-server() {
declare desc="get configured server"
declare APP="$1"
fn-plugin-property-get-default "letsencrypt" "$APP" "server" ""
}
fn-letsencrypt-symlink-certs() {
declare desc="symlink let's encrypt certificates so they can be found by dokku"
declare APP="$1" config_dir="$2"
local app_root="$DOKKU_ROOT/$APP"
local le_root="$app_root/letsencrypt"
local domain
dokku_log_info1 "Installing let's encrypt certificates"
# link the current config directory to 'current'
ln -nsf "$config_dir" "$le_root/certs/current"
# install the let's encrypt certificate for the app
unset DOKKU_APP_NAME
domain="$(get_app_domains "$APP" | xargs | awk '{print $1}')"
local fileSafeDomain
fileSafeDomain="${domain/\*/_}" # wildcards are using *.example.com which have certificates named _.example.com
dokku certs:add "$APP" "$config_dir/certificates/$fileSafeDomain.pem" "$config_dir/certificates/$fileSafeDomain.key"
rm -f "$app_root/tls/server.letsencrypt.crt" "$app_root/tls/server.crt"
cp "$config_dir/certificates/$fileSafeDomain.crt" "$app_root/tls/server.letsencrypt.crt"
cp "$config_dir/certificates/$fileSafeDomain.crt" "$app_root/tls/server.crt"
}

View File

@@ -0,0 +1,5 @@
[plugin]
description = "Automated installation of let's encrypt TLS certificates"
version = "0.20.4"
sponsors = ["orca-scan"]
[plugin.config]

View File

@@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
trigger-letsencrypt-post-app-clone-setup() {
declare desc="removes cert file when setting up a clone"
declare trigger="post-app-clone-setup"
declare OLD_APP="$1" NEW_APP="$2"
local APP_ROOT="$DOKKU_ROOT/$NEW_APP"
rm -rf "$APP_ROOT/letsencrypt"
}
trigger-letsencrypt-post-app-clone-setup "$@"

View File

@@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
trigger-letsencrypt-post-app-rename-setup() {
declare desc="removes cert file when setting up a rename"
declare trigger="post-app-clone-setup"
declare OLD_APP="$1" NEW_APP="$2"
local APP_ROOT="$DOKKU_ROOT/$NEW_APP"
rm -rf "$APP_ROOT/letsencrypt"
}
trigger-letsencrypt-post-app-rename-setup "$@"

14
plugins/letsencrypt/post-delete Executable file
View File

@@ -0,0 +1,14 @@
#!/usr/bin/env bash
source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
trigger-letsencrypt-post-delete() {
declare desc="destroys the letsencrypt properties for a given app"
declare trigger="post-delete"
declare APP="$1"
fn-plugin-property-destroy "letsencrypt" "$APP"
}
trigger-letsencrypt-post-delete "$@"

View File

@@ -0,0 +1,16 @@
#!/usr/bin/env bash
source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
trigger-letsencrypt-post-domains-update() {
declare desc="updates cert file when domains are added or set"
declare trigger="post-domains-update"
declare APP="$1" ACTION="$2"
if [[ "$ACTION" == "add" ]] || [[ "$ACTION" == "set" ]]; then
dokku_log_warn "Please run dokku letsencrypt:enable to add https support to the new domain"
fi
}
trigger-letsencrypt-post-domains-update "$@"

6
plugins/letsencrypt/report Executable file
View File

@@ -0,0 +1,6 @@
#!/usr/bin/env bash
source "$PLUGIN_AVAILABLE_PATH/letsencrypt/command-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-letsencrypt-report-single "$@"

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env bash
source "$PLUGIN_AVAILABLE_PATH/letsencrypt/command-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-letsencrypt-active "$@"

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env bash
source "$PLUGIN_AVAILABLE_PATH/letsencrypt/command-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-letsencrypt-auto-renew "$@"

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env bash
source "$PLUGIN_AVAILABLE_PATH/letsencrypt/command-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-letsencrypt-cleanup "$@"

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env bash
source "$PLUGIN_AVAILABLE_PATH/letsencrypt/command-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-letsencrypt-cron-job "$@"

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env bash
source "$PLUGIN_AVAILABLE_PATH/letsencrypt/help-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-letsencrypt-help "letsencrypt:help"

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env bash
source "$PLUGIN_AVAILABLE_PATH/letsencrypt/command-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-letsencrypt-disable "$@"

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env bash
source "$PLUGIN_AVAILABLE_PATH/letsencrypt/command-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-letsencrypt-enable "$@"

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env bash
source "$PLUGIN_AVAILABLE_PATH/letsencrypt/command-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-letsencrypt-list "$@"

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env bash
source "$PLUGIN_AVAILABLE_PATH/letsencrypt/command-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-letsencrypt-report "$@"

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env bash
source "$PLUGIN_AVAILABLE_PATH/letsencrypt/command-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-letsencrypt-revoke "$@"

View File

@@ -0,0 +1,6 @@
#!/usr/bin/env bash
source "$PLUGIN_AVAILABLE_PATH/letsencrypt/command-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
cmd-letsencrypt-set "$@"

View File

@@ -0,0 +1,7 @@
server {
listen 80;
server_name {{ .DOMAINS }};
include {{ $.DOKKU_ROOT }}/{{ $.APP }}/nginx.conf.d/*.conf;
access_log {{ $.NGINX_ACCESS_LOG_PATH }}{{ if and ($.NGINX_ACCESS_LOG_FORMAT) (ne $.NGINX_ACCESS_LOG_PATH "off") }} {{ $.NGINX_ACCESS_LOG_FORMAT }}{{ end }};
error_log {{ $.NGINX_ERROR_LOG_PATH }};
}

View File

@@ -0,0 +1,8 @@
location /.well-known/acme-challenge {
# allow every ip address
allow all;
# disable http-auth for /.well-known/acme-challenge
auth_basic off;
root {{ .DOKKU_LIB_ROOT }}/data/letsencrypt/{{ .APP }};
}

18
plugins/letsencrypt/uninstall Executable file
View File

@@ -0,0 +1,18 @@
#!/usr/bin/env bash
source "$PLUGIN_CORE_AVAILABLE_PATH/common/property-functions"
set -eo pipefail
[[ $DOKKU_TRACE ]] && set -x
trigger-letsencrypt-uninstall() {
declare desc="destroys all letsencrypt properties"
declare trigger="uninstall"
declare PLUGIN="$1"
if [[ "$PLUGIN" != "$PLUGIN_COMMAND_PREFIX" ]]; then
return
fi
fn-plugin-property-destroy "letsencrypt" "_all_"
}
trigger-letsencrypt-uninstall "$@"

1
plugins/letsencrypt/update Symbolic link
View File

@@ -0,0 +1 @@
install

View File

@@ -5,8 +5,4 @@
# Procfile for development using the new threaded worker (scheduler, twitter stream and delayed job)
cron: node worker.js
web: node web.js
<<<<<<< HEAD=0
worker: node worker.js
========0
worker: node worker-2.js
>>>>>>> 5bb7ec3e2a8f4f6565432a4fc82c92d4a3603d28=0