Compare commits

...

241 Commits

Author SHA1 Message Date
066a765a4d Merge pull request #1048 from jc21/develop
static-content <- develop
2021-04-29 21:36:38 +10:00
f2f653e345 Remove platform specific determination 2021-04-29 11:19:59 +10:00
b39a59ce72 Merge pull request #1045 from jc21/nginx-full
Switch to nginx-full base
2021-04-29 09:29:46 +10:00
9872daf29f Switch to nginx-full base 2021-04-29 08:28:40 +10:00
ba5f0c212c Bumped version 2021-03-17 14:56:22 +10:00
4eddb5d7f3 Merge pull request #769 from jc21/dependabot/npm_and_yarn/frontend/ini-1.3.8
Bump ini from 1.3.5 to 1.3.8 in /frontend
2021-03-17 14:54:45 +10:00
3b104710d5 Restrict redirect http code 2021-03-17 14:53:57 +10:00
74db0004bd Merge pull request #883 from baruffaldi/master
Forward scheme and http code added for redirection hosts
2021-03-17 11:30:26 +10:00
6e67352a0f Merge pull request #880 from jlesage/jwt-key-pair-no-restart
Removed the need to restart Nginx Proxy Manager after generating JWT key pair
2021-03-17 11:09:37 +10:00
b127f02468 Added upgrade steps 2021-03-17 08:58:45 +10:00
c9c53d9670 Merge pull request #903 from ogarai/edit-disabled-host
Prevent activating nginx config after editing disabled proxy host
2021-03-17 08:32:14 +10:00
d36dbb868b Merge pull request #942 from nielscil/develop-certbot-dns-transip
Added TransIP dns provider
2021-03-17 08:30:05 +10:00
b7fb2cfe92 Merge pull request #947 from phantomski77/patch-2
Update _hsts.conf template to increase HSTS max-age value
2021-03-17 08:29:46 +10:00
d0a0c77556 acmedns indentation fix 2021-03-16 13:36:17 +01:00
9469b9c78a fixed closing brace 2021-03-16 12:57:26 +01:00
e4988f34c7 Merge pull request #943 from phantomski77/patch-1
Update README.md
2021-03-16 19:51:03 +10:00
1fe9e24f0a Merge pull request #920 from jc21/dependabot/npm_and_yarn/docs/prismjs-1.23.0
Bump prismjs from 1.21.0 to 1.23.0 in /docs
2021-03-16 19:50:28 +10:00
9c39de3454 Merge pull request #935 from jc21/dependabot/npm_and_yarn/frontend/elliptic-6.5.4
Bump elliptic from 6.5.3 to 6.5.4 in /frontend
2021-03-16 19:50:14 +10:00
9bb68ad4eb Merge pull request #937 from jc21/dependabot/npm_and_yarn/docs/elliptic-6.5.4
Bump elliptic from 6.5.3 to 6.5.4 in /docs
2021-03-16 19:49:59 +10:00
5bf774bee1 Merge branch 'develop' into develop-certbot-dns-transip 2021-03-16 19:49:27 +10:00
99514464fb Merge pull request #946 from ijaron/dns-acme
Add ACME-DNS certbot plugin
2021-03-16 19:48:13 +10:00
3bf1c1e531 Merge pull request #948 from GurjinderSingh/patch-1
Update README.md
2021-03-16 13:59:04 +10:00
e2e31094aa Fix CI, no var needed for this 2021-03-16 11:32:38 +10:00
f29ff15167 Update README.md
found it in config file was going to add option
	# Custom
	include /data/nginx/custom/http_top[.]conf;
 
b7b808d98d/docker/rootfs/etc/nginx/nginx.conf (L72)
2021-03-14 05:13:26 -04:00
1c64252015 Update _hsts.conf template
I propose the change to max-age value of HSTS from 1 year to 2 years.
2021-03-13 12:40:47 +00:00
ead19915f3 Add ACME-DNS certbot plugin
uses https://github.com/pan-net-security/certbot-dns-acmedns in to implement ACME-DNS support.
Requires file in /data with following layout
# cat /data/acme-registration.json
{
  "something.acme.com": {
    "username": "6e14735c-2c6a-447e-b63d-a23ac4438bd7",
    "password": "dd6gnYS-IxrQfDLbdPRX3hrFhS_SLrwbS0kSl_i8",
    "fulldomain": "3b750a0e-c627-423f-9966-4799c6a9533b.auth.example.org",
    "subdomain": "3b750a0e-c627-423f-9966-4799c6a9533b",
    "allowfrom": []
  }
}
2021-03-12 14:24:56 -06:00
967e0dd98a Update README.md
Secrets need both global and local per-service declaration, otherwise fails as "not found". Local configuration added for both app and db.
2021-03-11 21:59:26 +00:00
bd0324dba0 credentials property naming for transip
credentials property naming for transip
2021-03-11 15:44:20 +01:00
607fb83a1e Added transIP certbot dns plugin 2021-03-11 13:33:42 +01:00
bb5fc58f3a Revert "Fix CI, use docker for a yarn command"
This reverts commit afbec0aca9.
2021-03-11 09:29:08 +10:00
afbec0aca9 Fix CI, use docker for a yarn command 2021-03-11 08:41:10 +10:00
1e5d9dfbff Bump elliptic from 6.5.3 to 6.5.4 in /docs
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.3 to 6.5.4.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.3...v6.5.4)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-10 10:39:22 +00:00
cfcb657cde Bump elliptic from 6.5.3 to 6.5.4 in /frontend
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.3 to 6.5.4.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.3...v6.5.4)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-10 08:19:41 +00:00
7f243e6f06 Bump prismjs from 1.21.0 to 1.23.0 in /docs
Bumps [prismjs](https://github.com/PrismJS/prism) from 1.21.0 to 1.23.0.
- [Release notes](https://github.com/PrismJS/prism/releases)
- [Changelog](https://github.com/PrismJS/prism/blob/master/CHANGELOG.md)
- [Commits](https://github.com/PrismJS/prism/compare/v1.21.0...v1.23.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-01 21:06:50 +00:00
7e7032c051 Prevent activating nginx config after editing disabled proxy host 2021-02-18 23:05:30 +00:00
b7b808d98d Merge pull request #899 from jc21/develop
Docs for a docker network
2021-02-17 20:56:10 +10:00
a21289bf11 Docs for a docker network 2021-02-17 20:55:12 +10:00
4a8d01224a Merge pull request #897 from jc21/develop
Update github templates and docs
2021-02-17 20:19:49 +10:00
f0eebc43e3 Update github templates and docs 2021-02-17 20:18:12 +10:00
49fbf031d1 $scheme is displayed as "auto" on hosts listing 2021-02-10 19:43:29 +01:00
4060718e5c Hotfix: This group of assignments is not aligned 2021-02-10 18:26:19 +01:00
49b0f11ae7 migrations 2021-02-10 18:13:12 +01:00
9b83d35ef4 Migrations 2021-02-10 18:12:02 +01:00
eb20add0c7 Migrations hotfix 2021-02-10 17:16:33 +01:00
1f122e9145 Database migrations created 2021-02-10 15:57:28 +01:00
329d0ecaed Added forward scheme to table columns 2021-02-10 15:27:11 +01:00
77a2ee948a Updated list table
Added state code columns
Better form columns sizing
2021-02-10 15:25:19 +01:00
ebeda6345e Globalization done
Ready to merge
2021-02-10 14:11:01 +01:00
e35138ebed Added scheme to definitions 2021-02-10 10:42:34 +01:00
8ba6c4f7e7 Added http_code definition 2021-02-10 09:55:41 +01:00
6df7b72e08 Forward scheme and http code added for redirection hosts
You can now configure the forward_scheme and forward_http_code on user interface (section redirection hosts)
2021-02-09 11:23:15 +01:00
fe13b12f43 Removed the need to restart Nginx Proxy Manager after generating JWT key pair. 2021-02-08 17:36:58 -05:00
ea28da90b2 Merge pull request #876 from jc21/develop
v2.8.0
2021-02-08 15:01:31 +10:00
5a3d32db7b Frontend form and marionette modifications to allow static hosts and locations 2021-02-08 12:49:49 +10:00
8de118d875 Backend schema and migration modifications for allowing static hosts and locations 2021-02-08 12:47:00 +10:00
f61ab55b52 Added a restart dev script which destroys then starts dev 2021-02-08 12:47:00 +10:00
b243324c65 Added contributors and bumped version 2021-02-08 12:23:52 +10:00
a2dde00f40 Merge pull request #868 from lorisbergeron/eurodns-as-provider
Add EuroDNS as a DNS provider
2021-02-08 12:22:33 +10:00
5ff07faa7e Merge pull request #872 from ahgraber/master
Add Docker secrets
2021-02-08 11:59:23 +10:00
272c652847 Updated docs to use latest mariadb-aria image 2021-02-08 11:56:21 +10:00
3964bbf3fe update advanced-config/readme with secrets 2021-02-07 19:12:20 -05:00
11175aaa5f revert docker/dev before PR 2021-02-07 09:52:37 -05:00
7fcc4a7ef0 cleanup 2021-02-06 20:05:40 -05:00
5abb9458c7 fix linebreaks in secrets 2021-02-05 23:47:30 -05:00
0ca5587a6f EuroDNS fix key-spacing error 2021-02-05 23:40:31 +01:00
d29650882b EuroDNS fix key-spacing error 2021-02-05 23:29:01 +01:00
9c3a7b02ec Add EuroDNS as a DNS provider 2021-02-05 23:17:51 +01:00
ef3a073af5 local builds & secrets 2021-02-05 16:52:24 -05:00
15c4857a4b fix /docker/dev/docker-compose.yaml 2021-02-04 14:03:17 -05:00
63a71afbc8 beta s6 secrets 2021-02-04 11:25:26 -05:00
64761ee9c6 beta secrets 2021-02-04 11:15:31 -05:00
d6c344b5ec add local docker-compose to build 2021-02-02 08:58:45 -05:00
d27826d10e Merge pull request #850 from MooBaloo/master
Add new custom http.conf above includes for NPM-generated files
2021-02-01 12:10:31 +10:00
4ac52a0e25 Add custom .conf above includes for NPM-generated files.
Added a new clause for custom http_top.conf above the include clauses for NPM-generated files. Allows for more flexibility with adding custom nginx .conf files to NPM

Use case: adding a configuration change needs to be present before other custom configuration files are called and reference configuration from the custom http_top.conf file.
Example: add a new log_format in http_top.conf, then referencing it in a access_log clause in server_proxy.conf.
2021-01-28 05:52:41 -05:00
efa841d75a Merge pull request #842 from Shuro/patch-1
Use configured default page also for IPv6
2021-01-28 08:45:12 +10:00
d1fac583ea Use configured default page also for IPv6
Just a small check for the ipv6 variable, similar to _listen.conf,
so that the configured default page is also delivered on ipv6 requests.
2021-01-25 01:28:50 +01:00
8cb44c7b97 Merge pull request #830 from jc21/develop
Updated version fixes #823
2021-01-18 12:15:20 +10:00
f2293a9dda Merge branch 'master' into develop 2021-01-18 12:15:04 +10:00
da0d1d4a2f Updated version fixes #823 2021-01-18 12:14:13 +10:00
6a8d5e2166 Merge pull request #820 from jc21/develop
v2.7.3
2021-01-12 11:13:35 +10:00
d732665a23 Merge branch 'master' into develop 2021-01-12 09:06:34 +10:00
e0748c9bc7 Bumped version 2021-01-12 09:05:27 +10:00
23573543a3 Bump ini from 1.3.5 to 1.3.8 in /frontend
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.8.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.8)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-06 23:36:33 +00:00
bfb328238e Merge pull request #806 from jc21/develop
v2.7.2
2021-01-07 09:35:35 +10:00
64cc4f57d6 Version bump and acknowledgements 2021-01-06 21:48:10 +10:00
7a3c91c6a4 Merge pull request #804 from lebrou34/master
Add Gandi Live DNS to certbot-dns-plugins.js
2021-01-06 13:14:54 +10:00
508bc62852 Update certbot-dns-plugins.js 2021-01-05 18:47:22 +01:00
59e8446d47 Update certbot-dns-plugins.js 2021-01-05 18:41:26 +01:00
d13596d2f7 Update certbot-dns-plugins.js 2021-01-05 18:35:18 +01:00
9adccfa341 Update certbot-dns-plugins.js 2021-01-05 18:19:27 +01:00
5cc3b53378 Update certbot-dns-plugins.js 2021-01-05 18:11:10 +01:00
b62b0a2fb7 Update certbot-dns-plugins.js 2021-01-05 17:36:47 +01:00
1faac4edf2 Merge pull request #750 from klutchell/klutchell-patch-1
allow custom stream conf
2021-01-03 20:14:55 +10:00
4c60dce169 Merge pull request #796 from lightglitch/patch-1
Fix dead hosts verification count
2021-01-03 20:14:36 +10:00
771f31f44d Merge pull request #770 from jc21/dependabot/npm_and_yarn/backend/ini-1.3.8
Bump ini from 1.3.5 to 1.3.8 in /backend
2021-01-03 20:14:21 +10:00
8bedb95e1d Merge pull request #771 from jc21/dependabot/npm_and_yarn/test/ini-1.3.8
Bump ini from 1.3.5 to 1.3.8 in /test
2021-01-03 20:14:03 +10:00
ac4be08df2 Merge pull request #772 from jc21/dependabot/npm_and_yarn/docs/ini-1.3.8
Bump ini from 1.3.5 to 1.3.8 in /docs
2021-01-03 20:13:49 +10:00
0d6e058e23 Merge pull request #774 from chaptergy/better-custom-certificate-handling
Better custom certificate handling
2021-01-03 12:09:55 +10:00
bee2ceb667 Fix dead hosts verification count 2021-01-02 04:58:14 +00:00
6af13d4f40 Removes explicit privkeytype check and adds passphrase error 2020-12-14 12:08:39 +01:00
9dd0ebd899 Bump ini from 1.3.5 to 1.3.8 in /docs
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.8.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.8)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-12 10:17:11 +00:00
6e97bfa717 Bump ini from 1.3.5 to 1.3.8 in /test
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.8.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.8)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-12 08:23:59 +00:00
07b69f41eb Bump ini from 1.3.5 to 1.3.8 in /backend
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.8.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.8)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-12 08:23:45 +00:00
6bd2ac7d6d Update README.md 2020-12-01 14:24:14 -05:00
528e5ef3bc allow custom stream conf
Allow a top-level custom `stream` configuration file to be loaded.
2020-12-01 14:22:31 -05:00
bc1c50ac92 Added contributor 2020-11-22 21:50:57 +10:00
8c2ab42b75 Merge pull request #738 from WaterCalm/master
add aliyun DNS plugin
2020-11-22 21:48:24 +10:00
62053d15d4 add aliyun DNS plugin
add aliyun DNS plugin
2020-11-22 16:08:56 +08:00
6fed642aba Cypress docker build should be faster and added mkcert for later 2020-11-22 16:57:12 +10:00
72ac549a58 Merge pull request #729 from jc21/develop
v2.7.1
2020-11-18 22:55:47 +10:00
9f38617135 Merge branch 'master' into develop 2020-11-18 22:55:37 +10:00
94eec805df Version bump 2020-11-18 21:46:21 +10:00
05a940e732 Fix instances where config file exists and env vars don't 2020-11-18 21:42:03 +10:00
1c43cc2181 Merge pull request #725 from jc21/develop
v2.7.0
2020-11-18 14:30:44 +10:00
657ee73ff1 Merge branch 'master' into develop 2020-11-18 12:22:34 +10:00
4ee5d993cf Bumped version 2020-11-18 12:21:35 +10:00
70a445e2d7 Merge pull request #704 from chaptergy/allow-setup-without-config-file
Removes the need of a config file and allows db config via environment
2020-11-18 12:18:45 +10:00
2115da210d Merge pull request #694 from chaptergy/visual-indicator-for-certificate-deletion
Adds visual indicator for certificate deletion
2020-11-09 10:10:55 +10:00
540554c4f6 Merge pull request #695 from chaptergy/failed-certificate-autoremove
Adds autoremove of failed certificate creations in DB
2020-11-09 10:10:00 +10:00
1337c50d28 Use latest tag in full setup instructions 2020-11-07 19:37:35 +01:00
c5ceb3b2b1 Removes obsolete file mount 2020-11-07 13:54:18 +01:00
57fc1d8f08 Removes the need of a config file and allows db config via environment 2020-11-07 13:24:01 +01:00
1518ecd1e9 Adds autoremove of failed certificate creations in DB 2020-11-06 12:29:38 +01:00
6be0343918 Adds visual indicator for certificate deletion 2020-11-06 11:51:42 +01:00
cf8812c932 Merge pull request #692 from jc21/develop
v2.6.2
2020-11-06 19:31:10 +10:00
5bc3e474a9 Merge branch 'real_ip' of github.com:jc21/nginx-proxy-manager into develop 2020-11-06 13:21:37 +10:00
13eaa346bc Use remote addr as real ip 2020-11-06 13:21:22 +10:00
d7437cc4a7 Test for real-ip header 2020-11-06 13:17:30 +10:00
ddb3c6590c Version bump 2020-11-06 13:06:15 +10:00
89d6773bda Merge branch 'develop' of github.com:jc21/nginx-proxy-manager into real_ip 2020-11-06 09:18:25 +10:00
3651b9484f Fix for pip install error when there are no plugins to install 2020-11-06 09:17:52 +10:00
2200c950b7 Merge branch 'develop' of github.com:jc21/nginx-proxy-manager into real_ip 2020-11-06 09:12:35 +10:00
14f84f01b5 Merge pull request #687 from chaptergy/allow-additional-dns-challenge-dependencies
Allow additional dns challenge dependencies
2020-11-06 09:02:35 +10:00
cb014027bb Makes sure credentials folder exist every time before saving credentials 2020-11-04 19:31:40 +01:00
32e5155783 Fixes Linting errors 2020-11-03 22:38:09 +01:00
a3159ad59e Converts tabs to spaces 2020-11-03 22:24:03 +01:00
60a40197f1 Always install additional dependencies for dns plugins 2020-11-03 21:59:18 +01:00
7d693a4271 Expands and refactors dns plugin list 2020-11-03 21:28:50 +01:00
f192748bf9 Use x-real-ip header for the real-ip module 2020-10-19 11:40:50 +10:00
96f401cba6 Merge pull request #664 from chaptergy/fixes-expiring-hosts-renewal
Adds certbot plugin installation check on startup
2020-10-19 08:50:44 +10:00
ffd2430160 Merge pull request #666 from MarceloLagos/master
Check key for RSA header otherwise use EC, and output fix.
2020-10-19 08:45:28 +10:00
190cd2d6bb Update certificate.js 2020-10-17 23:46:18 -06:00
7ba58bdbd3 Update certificate.js 2020-10-17 23:27:12 -06:00
08ab62108f Fixes eslint errors 2020-10-17 12:54:38 +02:00
1028de8158 Adds certbot plugin installation check on startup 2020-10-17 12:13:08 +02:00
301499dc52 Merge pull request #659 from jc21/develop
v2.6.1
2020-10-16 15:53:56 +10:00
5c2f13ed8e Merge branch 'master' into develop 2020-10-16 13:44:10 +10:00
e30ad81f69 Updated version 2020-10-16 13:43:13 +10:00
21f36f535f Don't spit out a ; if the preceeding value is empty 2020-10-16 13:41:08 +10:00
c14236823a Merge pull request #656 from chaptergy/fixes-custom-certificate-upload
Fixes custom certificate upload
2020-10-16 08:33:51 +10:00
551a9fe1c6 Fixes custom certificate upload 2020-10-15 14:58:05 +02:00
e3399e1035 Merge pull request #654 from jc21/develop
2.6.0 Release
2020-10-15 15:14:57 +10:00
c413b4af3f Added contributors 2020-10-15 14:06:21 +10:00
dbf5dec23b Bump version 2020-10-15 10:40:01 +10:00
10f0eb17d7 Fix linting errors 2020-10-15 10:33:51 +10:00
e3b680c351 Merge pull request #653 from jmwebslave/dont-pass-auth-header
Pass/Don't Pass Auth Header
2020-10-15 10:10:33 +10:00
0df0545777 Allows auth information from AccessList not to be passed to proxied hosts. Resolves issue #153.
Signed-off-by: James Morgan <jmorgan.au+github@gmail.com>
2020-10-15 10:23:09 +11:00
165bfc9f5f Merge pull request #607 from jc21/dependabot/npm_and_yarn/docs/node-forge-0.10.0
Bump node-forge from 0.9.1 to 0.10.0 in /docs
2020-10-15 08:34:14 +10:00
5830bd73b9 Merge pull request #608 from Philip-Mooney/master
Fix for access list getAll when not granted all permissions
2020-10-15 08:33:58 +10:00
3c4ce839b9 Merge pull request #635 from chaptergy/allow-more-dns-challenges
Allow DNS challenges not just for cloudflare
2020-10-14 19:12:15 +10:00
ac9f052309 Fixes linting errors 2020-10-14 09:55:45 +02:00
049e424957 Adds special case for Route53 2020-10-14 09:20:52 +02:00
07e78aec48 Adds error stack information in prod environment for certificates 2020-10-08 15:30:13 +02:00
3fec135fe5 Fixes ESlint formatting errors 2020-10-08 14:38:19 +02:00
867fe1322b Unifies directory structure in dev and prod containers 2020-10-08 13:38:20 +02:00
95208a50a7 Increases timeouts in front- and backend 2020-10-08 13:21:17 +02:00
514b13fcc2 Fixes build issues due to globally used file 2020-10-06 16:12:12 +02:00
4cbc1f5bbe Minor refactoring 2020-10-06 15:37:51 +02:00
64de36cdf2 Adds more DNS plugins 2020-10-06 15:16:45 +02:00
093b48ad7b Implements backend changes to allow more dns challenges 2020-10-06 14:52:06 +02:00
05f6a55a0b Adds frontend improvements and fixes 2020-10-06 14:49:02 +02:00
2523424f68 Updates dockerfiles 2020-10-05 01:04:18 +02:00
b81325d7bf Implements dns challenge provider selection in frontend 2020-10-05 01:04:06 +02:00
3e10b7b2b1 Fix for access list getAll when not granted all permissions 2020-09-19 22:16:16 +01:00
e5cb750015 Bump node-forge from 0.9.1 to 0.10.0 in /docs
Bumps [node-forge](https://github.com/digitalbazaar/forge) from 0.9.1 to 0.10.0.
- [Release notes](https://github.com/digitalbazaar/forge/releases)
- [Changelog](https://github.com/digitalbazaar/forge/blob/master/CHANGELOG.md)
- [Commits](https://github.com/digitalbazaar/forge/compare/0.9.1...0.10.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-17 23:42:28 +00:00
28f72086ec Merge pull request #592 from jc21/develop
v2.5.0
2020-09-04 09:07:47 +10:00
a6b9bd7b01 Version bump and contributors 2020-09-03 14:11:44 +10:00
2c5eac9dad Merge branch 'master' of github.com:jc21/nginx-proxy-manager into develop 2020-09-03 14:03:43 +10:00
87f61b8527 Merge pull request #572 from jipjan/features/dns-cloudflare
Add DNS CloudFlare with wildcard support
2020-09-03 14:01:05 +10:00
74bfe490c6 Merge pull request #587 from duhruh/bug/custom-ssl-inputs
Allow inputs to update
2020-09-03 13:53:17 +10:00
015167f34d Allow inputs to update 2020-08-29 20:24:51 -07:00
4bafc7ff1a Merge pull request #546 from jc21/dependabot/npm_and_yarn/docs/prismjs-1.21.0
Bump prismjs from 1.20.0 to 1.21.0 in /docs
2020-08-25 10:51:11 +10:00
bf8beb50b4 Merge pull request #559 from jlesage/remove-webroot-certbot-arg
Removed the hardcoded `--webroot` certbot argument to better support DNS challenge
2020-08-25 08:44:00 +10:00
e5034a34f5 Merge pull request #570 from jc21/dependabot/npm_and_yarn/backend/bcrypt-5.0.0
Bump bcrypt from 4.0.1 to 5.0.0 in /backend
2020-08-25 08:31:48 +10:00
a561605653 show in ssl certificates list that CloudFlare is used 2020-08-24 09:09:52 +00:00
e8596c1554 cloudflare DNS also possible while adding proxy, redirection and 404 2020-08-24 09:00:00 +00:00
ab67481e99 fix eslint errors 2020-08-23 18:56:25 +00:00
1b611e67c8 Merge commit 'c5aa2b9f771cbd4c78c239ed0791aeb8d9e4d2e4' into features/dns-cloudflare 2020-08-23 18:30:07 +00:00
c5aa2b9f77 add cloudflare renew and make revoke working for both by deleting unnecessary config command 2020-08-23 18:29:16 +00:00
cff6c4d1f5 - prevent wildcard generation when not using Cloudflare dns
- fix cloudflare token required logic
2020-08-23 16:48:14 +00:00
077cf75ef2 wildcard support 2020-08-23 13:24:20 +00:00
ff1770204c request via cloudflare dns working 2020-08-23 12:50:41 +00:00
b9a95840e0 add cloudflare dns option to letsencrypt via manual certificate 2020-08-23 11:40:41 +00:00
2d7576c57e add cloudflare dns also to dev docker file 2020-08-23 10:54:36 +00:00
251aac716a Add CloudFlare DNS plugin to certbot 2020-08-21 09:49:43 +02:00
6694a42270 Merge pull request #560 from jlesage/remove-from-unixtime
Removed usage of `FROM_UNIXTIME` mysql-specific function.
2020-08-21 14:21:40 +10:00
f78a4c6ad1 Bump bcrypt from 4.0.1 to 5.0.0 in /backend
Bumps [bcrypt](https://github.com/kelektiv/node.bcrypt.js) from 4.0.1 to 5.0.0.
- [Release notes](https://github.com/kelektiv/node.bcrypt.js/releases)
- [Changelog](https://github.com/kelektiv/node.bcrypt.js/blob/master/CHANGELOG.md)
- [Commits](https://github.com/kelektiv/node.bcrypt.js/compare/v4.0.1...v5.0.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-08-20 17:01:00 +00:00
83fad8bcda Removed usage of FROM_UNIXTIME mysql-specific function.
This provide better interoperability with different databases (e.g. sqlite).
Fixes #557
2020-08-14 19:31:05 -04:00
f539e813aa Removed the hardcoded --webroot certbot argument to better support DNS challenge. Also, this option is already set in the default letsencrypt.ini. 2020-08-14 14:28:03 -04:00
5d65166777 Ignore local subnets for real IP determination 2020-08-12 09:32:40 +10:00
70346138a7 Bump prismjs from 1.20.0 to 1.21.0 in /docs
Bumps [prismjs](https://github.com/PrismJS/prism) from 1.20.0 to 1.21.0.
- [Release notes](https://github.com/PrismJS/prism/releases)
- [Changelog](https://github.com/PrismJS/prism/blob/master/CHANGELOG.md)
- [Commits](https://github.com/PrismJS/prism/compare/v1.20.0...v1.21.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-08-08 00:02:04 +00:00
d68656559c Merge pull request #544 from jlesage/sqlite-now-helper-fix
Fixed now_helper for sqlite (time is missing)
2020-08-07 08:37:00 +10:00
01660b5b80 Fixed now_helper for sqlite: it should also returns the time. 2020-08-06 17:16:22 -04:00
74010acd85 Merge pull request #543 from jc21/develop
v2.4.0
2020-08-06 16:00:10 +10:00
7c7d255172 Added another contributor 2020-08-06 14:46:19 +10:00
058f1e9835 Merge pull request #464 from vrenjith/patch-1
Update location-item.ejs - forward_host size increase to 200
2020-08-06 14:45:09 +10:00
b4fc629ec0 Bumped version 2020-08-06 14:43:34 +10:00
ae06b2da75 Updated deps and added contributor 2020-08-06 14:40:54 +10:00
54d423a11f Updated doc for sqlite 2020-08-06 14:27:29 +10:00
5da6c97a00 Pull cypress tests from correct location 2020-08-06 13:57:33 +10:00
bf2f13443f Cypress fixes 2020-08-06 12:47:24 +10:00
9ce4c3fe2f CI fix 2020-08-06 12:02:47 +10:00
4a07bf666d Added users cypress tests 2020-08-06 11:57:31 +10:00
5be46b4b20 Cypress fixes 2020-08-06 11:26:37 +10:00
7fd825b76b Use development config file in CI 2020-08-06 10:59:25 +10:00
b23d59dec7 Updated cypress to 4.12.1 2020-08-06 09:00:52 +10:00
492d450d26 Sqlite Tweaks
- Added cypress testing in CI for sqlite
- Cleaned up promises in setup
- Ensure check for settings is strict
2020-08-06 08:58:20 +10:00
04412f3624 Merge pull request #510 from tg44/multidb-re
Multidb - sqlite support
2020-08-06 08:33:00 +10:00
c41057b28a Revert builx push experiment 2020-07-31 09:28:45 +10:00
8312bc0100 Use same tags for experiment 2020-07-30 14:00:59 +10:00
85ac43bc5e Merge branch 'master' of github.com:jc21/nginx-proxy-manager into develop 2020-07-30 08:31:18 +10:00
d1a0780c7a Attempt to circumvent docker login token timeouts 2020-07-30 08:30:26 +10:00
f9b8d76527 Merge pull request #513 from jc21/dependabot/npm_and_yarn/frontend/lodash-4.17.19
Bump lodash from 4.17.15 to 4.17.19 in /frontend
2020-07-20 12:39:10 +10:00
26f00eeae4 Merge branch 'master' into dependabot/npm_and_yarn/frontend/lodash-4.17.19 2020-07-20 10:59:15 +10:00
1bc2df2178 Merge pull request #514 from jc21/dependabot/npm_and_yarn/docs/lodash-4.17.19
Bump lodash from 4.17.15 to 4.17.19 in /docs
2020-07-20 10:58:36 +10:00
8dfbcef198 Bump lodash from 4.17.15 to 4.17.19 in /docs
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-19 20:33:49 +00:00
6690b7735d sqlite3 and psql support 2020-07-19 20:04:29 +02:00
a9e7222e5e introduced now_helper for multidb capabilities 2020-07-19 20:03:53 +02:00
f8edeb2775 fixed migration and setup
more info: https://github.com/knex/knex/issues/2820
2020-07-19 20:02:20 +02:00
d1786fe159 Bump lodash from 4.17.15 to 4.17.19 in /frontend
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.19.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.19)

Signed-off-by: dependabot[bot] <support@github.com>
2020-07-19 15:20:28 +00:00
157a12fb7c Update location-item.ejs 2020-06-19 01:56:16 +05:30
3f723b1638 Merge pull request #454 from jc21/develop
v2.3.1
2020-06-09 09:47:31 +10:00
e2e9835d01 Version bump 2020-06-09 09:17:25 +10:00
7599617b67 Merge pull request #452 from jc21/dependabot/npm_and_yarn/docs/websocket-extensions-0.1.4
Bump websocket-extensions from 0.1.3 to 0.1.4 in /docs
2020-06-08 11:14:20 +10:00
18a5b11033 Bump websocket-extensions from 0.1.3 to 0.1.4 in /docs
Bumps [websocket-extensions](https://github.com/faye/websocket-extensions-node) from 0.1.3 to 0.1.4.
- [Release notes](https://github.com/faye/websocket-extensions-node/releases)
- [Changelog](https://github.com/faye/websocket-extensions-node/blob/master/CHANGELOG.md)
- [Commits](https://github.com/faye/websocket-extensions-node/compare/0.1.3...0.1.4)

Signed-off-by: dependabot[bot] <support@github.com>
2020-06-07 23:36:47 +00:00
fff31b0f34 Ensure we're using the latest node image 2020-06-03 10:30:29 +10:00
c02e30663a Revert last 2020-06-02 20:21:27 +10:00
4c6527cafc Ensure python2 is installed for frontend build 2020-06-02 20:09:27 +10:00
55bddb12e5 Merge pull request #435 from Subv/access_lists_ips
Don't use duplicate relations when eager-loading access list items and clients.
2020-06-02 19:42:27 +10:00
d95861e1fb Don't use duplicate relations when eager-loading access list items and clients.
This fixes an Objection warning: 'Duplicate relation "proxy_hosts" in a relation expression. You should use "a.[b, c]" instead of "[a.b, a.c]". This will cause an error in objection 2.0'.

It also fixes the access list clients not being properly eager-loaded when building the proxy host nginx configuration files. Closes #434
2020-05-29 20:29:34 -05:00
94754a5cb3 Revert CI debug 2020-05-28 20:26:16 +10:00
109 changed files with 7339 additions and 5149 deletions

View File

@ -7,6 +7,11 @@ assignees: ''
---
**Are you in the right place?**
- If you are looking for support on how to get your upstream server forwarding, please consider asking the community on Reddit.
- If you are writing code changes to contribute and need to ask about the internals of the software, Gitter is the best place to ask.
- If you think you found a bug with NPM (not Nginx, or your upstream server or MySql) then you are in the *right place.*
**Checklist**
- Have you pulled and found the error with `jc21/nginx-proxy-manager:latest` docker image?
- Are you sure you're not using someone else's docker image?

View File

@ -7,6 +7,11 @@ assignees: ''
---
**Are you in the right place?**
- If you are looking for support on how to get your upstream server forwarding, please consider asking the community on Reddit.
- If you are writing code changes to contribute and need to ask about the internals of the software, Gitter is the best place to ask.
- If you have a feature request for NPM then you are in the *right place.*
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

View File

@ -1,16 +0,0 @@
---
name: Product Support
about: Need help configuring the software?
title: ''
labels: product-support
assignees: ''
---
**Checklist**
- Please read the [setup instructions](https://nginxproxymanager.com/setup/)
- Please read the [FAQ](https://nginxproxymanager.com/faq/)
**What is troubling you?**
_Clear and concise description of what you're trying to do and what isn't working for you_

View File

@ -1,10 +0,0 @@
{
"database": {
"engine": "mysql",
"host": "db",
"name": "npm",
"user": "npm",
"password": "npm",
"port": 3306
}
}

View File

@ -1 +1 @@
2.3.0
2.8.1

46
Jenkinsfile vendored
View File

@ -65,6 +65,7 @@ pipeline {
// See: https://github.com/yarnpkg/yarn/issues/3254
sh '''docker run --rm \\
-v "$(pwd)/backend:/app" \\
-v "$(pwd)/global:/app/global" \\
-w /app \\
node:latest \\
sh -c "yarn install && yarn eslint . && rm -rf node_modules"
@ -83,23 +84,49 @@ pipeline {
'''
}
}
stage('Test') {
stage('Integration Tests Sqlite') {
steps {
// Bring up a stack
sh 'docker-compose up -d fullstack'
sh './scripts/wait-healthy $(docker-compose ps -q fullstack) 120'
sh 'docker-compose up -d fullstack-sqlite'
sh './scripts/wait-healthy $(docker-compose ps -q fullstack-sqlite) 120'
// Run tests
sh 'rm -rf test/results'
sh 'docker-compose up cypress'
sh 'docker-compose up cypress-sqlite'
// Get results
sh 'docker cp -L "$(docker-compose ps -q cypress):/results" test/'
sh 'docker cp -L "$(docker-compose ps -q cypress-sqlite):/test/results" test/'
}
post {
always {
// Dumps to analyze later
sh 'mkdir -p debug'
sh 'docker-compose logs fullstack | gzip > debug/docker_fullstack.log.gz'
sh 'docker-compose logs fullstack-sqlite | gzip > debug/docker_fullstack_sqlite.log.gz'
sh 'docker-compose logs db | gzip > debug/docker_db.log.gz'
// Cypress videos and screenshot artifacts
dir(path: 'test/results') {
archiveArtifacts allowEmptyArchive: true, artifacts: '**/*', excludes: '**/*.xml'
}
junit 'test/results/junit/*'
}
}
}
stage('Integration Tests Mysql') {
steps {
// Bring up a stack
sh 'docker-compose up -d fullstack-mysql'
sh './scripts/wait-healthy $(docker-compose ps -q fullstack-mysql) 120'
// Run tests
sh 'rm -rf test/results'
sh 'docker-compose up cypress-mysql'
// Get results
sh 'docker cp -L "$(docker-compose ps -q cypress-mysql):/test/results" test/'
}
post {
always {
// Dumps to analyze later
sh 'mkdir -p debug'
sh 'docker-compose logs fullstack-mysql | gzip > debug/docker_fullstack_mysql.log.gz'
sh 'docker-compose logs db | gzip > debug/docker_db.log.gz'
// Cypress videos and screenshot artifacts
dir(path: 'test/results') {
@ -136,8 +163,9 @@ pipeline {
}
steps {
withCredentials([usernamePassword(credentialsId: 'jc21-dockerhub', passwordVariable: 'dpass', usernameVariable: 'duser')]) {
// Docker Login
sh "docker login -u '${duser}' -p '${dpass}'"
// Buildx with push
// Buildx with push from cache
sh "./scripts/buildx --push ${BUILDX_PUSH_TAGS}"
}
}
@ -192,9 +220,9 @@ pipeline {
}
post {
always {
//sh 'docker-compose down --rmi all --remove-orphans --volumes -t 30'
sh 'docker-compose down --rmi all --remove-orphans --volumes -t 30'
sh 'echo Reverting ownership'
sh 'docker run --rm -v $(pwd):/data ${DOCKER_CI_TOOLS} chown -R $(id -u):$(id -g) /data'
sh 'docker run --rm -v $(pwd):/data jc21/ci-tools chown -R $(id -u):$(id -g) /data'
}
success {
juxtapose event: 'success'

View File

@ -1,7 +1,7 @@
<p align="center">
<img src="https://nginxproxymanager.com/github.png">
<br><br>
<img src="https://img.shields.io/badge/version-2.3.0-green.svg?style=for-the-badge">
<img src="https://img.shields.io/badge/version-2.8.1-green.svg?style=for-the-badge">
<a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager">
<img src="https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge">
</a>
@ -155,14 +155,106 @@ Special thanks to the following contributors:
<br /><sub><b>OhHeyAlan</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/dogmatic69">
<img src="https://avatars2.githubusercontent.com/u/94674?s=460&u=ca7647de53145c6283b6373ade5dc94ba99347db&v=4" width="80px;" alt=""/>
<br /><sub><b>Carl Sutton</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/tg44">
<img src="https://avatars0.githubusercontent.com/u/31839?s=460&u=ad32f4cadfef5e5fb09cdfa4b7b7b36a99ba6811&v=4" width="80px;" alt=""/>
<br /><sub><b>Gergő Törcsvári</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/vrenjith">
<img src="https://avatars3.githubusercontent.com/u/2093241?s=460&u=96ce93a9bebabdd0a60a2dc96cd093a41d5edaba&v=4" width="80px;" alt=""/>
<br /><sub><b>vrenjith</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/duhruh">
<img src="https://avatars2.githubusercontent.com/u/1133969?s=460&u=c0691e6131ec6d516416c1c6fcedb5034f877bbe&v=4" width="80px;" alt=""/>
<br /><sub><b>David Rivera</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/jipjan">
<img src="https://avatars2.githubusercontent.com/u/1384618?s=460&v=4" width="80px;" alt=""/>
<br /><sub><b>Jaap-Jan de Wit</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/jmwebslave">
<img src="https://avatars2.githubusercontent.com/u/6118262?s=460&u=7db409c47135b1e141c366bbb03ed9fae6ac2638&v=4" width="80px;" alt=""/>
<br /><sub><b>James Morgan</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/chaptergy">
<img src="https://avatars2.githubusercontent.com/u/26956711?s=460&u=7d9adebabb6b4e7af7cb05d98d751087a372304b&v=4" width="80px;" alt=""/>
<br /><sub><b>chaptergy</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Philip-Mooney">
<img src="https://avatars0.githubusercontent.com/u/48624631?s=460&v=4" width="80px;" alt=""/>
<br /><sub><b>Philip Mooney</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/WaterCalm">
<img src="https://avatars1.githubusercontent.com/u/23502129?s=400&v=4" width="80px;" alt=""/>
<br /><sub><b>WaterCalm</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/lebrou34">
<img src="https://avatars1.githubusercontent.com/u/16373103?s=460&v=4" width="80px;" alt=""/>
<br /><sub><b>lebrou34</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/lightglitch">
<img src="https://avatars0.githubusercontent.com/u/196953?s=460&v=4" width="80px;" alt=""/>
<br /><sub><b>Mário Franco</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/klutchell">
<img src="https://avatars3.githubusercontent.com/u/20458272?s=460&v=4" width="80px;" alt=""/>
<br /><sub><b>Kyle Harding</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/ahgraber">
<img src="https://avatars.githubusercontent.com/u/24922003?s=460&u=8376c9f00af9b6057ba4d2fb03b4f1b20a75277f&v=4" width="80px;" alt=""/>
<br /><sub><b>Alex Graber</b></sub>
</a>
</td>
</tr>
<tr>
<td align="center">
<a href="https://github.com/MooBaloo">
<img src="https://avatars.githubusercontent.com/u/9493496?s=460&v=4" width="80px;" alt=""/>
<br /><sub><b>MooBaloo</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/Shuro">
<img src="https://avatars.githubusercontent.com/u/944030?s=460&v=4" width="80px;" alt=""/>
<br /><sub><b>Shuro</b></sub>
</a>
</td>
<td align="center">
<a href="https://github.com/lorisbergeron">
<img src="https://avatars.githubusercontent.com/u/51918567?s=460&u=778e4ff284b7d7304450f98421c99f79298371fb&v=4" width="80px;" alt=""/>
<br /><sub><b>Loris Bergeron</b></sub>
</a>
</td>
</tr>
</table>
<!-- markdownlint-enable -->

View File

@ -66,7 +66,7 @@ app.use(function (err, req, res, next) {
}
};
if (process.env.NODE_ENV === 'development') {
if (process.env.NODE_ENV === 'development' || (req.baseUrl + req.path).includes('nginx/certificates')) {
payload.debug = {
stack: typeof err.stack !== 'undefined' && err.stack ? err.stack.split('\n') : null,
previous: err.previous

View File

@ -0,0 +1,26 @@
{
"database": {
"engine": "knex-native",
"knex": {
"client": "sqlite3",
"connection": {
"filename": "/app/config/mydb.sqlite"
},
"pool": {
"min": 0,
"max": 1,
"createTimeoutMillis": 3000,
"acquireTimeoutMillis": 30000,
"idleTimeoutMillis": 30000,
"reapIntervalMillis": 1000,
"createRetryIntervalMillis": 100,
"propagateCreateError": false
},
"migrations": {
"tableName": "migrations",
"stub": "src/backend/lib/migrate_template.js",
"directory": "src/backend/migrations"
}
}
}
}

View File

@ -4,19 +4,27 @@ if (!config.has('database')) {
throw new Error('Database config does not exist! Please read the instructions: https://github.com/jc21/nginx-proxy-manager/blob/master/doc/INSTALL.md');
}
let data = {
client: config.database.engine,
connection: {
host: config.database.host,
user: config.database.user,
password: config.database.password,
database: config.database.name,
port: config.database.port
},
migrations: {
tableName: 'migrations'
}
};
function generateDbConfig() {
if (config.database.engine === 'knex-native') {
return config.database.knex;
} else
return {
client: config.database.engine,
connection: {
host: config.database.host,
user: config.database.user,
password: config.database.password,
database: config.database.name,
port: config.database.port
},
migrations: {
tableName: 'migrations'
}
};
}
let data = generateDbConfig();
if (typeof config.database.version !== 'undefined') {
data.version = config.database.version;

View File

@ -2,7 +2,10 @@
const logger = require('./logger').global;
function appStart () {
async function appStart () {
// Create config file db settings if environment variables have been set
await createDbConfigFromEnvironment();
const migrate = require('./migrate');
const setup = require('./setup');
const app = require('./app');
@ -39,9 +42,92 @@ function appStart () {
});
}
async function createDbConfigFromEnvironment() {
return new Promise((resolve, reject) => {
const envMysqlHost = process.env.DB_MYSQL_HOST || null;
const envMysqlPort = process.env.DB_MYSQL_PORT || null;
const envMysqlUser = process.env.DB_MYSQL_USER || null;
const envMysqlName = process.env.DB_MYSQL_NAME || null;
const envSqliteFile = process.env.DB_SQLITE_FILE || null;
if ((envMysqlHost && envMysqlPort && envMysqlUser && envMysqlName) || envSqliteFile) {
const fs = require('fs');
const filename = (process.env.NODE_CONFIG_DIR || './config') + '/' + (process.env.NODE_ENV || 'default') + '.json';
let configData = {};
try {
configData = require(filename);
} catch (err) {
// do nothing
}
if (configData.database && configData.database.engine && !configData.database.fromEnv) {
logger.info('Manual db configuration already exists, skipping config creation from environment variables');
resolve();
return;
}
if (envMysqlHost && envMysqlPort && envMysqlUser && envMysqlName) {
const newConfig = {
fromEnv: true,
engine: 'mysql',
host: envMysqlHost,
port: envMysqlPort,
user: envMysqlUser,
password: process.env.DB_MYSQL_PASSWORD,
name: envMysqlName,
};
if (JSON.stringify(configData.database) === JSON.stringify(newConfig)) {
// Config is unchanged, skip overwrite
resolve();
return;
}
logger.info('Generating MySQL db configuration from environment variables');
configData.database = newConfig;
} else {
const newConfig = {
fromEnv: true,
engine: 'knex-native',
knex: {
client: 'sqlite3',
connection: {
filename: envSqliteFile
}
}
};
if (JSON.stringify(configData.database) === JSON.stringify(newConfig)) {
// Config is unchanged, skip overwrite
resolve();
return;
}
logger.info('Generating Sqlite db configuration from environment variables');
configData.database = newConfig;
}
// Write config
fs.writeFile(filename, JSON.stringify(configData, null, 2), (err) => {
if (err) {
logger.error('Could not write db config to config file: ' + filename);
reject(err);
} else {
logger.info('Wrote db configuration to config file: ' + filename);
resolve();
}
});
} else {
resolve();
}
});
}
try {
appStart();
} catch (err) {
logger.error(err.message, err);
process.exit(1);
}

View File

@ -31,6 +31,7 @@ const internalAccessList = {
.insertAndFetch({
name: data.name,
satisfy_any: data.satisfy_any,
pass_auth: data.pass_auth,
owner_user_id: access.token.getUserId(1)
});
})
@ -71,7 +72,7 @@ const internalAccessList = {
// re-fetch with expansions
return internalAccessList.get(access, {
id: data.id,
expand: ['owner', 'items', 'clients', 'proxy_hosts.access_list.clients', 'proxy_hosts.access_list.items']
expand: ['owner', 'items', 'clients', 'proxy_hosts.access_list.[clients,items]']
}, true /* <- skip masking */);
})
.then((row) => {
@ -128,6 +129,7 @@ const internalAccessList = {
.patch({
name: data.name,
satisfy_any: data.satisfy_any,
pass_auth: data.pass_auth,
});
}
})
@ -216,7 +218,7 @@ const internalAccessList = {
// re-fetch with expansions
return internalAccessList.get(access, {
id: data.id,
expand: ['owner', 'items', 'clients', 'proxy_hosts.access_list.clients', 'proxy_hosts.access_list.items']
expand: ['owner', 'items', 'clients', 'proxy_hosts.access_list.[clients,items]']
}, true /* <- skip masking */);
})
.then((row) => {
@ -254,7 +256,7 @@ const internalAccessList = {
.joinRaw('LEFT JOIN `proxy_host` ON `proxy_host`.`access_list_id` = `access_list`.`id` AND `proxy_host`.`is_deleted` = 0')
.where('access_list.is_deleted', 0)
.andWhere('access_list.id', data.id)
.allowEager('[owner,items,clients,proxy_hosts,proxy_hosts.access_list.clients,proxy_hosts.access_list.items]')
.allowEager('[owner,items,clients,proxy_hosts.[*, access_list.[clients,items]]]')
.omit(['access_list.is_deleted'])
.first();
@ -384,7 +386,7 @@ const internalAccessList = {
.orderBy('access_list.name', 'ASC');
if (access_data.permission_visibility !== 'all') {
query.andWhere('owner_user_id', access.token.getUserId(1));
query.andWhere('access_list.owner_user_id', access.token.getUserId(1));
}
// Query is used for searching

View File

@ -13,6 +13,7 @@ const internalNginx = require('./nginx');
const internalHost = require('./host');
const certbot_command = '/usr/bin/certbot';
const le_config = '/etc/letsencrypt.ini';
const dns_plugins = require('../global/certbot-dns-plugins');
function omissions() {
return ['is_deleted'];
@ -77,7 +78,7 @@ const internalCertificate = {
.where('id', certificate.id)
.andWhere('provider', 'letsencrypt')
.patch({
expires_on: certificateModel.raw('FROM_UNIXTIME(' + cert_info.dates.to + ')')
expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss')
});
})
.catch((err) => {
@ -141,36 +142,60 @@ const internalCertificate = {
});
})
.then((in_use_result) => {
// 3. Generate the LE config
return internalNginx.generateLetsEncryptRequestConfig(certificate)
.then(internalNginx.reload)
.then(() => {
// With DNS challenge no config is needed, so skip 3 and 5.
if (certificate.meta.dns_challenge) {
return internalNginx.reload().then(() => {
// 4. Request cert
return internalCertificate.requestLetsEncryptSsl(certificate);
return internalCertificate.requestLetsEncryptSslWithDnsChallenge(certificate);
})
.then(() => {
// 5. Remove LE config
return internalNginx.deleteLetsEncryptRequestConfig(certificate);
})
.then(internalNginx.reload)
.then(() => {
// 6. Re-instate previously disabled hosts
return internalCertificate.enableInUseHosts(in_use_result);
})
.then(() => {
return certificate;
})
.catch((err) => {
// In the event of failure, revert things and throw err back
return internalNginx.deleteLetsEncryptRequestConfig(certificate)
.then(() => {
return internalCertificate.enableInUseHosts(in_use_result);
})
.then(internalNginx.reload)
.then(() => {
throw err;
});
});
.then(internalNginx.reload)
.then(() => {
// 6. Re-instate previously disabled hosts
return internalCertificate.enableInUseHosts(in_use_result);
})
.then(() => {
return certificate;
})
.catch((err) => {
// In the event of failure, revert things and throw err back
return internalCertificate.enableInUseHosts(in_use_result)
.then(internalNginx.reload)
.then(() => {
throw err;
});
});
} else {
// 3. Generate the LE config
return internalNginx.generateLetsEncryptRequestConfig(certificate)
.then(internalNginx.reload)
.then(() => {
// 4. Request cert
return internalCertificate.requestLetsEncryptSsl(certificate);
})
.then(() => {
// 5. Remove LE config
return internalNginx.deleteLetsEncryptRequestConfig(certificate);
})
.then(internalNginx.reload)
.then(() => {
// 6. Re-instate previously disabled hosts
return internalCertificate.enableInUseHosts(in_use_result);
})
.then(() => {
return certificate;
})
.catch((err) => {
// In the event of failure, revert things and throw err back
return internalNginx.deleteLetsEncryptRequestConfig(certificate)
.then(() => {
return internalCertificate.enableInUseHosts(in_use_result);
})
.then(internalNginx.reload)
.then(() => {
throw err;
});
});
}
})
.then(() => {
// At this point, the letsencrypt cert should exist on disk.
@ -180,7 +205,7 @@ const internalCertificate = {
return certificateModel
.query()
.patchAndFetchById(certificate.id, {
expires_on: certificateModel.raw('FROM_UNIXTIME(' + cert_info.dates.to + ')')
expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss')
})
.then((saved_row) => {
// Add cert data for audit log
@ -191,6 +216,13 @@ const internalCertificate = {
return saved_row;
});
});
}).catch(async (error) => {
// Delete the certificate from the database if it was not created successfully
await certificateModel
.query()
.deleteById(certificate.id);
throw error;
});
} else {
return certificate;
@ -558,7 +590,7 @@ const internalCertificate = {
// TODO: This uses a mysql only raw function that won't translate to postgres
return internalCertificate.update(access, {
id: data.id,
expires_on: certificateModel.raw('FROM_UNIXTIME(' + validations.certificate.dates.to + ')'),
expires_on: moment(validations.certificate.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss'),
domain_names: [validations.certificate.cn],
meta: _.clone(row.meta) // Prevent the update method from changing this value that we'll use later
})
@ -583,18 +615,26 @@ const internalCertificate = {
checkPrivateKey: (private_key) => {
return tempWrite(private_key, '/tmp')
.then((filepath) => {
return utils.exec('openssl rsa -in ' + filepath + ' -check -noout')
.then((result) => {
if (!result.toLowerCase().includes('key ok')) {
throw new error.ValidationError(result);
}
fs.unlinkSync(filepath);
return true;
}).catch((err) => {
fs.unlinkSync(filepath);
throw new error.ValidationError('Certificate Key is not valid (' + err.message + ')', err);
});
return new Promise((resolve, reject) => {
const failTimeout = setTimeout(() => {
reject(new error.ValidationError('Result Validation Error: Validation timed out. This could be due to the key being passphrase-protected.'));
}, 10000);
utils
.exec('openssl pkey -in ' + filepath + ' -check -noout 2>&1 ')
.then((result) => {
clearTimeout(failTimeout);
if (!result.toLowerCase().includes('key is valid')) {
reject(new error.ValidationError('Result Validation Error: ' + result));
}
fs.unlinkSync(filepath);
resolve(true);
})
.catch((err) => {
clearTimeout(failTimeout);
fs.unlinkSync(filepath);
reject(new error.ValidationError('Certificate Key is not valid (' + err.message + ')', err));
});
});
});
},
@ -733,7 +773,6 @@ const internalCertificate = {
'--agree-tos ' +
'--email "' + certificate.meta.letsencrypt_email + '" ' +
'--preferred-challenges "dns,http" ' +
'--webroot ' +
'--domains "' + certificate.domain_names.join(',') + '" ' +
(le_staging ? '--staging' : '');
@ -748,6 +787,76 @@ const internalCertificate = {
});
},
/**
* @param {Object} certificate the certificate row
* @param {String} dns_provider the dns provider name (key used in `certbot-dns-plugins.js`)
* @param {String | null} credentials the content of this providers credentials file
* @param {String} propagation_seconds the cloudflare api token
* @returns {Promise}
*/
requestLetsEncryptSslWithDnsChallenge: (certificate) => {
const dns_plugin = dns_plugins[certificate.meta.dns_provider];
if (!dns_plugin) {
throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`);
}
logger.info(`Requesting Let'sEncrypt certificates via ${dns_plugin.display_name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);
const credentials_loc = '/etc/letsencrypt/credentials/credentials-' + certificate.id;
const credentials_cmd = 'mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo \'' + certificate.meta.dns_provider_credentials.replace('\'', '\\\'') + '\' > \'' + credentials_loc + '\' && chmod 600 \'' + credentials_loc + '\'';
const prepare_cmd = 'pip3 install ' + dns_plugin.package_name + '==' + dns_plugin.package_version + ' ' + dns_plugin.dependencies;
// Whether the plugin has a --<name>-credentials argument
const has_config_arg = certificate.meta.dns_provider !== 'route53';
let main_cmd =
certbot_command + ' certonly --non-interactive ' +
'--cert-name "npm-' + certificate.id + '" ' +
'--agree-tos ' +
'--email "' + certificate.meta.letsencrypt_email + '" ' +
'--domains "' + certificate.domain_names.join(',') + '" ' +
'--authenticator ' + dns_plugin.full_plugin_name + ' ' +
(
has_config_arg
? '--' + dns_plugin.full_plugin_name + '-credentials "' + credentials_loc + '"'
: ''
) +
(
certificate.meta.propagation_seconds !== undefined
? ' --' + dns_plugin.full_plugin_name + '-propagation-seconds ' + certificate.meta.propagation_seconds
: ''
) +
(le_staging ? ' --staging' : '');
// Prepend the path to the credentials file as an environment variable
if (certificate.meta.dns_provider === 'route53') {
main_cmd = 'AWS_CONFIG_FILE=\'' + credentials_loc + '\' ' + main_cmd;
}
if (debug_mode) {
logger.info('Command:', `${credentials_cmd} && ${prepare_cmd} && ${main_cmd}`);
}
return utils.exec(credentials_cmd)
.then(() => {
return utils.exec(prepare_cmd)
.then(() => {
return utils.exec(main_cmd)
.then(async (result) => {
logger.info(result);
return result;
});
});
}).catch(async (err) => {
// Don't fail if file does not exist
const delete_credentials_cmd = `rm -f '${credentials_loc}' || true`;
await utils.exec(delete_credentials_cmd);
throw err;
});
},
/**
* @param {Access} access
* @param {Object} data
@ -761,7 +870,9 @@ const internalCertificate = {
})
.then((certificate) => {
if (certificate.provider === 'letsencrypt') {
return internalCertificate.renewLetsEncryptSsl(certificate)
let renewMethod = certificate.meta.dns_challenge ? internalCertificate.renewLetsEncryptSslWithDnsChallenge : internalCertificate.renewLetsEncryptSsl;
return renewMethod(certificate)
.then(() => {
return internalCertificate.getCertificateInfoFromFile('/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem');
})
@ -769,7 +880,7 @@ const internalCertificate = {
return certificateModel
.query()
.patchAndFetchById(certificate.id, {
expires_on: certificateModel.raw('FROM_UNIXTIME(' + cert_info.dates.to + ')')
expires_on: moment(cert_info.dates.to, 'X').format('YYYY-MM-DD HH:mm:ss')
});
})
.then((updated_certificate) => {
@ -815,6 +926,42 @@ const internalCertificate = {
});
},
/**
* @param {Object} certificate the certificate row
* @returns {Promise}
*/
renewLetsEncryptSslWithDnsChallenge: (certificate) => {
const dns_plugin = dns_plugins[certificate.meta.dns_provider];
if (!dns_plugin) {
throw Error(`Unknown DNS provider '${certificate.meta.dns_provider}'`);
}
logger.info(`Renewing Let'sEncrypt certificates via ${dns_plugin.display_name} for Cert #${certificate.id}: ${certificate.domain_names.join(', ')}`);
let main_cmd =
certbot_command + ' renew --non-interactive ' +
'--cert-name "npm-' + certificate.id + '" ' +
'--disable-hook-validation' +
(le_staging ? ' --staging' : '');
// Prepend the path to the credentials file as an environment variable
if (certificate.meta.dns_provider === 'route53') {
const credentials_loc = '/etc/letsencrypt/credentials/credentials-' + certificate.id;
main_cmd = 'AWS_CONFIG_FILE=\'' + credentials_loc + '\' ' + main_cmd;
}
if (debug_mode) {
logger.info('Command:', main_cmd);
}
return utils.exec(main_cmd)
.then(async (result) => {
logger.info(result);
return result;
});
},
/**
* @param {Object} certificate the certificate row
* @param {Boolean} [throw_errors]
@ -823,21 +970,21 @@ const internalCertificate = {
revokeLetsEncryptSsl: (certificate, throw_errors) => {
logger.info('Revoking Let\'sEncrypt certificates for Cert #' + certificate.id + ': ' + certificate.domain_names.join(', '));
let cmd = certbot_command + ' revoke --non-interactive ' +
'--config "' + le_config + '" ' +
const main_cmd = certbot_command + ' revoke --non-interactive ' +
'--cert-path "/etc/letsencrypt/live/npm-' + certificate.id + '/fullchain.pem" ' +
'--delete-after-revoke ' +
(le_staging ? '--staging' : '');
// Don't fail command if file does not exist
const delete_credentials_cmd = `rm -f '/etc/letsencrypt/credentials/credentials-${certificate.id}' || true`;
if (debug_mode) {
logger.info('Command:', cmd);
logger.info('Command:', main_cmd + '; ' + delete_credentials_cmd);
}
return utils.exec(cmd)
.then((result) => {
if (debug_mode) {
logger.info('Command:', cmd);
}
return utils.exec(main_cmd)
.then(async (result) => {
await utils.exec(delete_credentials_cmd);
logger.info(result);
return result;
})

View File

@ -106,7 +106,7 @@ const internalHost = {
response_object.total_count += response_object.redirection_hosts.length;
}
if (promises_results[1]) {
if (promises_results[2]) {
// Dead Hosts
response_object.dead_hosts = internalHost._getHostsWithDomains(promises_results[2], domain_names);
response_object.total_count += response_object.dead_hosts.length;
@ -158,7 +158,7 @@ const internalHost = {
}
}
if (promises_results[1]) {
if (promises_results[2]) {
// Dead Hosts
if (internalHost._checkHostnameRecordsTaken(hostname, promises_results[2], ignore_type === 'dead' && ignore_id ? ignore_id : 0)) {
is_taken = true;

View File

@ -73,7 +73,7 @@ const internalProxyHost = {
// re-fetch with cert
return internalProxyHost.get(access, {
id: row.id,
expand: ['certificate', 'owner', 'access_list.clients', 'access_list.items']
expand: ['certificate', 'owner', 'access_list.[clients,items]']
});
})
.then((row) => {
@ -186,9 +186,13 @@ const internalProxyHost = {
.then(() => {
return internalProxyHost.get(access, {
id: data.id,
expand: ['owner', 'certificate', 'access_list.clients', 'access_list.items']
expand: ['owner', 'certificate', 'access_list.[clients,items]']
})
.then((row) => {
if (!row.enabled) {
// No need to add nginx config if host is disabled
return row;
}
// Configure nginx
return internalNginx.configure(proxyHostModel, 'proxy_host', row)
.then((new_meta) => {
@ -219,7 +223,7 @@ const internalProxyHost = {
.query()
.where('is_deleted', 0)
.andWhere('id', data.id)
.allowEager('[owner,access_list,access_list.clients,access_list.items,certificate]')
.allowEager('[owner,access_list,access_list.[clients,items],certificate]')
.first();
if (access_data.permission_visibility !== 'all') {

View File

@ -69,6 +69,9 @@ exports.up = function (knex/*, Promise*/) {
table.json('domain_names').notNull();
table.string('forward_ip').notNull();
table.integer('forward_port').notNull().unsigned();
table.string('root_dir').notNull();
table.string('index_file').notNull();
table.integer('static').notNull().unsigned().defaultTo(0);
table.integer('access_list_id').notNull().unsigned().defaultTo(0);
table.integer('certificate_id').notNull().unsigned().defaultTo(0);
table.integer('ssl_forced').notNull().unsigned().defaultTo(0);

View File

@ -22,22 +22,6 @@ exports.up = function (knex/*, Promise*/) {
})
.then(() => {
logger.info('[' + migrate_name + '] setting Table created');
// TODO: add settings
let settingModel = require('../models/setting');
return settingModel
.query()
.insert({
id: 'default-site',
name: 'Default Site',
description: 'What to show when Nginx is hit with an unknown Host',
value: 'congratulations',
meta: {}
});
})
.then(() => {
logger.info('[' + migrate_name + '] Default settings added');
});
};

View File

@ -0,0 +1,41 @@
const migrate_name = 'pass_auth';
const logger = require('../logger').migrate;
/**
* Migrate
*
* @see http://knexjs.org/#Schema
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.up = function (knex/*, Promise*/) {
logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.table('access_list', function (access_list) {
access_list.integer('pass_auth').notNull().defaultTo(1);
})
.then(() => {
logger.info('[' + migrate_name + '] access_list Table altered');
});
};
/**
* Undo Migrate
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.down = function (knex/*, Promise*/) {
logger.info('[' + migrate_name + '] Migrating Down...');
return knex.schema.table('access_list', function (access_list) {
access_list.dropColumn('pass_auth');
})
.then(() => {
logger.info('[' + migrate_name + '] access_list pass_auth Column dropped');
});
};

View File

@ -0,0 +1,41 @@
const migrate_name = 'redirection_scheme';
const logger = require('../logger').migrate;
/**
* Migrate
*
* @see http://knexjs.org/#Schema
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.up = function (knex/*, Promise*/) {
logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.table('redirection_host', (table) => {
table.string('forward_scheme').notNull().defaultTo('$scheme');
})
.then(function () {
logger.info('[' + migrate_name + '] redirection_host Table altered');
});
};
/**
* Undo Migrate
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.down = function (knex/*, Promise*/) {
logger.info('[' + migrate_name + '] Migrating Down...');
return knex.schema.table('redirection_host', (table) => {
table.dropColumn('forward_scheme');
})
.then(function () {
logger.info('[' + migrate_name + '] redirection_host Table altered');
});
};

View File

@ -0,0 +1,41 @@
const migrate_name = 'redirection_status_code';
const logger = require('../logger').migrate;
/**
* Migrate
*
* @see http://knexjs.org/#Schema
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.up = function (knex/*, Promise*/) {
logger.info('[' + migrate_name + '] Migrating Up...');
return knex.schema.table('redirection_host', (table) => {
table.integer('forward_http_code').notNull().unsigned().defaultTo(302);
})
.then(function () {
logger.info('[' + migrate_name + '] redirection_host Table altered');
});
};
/**
* Undo Migrate
*
* @param {Object} knex
* @param {Promise} Promise
* @returns {Promise}
*/
exports.down = function (knex/*, Promise*/) {
logger.info('[' + migrate_name + '] Migrating Down...');
return knex.schema.table('redirection_host', (table) => {
table.dropColumn('forward_http_code');
})
.then(function () {
logger.info('[' + migrate_name + '] redirection_host Table altered');
});
};

View File

@ -6,13 +6,14 @@ const Model = require('objection').Model;
const User = require('./user');
const AccessListAuth = require('./access_list_auth');
const AccessListClient = require('./access_list_client');
const now = require('./now_helper');
Model.knex(db);
class AccessList extends Model {
$beforeInsert () {
this.created_on = Model.raw('NOW()');
this.modified_on = Model.raw('NOW()');
this.created_on = now();
this.modified_on = now();
// Default for meta
if (typeof this.meta === 'undefined') {
@ -21,7 +22,7 @@ class AccessList extends Model {
}
$beforeUpdate () {
this.modified_on = Model.raw('NOW()');
this.modified_on = now();
}
static get name () {
@ -92,6 +93,10 @@ class AccessList extends Model {
get satisfy() {
return this.satisfy_any ? 'satisfy any' : 'satisfy all';
}
get passauth() {
return this.pass_auth ? '' : 'proxy_set_header Authorization "";';
}
}
module.exports = AccessList;

View File

@ -3,13 +3,14 @@
const db = require('../db');
const Model = require('objection').Model;
const now = require('./now_helper');
Model.knex(db);
class AccessListAuth extends Model {
$beforeInsert () {
this.created_on = Model.raw('NOW()');
this.modified_on = Model.raw('NOW()');
this.created_on = now();
this.modified_on = now();
// Default for meta
if (typeof this.meta === 'undefined') {
@ -18,7 +19,7 @@ class AccessListAuth extends Model {
}
$beforeUpdate () {
this.modified_on = Model.raw('NOW()');
this.modified_on = now();
}
static get name () {

View File

@ -3,13 +3,14 @@
const db = require('../db');
const Model = require('objection').Model;
const now = require('./now_helper');
Model.knex(db);
class AccessListClient extends Model {
$beforeInsert () {
this.created_on = Model.raw('NOW()');
this.modified_on = Model.raw('NOW()');
this.created_on = now();
this.modified_on = now();
// Default for meta
if (typeof this.meta === 'undefined') {
@ -18,7 +19,7 @@ class AccessListClient extends Model {
}
$beforeUpdate () {
this.modified_on = Model.raw('NOW()');
this.modified_on = now();
}
static get name () {

View File

@ -4,13 +4,14 @@
const db = require('../db');
const Model = require('objection').Model;
const User = require('./user');
const now = require('./now_helper');
Model.knex(db);
class AuditLog extends Model {
$beforeInsert () {
this.created_on = Model.raw('NOW()');
this.modified_on = Model.raw('NOW()');
this.created_on = now();
this.modified_on = now();
// Default for meta
if (typeof this.meta === 'undefined') {
@ -19,7 +20,7 @@ class AuditLog extends Model {
}
$beforeUpdate () {
this.modified_on = Model.raw('NOW()');
this.modified_on = now();
}
static get name () {

View File

@ -5,6 +5,7 @@ const bcrypt = require('bcrypt');
const db = require('../db');
const Model = require('objection').Model;
const User = require('./user');
const now = require('./now_helper');
Model.knex(db);
@ -24,8 +25,8 @@ function encryptPassword () {
class Auth extends Model {
$beforeInsert (queryContext) {
this.created_on = Model.raw('NOW()');
this.modified_on = Model.raw('NOW()');
this.created_on = now();
this.modified_on = now();
// Default for meta
if (typeof this.meta === 'undefined') {
@ -36,7 +37,7 @@ class Auth extends Model {
}
$beforeUpdate (queryContext) {
this.modified_on = Model.raw('NOW()');
this.modified_on = now();
return encryptPassword.apply(this, queryContext);
}

View File

@ -4,17 +4,18 @@
const db = require('../db');
const Model = require('objection').Model;
const User = require('./user');
const now = require('./now_helper');
Model.knex(db);
class Certificate extends Model {
$beforeInsert () {
this.created_on = Model.raw('NOW()');
this.modified_on = Model.raw('NOW()');
this.created_on = now();
this.modified_on = now();
// Default for expires_on
if (typeof this.expires_on === 'undefined') {
this.expires_on = Model.raw('NOW()');
this.expires_on = now();
}
// Default for domain_names
@ -31,7 +32,7 @@ class Certificate extends Model {
}
$beforeUpdate () {
this.modified_on = Model.raw('NOW()');
this.modified_on = now();
// Sort domain_names
if (typeof this.domain_names !== 'undefined') {

View File

@ -5,13 +5,14 @@ const db = require('../db');
const Model = require('objection').Model;
const User = require('./user');
const Certificate = require('./certificate');
const now = require('./now_helper');
Model.knex(db);
class DeadHost extends Model {
$beforeInsert () {
this.created_on = Model.raw('NOW()');
this.modified_on = Model.raw('NOW()');
this.created_on = now();
this.modified_on = now();
// Default for domain_names
if (typeof this.domain_names === 'undefined') {
@ -27,7 +28,7 @@ class DeadHost extends Model {
}
$beforeUpdate () {
this.modified_on = Model.raw('NOW()');
this.modified_on = now();
// Sort domain_names
if (typeof this.domain_names !== 'undefined') {

View File

@ -0,0 +1,13 @@
const db = require('../db');
const config = require('config');
const Model = require('objection').Model;
Model.knex(db);
module.exports = function () {
if (config.database.knex && config.database.knex.client === 'sqlite3') {
return Model.raw('datetime(\'now\',\'localtime\')');
} else {
return Model.raw('NOW()');
}
};

View File

@ -6,13 +6,14 @@ const Model = require('objection').Model;
const User = require('./user');
const AccessList = require('./access_list');
const Certificate = require('./certificate');
const now = require('./now_helper');
Model.knex(db);
class ProxyHost extends Model {
$beforeInsert () {
this.created_on = Model.raw('NOW()');
this.modified_on = Model.raw('NOW()');
this.created_on = now();
this.modified_on = now();
// Default for domain_names
if (typeof this.domain_names === 'undefined') {
@ -28,7 +29,7 @@ class ProxyHost extends Model {
}
$beforeUpdate () {
this.modified_on = Model.raw('NOW()');
this.modified_on = now();
// Sort domain_names
if (typeof this.domain_names !== 'undefined') {

View File

@ -5,13 +5,14 @@ const db = require('../db');
const Model = require('objection').Model;
const User = require('./user');
const Certificate = require('./certificate');
const now = require('./now_helper');
Model.knex(db);
class RedirectionHost extends Model {
$beforeInsert () {
this.created_on = Model.raw('NOW()');
this.modified_on = Model.raw('NOW()');
this.created_on = now();
this.modified_on = now();
// Default for domain_names
if (typeof this.domain_names === 'undefined') {
@ -27,7 +28,7 @@ class RedirectionHost extends Model {
}
$beforeUpdate () {
this.modified_on = Model.raw('NOW()');
this.modified_on = now();
// Sort domain_names
if (typeof this.domain_names !== 'undefined') {

View File

@ -4,13 +4,14 @@
const db = require('../db');
const Model = require('objection').Model;
const User = require('./user');
const now = require('./now_helper');
Model.knex(db);
class Stream extends Model {
$beforeInsert () {
this.created_on = Model.raw('NOW()');
this.modified_on = Model.raw('NOW()');
this.created_on = now();
this.modified_on = now();
// Default for meta
if (typeof this.meta === 'undefined') {
@ -19,7 +20,7 @@ class Stream extends Model {
}
$beforeUpdate () {
this.modified_on = Model.raw('NOW()');
this.modified_on = now();
}
static get name () {

View File

@ -4,15 +4,23 @@
*/
const _ = require('lodash');
const config = require('config');
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const error = require('../lib/error');
const ALGO = 'RS256';
let public_key = null;
let private_key = null;
function checkJWTKeyPair() {
if (!public_key || !private_key) {
let config = require('config');
public_key = config.get('jwt.pub');
private_key = config.get('jwt.key');
}
}
module.exports = function () {
const public_key = config.get('jwt.pub');
const private_key = config.get('jwt.key');
let token_data = {};
@ -32,6 +40,8 @@ module.exports = function () {
.toString('base64')
.substr(-8);
checkJWTKeyPair();
return new Promise((resolve, reject) => {
jwt.sign(payload, private_key, options, (err, token) => {
if (err) {
@ -53,6 +63,7 @@ module.exports = function () {
*/
load: function (token) {
return new Promise((resolve, reject) => {
checkJWTKeyPair();
try {
if (!token || token === null || token === 'null') {
reject(new error.AuthError('Empty token'));

View File

@ -4,13 +4,14 @@
const db = require('../db');
const Model = require('objection').Model;
const UserPermission = require('./user_permission');
const now = require('./now_helper');
Model.knex(db);
class User extends Model {
$beforeInsert () {
this.created_on = Model.raw('NOW()');
this.modified_on = Model.raw('NOW()');
this.created_on = now();
this.modified_on = now();
// Default for roles
if (typeof this.roles === 'undefined') {
@ -19,7 +20,7 @@ class User extends Model {
}
$beforeUpdate () {
this.modified_on = Model.raw('NOW()');
this.modified_on = now();
}
static get name () {

View File

@ -3,17 +3,18 @@
const db = require('../db');
const Model = require('objection').Model;
const now = require('./now_helper');
Model.knex(db);
class UserPermission extends Model {
$beforeInsert () {
this.created_on = Model.raw('NOW()');
this.modified_on = Model.raw('NOW()');
this.created_on = now();
this.modified_on = now();
}
$beforeUpdate () {
this.modified_on = Model.raw('NOW()');
this.modified_on = now();
}
static get name () {

View File

@ -6,28 +6,30 @@
"dependencies": {
"ajv": "^6.12.0",
"batchflow": "^0.4.0",
"bcrypt": "^4.0.1",
"bcrypt": "^5.0.0",
"body-parser": "^1.19.0",
"compression": "^1.7.4",
"config": "^3.3.1",
"diskdb": "^0.1.17",
"express": "^4.17.1",
"express-fileupload": "^1.1.6",
"express-fileupload": "^1.1.9",
"gravatar": "^1.8.0",
"html-entities": "^1.2.1",
"json-schema-ref-parser": "^8.0.0",
"jsonwebtoken": "^8.5.1",
"knex": "^0.20.13",
"liquidjs": "^9.11.10",
"lodash": "^4.17.15",
"lodash": "^4.17.19",
"moment": "^2.24.0",
"mysql": "^2.18.1",
"node-rsa": "^1.0.8",
"nodemon": "^2.0.2",
"objection": "^2.1.3",
"path": "^0.12.7",
"pg": "^7.12.1",
"restler": "^3.4.0",
"signale": "^1.4.0",
"sqlite3": "^4.1.1",
"temp-write": "^4.0.0",
"unix-timestamp": "^0.2.0"
},

View File

@ -58,6 +58,7 @@ router
.post((req, res, next) => {
apiValidator({$ref: 'endpoints/certificates#/links/1/schema'}, req.body)
.then((payload) => {
req.setTimeout(900000); // 15 minutes timeout
return internalCertificate.create(res.locals.access, payload);
})
.then((result) => {
@ -197,6 +198,7 @@ router
* Renew certificate
*/
.post((req, res, next) => {
req.setTimeout(900000); // 15 minutes timeout
internalCertificate.renew(res.locals.access, {
id: parseInt(req.params.certificate_id, 10)
})

View File

@ -179,6 +179,19 @@
"pattern": "^(?:\\*\\.)?(?:[^.*]+\\.?)+[^.]$"
}
},
"http_code": {
"description": "Redirect HTTP Status Code",
"example": 302,
"type": "integer",
"minimum": 300,
"maximum": 308
},
"scheme": {
"description": "RFC Protocol",
"example": "HTTPS or $scheme",
"type": "string",
"minLength": 4
},
"enabled": {
"description": "Is Enabled",
"example": true,
@ -222,6 +235,11 @@
"description": "Should we cache assets",
"example": true,
"type": "boolean"
},
"static": {
"description": "Should the proxy point to static files",
"example": true,
"type": "boolean"
}
}
}

View File

@ -42,6 +42,9 @@
"satisfy_any": {
"type": "boolean"
},
"pass_auth": {
"type": "boolean"
},
"meta": {
"type": "object"
}
@ -102,6 +105,9 @@
"satisfy_any": {
"$ref": "#/definitions/satisfy_any"
},
"pass_auth": {
"$ref": "#/definitions/pass_auth"
},
"items": {
"type": "array",
"minItems": 0,
@ -167,6 +173,9 @@
"satisfy_any": {
"$ref": "#/definitions/satisfy_any"
},
"pass_auth": {
"$ref": "#/definitions/pass_auth"
},
"items": {
"type": "array",
"minItems": 0,

View File

@ -41,6 +41,24 @@
},
"letsencrypt_agree": {
"type": "boolean"
},
"dns_challenge": {
"type": "boolean"
},
"dns_provider": {
"type": "string"
},
"dns_provider_credentials": {
"type": "string"
},
"propagation_seconds": {
"anyOf": [
{
"type": "integer",
"minimum": 0
}
]
}
}
}

View File

@ -24,14 +24,22 @@
},
"forward_host": {
"type": "string",
"minLength": 1,
"minLength": 0,
"maxLength": 255
},
"forward_port": {
"type": "integer",
"minimum": 1,
"minimum": 0,
"maximum": 65535
},
"root_dir": {
"type": "string",
"minLength": 0,
},
"index_file": {
"type": "string",
"minLength": 0,
},
"certificate_id": {
"$ref": "../definitions.json#/definitions/certificate_id"
},
@ -53,6 +61,9 @@
"caching_enabled": {
"$ref": "../definitions.json#/definitions/caching_enabled"
},
"static": {
"$ref": "../definitions.json#/definitions/static"
},
"allow_websocket_upgrade": {
"description": "Allow Websocket Upgrade for all paths",
"example": true,
@ -76,10 +87,7 @@
"items": {
"type": "object",
"required": [
"forward_scheme",
"forward_host",
"forward_port",
"path"
"forward_scheme"
],
"additionalProperties": false,
"properties": {
@ -99,6 +107,15 @@
"forward_port": {
"$ref": "#/definitions/forward_port"
},
"root_dir": {
"$ref": "#/definitions/root_dir"
},
"index_file": {
"$ref": "#/definitions/index_file"
},
"static": {
"$ref": "#/definitions/static"
},
"forward_path": {
"type": "string"
},
@ -131,6 +148,12 @@
"forward_port": {
"$ref": "#/definitions/forward_port"
},
"root_dir": {
"$ref": "#/definitions/root_dir"
},
"index_file": {
"$ref": "#/definitions/index_file"
},
"certificate_id": {
"$ref": "#/definitions/certificate_id"
},
@ -152,6 +175,9 @@
"caching_enabled": {
"$ref": "#/definitions/caching_enabled"
},
"static": {
"$ref": "#/definitions/static"
},
"allow_websocket_upgrade": {
"$ref": "#/definitions/allow_websocket_upgrade"
},
@ -204,9 +230,7 @@
"additionalProperties": false,
"required": [
"domain_names",
"forward_scheme",
"forward_host",
"forward_port"
"forward_scheme"
],
"properties": {
"domain_names": {
@ -221,6 +245,12 @@
"forward_port": {
"$ref": "#/definitions/forward_port"
},
"root_dir": {
"$ref": "#/definitions/root_dir"
},
"index_file": {
"$ref": "#/definitions/index_file"
},
"certificate_id": {
"$ref": "#/definitions/certificate_id"
},
@ -242,6 +272,9 @@
"caching_enabled": {
"$ref": "#/definitions/caching_enabled"
},
"static": {
"$ref": "#/definitions/static"
},
"allow_websocket_upgrade": {
"$ref": "#/definitions/allow_websocket_upgrade"
},
@ -294,6 +327,12 @@
"forward_port": {
"$ref": "#/definitions/forward_port"
},
"root_dir": {
"$ref": "#/definitions/root_dir"
},
"index_file": {
"$ref": "#/definitions/index_file"
},
"certificate_id": {
"$ref": "#/definitions/certificate_id"
},
@ -315,6 +354,9 @@
"caching_enabled": {
"$ref": "#/definitions/caching_enabled"
},
"static": {
"$ref": "#/definitions/static"
},
"allow_websocket_upgrade": {
"$ref": "#/definitions/allow_websocket_upgrade"
},

View File

@ -18,6 +18,12 @@
"domain_names": {
"$ref": "../definitions.json#/definitions/domain_names"
},
"forward_http_code": {
"$ref": "../definitions.json#/definitions/http_code"
},
"forward_scheme": {
"$ref": "../definitions.json#/definitions/scheme"
},
"forward_domain_name": {
"$ref": "../definitions.json#/definitions/domain_name"
},
@ -67,6 +73,12 @@
"domain_names": {
"$ref": "#/definitions/domain_names"
},
"forward_http_code": {
"$ref": "#/definitions/forward_http_code"
},
"forward_scheme": {
"$ref": "#/definitions/forward_scheme"
},
"forward_domain_name": {
"$ref": "#/definitions/forward_domain_name"
},
@ -134,12 +146,20 @@
"additionalProperties": false,
"required": [
"domain_names",
"forward_scheme",
"forward_http_code",
"forward_domain_name"
],
"properties": {
"domain_names": {
"$ref": "#/definitions/domain_names"
},
"forward_http_code": {
"$ref": "#/definitions/forward_http_code"
},
"forward_scheme": {
"$ref": "#/definitions/forward_scheme"
},
"forward_domain_name": {
"$ref": "#/definitions/forward_domain_name"
},
@ -195,6 +215,12 @@
"domain_names": {
"$ref": "#/definitions/domain_names"
},
"forward_http_code": {
"$ref": "#/definitions/forward_http_code"
},
"forward_scheme": {
"$ref": "#/definitions/forward_scheme"
},
"forward_domain_name": {
"$ref": "#/definitions/forward_domain_name"
},

View File

@ -2,12 +2,21 @@ const fs = require('fs');
const NodeRSA = require('node-rsa');
const config = require('config');
const logger = require('./logger').setup;
const certificateModel = require('./models/certificate');
const userModel = require('./models/user');
const userPermissionModel = require('./models/user_permission');
const utils = require('./lib/utils');
const authModel = require('./models/auth');
const settingModel = require('./models/setting');
const dns_plugins = require('./global/certbot-dns-plugins');
const debug_mode = process.env.NODE_ENV !== 'production' || !!process.env.DEBUG;
module.exports = function () {
/**
* Creates a new JWT RSA Keypair if not alread set on the config
*
* @returns {Promise}
*/
const setupJwt = () => {
return new Promise((resolve, reject) => {
// Now go and check if the jwt gpg keys have been created and if not, create them
if (!config.has('jwt') || !config.has('jwt.key') || !config.has('jwt.pub')) {
@ -27,12 +36,12 @@ module.exports = function () {
}
// Now create the keys and save them in the config.
let key = new NodeRSA({b: 2048});
let key = new NodeRSA({ b: 2048 });
key.generateKeyPair();
config_data.jwt = {
key: key.exportKey('private').toString(),
pub: key.exportKey('public').toString()
pub: key.exportKey('public').toString(),
};
// Write config
@ -42,12 +51,10 @@ module.exports = function () {
reject(err);
} else {
logger.info('Wrote JWT key pair to config file: ' + filename);
logger.warn('Restarting interface to apply new configuration');
process.exit(0);
delete require.cache[require.resolve('config')];
resolve();
}
});
} else {
// JWT key pair exists
if (debug_mode) {
@ -56,14 +63,20 @@ module.exports = function () {
resolve();
}
})
.then(() => {
return userModel
.query()
.select(userModel.raw('COUNT(`id`) as `count`'))
.where('is_deleted', 0)
.first();
})
});
};
/**
* Creates a default admin users if one doesn't already exist in the database
*
* @returns {Promise}
*/
const setupDefaultUser = () => {
return userModel
.query()
.select(userModel.raw('COUNT(`id`) as `count`'))
.where('is_deleted', 0)
.first()
.then((row) => {
if (!row.count) {
// Create a new user and set password
@ -75,7 +88,7 @@ module.exports = function () {
name: 'Administrator',
nickname: 'Admin',
avatar: '',
roles: ['admin']
roles: ['admin'],
};
return userModel
@ -88,28 +101,109 @@ module.exports = function () {
user_id: user.id,
type: 'password',
secret: 'changeme',
meta: {}
meta: {},
})
.then(() => {
return userPermissionModel
.query()
.insert({
user_id: user.id,
visibility: 'all',
proxy_hosts: 'manage',
redirection_hosts: 'manage',
dead_hosts: 'manage',
streams: 'manage',
access_lists: 'manage',
certificates: 'manage'
});
return userPermissionModel.query().insert({
user_id: user.id,
visibility: 'all',
proxy_hosts: 'manage',
redirection_hosts: 'manage',
dead_hosts: 'manage',
streams: 'manage',
access_lists: 'manage',
certificates: 'manage',
});
});
})
.then(() => {
logger.info('Initial setup completed');
logger.info('Initial admin setup completed');
});
} else if (debug_mode) {
logger.debug('Admin user setup not required');
}
});
};
/**
* Creates default settings if they don't already exist in the database
*
* @returns {Promise}
*/
const setupDefaultSettings = () => {
return settingModel
.query()
.select(settingModel.raw('COUNT(`id`) as `count`'))
.where({id: 'default-site'})
.first()
.then((row) => {
if (!row.count) {
settingModel
.query()
.insert({
id: 'default-site',
name: 'Default Site',
description: 'What to show when Nginx is hit with an unknown Host',
value: 'congratulations',
meta: {},
})
.then(() => {
logger.info('Default settings added');
});
}
if (debug_mode) {
logger.debug('Default setting setup not required');
}
});
};
/**
* Installs all Certbot plugins which are required for an installed certificate
*
* @returns {Promise}
*/
const setupCertbotPlugins = () => {
return certificateModel
.query()
.where('is_deleted', 0)
.andWhere('provider', 'letsencrypt')
.then((certificates) => {
if (certificates && certificates.length) {
let plugins = [];
let promises = [];
certificates.map(function (certificate) {
if (certificate.meta && certificate.meta.dns_challenge === true) {
const dns_plugin = dns_plugins[certificate.meta.dns_provider];
const packages_to_install = `${dns_plugin.package_name}==${dns_plugin.package_version} ${dns_plugin.dependencies}`;
if (plugins.indexOf(packages_to_install) === -1) plugins.push(packages_to_install);
// Make sure credentials file exists
const credentials_loc = '/etc/letsencrypt/credentials/credentials-' + certificate.id;
const credentials_cmd = '[ -f \'' + credentials_loc + '\' ] || { mkdir -p /etc/letsencrypt/credentials 2> /dev/null; echo \'' + certificate.meta.dns_provider_credentials.replace('\'', '\\\'') + '\' > \'' + credentials_loc + '\' && chmod 600 \'' + credentials_loc + '\'; }';
promises.push(utils.exec(credentials_cmd));
}
});
if (plugins.length) {
const install_cmd = 'pip3 install ' + plugins.join(' ');
promises.push(utils.exec(install_cmd));
}
if (promises.length) {
return Promise.all(promises)
.then(() => {
logger.info('Added Certbot plugins ' + plugins.join(', '));
});
}
}
});
};
module.exports = function () {
return setupJwt()
.then(setupDefaultUser)
.then(setupDefaultSettings)
.then(setupCertbotPlugins);
};

View File

@ -1,8 +1,8 @@
{% if certificate and certificate_id > 0 -%}
{% if ssl_forced == 1 or ssl_forced == true %}
{% if hsts_enabled == 1 or hsts_enabled == true %}
# HSTS (ngx_http_headers_module is required) (31536000 seconds = 1 year)
add_header Strict-Transport-Security "max-age=31536000;{% if hsts_subdomains == 1 or hsts_subdomains == true -%} includeSubDomains;{% endif %} preload" always;
# HSTS (ngx_http_headers_module is required) (63072000 seconds = 2 years)
add_header Strict-Transport-Security "max-age=63072000;{% if hsts_subdomains == 1 or hsts_subdomains == true -%} includeSubDomains;{% endif %} preload" always;
{% endif %}
{% endif %}
{% endif %}
{% endif %}

View File

@ -13,3 +13,8 @@
{% endif %}
{% endif %}
server_name {{ domain_names | join: " " }};
{% if static == 1 or static == true %}
root {{ root_dir }};
index {{ index_file }};
{% endif %}

View File

@ -1,9 +1,16 @@
location {{ path }} {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Scheme $scheme;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass {{ forward_scheme }}://{{ forward_host }}:{{ forward_port }}{{ forward_path }};
{% if static == 0 or static == false %}
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Scheme $scheme;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass {{ forward_scheme }}://{{ forward_host }}:{{ forward_port }}{{ forward_path }};
{% else %}
alias {{ root_dir }}/$1;
try_files $uri /{{ index_file }} =200;
{% endif %}
{{ advanced_config }}
}

View File

@ -6,6 +6,11 @@
{%- else %}
server {
listen 80 default;
{% if ipv6 -%}
listen [::]:80;
{% else -%}
#listen [::]:80;
{% endif %}
server_name default-host.localhost;
access_log /data/logs/default_host.log combined;
{% include "_exploits.conf" %}
@ -24,6 +29,7 @@ server {
{%- if value == "html" %}
root /data/nginx/default_www;
# root /var/www/test2;
location / {
try_files $uri /index.html;
}

View File

@ -27,6 +27,8 @@ server {
# Authorization
auth_basic "Authorization required";
auth_basic_user_file /data/access/{{ access_list_id }};
{{ access_list.passauth }}
{% endif %}
# Access Rules
@ -35,7 +37,9 @@ server {
{% endfor %}deny all;
# Access checks must...
{% if access_list.satisfy %}
{{ access_list.satisfy }};
{% endif %}
{% endif %}
@ -48,8 +52,14 @@ server {
proxy_http_version 1.1;
{% endif %}
# Proxy!
include conf.d/include/proxy.conf;
{% if static == 1 or static == true %}
alias {{ root_dir }}/$1;
try_files $uri /{{index_file}} =200;
{% else %}
# Proxy!
include conf.d/include/proxy.conf;
{% endif %}
}
{% endif %}

View File

@ -18,9 +18,9 @@ server {
{% include "_hsts.conf" %}
{% if preserve_path == 1 or preserve_path == true %}
return 301 $scheme://{{ forward_domain_name }}$request_uri;
return {{ forward_http_code }} {{ forward_scheme }}://{{ forward_domain_name }}$request_uri;
{% else %}
return 301 $scheme://{{ forward_domain_name }};
return {{ forward_http_code }} {{ forward_scheme }}://{{ forward_domain_name }};
{% endif %}
}
{% endif %}

File diff suppressed because it is too large Load Diff

View File

@ -3,44 +3,54 @@
# This file assumes that the frontend has been built using ./scripts/frontend-build
FROM --platform=${TARGETPLATFORM:-linux/amd64} jc21/alpine-nginx-full:node
FROM jc21/nginx-full:node
ARG TARGETPLATFORM
ARG BUILDPLATFORM
ARG BUILD_VERSION
ARG BUILD_COMMIT
ARG BUILD_DATE
ENV SUPPRESS_NO_CONFIG_WARNING=1
ENV S6_FIX_ATTRS_HIDDEN=1
ENV NODE_ENV=production
ENV SUPPRESS_NO_CONFIG_WARNING=1 \
S6_FIX_ATTRS_HIDDEN=1 \
S6_BEHAVIOUR_IF_STAGE2_FAILS=1 \
NODE_ENV=production \
NPM_BUILD_VERSION="${BUILD_VERSION}" \
NPM_BUILD_COMMIT="${BUILD_COMMIT}" \
NPM_BUILD_DATE="${BUILD_DATE}"
RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \
&& apk update \
&& apk add python2 certbot jq \
&& rm -rf /var/cache/apk/*
ENV NPM_BUILD_VERSION="${BUILD_VERSION}" NPM_BUILD_COMMIT="${BUILD_COMMIT}" NPM_BUILD_DATE="${BUILD_DATE}"
&& apt-get update \
&& apt-get install -y certbot jq python3-pip \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# s6 overlay
COPY scripts/install-s6 /tmp/install-s6
RUN /tmp/install-s6 "${TARGETPLATFORM}" && rm -f /tmp/install-s6
EXPOSE 80
EXPOSE 81
EXPOSE 443
EXPOSE 80 81 443
COPY docker/rootfs /
ADD backend /app
ADD frontend/dist /app/frontend
COPY backend /app
COPY frontend/dist /app/frontend
COPY global /app/global
WORKDIR /app
RUN yarn install
# add late to limit cache-busting by modifications
COPY docker/rootfs /
# Remove frontend service not required for prod, dev nginx config as well
RUN rm -rf /etc/services.d/frontend RUN rm -f /etc/nginx/conf.d/dev.conf
VOLUME [ "/data", "/etc/letsencrypt" ]
CMD [ "/init" ]
ENTRYPOINT [ "/init" ]
HEALTHCHECK --interval=5s --timeout=3s CMD /bin/check-health
LABEL org.label-schema.schema-version="1.0" \
org.label-schema.license="MIT" \
org.label-schema.name="nginx-proxy-manager" \
org.label-schema.description="Docker container for managing Nginx proxy hosts with a simple, powerful interface " \
org.label-schema.url="https://github.com/jc21/nginx-proxy-manager" \
org.label-schema.vcs-url="https://github.com/jc21/nginx-proxy-manager.git" \
org.label-schema.cmd="docker run --rm -ti jc21/nginx-proxy-manager:latest"

View File

@ -1,14 +1,15 @@
FROM jc21/alpine-nginx-full:node
FROM jc21/nginx-full:node
LABEL maintainer="Jamie Curnow <jc@jc21.com>"
ENV S6_LOGGING=0
ENV SUPPRESS_NO_CONFIG_WARNING=1
ENV S6_FIX_ATTRS_HIDDEN=1
ENV S6_LOGGING=0 \
SUPPRESS_NO_CONFIG_WARNING=1 \
S6_FIX_ATTRS_HIDDEN=1
RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \
&& apk update \
&& apk add python2 certbot jq \
&& rm -rf /var/cache/apk/*
&& apt-get update \
&& apt-get install -y certbot jq python3-pip \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Task
RUN cd /usr \
@ -22,10 +23,6 @@ RUN rm -f /etc/nginx/conf.d/production.conf
RUN curl -L -o /tmp/s6-overlay-amd64.tar.gz "https://github.com/just-containers/s6-overlay/releases/download/v1.22.1.0/s6-overlay-amd64.tar.gz" \
&& tar -xzf /tmp/s6-overlay-amd64.tar.gz -C /
EXPOSE 80
EXPOSE 81
EXPOSE 443
CMD [ "/init" ]
EXPOSE 80 81 443
ENTRYPOINT [ "/init" ]
HEALTHCHECK --interval=5s --timeout=3s CMD /bin/check-health

View File

@ -2,14 +2,18 @@
version: "3"
services:
fullstack:
fullstack-mysql:
image: ${IMAGE}:ci-${BUILD_NUMBER}
environment:
- NODE_ENV=development
- FORCE_COLOR=1
NODE_ENV: "development"
FORCE_COLOR: 1
DB_MYSQL_HOST: "db"
DB_MYSQL_PORT: 3306
DB_MYSQL_USER: "npm"
DB_MYSQL_PASSWORD: "npm"
DB_MYSQL_NAME: "npm"
volumes:
- npm_data:/data
- ../.jenkins/config.json:/app/config/production.json
expose:
- 81
- 80
@ -17,6 +21,19 @@ services:
depends_on:
- db
fullstack-sqlite:
image: ${IMAGE}:ci-${BUILD_NUMBER}
environment:
NODE_ENV: "development"
FORCE_COLOR: 1
DB_SQLITE_FILE: "/data/database.sqlite"
volumes:
- npm_data:/data
expose:
- 81
- 80
- 443
db:
image: jc21/mariadb-aria
environment:
@ -27,13 +44,24 @@ services:
volumes:
- db_data:/var/lib/mysql
cypress:
cypress-mysql:
image: ${IMAGE}-cypress:ci-${BUILD_NUMBER}
build:
context: ../
dockerfile: test/cypress/Dockerfile
context: ../test/
dockerfile: cypress/Dockerfile
environment:
CYPRESS_baseUrl: "http://fullstack:81"
CYPRESS_baseUrl: "http://fullstack-mysql:81"
volumes:
- cypress-logs:/results
command: cypress run --browser chrome --config-file=${CYPRESS_CONFIG:-cypress/config/ci.json}
cypress-sqlite:
image: ${IMAGE}-cypress:ci-${BUILD_NUMBER}
build:
context: ../test/
dockerfile: cypress/Dockerfile
environment:
CYPRESS_baseUrl: "http://fullstack-sqlite:81"
volumes:
- cypress-logs:/results
command: cypress run --browser chrome --config-file=${CYPRESS_CONFIG:-cypress/config/ci.json}

View File

@ -11,21 +11,33 @@ services:
- 3080:80
- 3081:81
- 3443:443
networks:
- nginx_proxy_manager
environment:
- NODE_ENV=development
- FORCE_COLOR=1
- DEVELOPMENT=true
#- DISABLE_IPV6=true
NODE_ENV: "development"
FORCE_COLOR: 1
DEVELOPMENT: "true"
DB_MYSQL_HOST: "db"
DB_MYSQL_PORT: 3306
DB_MYSQL_USER: "npm"
DB_MYSQL_PASSWORD: "npm"
DB_MYSQL_NAME: "npm"
# DB_SQLITE_FILE: "/data/database.sqlite"
# DISABLE_IPV6: "true"
volumes:
- npm_data:/data
- le_data:/etc/letsencrypt
- ..:/app
- ../backend:/app
- ../frontend:/app/frontend
- ../global:/app/global
depends_on:
- db
working_dir: /app
db:
image: jc21/mariadb-aria
networks:
- nginx_proxy_manager
environment:
MYSQL_ROOT_PASSWORD: "npm"
MYSQL_DATABASE: "npm"
@ -38,6 +50,8 @@ services:
image: 'swaggerapi/swagger-ui:latest'
ports:
- 3001:80
networks:
- nginx_proxy_manager
environment:
URL: "http://127.0.0.1:3081/api/schema"
PORT: '80'
@ -48,3 +62,6 @@ volumes:
npm_data:
le_data:
db_data:
networks:
nginx_proxy_manager:

View File

@ -1,2 +1,3 @@
*
!.gitignore
!*.sh

View File

@ -0,0 +1,29 @@
#!/usr/bin/with-contenv bash
# ref: https://github.com/linuxserver/docker-baseimage-alpine/blob/master/root/etc/cont-init.d/01-envfile
# in s6, environmental variables are written as text files for s6 to monitor
# seach through full-path filenames for files ending in "__FILE"
for FILENAME in $(find /var/run/s6/container_environment/ | grep "__FILE$"); do
echo "[secret-init] Evaluating ${FILENAME##*/} ..."
# set SECRETFILE to the contents of the full-path textfile
SECRETFILE=$(cat ${FILENAME})
# SECRETFILE=${FILENAME}
# echo "[secret-init] Set SECRETFILE to ${SECRETFILE}" # DEBUG - rm for prod!
# if SECRETFILE exists / is not null
if [[ -f ${SECRETFILE} ]]; then
# strip the appended "__FILE" from environmental variable name ...
STRIPFILE=$(echo ${FILENAME} | sed "s/__FILE//g")
# echo "[secret-init] Set STRIPFILE to ${STRIPFILE}" # DEBUG - rm for prod!
# ... and set value to contents of secretfile
# since s6 uses text files, this is effectively "export ..."
printf $(cat ${SECRETFILE}) > ${STRIPFILE}
# echo "[secret-init] Set ${STRIPFILE##*/} to $(cat ${STRIPFILE})" # DEBUG - rm for prod!"
echo "[secret-init] Success! ${STRIPFILE##*/} set from ${FILENAME##*/}"
else
echo "[secret-init] cannot find secret in ${FILENAME}"
fi
done

View File

@ -17,6 +17,9 @@ server {
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass http://127.0.0.1:3000/;
proxy_read_timeout 15m;
proxy_send_timeout 15m;
}
location / {

View File

@ -1,196 +1,2 @@
set_real_ip_from 144.220.0.0/16;
set_real_ip_from 52.124.128.0/17;
set_real_ip_from 54.230.0.0/16;
set_real_ip_from 54.239.128.0/18;
set_real_ip_from 52.82.128.0/19;
set_real_ip_from 99.84.0.0/16;
set_real_ip_from 204.246.172.0/24;
set_real_ip_from 205.251.192.0/19;
set_real_ip_from 54.239.192.0/19;
set_real_ip_from 70.132.0.0/18;
set_real_ip_from 13.32.0.0/15;
set_real_ip_from 13.224.0.0/14;
set_real_ip_from 13.35.0.0/16;
set_real_ip_from 204.246.164.0/22;
set_real_ip_from 204.246.168.0/22;
set_real_ip_from 71.152.0.0/17;
set_real_ip_from 216.137.32.0/19;
set_real_ip_from 205.251.249.0/24;
set_real_ip_from 99.86.0.0/16;
set_real_ip_from 52.46.0.0/18;
set_real_ip_from 52.84.0.0/15;
set_real_ip_from 204.246.173.0/24;
set_real_ip_from 130.176.0.0/16;
set_real_ip_from 64.252.64.0/18;
set_real_ip_from 204.246.174.0/23;
set_real_ip_from 64.252.128.0/18;
set_real_ip_from 205.251.254.0/24;
set_real_ip_from 143.204.0.0/16;
set_real_ip_from 205.251.252.0/23;
set_real_ip_from 204.246.176.0/20;
set_real_ip_from 13.249.0.0/16;
set_real_ip_from 54.240.128.0/18;
set_real_ip_from 205.251.250.0/23;
set_real_ip_from 52.222.128.0/17;
set_real_ip_from 54.182.0.0/16;
set_real_ip_from 54.192.0.0/16;
set_real_ip_from 13.124.199.0/24;
set_real_ip_from 34.226.14.0/24;
set_real_ip_from 52.15.127.128/26;
set_real_ip_from 35.158.136.0/24;
set_real_ip_from 52.57.254.0/24;
set_real_ip_from 18.216.170.128/25;
set_real_ip_from 13.52.204.0/23;
set_real_ip_from 13.54.63.128/26;
set_real_ip_from 13.59.250.0/26;
set_real_ip_from 13.210.67.128/26;
set_real_ip_from 35.167.191.128/26;
set_real_ip_from 52.47.139.0/24;
set_real_ip_from 52.199.127.192/26;
set_real_ip_from 52.212.248.0/26;
set_real_ip_from 52.66.194.128/26;
set_real_ip_from 13.113.203.0/24;
set_real_ip_from 99.79.168.0/23;
set_real_ip_from 34.195.252.0/24;
set_real_ip_from 35.162.63.192/26;
set_real_ip_from 34.223.12.224/27;
set_real_ip_from 52.56.127.0/25;
set_real_ip_from 34.223.80.192/26;
set_real_ip_from 13.228.69.0/24;
set_real_ip_from 34.216.51.0/25;
set_real_ip_from 3.231.2.0/25;
set_real_ip_from 54.233.255.128/26;
set_real_ip_from 18.200.212.0/23;
set_real_ip_from 52.52.191.128/26;
set_real_ip_from 3.234.232.224/27;
set_real_ip_from 52.78.247.128/26;
set_real_ip_from 52.220.191.0/26;
set_real_ip_from 34.232.163.208/29;
set_real_ip_from 2600:9000:eee::/48;
set_real_ip_from 2600:9000:4000::/36;
set_real_ip_from 2600:9000:3000::/36;
set_real_ip_from 2600:9000:f000::/36;
set_real_ip_from 2600:9000:fff::/48;
set_real_ip_from 2600:9000:2000::/36;
set_real_ip_from 2600:9000:1000::/36;
set_real_ip_from 2600:9000:ddd::/48;
set_real_ip_from 2600:9000:5300::/40;
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/12;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 131.0.72.0/22;
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2a06:98c0::/29;
set_real_ip_from 2c0f:f248::/32;
# This should be left blank is it is populated programatically
# by the application backend.

View File

@ -3,4 +3,6 @@ proxy_set_header Host $host;
proxy_set_header X-Forwarded-Scheme $scheme;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_pass $forward_scheme://$server:$port;

View File

@ -18,6 +18,9 @@ server {
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_pass http://127.0.0.1:3000/;
proxy_read_timeout 15m;
proxy_send_timeout 15m;
}
location / {

View File

@ -27,9 +27,9 @@ http {
tcp_nodelay on;
client_body_temp_path /tmp/nginx/body 1 2;
keepalive_timeout 90s;
proxy_connect_timeout 90s;
proxy_send_timeout 90s;
proxy_read_timeout 90s;
proxy_connect_timeout 90s;
proxy_send_timeout 90s;
proxy_read_timeout 90s;
ssl_prefer_server_ciphers on;
gzip on;
proxy_ignore_client_abort off;
@ -60,12 +60,18 @@ http {
# Real IP Determination
# Docker subnet:
set_real_ip_from 172.0.0.0/8;
# Local subnets:
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 192.0.0.0/8;
# NPM generated CDN ip ranges:
include conf.d/include/ip_ranges.conf;
# always put the following 2 lines after ip subnets:
real_ip_header X-Forwarded-For;
real_ip_header X-Real-IP;
real_ip_recursive on;
# Custom
include /data/nginx/custom/http_top[.]conf;
# Files generated by NPM
include /etc/nginx/conf.d/*.conf;
include /data/nginx/default_host/*.conf;
@ -81,6 +87,9 @@ http {
stream {
# Files generated by NPM
include /data/nginx/stream/*.conf;
# Custom
include /data/nginx/custom/stream[.]conf;
}
# Custom

View File

@ -5,7 +5,7 @@ mkdir -p /data/letsencrypt-acme-challenge
cd /app || echo
if [ "$DEVELOPMENT" == "true" ]; then
cd /app/backend || exit 1
cd /app || exit 1
yarn install
node --max_old_space_size=250 --abort_on_uncaught_exception node_modules/nodemon/bin/nodemon.js
else

View File

@ -47,6 +47,7 @@ module.exports = {
["/screenshots/", "Screenshots"],
["/setup/", "Setup Instructions"],
["/advanced-config/", "Advanced Configuration"],
["/upgrading/", "Upgrading"],
["/faq/", "Frequently Asked Questions"],
["/third-party/", "Third Party"]
]

View File

@ -45,21 +45,7 @@ footer: MIT Licensed | Copyright © 2016-present jc21.com
- [Docker Install documentation](https://docs.docker.com/install/)
- [Docker-Compose Install documentation](https://docs.docker.com/compose/install/)
2. Create a config file for example
```json
{
"database": {
"engine": "mysql",
"host": "db",
"name": "npm",
"user": "npm",
"password": "npm",
"port": 3306
}
}
```
3. Create a docker-compose.yml file similar to this:
2. Create a docker-compose.yml file similar to this:
```yml
version: '3'
@ -70,12 +56,17 @@ services:
- '80:80'
- '81:81'
- '443:443'
environment:
DB_MYSQL_HOST: "db"
DB_MYSQL_PORT: 3306
DB_MYSQL_USER: "npm"
DB_MYSQL_PASSWORD: "npm"
DB_MYSQL_NAME: "npm"
volumes:
- ./config.json:/app/config/production.json
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
db:
image: 'jc21/mariadb-aria:10.4'
image: 'jc21/mariadb-aria:latest'
environment:
MYSQL_ROOT_PASSWORD: 'npm'
MYSQL_DATABASE: 'npm'
@ -85,13 +76,13 @@ services:
- ./data/mysql:/var/lib/mysql
```
4. Bring up your stack
3. Bring up your stack
```bash
docker-compose up -d
```
5. Log in to the Admin UI
4. Log in to the Admin UI
When your docker container is running, connect to it on port `81` for the admin interface.
Sometimes this can take a little bit because of the entropy of keys.
@ -106,3 +97,15 @@ Password: changeme
```
Immediately after logging in with this default user you will be asked to modify your details and change your password.
5. Upgrading to new versions
```bash
docker-compose pull
docker-compose up -d
```
This project will automatically update any databases or other requirements so you don't have to follow
any crazy instructions. These steps above will pull the latest updates and recreate the docker
containers.

View File

@ -1,5 +1,119 @@
# Advanced Configuration
## Best Practice: Use a docker network
For those who have a few of their upstream services running in docker on the same docker
host as NPM, here's a trick to secure things a bit better. By creating a custom docker network,
you don't need to publish ports for your upstream services to all of the docker host's interfaces.
Create a network, ie "scoobydoo":
```bash
docker network create scoobydoo
```
Then add the following to the `docker-compose.yml` file for both NPM and any other
services running on this docker host:
```yml
networks:
default:
external:
name: scoobydoo
```
Let's look at a Portainer example:
```yml
version: '3'
services:
portainer:
image: portainer/portainer
privileged: true
volumes:
- './data:/data'
- '/var/run/docker.sock:/var/run/docker.sock'
restart: always
networks:
default:
external:
name: scoobydoo
```
Now in the NPM UI you can create a proxy host with `portainer` as the hostname,
and port `9000` as the port. Even though this port isn't listed in the docker-compose
file, it's "exposed" by the portainer docker image for you and not available on
the docker host outside of this docker network. The service name is used as the
hostname, so make sure your service names are unique when using the same network.
## Docker Secrets
This image supports the use of Docker secrets to import from file and keep sensitive usernames or passwords from being passed or preserved in plaintext.
You can set any environment variable from a file by appending `__FILE` (double-underscore FILE) to the environmental variable name.
```yml
version: "3.7"
secrets:
# Secrets are single-line text files where the sole content is the secret
# Paths in this example assume that secrets are kept in local folder called ".secrets"
DB_ROOT_PWD:
file: .secrets/db_root_pwd.txt
MYSQL_PWD:
file: .secrets/mysql_pwd.txt
services:
app:
image: 'jc21/nginx-proxy-manager:latest'
restart: always
ports:
# Public HTTP Port:
- '80:80'
# Public HTTPS Port:
- '443:443'
# Admin Web Port:
- '81:81'
environment:
# These are the settings to access your db
DB_MYSQL_HOST: "db"
DB_MYSQL_PORT: 3306
DB_MYSQL_USER: "npm"
# DB_MYSQL_PASSWORD: "npm" # use secret instead
DB_MYSQL_PASSWORD__FILE: /run/secrets/MYSQL_PWD
DB_MYSQL_NAME: "npm"
# If you would rather use Sqlite uncomment this
# and remove all DB_MYSQL_* lines above
# DB_SQLITE_FILE: "/data/database.sqlite"
# Uncomment this if IPv6 is not enabled on your host
# DISABLE_IPV6: 'true'
volumes:
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
secrets:
- MYSQL_PWD
depends_on:
- db
db:
image: jc21/mariadb-aria
restart: always
environment:
# MYSQL_ROOT_PASSWORD: "npm" # use secret instead
MYSQL_ROOT_PASSWORD__FILE: /run/secrets/DB_ROOT_PWD
MYSQL_DATABASE: "npm"
MYSQL_USER: "npm"
# MYSQL_PASSWORD: "npm" # use secret instead
MYSQL_PASSWORD__FILE: /run/secrets/MYSQL_PWD
volumes:
- ./data/mysql:/var/lib/mysql
secrets:
- DB_ROOT_PWD
- MYSQL_PWD
```
## Disabling IPv6
On some docker hosts IPv6 may not be enabled. In these cases, the following message may be seen in the log:
@ -23,7 +137,9 @@ NPM has the ability to include different custom configuration snippets in differ
You can add your custom configuration snippet files at `/data/nginx/custom` as follow:
- `/data/nginx/custom/root.conf`: Included at the very end of nginx.conf
- `/data/nginx/custom/http_top.conf`: Included at the top of the main http block
- `/data/nginx/custom/http.conf`: Included at the end of the main http block
- `/data/nginx/custom/stream.conf`: Included at the end of the main stream block
- `/data/nginx/custom/server_proxy.conf`: Included at the end of every proxy server block
- `/data/nginx/custom/server_redirect.conf`: Included at the end of every redirection server block
- `/data/nginx/custom/server_stream.conf`: Included at the end of every stream server block

View File

@ -14,3 +14,10 @@ of dependencies.
Yes! The docker image is multi-arch and is built for a variety of architectures. If yours is
[not listed](https://hub.docker.com/r/jc21/nginx-proxy-manager/tags) please open a
[GitHub issue](https://github.com/jc21/nginx-proxy-manager/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=).
## I can't get my service to proxy properly?
Your best bet is to ask the [Reddit community for support](https://www.reddit.com/r/nginxproxymanager/). There's safety in numbers.
Gitter is best left for anyone contributing to the project to ask for help about internals, code reviews etc.

View File

@ -4,15 +4,15 @@
"description": "",
"main": "index.js",
"dependencies": {
"@vuepress/plugin-google-analytics": "^1.4.0",
"@vuepress/plugin-google-analytics": "^1.5.3",
"abbrev": "^1.1.1",
"accepts": "^1.3.7",
"acorn": "^7.1.1",
"agentkeepalive": "^4.1.0",
"ajv": "^6.12.0",
"acorn": "^7.4.0",
"agentkeepalive": "^4.1.3",
"ajv": "^6.12.3",
"ajv-errors": "^1.0.1",
"ajv-keywords": "^3.4.1",
"algoliasearch": "^4.1.0",
"ajv-keywords": "^3.5.2",
"algoliasearch": "^4.3.1",
"alphanum-sort": "^1.0.2",
"ansi-colors": "^4.1.1",
"ansi-escapes": "^4.3.1",
@ -30,7 +30,7 @@
"array-uniq": "^2.1.0",
"array-unique": "^0.3.2",
"asn1": "^0.2.4",
"asn1.js": "^5.3.0",
"asn1.js": "^5.4.1",
"assert": "^2.0.0",
"assert-plus": "^1.0.0",
"assign-symbols": "^2.0.2",
@ -40,11 +40,11 @@
"asynckit": "^0.4.0",
"atob": "^2.1.2",
"autocomplete.js": "^0.37.1",
"autoprefixer": "^9.7.6",
"autoprefixer": "^9.8.6",
"aws-sign2": "^0.7.0",
"aws4": "^1.9.1",
"aws4": "^1.10.0",
"babel-loader": "^8.1.0",
"babel-plugin-dynamic-import-node": "^2.3.0",
"babel-plugin-dynamic-import-node": "^2.3.3",
"babel-plugin-module-resolver": "^4.0.0",
"balanced-match": "^1.0.0",
"base": "^3.0.0",
@ -52,9 +52,9 @@
"batch": "^0.6.1",
"bcrypt-pbkdf": "^1.0.2",
"big.js": "^5.2.2",
"binary-extensions": "^2.0.0",
"binary-extensions": "^2.1.0",
"bluebird": "^3.7.2",
"bn.js": "^5.1.1",
"bn.js": "^5.1.2",
"body-parser": "^1.19.0",
"bonjour": "^3.5.0",
"boolbase": "^1.0.0",
@ -65,18 +65,18 @@
"browserify-cipher": "^1.0.1",
"browserify-des": "^1.0.2",
"browserify-rsa": "^4.0.1",
"browserify-sign": "^4.0.4",
"browserify-sign": "^4.2.1",
"browserify-zlib": "^0.2.0",
"browserslist": "^4.11.1",
"buffer": "^5.5.0",
"browserslist": "^4.13.0",
"buffer": "^5.6.0",
"buffer-from": "^1.1.1",
"buffer-indexof": "^1.1.1",
"buffer-json": "^2.0.0",
"buffer-xor": "^2.0.2",
"builtin-status-codes": "^3.0.0",
"bytes": "^3.1.0",
"cac": "^6.5.8",
"cacache": "^15.0.0",
"cac": "^6.6.1",
"cacache": "^15.0.5",
"cache-base": "^4.0.0",
"cache-loader": "^4.1.0",
"call-me-maybe": "^1.0.1",
@ -84,12 +84,12 @@
"caller-path": "^3.0.0",
"callsites": "^3.1.0",
"camel-case": "^4.1.1",
"camelcase": "^5.3.1",
"camelcase": "^6.0.0",
"caniuse-api": "^3.0.0",
"caniuse-lite": "^1.0.30001039",
"caniuse-lite": "^1.0.30001111",
"caseless": "^0.12.0",
"chalk": "^4.0.0",
"chokidar": "^3.3.1",
"chalk": "^4.1.0",
"chokidar": "^3.4.1",
"chownr": "^2.0.0",
"chrome-trace-event": "^1.0.2",
"ci-info": "^2.0.0",
@ -106,7 +106,7 @@
"color-name": "^1.1.4",
"color-string": "^1.5.3",
"combined-stream": "^1.0.8",
"commander": "^5.0.0",
"commander": "^6.0.0",
"commondir": "^1.0.1",
"component-emitter": "^1.3.0",
"compressible": "^2.0.18",
@ -114,36 +114,36 @@
"concat-map": "^0.0.1",
"concat-stream": "^2.0.0",
"connect-history-api-fallback": "^1.6.0",
"consola": "^2.11.3",
"consola": "^2.15.0",
"console-browserify": "^1.2.0",
"consolidate": "^0.15.1",
"constants-browserify": "^1.0.0",
"content-disposition": "^0.5.3",
"content-type": "^1.0.4",
"convert-source-map": "^1.7.0",
"cookie": "^0.4.0",
"cookie": "^0.4.1",
"cookie-signature": "^1.1.0",
"copy-concurrently": "^1.0.5",
"copy-descriptor": "^0.1.1",
"copy-webpack-plugin": "^5.1.1",
"core-js": "^3.6.4",
"copy-webpack-plugin": "^6.0.3",
"core-js": "^3.6.5",
"core-util-is": "^1.0.2",
"cosmiconfig": "^6.0.0",
"create-ecdh": "^4.0.3",
"cosmiconfig": "^7.0.0",
"create-ecdh": "^4.0.4",
"create-hash": "^1.2.0",
"create-hmac": "^1.1.7",
"cross-spawn": "^7.0.2",
"cross-spawn": "^7.0.3",
"crypto-browserify": "^3.12.0",
"css": "^2.2.4",
"css": "^3.0.0",
"css-color-names": "^1.0.1",
"css-declaration-sorter": "^5.1.2",
"css-loader": "^3.5.0",
"css-loader": "^4.2.0",
"css-parse": "^2.0.0",
"css-select": "^2.1.0",
"css-select-base-adapter": "^0.1.1",
"css-tree": "^1.0.0-alpha.39",
"css-unit-converter": "^1.1.1",
"css-what": "^3.2.1",
"css-unit-converter": "^1.1.2",
"css-what": "^3.3.0",
"cssesc": "^3.0.0",
"cssnano": "^4.1.10",
"cssnano-preset-default": "^4.0.7",
@ -158,9 +158,9 @@
"debug": "^4.1.1",
"decamelize": "^4.0.0",
"decode-uri-component": "^0.2.0",
"deep-equal": "^2.0.2",
"deep-equal": "^2.0.3",
"deepmerge": "^4.2.2",
"default-gateway": "^6.0.0",
"default-gateway": "^6.0.1",
"define-properties": "^1.1.3",
"define-property": "^2.0.2",
"del": "^5.1.0",
@ -178,52 +178,52 @@
"dns-txt": "^2.0.2",
"docsearch.js": "^2.6.3",
"dom-converter": "^0.2.0",
"dom-serializer": "^0.2.2",
"dom-serializer": "^1.0.1",
"dom-walk": "^0.1.2",
"domain-browser": "^4.0.0",
"domain-browser": "^4.16.0",
"domelementtype": "^2.0.1",
"domhandler": "^3.0.0",
"domutils": "^2.0.0",
"domutils": "^2.1.0",
"dot-prop": "^5.2.0",
"duplexify": "^4.1.1",
"ecc-jsbn": "^0.2.0",
"ee-first": "^1.1.1",
"electron-to-chromium": "^1.3.397",
"elliptic": "^6.5.2",
"emoji-regex": "^8.0.0",
"electron-to-chromium": "^1.3.522",
"elliptic": "^6.5.3",
"emoji-regex": "^9.0.0",
"emojis-list": "^3.0.0",
"encodeurl": "^1.0.2",
"end-of-stream": "^1.4.4",
"enhanced-resolve": "^4.1.1",
"entities": "^2.0.0",
"enhanced-resolve": "^4.3.0",
"entities": "^2.0.3",
"envify": "^4.1.0",
"envinfo": "^7.5.0",
"envinfo": "^7.7.2",
"errno": "^0.1.7",
"error-ex": "^1.3.2",
"es-abstract": "^1.17.5",
"es-abstract": "^1.17.6",
"es-to-primitive": "^1.2.1",
"es6-promise": "^4.2.8",
"escape-html": "^1.0.3",
"escape-string-regexp": "^2.0.0",
"eslint-scope": "^5.0.0",
"escape-string-regexp": "^4.0.0",
"eslint-scope": "^5.1.0",
"esprima": "^4.0.1",
"esrecurse": "^4.2.1",
"estraverse": "^5.0.0",
"estraverse": "^5.2.0",
"esutils": "^2.0.3",
"etag": "^1.8.1",
"eventemitter3": "^4.0.0",
"events": "^3.1.0",
"eventemitter3": "^4.0.4",
"events": "^3.2.0",
"eventsource": "^1.0.7",
"evp_bytestokey": "^1.0.3",
"execa": "^4.0.0",
"execa": "^4.0.3",
"expand-brackets": "^4.0.0",
"express": "^4.17.1",
"extend": "^3.0.2",
"extend-shallow": "^3.0.2",
"extglob": "^3.0.0",
"extsprintf": "^1.4.0",
"fast-deep-equal": "^3.1.1",
"fast-glob": "^3.2.2",
"fast-deep-equal": "^3.1.3",
"fast-glob": "^3.2.4",
"fast-json-stable-stringify": "^2.1.0",
"faye-websocket": "^0.11.3",
"figgy-pudding": "^3.5.2",
@ -235,7 +235,7 @@
"find-cache-dir": "^3.3.1",
"find-up": "^4.1.0",
"flush-write-stream": "^2.0.0",
"follow-redirects": "^1.11.0",
"follow-redirects": "^1.12.1",
"for-in": "^1.0.2",
"foreach": "^2.0.5",
"forever-agent": "^0.6.1",
@ -244,7 +244,7 @@
"fragment-cache": "^0.2.1",
"fresh": "^0.5.2",
"from2": "^2.3.0",
"fs-extra": "^9.0.0",
"fs-extra": "^9.0.1",
"fs-write-stream-atomic": "^1.0.10",
"fs.realpath": "^1.0.0",
"function-bind": "^1.1.1",
@ -257,21 +257,21 @@
"glob-parent": "^5.1.1",
"glob-to-regexp": "^0.4.1",
"global": "^4.4.0",
"globals": "^12.4.0",
"globby": "^11.0.0",
"globals": "^13.1.0",
"globby": "^11.0.1",
"good-listener": "^1.2.2",
"graceful-fs": "^4.2.3",
"graceful-fs": "^4.2.4",
"gray-matter": "^4.0.2",
"handle-thing": "^2.0.1",
"har-schema": "^2.0.0",
"har-validator": "^5.1.3",
"har-validator": "^5.1.5",
"has": "^1.0.3",
"has-ansi": "^4.0.0",
"has-flag": "^4.0.0",
"has-symbols": "^1.0.1",
"has-value": "^2.0.2",
"has-values": "^2.0.1",
"hash-base": "^3.0.4",
"hash-base": "^3.1.0",
"hash-sum": "^2.0.0",
"hash.js": "^1.1.7",
"he": "^1.2.0",
@ -282,24 +282,24 @@
"hsl-regex": "^1.0.0",
"hsla-regex": "^1.0.0",
"html-comment-regex": "^1.1.2",
"html-entities": "^1.2.1",
"html-entities": "^1.3.1",
"html-minifier": "^4.0.0",
"html-tags": "^3.1.0",
"htmlparser2": "^4.1.0",
"http-deceiver": "^1.2.7",
"http-errors": "^1.7.3",
"http-errors": "^1.8.0",
"http-parser-js": "^0.5.2",
"http-proxy": "^1.18.0",
"http-proxy-middleware": "^1.0.3",
"http-proxy": "^1.18.1",
"http-proxy-middleware": "^1.0.5",
"http-signature": "^1.3.4",
"https-browserify": "^1.0.0",
"iconv-lite": "^0.5.1",
"iconv-lite": "^0.6.2",
"icss-replace-symbols": "^1.1.0",
"icss-utils": "^4.1.1",
"ieee754": "^1.1.13",
"iferr": "^1.0.2",
"ignore": "^5.1.4",
"immediate": "^3.2.3",
"ignore": "^5.1.8",
"immediate": "^3.3.0",
"import-cwd": "^3.0.0",
"import-fresh": "^3.2.1",
"import-from": "^3.0.0",
@ -309,9 +309,9 @@
"infer-owner": "^1.0.4",
"inflight": "^1.0.6",
"inherits": "^2.0.4",
"internal-ip": "^6.0.0",
"internal-ip": "^6.1.0",
"invariant": "^2.2.4",
"invert-kv": "^3.0.0",
"invert-kv": "^3.0.1",
"ip": "^1.1.5",
"ip-regex": "^4.1.0",
"ipaddr.js": "^1.9.1",
@ -321,7 +321,7 @@
"is-arrayish": "^0.3.2",
"is-binary-path": "^2.1.0",
"is-buffer": "^2.0.4",
"is-callable": "^1.1.5",
"is-callable": "^1.2.0",
"is-color-stop": "^1.1.0",
"is-data-descriptor": "^2.0.0",
"is-date-object": "^1.0.2",
@ -337,25 +337,25 @@
"is-path-in-cwd": "^3.0.0",
"is-path-inside": "^3.0.2",
"is-plain-obj": "^2.1.0",
"is-plain-object": "^3.0.0",
"is-regex": "^1.0.5",
"is-plain-object": "^4.1.1",
"is-regex": "^1.1.1",
"is-resolvable": "^1.1.0",
"is-stream": "^2.0.0",
"is-svg": "^4.2.1",
"is-symbol": "^1.0.3",
"is-typedarray": "^1.0.0",
"is-windows": "^1.0.2",
"is-wsl": "^2.1.1",
"is-wsl": "^2.2.0",
"isarray": "^2.0.5",
"isexe": "^2.0.0",
"isobject": "^4.0.0",
"isstream": "^0.1.2",
"javascript-stringify": "^2.0.1",
"js-levenshtein": "^1.1.6",
"js-tokens": "^5.0.0",
"js-yaml": "^3.13.1",
"js-tokens": "^6.0.0",
"js-yaml": "^3.14.0",
"jsbn": "^1.1.0",
"jsesc": "^2.5.2",
"jsesc": "^3.0.1",
"json-parse-better-errors": "^1.0.2",
"json-schema": "^0.2.5",
"json-schema-traverse": "^0.4.1",
@ -368,12 +368,12 @@
"kind-of": "^6.0.3",
"last-call-webpack-plugin": "^3.0.0",
"lcid": "^3.1.1",
"linkify-it": "^2.2.0",
"linkify-it": "^3.0.2",
"load-script": "^1.0.0",
"loader-runner": "^3.1.0",
"loader-runner": "^4.0.0",
"loader-utils": "^2.0.0",
"locate-path": "^5.0.0",
"lodash": "^4.17.15",
"lodash": "^4.17.19",
"lodash._reinterpolate": "^3.0.0",
"lodash.chunk": "^4.2.0",
"lodash.clonedeep": "^4.5.0",
@ -385,37 +385,37 @@
"lodash.template": "^4.5.0",
"lodash.templatesettings": "^4.2.0",
"lodash.uniq": "^4.5.0",
"loglevel": "^1.6.7",
"loglevel": "^1.6.8",
"loose-envify": "^1.4.0",
"lower-case": "^2.0.1",
"lru-cache": "^5.1.1",
"make-dir": "^3.0.2",
"lru-cache": "^6.0.0",
"make-dir": "^3.1.0",
"mamacro": "^0.0.7",
"map-age-cleaner": "^0.1.3",
"map-cache": "^0.2.2",
"map-visit": "^1.0.0",
"markdown-it": "^10.0.0",
"markdown-it-anchor": "^5.2.7",
"markdown-it": "^11.0.0",
"markdown-it-anchor": "^5.3.0",
"markdown-it-chain": "^1.3.0",
"markdown-it-container": "^2.0.0",
"markdown-it-container": "^3.0.0",
"markdown-it-emoji": "^1.4.0",
"markdown-it-table-of-contents": "^0.4.4",
"md5.js": "^1.3.5",
"mdn-data": "^2.0.8",
"mdn-data": "^2.0.11",
"mdurl": "^1.0.1",
"media-typer": "^1.1.0",
"mem": "^6.0.1",
"mem": "^6.1.0",
"memory-fs": "^0.5.0",
"merge-descriptors": "^1.0.1",
"merge-source-map": "^1.1.0",
"merge2": "^1.3.0",
"merge2": "^1.4.1",
"methods": "^1.1.2",
"micromatch": "^4.0.2",
"miller-rabin": "^4.0.1",
"mime": "^2.4.4",
"mime-db": "^1.43.0",
"mime-types": "^2.1.26",
"mimic-fn": "^3.0.0",
"mime": "^2.4.6",
"mime-db": "^1.44.0",
"mime-types": "^2.1.27",
"mimic-fn": "^3.1.0",
"min-document": "^2.19.0",
"mini-css-extract-plugin": "^0.9.0",
"minimalistic-assert": "^1.0.1",
@ -431,16 +431,16 @@
"multicast-dns-service-types": "^1.1.0",
"nanomatch": "^1.2.13",
"negotiator": "^0.6.2",
"neo-async": "^2.6.1",
"neo-async": "^2.6.2",
"nice-try": "^2.0.1",
"no-case": "^3.0.3",
"node-forge": "^0.9.1",
"node-forge": "^0.10.0",
"node-libs-browser": "^2.2.1",
"node-releases": "^1.1.53",
"node-releases": "^1.1.60",
"nopt": "^4.0.3",
"normalize-path": "^3.0.0",
"normalize-range": "^0.1.2",
"normalize-url": "^5.0.0",
"normalize-url": "^5.1.0",
"npm-run-path": "^4.0.1",
"nprogress": "^0.2.0",
"nth-check": "^1.0.2",
@ -449,8 +449,8 @@
"oauth-sign": "^0.9.0",
"object-assign": "^4.1.1",
"object-copy": "^1.0.0",
"object-inspect": "^1.7.0",
"object-is": "^1.0.2",
"object-inspect": "^1.8.0",
"object-is": "^1.1.2",
"object-keys": "^1.1.1",
"object-visit": "^1.0.1",
"object.assign": "^4.1.0",
@ -461,7 +461,7 @@
"on-finished": "^2.3.0",
"on-headers": "^1.0.2",
"once": "^1.4.0",
"opencollective-postinstall": "^2.0.2",
"opencollective-postinstall": "^2.0.3",
"opn": "^6.0.0",
"optimize-css-assets-webpack-plugin": "^5.0.3",
"original": "^1.0.2",
@ -470,7 +470,7 @@
"p-defer": "^3.0.0",
"p-finally": "^2.0.1",
"p-is-promise": "^3.0.0",
"p-limit": "^2.3.0",
"p-limit": "^3.0.2",
"p-locate": "^4.1.0",
"p-map": "^4.0.0",
"p-retry": "^4.2.0",
@ -479,7 +479,7 @@
"parallel-transform": "^1.2.0",
"param-case": "^3.0.3",
"parse-asn1": "^5.1.5",
"parse-json": "^5.0.0",
"parse-json": "^5.0.1",
"parseurl": "^1.3.3",
"pascalcase": "^1.0.0",
"path-browserify": "^1.0.1",
@ -491,16 +491,16 @@
"path-parse": "^1.0.6",
"path-to-regexp": "^6.1.0",
"path-type": "^4.0.0",
"pbkdf2": "^3.0.17",
"pbkdf2": "^3.1.1",
"performance-now": "^2.1.0",
"pify": "^5.0.0",
"pinkie": "^2.0.4",
"pinkie-promise": "^2.0.1",
"pkg-dir": "^4.2.0",
"pkg-up": "^3.1.0",
"portfinder": "^1.0.25",
"portfinder": "^1.0.28",
"posix-character-classes": "^1.0.0",
"postcss": "^7.0.27",
"postcss": "^7.0.32",
"postcss-calc": "^7.0.2",
"postcss-colormin": "^4.0.3",
"postcss-convert-values": "^4.0.1",
@ -517,7 +517,7 @@
"postcss-minify-params": "^4.0.2",
"postcss-minify-selectors": "^4.0.2",
"postcss-modules-extract-imports": "^2.0.0",
"postcss-modules-local-by-default": "^3.0.2",
"postcss-modules-local-by-default": "^3.0.3",
"postcss-modules-scope": "^2.2.0",
"postcss-modules-values": "^3.0.0",
"postcss-normalize-charset": "^4.0.1",
@ -536,9 +536,9 @@
"postcss-selector-parser": "^6.0.2",
"postcss-svgo": "^4.0.2",
"postcss-unique-selectors": "^4.0.1",
"postcss-value-parser": "^4.0.3",
"postcss-value-parser": "^4.1.0",
"prepend-http": "^3.0.1",
"prettier": "^2.0.4",
"prettier": "^2.0.5",
"pretty-error": "^2.1.1",
"pretty-time": "^1.1.0",
"prismjs": "^1.20.0",
@ -555,8 +555,8 @@
"pumpify": "^2.0.1",
"punycode": "^2.1.1",
"q": "^1.5.1",
"qs": "^6.9.3",
"query-string": "^6.12.0",
"qs": "^6.9.4",
"query-string": "^6.13.1",
"querystring": "^0.2.0",
"querystring-es3": "^0.2.1",
"querystringify": "^2.1.1",
@ -567,14 +567,14 @@
"readable-stream": "^3.6.0",
"readdirp": "^3.4.0",
"reduce": "^1.0.2",
"regenerate": "^1.4.0",
"regenerate": "^1.4.1",
"regenerate-unicode-properties": "^8.2.0",
"regenerator-runtime": "^0.13.5",
"regenerator-transform": "^0.14.4",
"regenerator-runtime": "^0.13.7",
"regenerator-transform": "^0.14.5",
"regex-not": "^1.0.2",
"regexp.prototype.flags": "^1.3.0",
"regexpu-core": "^4.7.0",
"regjsgen": "^0.5.1",
"regjsgen": "^0.5.2",
"regjsparser": "^0.6.4",
"relateurl": "^0.2.7",
"remove-trailing-separator": "^1.1.0",
@ -586,7 +586,7 @@
"require-main-filename": "^2.0.0",
"requires-port": "^1.0.0",
"reselect": "^4.0.0",
"resolve": "^1.15.1",
"resolve": "^1.17.0",
"resolve-cwd": "^3.0.0",
"resolve-from": "^5.0.0",
"resolve-url": "^0.2.1",
@ -597,18 +597,18 @@
"rimraf": "^3.0.2",
"ripemd160": "^2.0.2",
"run-queue": "^2.0.1",
"safe-buffer": "^5.2.0",
"safe-buffer": "^5.2.1",
"safe-regex": "^2.1.1",
"safer-buffer": "^2.1.2",
"sax": "^1.2.4",
"schema-utils": "^2.6.5",
"schema-utils": "^2.7.0",
"section-matter": "^1.0.0",
"select": "^1.1.2",
"select-hose": "^2.0.0",
"selfsigned": "^1.10.7",
"semver": "^7.2.1",
"semver": "^7.3.2",
"send": "^0.17.1",
"serialize-javascript": "^3.0.0",
"serialize-javascript": "^4.0.0",
"serve-index": "^1.9.1",
"serve-static": "^1.14.1",
"set-blocking": "^2.0.0",
@ -620,19 +620,19 @@
"shebang-regex": "^3.0.0",
"signal-exit": "^3.0.3",
"simple-swizzle": "^0.2.2",
"sitemap": "^6.1.0",
"sitemap": "^6.2.0",
"slash": "^3.0.0",
"smoothscroll-polyfill": "^0.4.4",
"snapdragon": "^0.12.0",
"snapdragon-node": "^3.0.0",
"snapdragon-util": "^5.0.1",
"sockjs": "^0.3.20",
"sockjs-client": "^1.4.0",
"sockjs": "^0.3.21",
"sockjs-client": "^1.5.0",
"sort-keys": "^4.0.0",
"source-list-map": "^2.0.1",
"source-map": "^0.7.3",
"source-map-resolve": "^0.6.0",
"source-map-support": "^0.5.16",
"source-map-support": "^0.5.19",
"source-map-url": "^0.4.0",
"spdy": "^4.0.2",
"spdy-transport": "^3.0.0",
@ -641,13 +641,13 @@
"sshpk": "^1.16.1",
"ssri": "^8.0.0",
"stable": "^0.1.8",
"stack-utils": "^2.0.1",
"stack-utils": "^2.0.2",
"static-extend": "^0.1.2",
"statuses": "^1.5.0",
"statuses": "^2.0.0",
"std-env": "^2.2.1",
"stream-browserify": "^2.0.2",
"stream-browserify": "^3.0.0",
"stream-each": "^1.2.3",
"stream-http": "^3.1.0",
"stream-http": "^3.1.1",
"stream-shift": "^1.0.1",
"strict-uri-encode": "^2.0.0",
"string-width": "^4.2.0",
@ -658,17 +658,17 @@
"strip-bom-string": "^1.0.0",
"strip-eof": "^2.0.0",
"stylehacks": "^4.0.3",
"stylus": "^0.54.7",
"stylus": "^0.54.8",
"stylus-loader": "^3.0.2",
"supports-color": "^7.1.0",
"svg-tags": "^1.0.0",
"svgo": "^1.3.2",
"tapable": "^1.1.3",
"terser": "^4.6.10",
"terser-webpack-plugin": "^2.3.5",
"terser": "^5.0.0",
"terser-webpack-plugin": "^4.0.0",
"text-table": "^0.2.0",
"through": "^2.3.8",
"through2": "^3.0.1",
"through2": "^4.0.2",
"thunky": "^1.1.0",
"timers-browserify": "^2.0.11",
"timsort": "^0.3.0",
@ -684,15 +684,15 @@
"toposort": "^2.0.2",
"tough-cookie": "^4.0.0",
"tr46": "^2.0.2",
"tslib": "^1.11.1",
"tslib": "^2.0.0",
"tty-browserify": "^0.0.1",
"tunnel-agent": "^0.6.0",
"tweetnacl": "^1.0.3",
"type-fest": "^0.13.0",
"type-fest": "^0.16.0",
"type-is": "^1.6.18",
"typedarray": "^0.0.6",
"uc.micro": "^1.0.6",
"uglify-js": "^3.8.1",
"uglify-js": "^3.10.1",
"unicode-canonical-property-names-ecmascript": "^1.0.4",
"unicode-match-property-ecmascript": "^1.0.4",
"unicode-match-property-value-ecmascript": "^1.2.0",
@ -702,7 +702,7 @@
"uniqs": "^2.0.0",
"unique-filename": "^1.1.1",
"unique-slug": "^2.0.2",
"universalify": "^1.0.0",
"universalify": "^2.0.0",
"unpipe": "^1.0.0",
"unquote": "^1.1.1",
"unset-value": "^1.0.0",
@ -711,60 +711,60 @@
"uri-js": "^4.2.2",
"urix": "^0.1.0",
"url": "^0.11.0",
"url-loader": "^4.0.0",
"url-loader": "^4.1.0",
"url-parse": "^1.4.7",
"use": "^3.1.1",
"util": "^0.12.2",
"util": "^0.12.3",
"util-deprecate": "^1.0.2",
"util.promisify": "^1.0.1",
"utila": "^0.4.0",
"utils-merge": "^1.0.1",
"uuid": "^7.0.3",
"uuid": "^8.3.0",
"vary": "^1.1.2",
"vendors": "^1.0.4",
"verror": "^1.10.0",
"vm-browserify": "^1.1.2",
"vue": "^2.6.11",
"vue-hot-reload-api": "^2.3.4",
"vue-loader": "^15.9.1",
"vue-router": "^3.1.6",
"vue-loader": "^15.9.3",
"vue-router": "^3.4.0",
"vue-server-renderer": "^2.6.11",
"vue-style-loader": "^4.1.2",
"vue-template-compiler": "^2.6.11",
"vue-template-es2015-compiler": "^1.9.1",
"vuepress": "^1.4.0",
"vuepress": "^1.5.3",
"vuepress-html-webpack-plugin": "^3.2.0",
"vuepress-plugin-container": "^2.1.2",
"vuepress-plugin-container": "^2.1.4",
"vuepress-plugin-sitemap": "^2.3.1",
"vuepress-plugin-smooth-scroll": "^0.0.9",
"vuepress-plugin-zooming": "^1.1.7",
"watchpack": "^1.6.1",
"watchpack": "^1.7.4",
"wbuf": "^1.7.3",
"webidl-conversions": "^6.0.0",
"webpack": "^4.42.1",
"webpack-chain": "^6.4.0",
"webidl-conversions": "^6.1.0",
"webpack": "^4.44.1",
"webpack-chain": "^6.5.1",
"webpack-dev-middleware": "^3.7.2",
"webpack-dev-server": "^3.10.3",
"webpack-dev-server": "^3.11.0",
"webpack-log": "^3.0.1",
"webpack-merge": "^4.2.2",
"webpack-merge": "^5.1.1",
"webpack-sources": "^1.4.3",
"webpackbar": "^4.0.0",
"websocket-driver": "^0.7.3",
"websocket-extensions": "^0.1.3",
"whatwg-url": "^8.0.0",
"websocket-driver": "^0.7.4",
"websocket-extensions": "^0.1.4",
"whatwg-url": "^8.1.0",
"when": "^3.7.8",
"which": "^2.0.2",
"which-module": "^2.0.0",
"worker-farm": "^1.7.0",
"wrap-ansi": "^6.2.0",
"wrap-ansi": "^7.0.0",
"wrappy": "^1.0.2",
"ws": "^7.2.3",
"xmlbuilder": "^15.1.0",
"ws": "^7.3.1",
"xmlbuilder": "^15.1.1",
"xtend": "^4.0.2",
"y18n": "^4.0.0",
"yallist": "^4.0.0",
"yargs": "^15.3.1",
"yargs-parser": "^18.1.2",
"yargs": "^15.4.1",
"yargs-parser": "^18.1.3",
"zepto": "^1.2.0"
},
"devDependencies": {},

View File

@ -1,37 +1,8 @@
# Full Setup Instructions
### Configuration File
### MySQL Database
**The configuration file needs to be provided by you!**
Don't worry, this is easy to do.
The app requires a configuration file to let it know what database you're using. By default, this file is called `config.json`
Here's an example configuration for `mysql` (or mariadb) that is compatible with the docker-compose example below:
```json
{
"database": {
"engine": "mysql",
"host": "db",
"name": "npm",
"user": "npm",
"password": "npm",
"port": 3306
}
}
```
Once you've created your configuration file it's easy to mount it in the docker container.
**Note:** After the first run of the application, the config file will be altered to include generated encryption keys unique to your installation. These keys
affect the login and session management of the application. If these keys change for any reason, all users will be logged out.
### Database
This app doesn't come with a database, you have to provide one yourself. Currently only `mysql/mariadb` is supported for the minimum versions:
If you opt for the MySQL configuration you will have to provide the database server yourself. You can also use MariaDB. Here are the minimum supported versions:
- MySQL v5.7.8+
- MariaDB v10.2.7+
@ -45,7 +16,6 @@ When using a `mariadb` database, the NPM configuration file should still use the
:::
### Running the App
Via `docker-compose`:
@ -54,7 +24,7 @@ Via `docker-compose`:
version: "3"
services:
app:
image: jc21/nginx-proxy-manager:2
image: 'jc21/nginx-proxy-manager:latest'
restart: always
ports:
# Public HTTP Port:
@ -64,17 +34,24 @@ services:
# Admin Web Port:
- '81:81'
environment:
# These are the settings to access your db
DB_MYSQL_HOST: "db"
DB_MYSQL_PORT: 3306
DB_MYSQL_USER: "npm"
DB_MYSQL_PASSWORD: "npm"
DB_MYSQL_NAME: "npm"
# If you would rather use Sqlite uncomment this
# and remove all DB_MYSQL_* lines above
# DB_SQLITE_FILE: "/data/database.sqlite"
# Uncomment this if IPv6 is not enabled on your host
# DISABLE_IPV6: 'true'
volumes:
# Make sure this config.json file exists as per instructions above:
- ./config.json:/app/config/production.json
- ./data:/data
- ./letsencrypt:/etc/letsencrypt
depends_on:
- db
db:
image: jc21/mariadb-aria:10.4
image: 'jc21/mariadb-aria:latest'
restart: always
environment:
MYSQL_ROOT_PASSWORD: 'npm'
@ -85,14 +62,14 @@ services:
- ./data/mysql:/var/lib/mysql
```
_Please note, that `DB_MYSQL_*` environment variables will take precedent over `DB_SQLITE_*` variables. So if you keep the MySQL variables, you will not be able to use Sqlite._
Then:
```bash
docker-compose up -d
```
The config file (config.json) must be present in this directory.
### Running on Raspberry PI / ARM devices
The docker images support the following architectures:
@ -130,3 +107,49 @@ Password: changeme
```
Immediately after logging in with this default user you will be asked to modify your details and change your password.
### Configuration File
::: warning
This section is meant for advanced users
:::
If you would like more control over the database settings you can define a custom config JSON file.
Here's an example for `sqlite` configuration as it is generated from the environment variables:
```json
{
"database": {
"engine": "knex-native",
"knex": {
"client": "sqlite3",
"connection": {
"filename": "/data/database.sqlite"
}
}
}
}
```
You can modify the `knex` object with your custom configuration, but note that not all knex clients might be installed in the image.
Once you've created your configuration file you can mount it to `/app/config/production.json` inside you container using:
```
[...]
services:
app:
image: 'jc21/nginx-proxy-manager:latest'
[...]
volumes:
- ./config.json:/app/config/production.json
[...]
[...]
```
**Note:** After the first run of the application, the config file will be altered to include generated encryption keys unique to your installation.
These keys affect the login and session management of the application. If these keys change for any reason, all users will be logged out.

View File

@ -7,6 +7,10 @@ Known integrations:
- [HomeAssistant Hass.io plugin](https://github.com/hassio-addons/addon-nginx-proxy-manager)
- [UnRaid / Synology](https://github.com/jlesage/docker-nginx-proxy-manager)
- [Proxmox Scripts](https://github.com/ej52/proxmox-scripts/tree/main/lxc/nginx-proxy-manager)
- [nginxproxymanagerGraf](https://github.com/ma-karai/nginxproxymanagerGraf)
If you would like your integration of NPM listed, please open a
[Github issue](https://github.com/jc21/nginx-proxy-manager/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=)

11
docs/upgrading/README.md Normal file
View File

@ -0,0 +1,11 @@
# Upgrading
```bash
docker-compose pull
docker-compose up -d
```
This project will automatically update any databases or other requirements so you don't have to follow
any crazy instructions. These steps above will pull the latest updates and recreate the docker
containers.

File diff suppressed because it is too large Load Diff

View File

@ -53,7 +53,7 @@ function fetch(verb, path, data, options) {
contentType: options.contentType || 'application/json; charset=UTF-8',
processData: options.processData || true,
crossDomain: true,
timeout: options.timeout ? options.timeout : 30000,
timeout: options.timeout ? options.timeout : 180000,
xhrFields: {
withCredentials: true
},
@ -139,7 +139,11 @@ function FileUpload(path, fd) {
xhr.onreadystatechange = function () {
if (this.readyState === XMLHttpRequest.DONE) {
if (xhr.status !== 200 && xhr.status !== 201) {
reject(new Error('Upload failed: ' + xhr.status));
try {
reject(new Error('Upload failed: ' + JSON.parse(xhr.responseText).error.message));
} catch (err) {
reject(new Error('Upload failed: ' + xhr.status));
}
} else {
resolve(xhr.responseText);
}
@ -587,7 +591,9 @@ module.exports = {
* @param {Object} data
*/
create: function (data) {
return fetch('post', 'nginx/certificates', data);
const timeout = 180000 + (data && data.meta && data.meta.propagation_seconds ? Number(data.meta.propagation_seconds) * 1000 : 0);
return fetch('post', 'nginx/certificates', data, {timeout});
},
/**
@ -630,8 +636,8 @@ module.exports = {
* @param {Number} id
* @returns {Promise}
*/
renew: function (id) {
return fetch('post', 'nginx/certificates/' + id + '/renew');
renew: function (id, timeout = 180000) {
return fetch('post', 'nginx/certificates/' + id + '/renew', undefined, {timeout});
}
}
},

View File

@ -31,6 +31,16 @@
</label>
</div>
</div>
<div class="col-sm-6 col-md-6">
<div class="form-group">
<label class="custom-switch">
<input type="checkbox" class="custom-switch-input" name="pass_auth" value="1"<%- typeof pass_auth !== 'undefined' && pass_auth ? ' checked' : '' %>>
<span class="custom-switch-indicator"></span>
<span class="custom-switch-description"><%- i18n('access-lists', 'pass-auth') %></span>
</label>
</div>
</div>
</div>
</div>

View File

@ -73,6 +73,7 @@ module.exports = Mn.View.extend({
let data = {
name: form_data.name,
satisfy_any: !!form_data.satisfy_any,
pass_auth: !!form_data.pass_auth,
items: items_data,
clients: clients_data
};

View File

@ -16,6 +16,8 @@ module.exports = Mn.View.extend({
events: {
'click @ui.save': function (e) {
e.preventDefault();
this.ui.save.addClass('btn-loading');
this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
App.Api.Nginx.Certificates.delete(this.model.get('id'))
.then(() => {
@ -25,6 +27,7 @@ module.exports = Mn.View.extend({
.catch(err => {
alert(err.message);
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
this.ui.save.removeClass('btn-loading');
});
}
}

View File

@ -1,10 +1,15 @@
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><%- i18n('certificates', 'form-title', {provider: provider}) %></h5>
<button type="button" class="close cancel" aria-label="Close" data-dismiss="modal">&nbsp;</button>
<button type="button" class="close cancel non-loader-content" aria-label="Close" data-dismiss="modal">&nbsp;</button>
</div>
<div class="modal-body">
<form>
<div class="alert alert-danger mb-0 rounded-0" id="le-error-info" role="alert"></div>
<div class="text-center loader-content">
<div class="loader mx-auto my-6"></div>
<p><%- i18n('ssl', 'processing-info') %></p>
</div>
<form class="non-loader-content">
<div class="row">
<% if (provider === 'letsencrypt') { %>
<div class="col-sm-12 col-md-12">
@ -20,6 +25,99 @@
<input name="meta[letsencrypt_email]" type="email" class="form-control" placeholder="" value="<%- getLetsencryptEmail() %>" required>
</div>
</div>
<!-- DNS challenge -->
<div class="col-sm-12 col-md-12">
<div class="form-group">
<label class="custom-switch">
<input
type="checkbox"
class="custom-switch-input"
name="meta[dns_challenge]"
value="1"
<%- getUseDnsChallenge() ? 'checked' : '' %>
>
<span class="custom-switch-indicator"></span>
<span class="custom-switch-description"><%= i18n('ssl', 'dns-challenge') %></span>
</label>
</div>
</div>
<div class="col-sm-12 col-md-12">
<fieldset class="form-fieldset dns-challenge">
<div class="text-red mb-4"><i class="fe fe-alert-triangle"></i> <%= i18n('ssl', 'certbot-warning') %></div>
<!-- Certbot DNS plugin selection -->
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="form-group">
<label class="form-label"><%- i18n('ssl', 'dns-provider') %> <span class="form-required">*</span></label>
<select
name="meta[dns_provider]"
id="dns_provider"
class="form-control custom-select"
>
<option
value=""
disabled
hidden
<%- getDnsProvider() === null ? 'selected' : '' %>
>Please Choose...</option>
<% _.each(dns_plugins, function(plugin_info, plugin_name){ %>
<option
value="<%- plugin_name %>"
<%- getDnsProvider() === plugin_name ? 'selected' : '' %>
><%- plugin_info.display_name %></option>
<% }); %>
</select>
</div>
</div>
</div>
<!-- Certbot credentials file content -->
<div class="row credentials-file-content">
<div class="col-sm-12 col-md-12">
<div class="form-group">
<label class="form-label"><%- i18n('ssl', 'credentials-file-content') %> <span class="form-required">*</span></label>
<textarea
name="meta[dns_provider_credentials]"
class="form-control text-monospace"
id="dns_provider_credentials"
><%- getDnsProviderCredentials() %></textarea>
<div class="text-secondary small">
<i class="fe fe-info"></i>
<%= i18n('ssl', 'credentials-file-content-info') %>
</div>
<div class="text-red small">
<i class="fe fe-alert-triangle"></i>
<%= i18n('ssl', 'stored-as-plaintext-info') %>
</div>
</div>
</div>
</div>
<!-- DNS propagation delay -->
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="form-group mb-0">
<label class="form-label"><%- i18n('ssl', 'propagation-seconds') %></label>
<input
type="number"
min="0"
name="meta[propagation_seconds]"
class="form-control"
id="propagation_seconds"
value="<%- getPropagationSeconds() %>"
>
<div class="text-secondary small">
<i class="fe fe-info"></i>
<%= i18n('ssl', 'propagation-seconds-info') %>
</div>
</div>
</div>
</div>
</fieldset>
</div>
<div class="col-sm-12 col-md-12">
<div class="form-group">
<label class="custom-switch">
@ -31,6 +129,9 @@
</div>
<% } else if (provider === 'other') { %>
<!-- Other -->
<div class="col-sm-12 col-md-12">
<div class="text-blue mb-4"><i class="fe fe-alert-triangle"></i> <%= i18n('ssl', 'passphrase-protection-support-info') %></div>
</div>
<div class="col-sm-12 col-md-12">
<div class="form-group">
<label class="form-label"><%- i18n('str', 'name') %> <span class="form-required">*</span></label>
@ -42,7 +143,7 @@
<div class="form-label"><%- i18n('certificates', 'other-certificate-key') %><span class="form-required">*</span></div>
<div class="custom-file">
<input type="file" class="custom-file-input" name="meta[other_certificate_key]" id="other_certificate_key" required>
<label class="custom-file-label"><%- i18n('str', 'choose-file') %></label>
<label id="other_certificate_key_label" class="custom-file-label"><%- i18n('str', 'choose-file') %></label>
</div>
</div>
</div>
@ -51,7 +152,7 @@
<div class="form-label"><%- i18n('certificates', 'other-certificate') %><span class="form-required">*</span></div>
<div class="custom-file">
<input type="file" class="custom-file-input" name="meta[other_certificate]" id="other_certificate">
<label class="custom-file-label"><%- i18n('str', 'choose-file') %></label>
<label id="other_certificate_label" class="custom-file-label"><%- i18n('str', 'choose-file') %></label>
</div>
</div>
</div>
@ -60,7 +161,7 @@
<div class="form-label"><%- i18n('certificates', 'other-intermediate-certificate') %></div>
<div class="custom-file">
<input type="file" class="custom-file-input" name="meta[other_intermediate_certificate]" id="other_intermediate_certificate">
<label class="custom-file-label"><%- i18n('str', 'choose-file') %></label>
<label id="other_intermediate_certificate_label" class="custom-file-label"><%- i18n('str', 'choose-file') %></label>
</div>
</div>
</div>
@ -69,7 +170,7 @@
</div>
</form>
</div>
<div class="modal-footer">
<div class="modal-footer non-loader-content">
<button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button>
<button type="button" class="btn btn-teal save"><%- i18n('str', 'save') %></button>
</div>

View File

@ -3,6 +3,8 @@ const Mn = require('backbone.marionette');
const App = require('../../main');
const CertificateModel = require('../../../models/certificate');
const template = require('./form.ejs');
const i18n = require('../../i18n');
const dns_providers = require('../../../../../global/certbot-dns-plugins');
require('jquery-serializejson');
require('selectize');
@ -13,42 +15,105 @@ module.exports = Mn.View.extend({
max_file_size: 102400,
ui: {
form: 'form',
domain_names: 'input[name="domain_names"]',
buttons: '.modal-footer button',
cancel: 'button.cancel',
save: 'button.save',
other_certificate: '#other_certificate',
other_certificate_key: '#other_certificate_key',
other_intermediate_certificate: '#other_intermediate_certificate'
form: 'form',
loader_content: '.loader-content',
non_loader_content: '.non-loader-content',
le_error_info: '#le-error-info',
domain_names: 'input[name="domain_names"]',
buttons: '.modal-footer button',
cancel: 'button.cancel',
save: 'button.save',
other_certificate: '#other_certificate',
other_certificate_label: '#other_certificate_label',
other_certificate_key: '#other_certificate_key',
dns_challenge_switch: 'input[name="meta[dns_challenge]"]',
dns_challenge_content: '.dns-challenge',
dns_provider: 'select[name="meta[dns_provider]"]',
credentials_file_content: '.credentials-file-content',
dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]',
propagation_seconds: 'input[name="meta[propagation_seconds]"]',
other_certificate_key_label: '#other_certificate_key_label',
other_intermediate_certificate: '#other_intermediate_certificate',
other_intermediate_certificate_label: '#other_intermediate_certificate_label'
},
events: {
'change @ui.dns_challenge_switch': function () {
const checked = this.ui.dns_challenge_switch.prop('checked');
if (checked) {
this.ui.dns_provider.prop('required', 'required');
const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){
this.ui.dns_provider_credentials.prop('required', 'required');
}
this.ui.dns_challenge_content.show();
} else {
this.ui.dns_provider.prop('required', false);
this.ui.dns_provider_credentials.prop('required', false);
this.ui.dns_challenge_content.hide();
}
},
'change @ui.dns_provider': function () {
const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) {
this.ui.dns_provider_credentials.prop('required', 'required');
this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials;
this.ui.credentials_file_content.show();
} else {
this.ui.dns_provider_credentials.prop('required', false);
this.ui.credentials_file_content.hide();
}
},
'click @ui.save': function (e) {
e.preventDefault();
this.ui.le_error_info.hide();
if (!this.ui.form[0].checkValidity()) {
$('<input type="submit">').hide().appendTo(this.ui.form).click().remove();
$(this).removeClass('btn-loading');
return;
}
let view = this;
let data = this.ui.form.serializeJSON();
data.provider = this.model.get('provider');
// Manipulate
if (typeof data.meta !== 'undefined' && typeof data.meta.letsencrypt_agree !== 'undefined') {
data.meta.letsencrypt_agree = !!data.meta.letsencrypt_agree;
}
if (typeof data.domain_names === 'string' && data.domain_names) {
data.domain_names = data.domain_names.split(',');
}
let ssl_files = [];
// check files are attached
if (this.model.get('provider') === 'other' && !this.model.hasSslFiles()) {
if (data.provider === 'letsencrypt') {
if (typeof data.meta === 'undefined') data.meta = {};
let domain_err = false;
if (!data.meta.dns_challenge) {
data.domain_names.split(',').map(function (name) {
if (name.match(/\*/im)) {
domain_err = true;
}
});
}
if (domain_err) {
alert(i18n('ssl', 'no-wildcard-without-dns'));
return;
}
// Manipulate
data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1;
data.meta.dns_challenge = data.meta.dns_challenge == 1;
if(!data.meta.dns_challenge){
data.meta.dns_provider = undefined;
data.meta.dns_provider_credentials = undefined;
data.meta.propagation_seconds = undefined;
} else {
if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined;
}
if (typeof data.domain_names === 'string' && data.domain_names) {
data.domain_names = data.domain_names.split(',');
}
} else if (data.provider === 'other' && !this.model.hasSslFiles()) {
// check files are attached
if (!this.ui.other_certificate[0].files.length || !this.ui.other_certificate[0].files[0].size) {
alert('Certificate file is not attached');
return;
@ -80,18 +145,19 @@ module.exports = Mn.View.extend({
}
}
this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
this.ui.loader_content.show();
this.ui.non_loader_content.hide();
// compile file data
let form_data = new FormData();
if (view.model.get('provider') && ssl_files.length) {
if (data.provider === 'other' && ssl_files.length) {
ssl_files.map(function (file) {
form_data.append(file.name, file.file);
});
}
new Promise(resolve => {
if (view.model.get('provider') === 'other') {
if (data.provider === 'other') {
resolve(App.Api.Nginx.Certificates.validate(form_data));
} else {
resolve();
@ -101,13 +167,13 @@ module.exports = Mn.View.extend({
return App.Api.Nginx.Certificates.create(data);
})
.then(result => {
view.model.set(result);
this.model.set(result);
// Now upload the certs if we need to
if (view.model.get('provider') === 'other') {
return App.Api.Nginx.Certificates.upload(view.model.get('id'), form_data)
if (data.provider === 'other') {
return App.Api.Nginx.Certificates.upload(this.model.get('id'), form_data)
.then(result => {
view.model.set('meta', _.assign({}, view.model.get('meta'), result));
this.model.set('meta', _.assign({}, this.model.get('meta'), result));
});
}
})
@ -117,20 +183,52 @@ module.exports = Mn.View.extend({
});
})
.catch(err => {
alert(err.message);
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
let more_info = '';
if (err.code === 500 && err.debug) {
try{
more_info = JSON.parse(err.debug).debug.stack.join("\n");
} catch(e) {}
}
this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `<pre class="mt-3">${more_info}</pre>`:''}`;
this.ui.le_error_info.show();
this.ui.le_error_info[0].scrollIntoView();
this.ui.loader_content.hide();
this.ui.non_loader_content.show();
});
},
'change @ui.other_certificate_key': function(e){
this.setFileName("other_certificate_key_label", e)
},
'change @ui.other_certificate': function(e){
this.setFileName("other_certificate_label", e)
},
'change @ui.other_intermediate_certificate': function(e){
this.setFileName("other_intermediate_certificate_label", e)
}
},
setFileName(ui, e){
this.getUI(ui).text(e.target.files[0].name)
},
templateContext: {
getLetsencryptEmail: function () {
return typeof this.meta.letsencrypt_email !== 'undefined' ? this.meta.letsencrypt_email : App.Cache.User.get('email');
},
getLetsencryptAgree: function () {
return typeof this.meta.letsencrypt_agree !== 'undefined' ? this.meta.letsencrypt_agree : false;
}
},
getUseDnsChallenge: function () {
return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false;
},
getDnsProvider: function () {
return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null;
},
getDnsProviderCredentials: function () {
return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : '';
},
getPropagationSeconds: function () {
return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : '';
},
dns_plugins: dns_providers,
},
onRender: function () {
@ -144,8 +242,12 @@ module.exports = Mn.View.extend({
text: input
};
},
createFilter: /^(?:[^.*]+\.?)+[^.]$/
createFilter: /^(?:[^.]+\.?)+[^.]$/
});
this.ui.dns_challenge_content.hide();
this.ui.credentials_file_content.hide();
this.ui.loader_content.hide();
this.ui.le_error_info.hide();
},
initialize: function (options) {

View File

@ -28,7 +28,7 @@
</div>
</td>
<td>
<%- i18n('ssl', provider) %>
<%- i18n('ssl', provider) %><% if (meta.dns_provider) { %> - <%- dns_providers[meta.dns_provider].display_name %><% } %>
</td>
<td class="<%- isExpired() ? 'text-danger' : '' %>">
<%- formatDbDate(expires_on, 'Do MMMM YYYY, h:mm a') %>

View File

@ -1,7 +1,8 @@
const Mn = require('backbone.marionette');
const moment = require('moment');
const App = require('../../../main');
const template = require('./item.ejs');
const Mn = require('backbone.marionette');
const moment = require('moment');
const App = require('../../../main');
const template = require('./item.ejs');
const dns_providers = require('../../../../../../global/certbot-dns-plugins')
module.exports = Mn.View.extend({
template: template,
@ -35,7 +36,8 @@ module.exports = Mn.View.extend({
canManage: App.Cache.User.canManage('certificates'),
isExpired: function () {
return moment(this.expires_on).isBefore(moment());
}
},
dns_providers: dns_providers
},
initialize: function () {

View File

@ -4,6 +4,7 @@
<button type="button" class="close cancel" aria-label="Close" data-dismiss="modal">&nbsp;</button>
</div>
<div class="modal-body has-tabs">
<div class="alert alert-danger mb-0 rounded-0" id="le-error-info" role="alert"></div>
<form>
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="nav-item"><a href="#details" aria-controls="tab1" role="tab" data-toggle="tab" class="nav-link active"><i class="fe fe-zap"></i> <%- i18n('all-hosts', 'details') %></a></li>
@ -73,6 +74,98 @@
</div>
</div>
<!-- DNS challenge -->
<div class="col-sm-12 col-md-12 letsencrypt">
<div class="form-group">
<label class="custom-switch">
<input
type="checkbox"
class="custom-switch-input"
name="meta[dns_challenge]"
value="1"
<%- getUseDnsChallenge() ? 'checked' : '' %>
>
<span class="custom-switch-indicator"></span>
<span class="custom-switch-description"><%= i18n('ssl', 'dns-challenge') %></span>
</label>
</div>
</div>
<div class="col-sm-12 col-md-12 letsencrypt">
<fieldset class="form-fieldset dns-challenge">
<div class="text-red mb-4"><i class="fe fe-alert-triangle"></i> <%= i18n('ssl', 'certbot-warning') %></div>
<!-- Certbot DNS plugin selection -->
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="form-group">
<label class="form-label"><%- i18n('ssl', 'dns-provider') %> <span class="form-required">*</span></label>
<select
name="meta[dns_provider]"
id="dns_provider"
class="form-control custom-select"
>
<option
value=""
disabled
hidden
<%- getDnsProvider() === null ? 'selected' : '' %>
>Please Choose...</option>
<% _.each(dns_plugins, function(plugin_info, plugin_name){ %>
<option
value="<%- plugin_name %>"
<%- getDnsProvider() === plugin_name ? 'selected' : '' %>
><%- plugin_info.display_name %></option>
<% }); %>
</select>
</div>
</div>
</div>
<!-- Certbot credentials file content -->
<div class="row credentials-file-content">
<div class="col-sm-12 col-md-12">
<div class="form-group">
<label class="form-label"><%- i18n('ssl', 'credentials-file-content') %> <span class="form-required">*</span></label>
<textarea
name="meta[dns_provider_credentials]"
class="form-control text-monospace"
id="dns_provider_credentials"
><%- getDnsProviderCredentials() %></textarea>
<div class="text-secondary small">
<i class="fe fe-info"></i>
<%= i18n('ssl', 'credentials-file-content-info') %>
</div>
<div class="text-red small">
<i class="fe fe-alert-triangle"></i>
<%= i18n('ssl', 'stored-as-plaintext-info') %>
</div>
</div>
</div>
</div>
<!-- DNS propagation delay -->
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="form-group mb-0">
<label class="form-label"><%- i18n('ssl', 'propagation-seconds') %></label>
<input
type="number"
min="0"
name="meta[propagation_seconds]"
class="form-control"
id="propagation_seconds"
value="<%- getPropagationSeconds() %>"
>
<div class="text-secondary small">
<i class="fe fe-info"></i>
<%= i18n('ssl', 'propagation-seconds-info') %>
</div>
</div>
</div>
</div>
</fieldset>
</div>
<!-- Lets encrypt -->
<div class="col-sm-12 col-md-12 letsencrypt">
<div class="form-group">

View File

@ -4,6 +4,8 @@ const DeadHostModel = require('../../../models/dead-host');
const template = require('./form.ejs');
const certListItemTemplate = require('../certificates-list-item.ejs');
const Helpers = require('../../../lib/helpers');
const i18n = require('../../i18n');
const dns_providers = require('../../../../../global/certbot-dns-plugins');
require('jquery-serializejson');
require('selectize');
@ -13,17 +15,24 @@ module.exports = Mn.View.extend({
className: 'modal-dialog',
ui: {
form: 'form',
domain_names: 'input[name="domain_names"]',
buttons: '.modal-footer button',
cancel: 'button.cancel',
save: 'button.save',
certificate_select: 'select[name="certificate_id"]',
ssl_forced: 'input[name="ssl_forced"]',
hsts_enabled: 'input[name="hsts_enabled"]',
hsts_subdomains: 'input[name="hsts_subdomains"]',
http2_support: 'input[name="http2_support"]',
letsencrypt: '.letsencrypt'
form: 'form',
domain_names: 'input[name="domain_names"]',
buttons: '.modal-footer button',
cancel: 'button.cancel',
save: 'button.save',
le_error_info: '#le-error-info',
certificate_select: 'select[name="certificate_id"]',
ssl_forced: 'input[name="ssl_forced"]',
hsts_enabled: 'input[name="hsts_enabled"]',
hsts_subdomains: 'input[name="hsts_subdomains"]',
http2_support: 'input[name="http2_support"]',
dns_challenge_switch: 'input[name="meta[dns_challenge]"]',
dns_challenge_content: '.dns-challenge',
dns_provider: 'select[name="meta[dns_provider]"]',
credentials_file_content: '.credentials-file-content',
dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]',
propagation_seconds: 'input[name="meta[propagation_seconds]"]',
letsencrypt: '.letsencrypt'
},
events: {
@ -31,10 +40,12 @@ module.exports = Mn.View.extend({
let id = this.ui.certificate_select.val();
if (id === 'new') {
this.ui.letsencrypt.show().find('input').prop('disabled', false);
this.ui.dns_challenge_content.hide();
} else {
this.ui.letsencrypt.hide().find('input').prop('disabled', true);
}
let enabled = id === 'new' || parseInt(id, 10) > 0;
let inputs = this.ui.ssl_forced.add(this.ui.http2_support);
@ -76,8 +87,37 @@ module.exports = Mn.View.extend({
}
},
'change @ui.dns_challenge_switch': function () {
const checked = this.ui.dns_challenge_switch.prop('checked');
if (checked) {
this.ui.dns_provider.prop('required', 'required');
const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){
this.ui.dns_provider_credentials.prop('required', 'required');
}
this.ui.dns_challenge_content.show();
} else {
this.ui.dns_provider.prop('required', false);
this.ui.dns_provider_credentials.prop('required', false);
this.ui.dns_challenge_content.hide();
}
},
'change @ui.dns_provider': function () {
const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) {
this.ui.dns_provider_credentials.prop('required', 'required');
this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials;
this.ui.credentials_file_content.show();
} else {
this.ui.dns_provider_credentials.prop('required', false);
this.ui.credentials_file_content.hide();
}
},
'click @ui.save': function (e) {
e.preventDefault();
this.ui.le_error_info.hide();
if (!this.ui.form[0].checkValidity()) {
$('<input type="submit">').hide().appendTo(this.ui.form).click().remove();
@ -88,30 +128,42 @@ module.exports = Mn.View.extend({
let data = this.ui.form.serializeJSON();
// Manipulate
data.hsts_enabled = !!data.hsts_enabled;
data.hsts_subdomains = !!data.hsts_subdomains;
data.http2_support = !!data.http2_support;
data.ssl_forced = !!data.ssl_forced;
data.hsts_enabled = !!data.hsts_enabled;
data.hsts_subdomains = !!data.hsts_subdomains;
data.http2_support = !!data.http2_support;
data.ssl_forced = !!data.ssl_forced;
if (typeof data.meta === 'undefined') data.meta = {};
data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1;
data.meta.dns_challenge = data.meta.dns_challenge == 1;
if(!data.meta.dns_challenge){
data.meta.dns_provider = undefined;
data.meta.dns_provider_credentials = undefined;
data.meta.propagation_seconds = undefined;
} else {
if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined;
}
if (typeof data.domain_names === 'string' && data.domain_names) {
data.domain_names = data.domain_names.split(',');
}
// Check for any domain names containing wildcards, which are not allowed with letsencrypt
if (data.certificate_id === 'new') {
if (data.certificate_id === 'new') {
let domain_err = false;
data.domain_names.map(function (name) {
if (name.match(/\*/im)) {
domain_err = true;
}
});
if (domain_err) {
alert('Cannot request Let\'s Encrypt Certificate for wildcard domains');
return;
if (!data.meta.dns_challenge) {
data.domain_names.map(function (name) {
if (name.match(/\*/im)) {
domain_err = true;
}
});
}
data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1';
if (domain_err) {
alert(i18n('ssl', 'no-wildcard-without-dns'));
return;
}
} else {
data.certificate_id = parseInt(data.certificate_id, 10);
}
@ -127,6 +179,8 @@ module.exports = Mn.View.extend({
}
this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
this.ui.save.addClass('btn-loading');
method(data)
.then(result => {
view.model.set(result);
@ -138,8 +192,17 @@ module.exports = Mn.View.extend({
});
})
.catch(err => {
alert(err.message);
let more_info = '';
if(err.code === 500 && err.debug){
try{
more_info = JSON.parse(err.debug).debug.stack.join("\n");
} catch(e) {}
}
this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `<pre class="mt-3">${more_info}</pre>`:''}`;
this.ui.le_error_info.show();
this.ui.le_error_info[0].scrollIntoView();
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
this.ui.save.removeClass('btn-loading');
});
}
},
@ -147,7 +210,20 @@ module.exports = Mn.View.extend({
templateContext: {
getLetsencryptEmail: function () {
return App.Cache.User.get('email');
}
},
getUseDnsChallenge: function () {
return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false;
},
getDnsProvider: function () {
return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null;
},
getDnsProviderCredentials: function () {
return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : '';
},
getPropagationSeconds: function () {
return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : '';
},
dns_plugins: dns_providers,
},
onRender: function () {
@ -168,6 +244,9 @@ module.exports = Mn.View.extend({
});
// Certificates
this.ui.le_error_info.hide();
this.ui.dns_challenge_content.hide();
this.ui.credentials_file_content.hide();
this.ui.letsencrypt.hide();
this.ui.certificate_select.selectize({
valueField: 'id',

View File

@ -4,6 +4,7 @@
<button type="button" class="close cancel" aria-label="Close" data-dismiss="modal">&nbsp;</button>
</div>
<div class="modal-body has-tabs">
<div class="alert alert-danger mb-0 rounded-0" id="le-error-info" role="alert"></div>
<form>
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="nav-item"><a href="#details" aria-controls="tab1" role="tab" data-toggle="tab" class="nav-link active"><i class="fe fe-zap"></i> <%- i18n('all-hosts', 'details') %></a></li>
@ -34,7 +35,7 @@
</div>
<div class="col-sm-3 col-md-3">
<div class="form-group">
<label class="form-label"><%- i18n('proxy-hosts', 'forward-scheme') %><span class="form-required">*</span></label>
<label class="form-label"><%- i18n('proxy-hosts', 'forward-scheme') %></label>
<select name="forward_scheme" class="form-control custom-select" placeholder="http">
<option value="http" <%- forward_scheme === 'http' ? 'selected' : '' %>>http</option>
<option value="https" <%- forward_scheme === 'https' ? 'selected' : '' %>>https</option>
@ -43,14 +44,26 @@
</div>
<div class="col-sm-5 col-md-5">
<div class="form-group">
<label class="form-label"><%- i18n('proxy-hosts', 'forward-host') %><span class="form-required">*</span></label>
<input type="text" name="forward_host" class="form-control text-monospace" placeholder="" value="<%- forward_host %>" autocomplete="off" maxlength="255" required>
<label class="form-label"><%- i18n('proxy-hosts', 'forward-host') %><% if (!static) { %> <span class="form-required">*</span><% } %></label>
<input type="text" name="forward_host" class="form-control text-monospace" placeholder="" value="<%- forward_host %>" <%- !static ? 'required' : '' %> autocomplete="off" maxlength="255">
</div>
</div>
<div class="col-sm-4 col-md-4">
<div class="form-group">
<label class="form-label"><%- i18n('proxy-hosts', 'forward-port') %> <span class="form-required">*</span></label>
<input name="forward_port" type="number" class="form-control text-monospace" placeholder="80" value="<%- forward_port %>" required>
<label class="form-label"><%- i18n('proxy-hosts', 'forward-port') %><% if (!static) { %> <span class="form-required">*</span><% } %> </label>
<input name="forward_port" type="number" class="form-control text-monospace" placeholder="80" value="<%- forward_port %>" <%- !static ? 'required' : '' %>>
</div>
</div>
<div class="col-sm-5 col-md-5">
<div class="form-group">
<label class="form-label"><%- i18n('proxy-hosts', 'root-dir') %><% if (static) { %> <span class="form-required">*</span><% } %></label>
<input type="text" name="root_dir" class="form-control text-monospace" placeholder="" value="<%- root_dir %>" <%- static ? 'required' : '' %> autocomplete="off" maxlength="255">
</div>
</div>
<div class="col-sm-5 col-md-5">
<div class="form-group">
<label class="form-label"><%- i18n('proxy-hosts', 'index-file') %><% if (static) { %> <span class="form-required">*</span><% } %></label>
<input type="text" name="index_file" class="form-control text-monospace" placeholder="" value="<%- index_file %>" <%- static ? 'required' : '' %> autocomplete="off" maxlength="255">
</div>
</div>
<div class="col-sm-6 col-md-6">
@ -80,6 +93,15 @@
</label>
</div>
</div>
<div class="col-sm-12 col-md-12">
<div class="form-group">
<label class="custom-switch">
<input type="checkbox" class="custom-switch-input static-checkbox" name="static" value="1"<%- static ? ' checked' : '' %>>
<span class="custom-switch-indicator"></span>
<span class="custom-switch-description"><%- i18n('proxy-hosts', 'static') %></span>
</label>
</div>
</div>
<div class="col-sm-12 col-md-12">
<div class="form-group">
@ -141,6 +163,98 @@
</div>
</div>
<!-- DNS challenge -->
<div class="col-sm-12 col-md-12 letsencrypt">
<div class="form-group">
<label class="custom-switch">
<input
type="checkbox"
class="custom-switch-input"
name="meta[dns_challenge]"
value="1"
<%- getUseDnsChallenge() ? 'checked' : '' %>
>
<span class="custom-switch-indicator"></span>
<span class="custom-switch-description"><%= i18n('ssl', 'dns-challenge') %></span>
</label>
</div>
</div>
<div class="col-sm-12 col-md-12 letsencrypt">
<fieldset class="form-fieldset dns-challenge">
<div class="text-red mb-4"><i class="fe fe-alert-triangle"></i> <%= i18n('ssl', 'certbot-warning') %></div>
<!-- Certbot DNS plugin selection -->
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="form-group">
<label class="form-label"><%- i18n('ssl', 'dns-provider') %> <span class="form-required">*</span></label>
<select
name="meta[dns_provider]"
id="dns_provider"
class="form-control custom-select"
>
<option
value=""
disabled
hidden
<%- getDnsProvider() === null ? 'selected' : '' %>
>Please Choose...</option>
<% _.each(dns_plugins, function(plugin_info, plugin_name){ %>
<option
value="<%- plugin_name %>"
<%- getDnsProvider() === plugin_name ? 'selected' : '' %>
><%- plugin_info.display_name %></option>
<% }); %>
</select>
</div>
</div>
</div>
<!-- Certbot credentials file content -->
<div class="row credentials-file-content">
<div class="col-sm-12 col-md-12">
<div class="form-group">
<label class="form-label"><%- i18n('ssl', 'credentials-file-content') %> <span class="form-required">*</span></label>
<textarea
name="meta[dns_provider_credentials]"
class="form-control text-monospace"
id="dns_provider_credentials"
><%- getDnsProviderCredentials() %></textarea>
<div class="text-secondary small">
<i class="fe fe-info"></i>
<%= i18n('ssl', 'credentials-file-content-info') %>
</div>
<div class="text-red small">
<i class="fe fe-alert-triangle"></i>
<%= i18n('ssl', 'stored-as-plaintext-info') %>
</div>
</div>
</div>
</div>
<!-- DNS propagation delay -->
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="form-group mb-0">
<label class="form-label"><%- i18n('ssl', 'propagation-seconds') %></label>
<input
type="number"
min="0"
name="meta[propagation_seconds]"
class="form-control"
id="propagation_seconds"
value="<%- getPropagationSeconds() %>"
>
<div class="text-secondary small">
<i class="fe fe-info"></i>
<%= i18n('ssl', 'propagation-seconds-info') %>
</div>
</div>
</div>
</div>
</fieldset>
</div>
<!-- Lets encrypt -->
<div class="col-sm-12 col-md-12 letsencrypt">
<div class="form-group">

View File

@ -7,6 +7,8 @@ const certListItemTemplate = require('../certificates-list-item.ejs');
const accessListItemTemplate = require('./access-list-item.ejs');
const CustomLocation = require('./location');
const Helpers = require('../../../lib/helpers');
const i18n = require('../../i18n');
const dns_providers = require('../../../../../global/certbot-dns-plugins');
require('jquery-serializejson');
@ -19,22 +21,32 @@ module.exports = Mn.View.extend({
locationsCollection: new ProxyLocationModel.Collection(),
ui: {
form: 'form',
domain_names: 'input[name="domain_names"]',
forward_host: 'input[name="forward_host"]',
buttons: '.modal-footer button',
cancel: 'button.cancel',
save: 'button.save',
add_location_btn: 'button.add_location',
locations_container:'.locations_container',
certificate_select: 'select[name="certificate_id"]',
access_list_select: 'select[name="access_list_id"]',
ssl_forced: 'input[name="ssl_forced"]',
hsts_enabled: 'input[name="hsts_enabled"]',
hsts_subdomains: 'input[name="hsts_subdomains"]',
http2_support: 'input[name="http2_support"]',
forward_scheme: 'select[name="forward_scheme"]',
letsencrypt: '.letsencrypt'
form: 'form',
domain_names: 'input[name="domain_names"]',
forward_host: 'input[name="forward_host"]',
buttons: '.modal-footer button',
cancel: 'button.cancel',
save: 'button.save',
add_location_btn: 'button.add_location',
locations_container: '.locations_container',
le_error_info: '#le-error-info',
certificate_select: 'select[name="certificate_id"]',
access_list_select: 'select[name="access_list_id"]',
ssl_forced: 'input[name="ssl_forced"]',
hsts_enabled: 'input[name="hsts_enabled"]',
hsts_subdomains: 'input[name="hsts_subdomains"]',
http2_support: 'input[name="http2_support"]',
dns_challenge_switch: 'input[name="meta[dns_challenge]"]',
dns_challenge_content: '.dns-challenge',
dns_provider: 'select[name="meta[dns_provider]"]',
credentials_file_content: '.credentials-file-content',
dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]',
propagation_seconds: 'input[name="meta[propagation_seconds]"]',
forward_scheme: 'select[name="forward_scheme"]',
letsencrypt: '.letsencrypt',
root_dir: 'input[name="root_dir"]',
index_file: 'input[name="index_file"]',
static: 'input[type="checkbox"].static-checkbox',
},
regions: {
@ -46,6 +58,7 @@ module.exports = Mn.View.extend({
let id = this.ui.certificate_select.val();
if (id === 'new') {
this.ui.letsencrypt.show().find('input').prop('disabled', false);
this.ui.dns_challenge_content.hide();
} else {
this.ui.letsencrypt.hide().find('input').prop('disabled', true);
}
@ -91,15 +104,53 @@ module.exports = Mn.View.extend({
}
},
'change @ui.dns_challenge_switch': function () {
const checked = this.ui.dns_challenge_switch.prop('checked');
if (checked) {
this.ui.dns_provider.prop('required', 'required');
const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){
this.ui.dns_provider_credentials.prop('required', 'required');
}
this.ui.dns_challenge_content.show();
} else {
this.ui.dns_provider.prop('required', false);
this.ui.dns_provider_credentials.prop('required', false);
this.ui.dns_challenge_content.hide();
}
},
'change @ui.dns_provider': function () {
const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) {
this.ui.dns_provider_credentials.prop('required', 'required');
this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials;
this.ui.credentials_file_content.show();
} else {
this.ui.dns_provider_credentials.prop('required', false);
this.ui.credentials_file_content.hide();
}
},
'click @ui.add_location_btn': function (e) {
e.preventDefault();
const model = new ProxyLocationModel.Model();
this.locationsCollection.add(model);
},
'click @ui.static': function(e){
const map = {};
let value = e.target.value
if(e.target.type == 'checkbox') value = e.target.checked;
map[e.target.name] = value;
this.model.set(map);
setTimeout(this.render.bind(this), 300)
},
'click @ui.save': function (e) {
e.preventDefault();
this.ui.le_error_info.hide();
if (!this.ui.form[0].checkValidity()) {
$('<input type="submit">').hide().appendTo(this.ui.form).click().remove();
@ -128,6 +179,19 @@ module.exports = Mn.View.extend({
data.hsts_enabled = !!data.hsts_enabled;
data.hsts_subdomains = !!data.hsts_subdomains;
data.ssl_forced = !!data.ssl_forced;
data.static = !!data.static;
if (typeof data.meta === 'undefined') data.meta = {};
data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1;
data.meta.dns_challenge = data.meta.dns_challenge == 1;
if(!data.meta.dns_challenge){
data.meta.dns_provider = undefined;
data.meta.dns_provider_credentials = undefined;
data.meta.propagation_seconds = undefined;
} else {
if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined;
}
if (typeof data.domain_names === 'string' && data.domain_names) {
data.domain_names = data.domain_names.split(',');
@ -136,18 +200,18 @@ module.exports = Mn.View.extend({
// Check for any domain names containing wildcards, which are not allowed with letsencrypt
if (data.certificate_id === 'new') {
let domain_err = false;
data.domain_names.map(function (name) {
if (name.match(/\*/im)) {
domain_err = true;
}
});
if (domain_err) {
alert('Cannot request Let\'s Encrypt Certificate for wildcard domains');
return;
if (!data.meta.dns_challenge) {
data.domain_names.map(function (name) {
if (name.match(/\*/im)) {
domain_err = true;
}
});
}
data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1';
if (domain_err) {
alert(i18n('ssl', 'no-wildcard-without-dns'));
return;
}
} else {
data.certificate_id = parseInt(data.certificate_id, 10);
}
@ -163,6 +227,8 @@ module.exports = Mn.View.extend({
}
this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
this.ui.save.addClass('btn-loading');
method(data)
.then(result => {
view.model.set(result);
@ -174,8 +240,17 @@ module.exports = Mn.View.extend({
});
})
.catch(err => {
alert(err.message);
let more_info = '';
if(err.code === 500 && err.debug){
try{
more_info = JSON.parse(err.debug).debug.stack.join("\n");
} catch(e) {}
}
this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `<pre class="mt-3">${more_info}</pre>`:''}`;
this.ui.le_error_info.show();
this.ui.le_error_info[0].scrollIntoView();
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
this.ui.save.removeClass('btn-loading');
});
}
},
@ -183,7 +258,20 @@ module.exports = Mn.View.extend({
templateContext: {
getLetsencryptEmail: function () {
return App.Cache.User.get('email');
}
},
getUseDnsChallenge: function () {
return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false;
},
getDnsProvider: function () {
return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null;
},
getDnsProviderCredentials: function () {
return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : '';
},
getPropagationSeconds: function () {
return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : '';
},
dns_plugins: dns_providers,
},
onRender: function () {
@ -203,7 +291,7 @@ module.exports = Mn.View.extend({
text: input
};
},
createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/
createFilter: /^(?:\.)?(?:[^.*]+\.?)+[^.]$/
});
// Access Lists
@ -237,6 +325,9 @@ module.exports = Mn.View.extend({
});
// Certificates
this.ui.le_error_info.hide();
this.ui.dns_challenge_content.hide();
this.ui.credentials_file_content.hide();
this.ui.letsencrypt.hide();
this.ui.certificate_select.selectize({
valueField: 'id',

View File

@ -23,7 +23,13 @@
</div>
</td>
<td>
<div class="text-monospace"><%- forward_scheme %>://<%- forward_host %>:<%- forward_port %></div>
<!-- <div> <%- static %> </div> -->
<% if (!static) { %>
<div class="text-monospace"><%- forward_scheme %>://<%- forward_host %>:<%- forward_port %></div>
<% } else { %>
<div class="text-monospace"><%- root_dir %></div>
<div class="text-monospace"><%- index_file %></div>
<% } %>
</td>
<td>
<div><%- certificate && certificate_id ? i18n('ssl', certificate.provider) : i18n('ssl', 'none') %></div>

View File

@ -16,7 +16,7 @@
<div class="col-auto">
<div class="selectgroup">
<label class="selectgroup-item">
<input type="checkbox" class="selectgroup-input">
<input type="checkbox" class="selectgroup-input settings-checkbox">
<span class="selectgroup-button">
<i class="fe fe-settings"></i>
</span>
@ -28,7 +28,7 @@
</div>
<div class="col-sm-3 col-md-3">
<div class="form-group">
<label class="form-label"><%- i18n('proxy-hosts', 'forward-scheme') %><span class="form-required">*</span></label>
<label class="form-label"><%- i18n('proxy-hosts', 'forward-scheme') %></label>
<select name="forward_scheme" class="form-control custom-select model" placeholder="http">
<option value="http" <%- forward_scheme === 'http' ? 'selected' : '' %>>http</option>
<option value="https" <%- forward_scheme === 'https' ? 'selected' : '' %>>https</option>
@ -37,17 +37,38 @@
</div>
<div class="col-sm-5 col-md-5">
<div class="form-group">
<label class="form-label"><%- i18n('proxy-hosts', 'forward-host') %><span class="form-required">*</span></label>
<input type="text" name="forward_host" class="form-control text-monospace model" placeholder="" value="<%- forward_host %>" autocomplete="off" maxlength="50" required>
<label class="form-label"><%- i18n('proxy-hosts', 'forward-host') %> <% if (!static) { %> <span class="form-required">*</span> <% } %> </label>
<input type="text" name="forward_host" class="form-control text-monospace model" placeholder="" value="<%- forward_host %>" <%- !static ? 'checked' : '' %> autocomplete="off" maxlength="200">
<span style="font-size: 9px;"><%- i18n('proxy-hosts', 'custom-forward-host-help') %></span>
</div>
</div>
<div class="col-sm-4 col-md-4">
<div class="form-group">
<label class="form-label"><%- i18n('proxy-hosts', 'forward-port') %> <span class="form-required">*</span></label>
<input name="forward_port" type="number" class="form-control text-monospace model" placeholder="80" value="<%- forward_port %>" required>
<label class="form-label"><%- i18n('proxy-hosts', 'forward-port') %> <% if (!static) { %> <span class="form-required">*</span><% } %> </label>
<input name="forward_port" type="number" class="form-control text-monospace model" placeholder="80" value="<%- forward_port %>" <%- !static ? 'checked' : '' %> >
</div>
</div>
<div class="col-sm-5 col-md-5">
<div class="form-group">
<label class="form-label"><%- i18n('proxy-hosts', 'root-dir') %><% if (static) { %> <span class="form-required">*</span><% } %></label>
<input type="text" name="root_dir" class="form-control text-monospace model" placeholder="" value="<%- root_dir %>" <%- static ? 'required' : '' %> autocomplete="off" maxlength="200">
</div>
</div>
<div class="col-sm-5 col-md-5">
<div class="form-group">
<label class="form-label"><%- i18n('proxy-hosts', 'index-file') %><% if (static) { %> <span class="form-required">*</span><% } %></label>
<input type="text" name="index_file" class="form-control text-monospace model" placeholder="" value="<%- index_file %>" <%- static ? 'required' : false %> autocomplete="off" maxlength="200">
</div>
</div>
<div class="col-sm-12 col-md-12">
<div class="form-group">
<label class="custom-switch">
<input type="checkbox" class="custom-switch-input location-static-checkbox model" name="static" value="1"<%- static ? ' checked' : '' %> >
<span class="custom-switch-indicator"></span>
<span class="custom-switch-description"><%- i18n('proxy-hosts', 'static') %></span>
</label>
</div>
</div>
</div>
<div class="row config">
<div class="col-md-12">
@ -61,4 +82,4 @@
<i class="fa fa-trash"></i> <%- i18n('locations', 'delete') %>
</a>
</div>
</div>
</div>

View File

@ -7,13 +7,15 @@ const LocationView = Mn.View.extend({
className: 'location_block',
ui: {
toggle: 'input[type="checkbox"]',
settings: 'input[type="checkbox"].settings-checkbox',
static: 'input[type="checkbox"].location-static-checkbox',
config: '.config',
delete: '.location-delete'
},
events: {
'change @ui.toggle': function(el) {
'change @ui.settings': function(el) {
if (el.target.checked) {
this.ui.config.show();
} else {
@ -22,11 +24,20 @@ const LocationView = Mn.View.extend({
},
'change .model': function (e) {
const map = {};
map[e.target.name] = e.target.value;
let value = e.target.value
if(e.target.type == 'checkbox') value = e.target.checked ? 1 : 0
map[e.target.name] = value
this.model.set(map);
setTimeout(this.render.bind(this), 300)
},
// 'click @ui.static': 'render',
'click @ui.delete': function () {
this.model.destroy();
}

View File

@ -4,6 +4,7 @@
<button type="button" class="close cancel" aria-label="Close" data-dismiss="modal">&nbsp;</button>
</div>
<div class="modal-body has-tabs">
<div class="alert alert-danger mb-0 rounded-0" id="le-error-info" role="alert"></div>
<form>
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="nav-item"><a href="#details" aria-controls="tab1" role="tab" data-toggle="tab" class="nav-link active"><i class="fe fe-zap"></i> <%- i18n('all-hosts', 'details') %></a></li>
@ -21,12 +22,35 @@
<input type="text" name="domain_names" class="form-control" id="input-domains" value="<%- domain_names.join(',') %>" required>
</div>
</div>
<div class="col-sm-12 col-md-12">
<div class="col-sm-3 col-md-3">
<div class="form-group">
<label class="form-label"><%- i18n('redirection-hosts', 'forward-scheme') %><span class="form-required">*</span></label>
<select name="forward_scheme" class="form-control custom-select" placeholder="$scheme">
<option value="$scheme" <%- forward_scheme === '$scheme' ? 'selected' : '' %>>auto</option>
<option value="http" <%- forward_scheme === 'http' ? 'selected' : '' %>>http</option>
<option value="https" <%- forward_scheme === 'https' ? 'selected' : '' %>>https</option>
</select>
</div>
</div>
<div class="col-sm-9 col-md-9">
<div class="form-group">
<label class="form-label"><%- i18n('redirection-hosts', 'forward-domain') %><span class="form-required">*</span></label>
<input type="text" name="forward_domain_name" class="form-control text-monospace" placeholder="" value="<%- forward_domain_name %>" required>
</div>
</div>
<div class="col-sm-12 col-md-12">
<div class="form-group">
<label class="form-label"><%- i18n('redirection-hosts', 'forward-http-status-code') %><span class="form-required">*</span></label>
<select name="forward_http_code" class="form-control custom-select" placeholder="301">
<option value="300" <%- forward_http_code == '300' ? 'selected' : '' %>>300 Multiple choices</option>
<option value="301" <%- forward_http_code == '301' ? 'selected' : '' %>>301 Moved permanently</option>
<option value="302" <%- forward_http_code == '302' ? 'selected' : '' %>>302 Found</option>
<option value="303" <%- forward_http_code == '303' ? 'selected' : '' %>>303 See other</option>
<option value="307" <%- forward_http_code == '307' ? 'selected' : '' %>>307 Temporary redirect</option>
<option value="308" <%- forward_http_code == '308' ? 'selected' : '' %>>308 Permanent redirect</option>
</select>
</div>
</div>
<div class="col-sm-6 col-md-6">
<div class="form-group">
<label class="custom-switch">
@ -97,6 +121,98 @@
</div>
</div>
<!-- DNS challenge -->
<div class="col-sm-12 col-md-12 letsencrypt">
<div class="form-group">
<label class="custom-switch">
<input
type="checkbox"
class="custom-switch-input"
name="meta[dns_challenge]"
value="1"
<%- getUseDnsChallenge() ? 'checked' : '' %>
>
<span class="custom-switch-indicator"></span>
<span class="custom-switch-description"><%= i18n('ssl', 'dns-challenge') %></span>
</label>
</div>
</div>
<div class="col-sm-12 col-md-12 letsencrypt">
<fieldset class="form-fieldset dns-challenge">
<div class="text-red mb-4"><i class="fe fe-alert-triangle"></i> <%= i18n('ssl', 'certbot-warning') %></div>
<!-- Certbot DNS plugin selection -->
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="form-group">
<label class="form-label"><%- i18n('ssl', 'dns-provider') %> <span class="form-required">*</span></label>
<select
name="meta[dns_provider]"
id="dns_provider"
class="form-control custom-select"
>
<option
value=""
disabled
hidden
<%- getDnsProvider() === null ? 'selected' : '' %>
>Please Choose...</option>
<% _.each(dns_plugins, function(plugin_info, plugin_name){ %>
<option
value="<%- plugin_name %>"
<%- getDnsProvider() === plugin_name ? 'selected' : '' %>
><%- plugin_info.display_name %></option>
<% }); %>
</select>
</div>
</div>
</div>
<!-- Certbot credentials file content -->
<div class="row credentials-file-content">
<div class="col-sm-12 col-md-12">
<div class="form-group">
<label class="form-label"><%- i18n('ssl', 'credentials-file-content') %> <span class="form-required">*</span></label>
<textarea
name="meta[dns_provider_credentials]"
class="form-control text-monospace"
id="dns_provider_credentials"
><%- getDnsProviderCredentials() %></textarea>
<div class="text-secondary small">
<i class="fe fe-info"></i>
<%= i18n('ssl', 'credentials-file-content-info') %>
</div>
<div class="text-red small">
<i class="fe fe-alert-triangle"></i>
<%= i18n('ssl', 'stored-as-plaintext-info') %>
</div>
</div>
</div>
</div>
<!-- DNS propagation delay -->
<div class="row">
<div class="col-sm-12 col-md-12">
<div class="form-group mb-0">
<label class="form-label"><%- i18n('ssl', 'propagation-seconds') %></label>
<input
type="number"
min="0"
name="meta[propagation_seconds]"
class="form-control"
id="propagation_seconds"
value="<%- getPropagationSeconds() %>"
>
<div class="text-secondary small">
<i class="fe fe-info"></i>
<%= i18n('ssl', 'propagation-seconds-info') %>
</div>
</div>
</div>
</div>
</fieldset>
</div>
<!-- Lets encrypt -->
<div class="col-sm-12 col-md-12 letsencrypt">
<div class="form-group">

View File

@ -4,6 +4,9 @@ const RedirectionHostModel = require('../../../models/redirection-host');
const template = require('./form.ejs');
const certListItemTemplate = require('../certificates-list-item.ejs');
const Helpers = require('../../../lib/helpers');
const i18n = require('../../i18n');
const dns_providers = require('../../../../../global/certbot-dns-plugins');
require('jquery-serializejson');
require('selectize');
@ -13,17 +16,24 @@ module.exports = Mn.View.extend({
className: 'modal-dialog',
ui: {
form: 'form',
domain_names: 'input[name="domain_names"]',
buttons: '.modal-footer button',
cancel: 'button.cancel',
save: 'button.save',
certificate_select: 'select[name="certificate_id"]',
ssl_forced: 'input[name="ssl_forced"]',
hsts_enabled: 'input[name="hsts_enabled"]',
hsts_subdomains: 'input[name="hsts_subdomains"]',
http2_support: 'input[name="http2_support"]',
letsencrypt: '.letsencrypt'
form: 'form',
domain_names: 'input[name="domain_names"]',
buttons: '.modal-footer button',
cancel: 'button.cancel',
save: 'button.save',
le_error_info: '#le-error-info',
certificate_select: 'select[name="certificate_id"]',
ssl_forced: 'input[name="ssl_forced"]',
hsts_enabled: 'input[name="hsts_enabled"]',
hsts_subdomains: 'input[name="hsts_subdomains"]',
http2_support: 'input[name="http2_support"]',
dns_challenge_switch: 'input[name="meta[dns_challenge]"]',
dns_challenge_content: '.dns-challenge',
dns_provider: 'select[name="meta[dns_provider]"]',
credentials_file_content: '.credentials-file-content',
dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]',
propagation_seconds: 'input[name="meta[propagation_seconds]"]',
letsencrypt: '.letsencrypt'
},
events: {
@ -31,6 +41,7 @@ module.exports = Mn.View.extend({
let id = this.ui.certificate_select.val();
if (id === 'new') {
this.ui.letsencrypt.show().find('input').prop('disabled', false);
this.ui.dns_challenge_content.hide();
} else {
this.ui.letsencrypt.hide().find('input').prop('disabled', true);
}
@ -76,8 +87,37 @@ module.exports = Mn.View.extend({
}
},
'change @ui.dns_challenge_switch': function () {
const checked = this.ui.dns_challenge_switch.prop('checked');
if (checked) {
this.ui.dns_provider.prop('required', 'required');
const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){
this.ui.dns_provider_credentials.prop('required', 'required');
}
this.ui.dns_challenge_content.show();
} else {
this.ui.dns_provider.prop('required', false);
this.ui.dns_provider_credentials.prop('required', false);
this.ui.dns_challenge_content.hide();
}
},
'change @ui.dns_provider': function () {
const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value;
if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) {
this.ui.dns_provider_credentials.prop('required', 'required');
this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials;
this.ui.credentials_file_content.show();
} else {
this.ui.dns_provider_credentials.prop('required', false);
this.ui.credentials_file_content.hide();
}
},
'click @ui.save': function (e) {
e.preventDefault();
this.ui.le_error_info.hide();
if (!this.ui.form[0].checkValidity()) {
$('<input type="submit">').hide().appendTo(this.ui.form).click().remove();
@ -88,32 +128,44 @@ module.exports = Mn.View.extend({
let data = this.ui.form.serializeJSON();
// Manipulate
data.block_exploits = !!data.block_exploits;
data.preserve_path = !!data.preserve_path;
data.http2_support = !!data.http2_support;
data.hsts_enabled = !!data.hsts_enabled;
data.hsts_subdomains = !!data.hsts_subdomains;
data.ssl_forced = !!data.ssl_forced;
data.block_exploits = !!data.block_exploits;
data.preserve_path = !!data.preserve_path;
data.http2_support = !!data.http2_support;
data.hsts_enabled = !!data.hsts_enabled;
data.hsts_subdomains = !!data.hsts_subdomains;
data.ssl_forced = !!data.ssl_forced;
if (typeof data.meta === 'undefined') data.meta = {};
data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1;
data.meta.dns_challenge = data.meta.dns_challenge == 1;
if(!data.meta.dns_challenge){
data.meta.dns_provider = undefined;
data.meta.dns_provider_credentials = undefined;
data.meta.propagation_seconds = undefined;
} else {
if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined;
}
if (typeof data.domain_names === 'string' && data.domain_names) {
data.domain_names = data.domain_names.split(',');
}
// Check for any domain names containing wildcards, which are not allowed with letsencrypt
if (data.certificate_id === 'new') {
if (data.certificate_id === 'new') {
let domain_err = false;
data.domain_names.map(function (name) {
if (name.match(/\*/im)) {
domain_err = true;
}
});
if (domain_err) {
alert('Cannot request Let\'s Encrypt Certificate for wildcard domains');
return;
if (!data.meta.dns_challenge) {
data.domain_names.map(function (name) {
if (name.match(/\*/im)) {
domain_err = true;
}
});
}
data.meta.letsencrypt_agree = data.meta.letsencrypt_agree === '1';
if (domain_err) {
alert(i18n('ssl', 'no-wildcard-without-dns'));
return;
}
} else {
data.certificate_id = parseInt(data.certificate_id, 10);
}
@ -129,6 +181,8 @@ module.exports = Mn.View.extend({
}
this.ui.buttons.prop('disabled', true).addClass('btn-disabled');
this.ui.save.addClass('btn-loading');
method(data)
.then(result => {
view.model.set(result);
@ -140,8 +194,17 @@ module.exports = Mn.View.extend({
});
})
.catch(err => {
alert(err.message);
let more_info = '';
if(err.code === 500 && err.debug){
try{
more_info = JSON.parse(err.debug).debug.stack.join("\n");
} catch(e) {}
}
this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `<pre class="mt-3">${more_info}</pre>`:''}`;
this.ui.le_error_info.show();
this.ui.le_error_info[0].scrollIntoView();
this.ui.buttons.prop('disabled', false).removeClass('btn-disabled');
this.ui.save.removeClass('btn-loading');
});
}
},
@ -149,7 +212,20 @@ module.exports = Mn.View.extend({
templateContext: {
getLetsencryptEmail: function () {
return App.Cache.User.get('email');
}
},
getUseDnsChallenge: function () {
return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false;
},
getDnsProvider: function () {
return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null;
},
getDnsProviderCredentials: function () {
return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : '';
},
getPropagationSeconds: function () {
return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : '';
},
dns_plugins: dns_providers,
},
onRender: function () {
@ -170,6 +246,9 @@ module.exports = Mn.View.extend({
});
// Certificates
this.ui.le_error_info.hide();
this.ui.dns_challenge_content.hide();
this.ui.credentials_file_content.hide();
this.ui.letsencrypt.hide();
this.ui.certificate_select.selectize({
valueField: 'id',

View File

@ -22,6 +22,12 @@
<%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %>
</div>
</td>
<td>
<div class="text-monospace"><%- forward_http_code %></div>
</td>
<td>
<div class="text-monospace"><%- forward_scheme == '$scheme' ? 'auto' : forward_scheme %></div>
</td>
<td>
<div class="text-monospace"><%- forward_domain_name %></div>
</td>

View File

@ -1,6 +1,8 @@
<thead>
<th width="30">&nbsp;</th>
<th><%- i18n('str', 'source') %></th>
<th><%- i18n('redirection-hosts', 'forward-http-status-code') %></th>
<th><%- i18n('redirection-hosts', 'forward-scheme') %></th>
<th><%- i18n('str', 'destination') %></th>
<th><%- i18n('str', 'ssl') %></th>
<th><%- i18n('str', 'status') %></th>

View File

@ -101,7 +101,19 @@
"letsencrypt-email": "Email Address for Let's Encrypt",
"letsencrypt-agree": "I Agree to the <a href=\"{url}\" target=\"_blank\">Let's Encrypt Terms of Service</a>",
"delete-ssl": "The SSL certificates attached will NOT be removed, they will need to be removed manually.",
"hosts-warning": "These domains must be already configured to point to this installation"
"hosts-warning": "These domains must be already configured to point to this installation",
"no-wildcard-without-dns": "Cannot request Let's Encrypt Certificate for wildcard domains when not using DNS challenge",
"dns-challenge": "Use a DNS Challenge",
"certbot-warning": "This section requires some knowledge about Certbot and its DNS plugins. Please consult the respective plugins documentation.",
"dns-provider": "DNS Provider",
"please-choose": "Please Choose...",
"credentials-file-content": "Credentials File Content",
"credentials-file-content-info": "This plugin requires a configuration file containing an API token or other credentials to your provider",
"stored-as-plaintext-info": "This data will be stored as plaintext in the database and in a file!",
"propagation-seconds": "Propagation Seconds",
"propagation-seconds-info": "Leave empty to use the plugins default value. Number of seconds to wait for DNS propagation.",
"processing-info": "Processing... This might take a few minutes.",
"passphrase-protection-support-info": "Key files protected with a passphrase are not supported."
},
"proxy-hosts": {
"title": "Proxy Hosts",
@ -111,6 +123,9 @@
"forward-scheme": "Scheme",
"forward-host": "Forward Hostname / IP",
"forward-port": "Forward Port",
"root-dir": "Root Directory",
"static": "Static File Proxy",
"index-file": "Index File",
"delete": "Delete Proxy Host",
"delete-confirm": "Are you sure you want to delete the Proxy host for: <strong>{domains}</strong>?",
"help-title": "What is a Proxy Host?",
@ -125,6 +140,8 @@
"empty": "There are no Redirection Hosts",
"add": "Add Redirection Host",
"form-title": "{id, select, undefined{New} other{Edit}} Redirection Host",
"forward-scheme": "Scheme",
"forward-http-status-code": "HTTP Code",
"forward-domain": "Forward Domain",
"preserve-path": "Preserve Path",
"delete": "Delete Proxy Host",
@ -195,7 +212,8 @@
"authorization": "Authorization",
"access": "Access",
"satisfy": "Satisfy",
"satisfy-any": "Satisfy Any"
"satisfy-any": "Satisfy Any",
"pass-auth": "Pass Auth to Host"
},
"users": {
"title": "Users",

View File

@ -9,8 +9,11 @@ const model = Backbone.Model.extend({
path: '',
advanced_config: '',
forward_scheme: 'http',
forward_host: '',
forward_port: '80'
forward_host: null,
forward_port: '80',
root_dir: null,
static: false,
index_file: 'index.html',
}
},

View File

@ -10,8 +10,11 @@ const model = Backbone.Model.extend({
modified_on: null,
domain_names: [],
forward_scheme: 'http',
forward_host: '',
forward_host: null,
forward_port: null,
root_dir: null,
static: false,
index_file: 'index.html',
access_list_id: 0,
certificate_id: 0,
ssl_forced: false,

View File

@ -9,6 +9,8 @@ const model = Backbone.Model.extend({
created_on: null,
modified_on: null,
domain_names: [],
forward_http_code: 0,
forward_scheme: null,
forward_domain_name: '',
preserve_path: true,
certificate_id: 0,

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,325 @@
/**
* This file contains info about available Certbot DNS plugins.
* This only works for plugins which use the standard argument structure, so:
* --authenticator <plugin-name> --<plugin-name>-credentials <FILE> --<plugin-name>-propagation-seconds <number>
*
* File Structure:
*
* {
* cloudflare: {
* display_name: "Name displayed to the user",
* package_name: "Package name in PyPi repo",
* package_version: "Package version in PyPi repo",
* dependencies: "Additional dependencies, space separated (as you would pass it to pip install)",
* credentials: `Template of the credentials file`,
* full_plugin_name: "The full plugin name as used in the commandline with certbot, including prefixes, e.g. 'certbot-dns-njalla:dns-njalla'",
* },
* ...
* }
*
*/
module.exports = {
aliyun: {
display_name: 'Aliyun',
package_name: 'certbot-dns-aliyun',
package_version: '0.38.1',
dependencies: '',
credentials: `certbot_dns_aliyun:dns_aliyun_access_key = 12345678
certbot_dns_aliyun:dns_aliyun_access_key_secret = 1234567890abcdef1234567890abcdef`,
full_plugin_name: 'certbot-dns-aliyun:dns-aliyun',
},
//####################################################//
cloudflare: {
display_name: 'Cloudflare',
package_name: 'certbot-dns-cloudflare',
package_version: '1.8.0',
dependencies: 'cloudflare',
credentials: `# Cloudflare API token
dns_cloudflare_api_token = 0123456789abcdef0123456789abcdef01234567`,
full_plugin_name: 'dns-cloudflare',
},
//####################################################//
cloudxns: {
display_name: 'CloudXNS',
package_name: 'certbot-dns-cloudxns',
package_version: '1.8.0',
dependencies: '',
credentials: `dns_cloudxns_api_key = 1234567890abcdef1234567890abcdef
dns_cloudxns_secret_key = 1122334455667788`,
full_plugin_name: 'dns-cloudxns',
},
//####################################################//
corenetworks: {
display_name: 'Core Networks',
package_name: 'certbot-dns-corenetworks',
package_version: '0.1.4',
dependencies: '',
credentials: `certbot_dns_corenetworks:dns_corenetworks_username = asaHB12r
certbot_dns_corenetworks:dns_corenetworks_password = secure_password`,
full_plugin_name: 'certbot-dns-corenetworks:dns-corenetworks',
},
//####################################################//
cpanel: {
display_name: 'cPanel',
package_name: 'certbot-dns-cpanel',
package_version: '0.2.2',
dependencies: '',
credentials: `certbot_dns_cpanel:cpanel_url = https://cpanel.example.com:2083
certbot_dns_cpanel:cpanel_username = user
certbot_dns_cpanel:cpanel_password = hunter2`,
full_plugin_name: 'certbot-dns-cpanel:cpanel',
},
//####################################################//
digitalocean: {
display_name: 'DigitalOcean',
package_name: 'certbot-dns-digitalocean',
package_version: '1.8.0',
dependencies: '',
credentials: 'dns_digitalocean_token = 0000111122223333444455556666777788889999aaaabbbbccccddddeeeeffff',
full_plugin_name: 'dns-digitalocean',
},
//####################################################//
directadmin: {
display_name: 'DirectAdmin',
package_name: 'certbot-dns-directadmin',
package_version: '0.0.20',
dependencies: '',
credentials: `directadmin_url = https://my.directadminserver.com:2222
directadmin_username = username
directadmin_password = aSuperStrongPassword`,
full_plugin_name: 'certbot-dns-directadmin:directadmin',
},
//####################################################//
dnsimple: {
display_name: 'DNSimple',
package_name: 'certbot-dns-dnsimple',
package_version: '1.8.0',
dependencies: '',
credentials: 'dns_dnsimple_token = MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw',
full_plugin_name: 'dns-dnsimple',
},
//####################################################//
dnsmadeeasy: {
display_name: 'DNS Made Easy',
package_name: 'certbot-dns-dnsmadeeasy',
package_version: '1.8.0',
dependencies: '',
credentials: `dns_dnsmadeeasy_api_key = 1c1a3c91-4770-4ce7-96f4-54c0eb0e457a
dns_dnsmadeeasy_secret_key = c9b5625f-9834-4ff8-baba-4ed5f32cae55`,
full_plugin_name: 'dns-dnsmadeeasy',
},
//####################################################//
dnspod: {
display_name: 'DNSPod',
package_name: 'certbot-dns-dnspod',
package_version: '0.1.0',
dependencies: '',
credentials: `certbot_dns_dnspod:dns_dnspod_email = "DNSPOD-API-REQUIRES-A-VALID-EMAIL"
certbot_dns_dnspod:dns_dnspod_api_token = "DNSPOD-API-TOKEN"`,
full_plugin_name: 'certbot-dns-dnspod:dns-dnspod',
},
//####################################################//
gandi: {
display_name: 'Gandi Live DNS',
package_name: 'certbot_plugin_gandi',
package_version: '1.2.5',
dependencies: '',
credentials: 'certbot_plugin_gandi:dns_api_key = APIKEY',
full_plugin_name: 'certbot-plugin-gandi:dns',
},
//####################################################//
google: {
display_name: 'Google',
package_name: 'certbot-dns-google',
package_version: '1.8.0',
dependencies: '',
credentials: `{
"type": "service_account",
...
}`,
full_plugin_name: 'dns-google',
},
//####################################################//
hetzner: {
display_name: 'Hetzner',
package_name: 'certbot-dns-hetzner',
package_version: '1.0.4',
dependencies: '',
credentials: 'certbot_dns_hetzner:dns_hetzner_api_token = 0123456789abcdef0123456789abcdef',
full_plugin_name: 'certbot-dns-hetzner:dns-hetzner',
},
//####################################################//
inwx: {
display_name: 'INWX',
package_name: 'certbot-dns-inwx',
package_version: '2.1.2',
dependencies: '',
credentials: `certbot_dns_inwx:dns_inwx_url = https://api.domrobot.com/xmlrpc/
certbot_dns_inwx:dns_inwx_username = your_username
certbot_dns_inwx:dns_inwx_password = your_password
certbot_dns_inwx:dns_inwx_shared_secret = your_shared_secret optional`,
full_plugin_name: 'certbot-dns-inwx:dns-inwx',
},
//####################################################//
ispconfig: {
display_name: 'ISPConfig',
package_name: 'certbot-dns-ispconfig',
package_version: '0.2.0',
dependencies: '',
credentials: `certbot_dns_ispconfig:dns_ispconfig_username = myremoteuser
certbot_dns_ispconfig:dns_ispconfig_password = verysecureremoteuserpassword
certbot_dns_ispconfig:dns_ispconfig_endpoint = https://localhost:8080`,
full_plugin_name: 'certbot-dns-ispconfig:dns-ispconfig',
},
//####################################################//
isset: {
display_name: 'Isset',
package_name: 'certbot-dns-isset',
package_version: '0.0.3',
dependencies: '',
credentials: `certbot_dns_isset:dns_isset_endpoint="https://customer.isset.net/api"
certbot_dns_isset:dns_isset_token="<token>"`,
full_plugin_name: 'certbot-dns-isset:dns-isset',
},
//####################################################//
linode: {
display_name: 'Linode',
package_name: 'certbot-dns-linode',
package_version: '1.8.0',
dependencies: '',
credentials: `dns_linode_key = 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ64
dns_linode_version = [<blank>|3|4]`,
full_plugin_name: 'dns-linode',
},
//####################################################//
luadns: {
display_name: 'LuaDNS',
package_name: 'certbot-dns-luadns',
package_version: '1.8.0',
dependencies: '',
credentials: `dns_luadns_email = user@example.com
dns_luadns_token = 0123456789abcdef0123456789abcdef`,
full_plugin_name: 'dns-luadns',
},
//####################################################//
netcup: {
display_name: 'netcup',
package_name: 'certbot-dns-netcup',
package_version: '1.0.0',
dependencies: '',
credentials: `dns_netcup_customer_id = 123456
dns_netcup_api_key = 0123456789abcdef0123456789abcdef01234567
dns_netcup_api_password = abcdef0123456789abcdef01234567abcdef0123`,
full_plugin_name: 'certbot-dns-netcup:dns-netcup',
},
//####################################################//
njalla: {
display_name: 'Njalla',
package_name: 'certbot-dns-njalla',
package_version: '1.0.0',
dependencies: '',
credentials: 'certbot_dns_njalla:dns_njalla_token = 0123456789abcdef0123456789abcdef01234567',
full_plugin_name: 'certbot-dns-njalla:dns-njalla',
},
//####################################################//
nsone: {
display_name: 'NS1',
package_name: 'certbot-dns-nsone',
package_version: '1.8.0',
dependencies: '',
credentials: 'dns_nsone_api_key = MDAwMDAwMDAwMDAwMDAw',
full_plugin_name: 'dns-nsone',
},
//####################################################//
ovh: {
display_name: 'OVH',
package_name: 'certbot-dns-ovh',
package_version: '1.8.0',
dependencies: '',
credentials: `dns_ovh_endpoint = ovh-eu
dns_ovh_application_key = MDAwMDAwMDAwMDAw
dns_ovh_application_secret = MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw
dns_ovh_consumer_key = MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAw`,
full_plugin_name: 'dns-ovh',
},
//####################################################//
powerdns: {
display_name: 'PowerDNS',
package_name: 'certbot-dns-powerdns',
package_version: '0.2.0',
dependencies: '',
credentials: `certbot_dns_powerdns:dns_powerdns_api_url = https://api.mypowerdns.example.org
certbot_dns_powerdns:dns_powerdns_api_key = AbCbASsd!@34`,
full_plugin_name: 'certbot-dns-powerdns:dns-powerdns',
},
//####################################################//
rfc2136: {
display_name: 'RFC 2136',
package_name: 'certbot-dns-rfc2136',
package_version: '1.8.0',
dependencies: '',
credentials: `# Target DNS server
dns_rfc2136_server = 192.0.2.1
# Target DNS port
dns_rfc2136_port = 53
# TSIG key name
dns_rfc2136_name = keyname.
# TSIG key secret
dns_rfc2136_secret = 4q4wM/2I180UXoMyN4INVhJNi8V9BCV+jMw2mXgZw/CSuxUT8C7NKKFs AmKd7ak51vWKgSl12ib86oQRPkpDjg==
# TSIG key algorithm
dns_rfc2136_algorithm = HMAC-SHA512`,
full_plugin_name: 'dns-rfc2136',
},
//####################################################//
route53: {
display_name: 'Route 53 (Amazon)',
package_name: 'certbot-dns-route53',
package_version: '1.8.0',
dependencies: '',
credentials: `[default]
aws_access_key_id=AKIAIOSFODNN7EXAMPLE
aws_secret_access_key=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY`,
full_plugin_name: 'dns-route53',
},
//####################################################//
vultr: {
display_name: 'Vultr',
package_name: 'certbot-dns-vultr',
package_version: '1.0.3',
dependencies: '',
credentials: 'certbot_dns_vultr:dns_vultr_key = YOUR_VULTR_API_KEY',
full_plugin_name: 'certbot-dns-vultr:dns-vultr',
},
//####################################################//
eurodns: {
display_name: 'EuroDNS',
package_name: 'certbot-dns-eurodns',
package_version: '0.0.4',
dependencies: '',
credentials: `dns_eurodns_applicationId = myuser
dns_eurodns_apiKey = mysecretpassword
dns_eurodns_endpoint = https://rest-api.eurodns.com/user-api-gateway/proxy`,
full_plugin_name: 'certbot-dns-eurodns:dns-eurodns',
},
//####################################################//
transip: {
display_name: 'TransIP',
package_name: 'certbot-dns-transip',
package_version: '0.3.3',
dependencies: '',
credentials: `certbot_dns_transip:dns_transip_username = my_username
certbot_dns_transip:dns_transip_key_file = /etc/letsencrypt/transip-rsa.key`,
full_plugin_name: 'certbot-dns-transip:dns-transip',
},
//####################################################//
acmedns: {
display_name: 'ACME-DNS',
package_name: 'certbot-dns-acmedns',
package_version: '0.1.0',
dependencies: '',
credentials: `certbot_dns_acmedns:dns_acmedns_api_url = http://acmedns-server/
certbot_dns_acmedns:dns_acmedns_registration_file = /data/acme-registration.json`,
full_plugin_name: 'certbot-dns-acmedns:dns-acmedns',
},
};

View File

@ -3,13 +3,14 @@
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
. "$DIR/.common.sh"
DOCKER_IMAGE=jc21/alpine-nginx-full:node
DOCKER_IMAGE=jc21/nginx-full:node
# Ensure docker exists
if hash docker 2>/dev/null; then
docker pull "${DOCKER_IMAGE}"
cd "${DIR}/.."
echo -e "${BLUE} ${CYAN}Building Frontend ...${RESET}"
docker run --rm -e CI=true -v "$(pwd)/frontend:/app/frontend" -w /app/frontend "$DOCKER_IMAGE" sh -c "yarn install && yarn build && yarn build && chown -R $(id -u):$(id -g) /app/frontend"
docker run --rm -e CI=true -v "$(pwd)/frontend:/app/frontend" -v "$(pwd)/global:/app/global" -w /app/frontend "$DOCKER_IMAGE" sh -c "yarn install && yarn build && yarn build && chown -R $(id -u):$(id -g) /app/frontend"
echo -e "${BLUE} ${GREEN}Building Frontend Complete${RESET}"
else
echo -e "${RED} docker command is not available${RESET}"

7
scripts/restart-dev Normal file
View File

@ -0,0 +1,7 @@
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
. "$DIR/.common.sh"
cd "${DIR}/.."
. scripts/destroy-dev
. scripts/start-dev

View File

@ -7,7 +7,7 @@ DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if hash docker-compose 2>/dev/null; then
cd "${DIR}/.."
echo -e "${BLUE} ${CYAN}Testing Dev Stack ...${RESET}"
docker-compose exec -T npm bash -c "cd /app/backend && task test"
docker-compose exec -T npm bash -c "cd /app && task test"
else
echo -e "${RED} docker-compose command is not available${RESET}"
fi

1
test/.dockerignore Normal file
View File

@ -0,0 +1 @@
node_modules

3
test/.gitignore vendored
View File

@ -1,3 +1,4 @@
.vscode
node_modules
results
cypress/videos

Some files were not shown because too many files have changed in this diff Show More