├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── dns_challenge_request.md │ └── feature_request.md └── workflows │ └── docker.yml ├── .gitignore ├── .version ├── Jenkinsfile ├── LICENSE ├── README-en.md ├── README.md ├── backend ├── .eslintrc.json ├── .gitignore ├── .prettierrc ├── app.js ├── config │ ├── README.md │ ├── default.json │ └── sqlite-test-db.json ├── db.js ├── index.js ├── internal │ ├── access-list.js │ ├── audit-log.js │ ├── certificate.js │ ├── dead-host.js │ ├── host.js │ ├── ip_ranges.js │ ├── nginx.js │ ├── proxy-host.js │ ├── redirection-host.js │ ├── report.js │ ├── setting.js │ ├── stream.js │ ├── token.js │ └── user.js ├── knexfile.js ├── lib │ ├── access.js │ ├── access │ │ ├── access_lists-create.json │ │ ├── access_lists-delete.json │ │ ├── access_lists-get.json │ │ ├── access_lists-list.json │ │ ├── access_lists-update.json │ │ ├── auditlog-list.json │ │ ├── certificates-create.json │ │ ├── certificates-delete.json │ │ ├── certificates-get.json │ │ ├── certificates-list.json │ │ ├── certificates-update.json │ │ ├── dead_hosts-create.json │ │ ├── dead_hosts-delete.json │ │ ├── dead_hosts-get.json │ │ ├── dead_hosts-list.json │ │ ├── dead_hosts-update.json │ │ ├── permissions.json │ │ ├── proxy_hosts-create.json │ │ ├── proxy_hosts-delete.json │ │ ├── proxy_hosts-get.json │ │ ├── proxy_hosts-list.json │ │ ├── proxy_hosts-update.json │ │ ├── redirection_hosts-create.json │ │ ├── redirection_hosts-delete.json │ │ ├── redirection_hosts-get.json │ │ ├── redirection_hosts-list.json │ │ ├── redirection_hosts-update.json │ │ ├── reports-hosts.json │ │ ├── roles.json │ │ ├── settings-get.json │ │ ├── settings-list.json │ │ ├── settings-update.json │ │ ├── streams-create.json │ │ ├── streams-delete.json │ │ ├── streams-get.json │ │ ├── streams-list.json │ │ ├── streams-update.json │ │ ├── users-create.json │ │ ├── users-delete.json │ │ ├── users-get.json │ │ ├── users-list.json │ │ ├── users-loginas.json │ │ ├── users-password.json │ │ ├── users-permissions.json │ │ └── users-update.json │ ├── certbot.js │ ├── config.js │ ├── error.js │ ├── express │ │ ├── cors.js │ │ ├── jwt-decode.js │ │ ├── jwt.js │ │ ├── pagination.js │ │ └── user-id-from-me.js │ ├── helpers.js │ ├── migrate_template.js │ ├── utils.js │ └── validator │ │ ├── api.js │ │ └── index.js ├── logger.js ├── migrate.js ├── migrations │ ├── 20180618015850_initial.js │ ├── 20180929054513_websockets.js │ ├── 20181019052346_forward_host.js │ ├── 20181113041458_http2_support.js │ ├── 20181213013211_forward_scheme.js │ ├── 20190104035154_disabled.js │ ├── 20190215115310_customlocations.js │ ├── 20190218060101_hsts.js │ ├── 20190227065017_settings.js │ ├── 20200410143839_access_list_client.js │ ├── 20200410143840_access_list_client_fix.js │ ├── 20201014143841_pass_auth.js │ ├── 20210210154702_redirection_scheme.js │ ├── 20210210154703_redirection_status_code.js │ ├── 20210423103500_stream_domain.js │ ├── 20211108145214_regenerate_default_host.js │ └── 20240427161436_stream_ssl.js ├── models │ ├── access_list.js │ ├── access_list_auth.js │ ├── access_list_client.js │ ├── audit-log.js │ ├── auth.js │ ├── certificate.js │ ├── dead_host.js │ ├── now_helper.js │ ├── proxy_host.js │ ├── redirection_host.js │ ├── setting.js │ ├── stream.js │ ├── token.js │ ├── user.js │ └── user_permission.js ├── nodemon.json ├── package.json ├── routes │ ├── audit-log.js │ ├── main.js │ ├── nginx │ │ ├── access_lists.js │ │ ├── certificates.js │ │ ├── dead_hosts.js │ │ ├── proxy_hosts.js │ │ ├── redirection_hosts.js │ │ └── streams.js │ ├── reports.js │ ├── schema.js │ ├── settings.js │ ├── tokens.js │ └── users.js ├── schema │ ├── common.json │ ├── components │ │ ├── access-list-object.json │ │ ├── audit-log-object.json │ │ ├── certificate-list.json │ │ ├── certificate-object.json │ │ ├── dead-host-list.json │ │ ├── dead-host-object.json │ │ ├── error-object.json │ │ ├── error.json │ │ ├── health-object.json │ │ ├── permission-object.json │ │ ├── proxy-host-list.json │ │ ├── proxy-host-object.json │ │ ├── redirection-host-list.json │ │ ├── redirection-host-object.json │ │ ├── security-schemes.json │ │ ├── setting-list.json │ │ ├── setting-object.json │ │ ├── stream-list.json │ │ ├── stream-object.json │ │ ├── token-object.json │ │ ├── user-list.json │ │ └── user-object.json │ ├── index.js │ ├── paths │ │ ├── audit-log │ │ │ └── get.json │ │ ├── get.json │ │ ├── nginx │ │ │ ├── access-lists │ │ │ │ ├── get.json │ │ │ │ ├── listID │ │ │ │ │ ├── delete.json │ │ │ │ │ ├── get.json │ │ │ │ │ └── put.json │ │ │ │ └── post.json │ │ │ ├── certificates │ │ │ │ ├── certID │ │ │ │ │ ├── delete.json │ │ │ │ │ ├── download │ │ │ │ │ │ └── get.json │ │ │ │ │ ├── get.json │ │ │ │ │ ├── renew │ │ │ │ │ │ └── post.json │ │ │ │ │ └── upload │ │ │ │ │ │ └── post.json │ │ │ │ ├── get.json │ │ │ │ ├── post.json │ │ │ │ ├── test-http │ │ │ │ │ └── get.json │ │ │ │ └── validate │ │ │ │ │ └── post.json │ │ │ ├── dead-hosts │ │ │ │ ├── get.json │ │ │ │ ├── hostID │ │ │ │ │ ├── delete.json │ │ │ │ │ ├── disable │ │ │ │ │ │ └── post.json │ │ │ │ │ ├── enable │ │ │ │ │ │ └── post.json │ │ │ │ │ ├── get.json │ │ │ │ │ └── put.json │ │ │ │ └── post.json │ │ │ ├── proxy-hosts │ │ │ │ ├── get.json │ │ │ │ ├── hostID │ │ │ │ │ ├── delete.json │ │ │ │ │ ├── disable │ │ │ │ │ │ └── post.json │ │ │ │ │ ├── enable │ │ │ │ │ │ └── post.json │ │ │ │ │ ├── get.json │ │ │ │ │ └── put.json │ │ │ │ └── post.json │ │ │ ├── redirection-hosts │ │ │ │ ├── get.json │ │ │ │ ├── hostID │ │ │ │ │ ├── delete.json │ │ │ │ │ ├── disable │ │ │ │ │ │ └── post.json │ │ │ │ │ ├── enable │ │ │ │ │ │ └── post.json │ │ │ │ │ ├── get.json │ │ │ │ │ └── put.json │ │ │ │ └── post.json │ │ │ └── streams │ │ │ │ ├── get.json │ │ │ │ ├── post.json │ │ │ │ └── streamID │ │ │ │ ├── delete.json │ │ │ │ ├── disable │ │ │ │ └── post.json │ │ │ │ ├── enable │ │ │ │ └── post.json │ │ │ │ ├── get.json │ │ │ │ └── put.json │ │ ├── reports │ │ │ └── hosts │ │ │ │ └── get.json │ │ ├── schema │ │ │ └── get.json │ │ ├── settings │ │ │ ├── get.json │ │ │ └── settingID │ │ │ │ ├── get.json │ │ │ │ └── put.json │ │ ├── tokens │ │ │ ├── get.json │ │ │ └── post.json │ │ └── users │ │ │ ├── get.json │ │ │ ├── post.json │ │ │ └── userID │ │ │ ├── auth │ │ │ └── put.json │ │ │ ├── delete.json │ │ │ ├── get.json │ │ │ ├── login │ │ │ └── post.json │ │ │ ├── permissions │ │ │ └── put.json │ │ │ └── put.json │ └── swagger.json ├── scripts │ └── install-certbot-plugins ├── setup.js ├── templates │ ├── _access.conf │ ├── _assets.conf │ ├── _certificates.conf │ ├── _certificates_stream.conf │ ├── _exploits.conf │ ├── _forced_ssl.conf │ ├── _header_comment.conf │ ├── _hsts.conf │ ├── _hsts_map.conf │ ├── _listen.conf │ ├── _location.conf │ ├── dead_host.conf │ ├── default.conf │ ├── ip_ranges.conf │ ├── letsencrypt-request.conf │ ├── proxy_host.conf │ ├── redirection_host.conf │ └── stream.conf ├── validate-schema.js └── yarn.lock ├── docker ├── .dive-ci ├── Dockerfile ├── Dockerfile-zh ├── ci.env ├── ci │ └── postgres │ │ └── authentik.sql.gz ├── dev │ ├── Dockerfile │ ├── dnsrouter-config.json │ ├── letsencrypt.ini │ ├── pdns-db.sql │ ├── pebble-config.json │ └── squid.conf ├── docker-compose.ci.mysql.yml ├── docker-compose.ci.postgres.yml ├── docker-compose.ci.sqlite.yml ├── docker-compose.ci.yml ├── docker-compose.dev.yml ├── rootfs │ ├── etc │ │ ├── letsencrypt.ini │ │ ├── logrotate.d │ │ │ └── nginx-proxy-manager │ │ ├── nginx │ │ │ ├── conf.d │ │ │ │ ├── default.conf │ │ │ │ ├── dev.conf │ │ │ │ ├── include │ │ │ │ │ ├── .gitignore │ │ │ │ │ ├── assets.conf │ │ │ │ │ ├── block-exploits.conf │ │ │ │ │ ├── force-ssl.conf │ │ │ │ │ ├── ip_ranges.conf │ │ │ │ │ ├── letsencrypt-acme-challenge.conf │ │ │ │ │ ├── log.conf │ │ │ │ │ ├── proxy.conf │ │ │ │ │ ├── ssl-cache-stream.conf │ │ │ │ │ ├── ssl-cache.conf │ │ │ │ │ └── ssl-ciphers.conf │ │ │ │ └── production.conf │ │ │ ├── mime.types │ │ │ └── nginx.conf │ │ └── s6-overlay │ │ │ └── s6-rc.d │ │ │ ├── backend │ │ │ ├── dependencies.d │ │ │ │ └── prepare │ │ │ ├── run │ │ │ └── type │ │ │ ├── frontend │ │ │ ├── dependencies.d │ │ │ │ └── prepare │ │ │ ├── run │ │ │ └── type │ │ │ ├── nginx │ │ │ ├── dependencies.d │ │ │ │ └── prepare │ │ │ ├── run │ │ │ └── type │ │ │ ├── prepare │ │ │ ├── 00-all.sh │ │ │ ├── 10-usergroup.sh │ │ │ ├── 20-paths.sh │ │ │ ├── 30-ownership.sh │ │ │ ├── 40-dynamic.sh │ │ │ ├── 50-ipv6.sh │ │ │ ├── 60-secrets.sh │ │ │ ├── 90-banner.sh │ │ │ ├── dependencies.d │ │ │ │ └── base │ │ │ ├── type │ │ │ └── up │ │ │ └── user │ │ │ └── contents.d │ │ │ ├── backend │ │ │ ├── frontend │ │ │ ├── nginx │ │ │ └── prepare │ ├── root │ │ └── .bashrc │ ├── usr │ │ └── bin │ │ │ ├── check-health │ │ │ └── common.sh │ └── var │ │ └── www │ │ └── html │ │ └── index.html └── scripts │ └── install-s6 ├── docs ├── .gitignore ├── .vitepress │ ├── config.mts │ └── theme │ │ ├── custom.css │ │ └── index.ts ├── package.json ├── src │ ├── advanced-config │ │ └── index.md │ ├── faq │ │ └── index.md │ ├── guide │ │ └── index.md │ ├── index.md │ ├── public │ │ ├── github.png │ │ ├── icon.png │ │ ├── logo.svg │ │ ├── robots.txt │ │ └── screenshots │ │ │ ├── access-lists.png │ │ │ ├── audit-log.png │ │ │ ├── certificates.png │ │ │ ├── custom-settings.png │ │ │ ├── dashboard.png │ │ │ ├── dead-hosts.png │ │ │ ├── login.png │ │ │ ├── permissions.png │ │ │ ├── proxy-hosts-add.png │ │ │ ├── proxy-hosts.png │ │ │ └── redirection-hosts.png │ ├── screenshots │ │ └── index.md │ ├── setup │ │ └── index.md │ ├── third-party │ │ └── index.md │ └── upgrading │ │ └── index.md └── yarn.lock ├── frontend ├── .babelrc ├── .gitignore ├── app-images │ ├── default-avatar.jpg │ ├── favicons │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── browserconfig.xml │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon.ico │ │ ├── mstile-150x150.png │ │ ├── safari-pinned-tab.svg │ │ └── site.webmanifest │ ├── logo-256.png │ └── logo-text-vertical-grey.png ├── fonts │ ├── feather │ └── source-sans-pro │ │ ├── source-sans-pro-v14-latin-ext_latin-700.woff │ │ ├── source-sans-pro-v14-latin-ext_latin-700.woff2 │ │ ├── source-sans-pro-v14-latin-ext_latin-700italic.woff │ │ ├── source-sans-pro-v14-latin-ext_latin-700italic.woff2 │ │ ├── source-sans-pro-v14-latin-ext_latin-italic.woff │ │ ├── source-sans-pro-v14-latin-ext_latin-italic.woff2 │ │ ├── source-sans-pro-v14-latin-ext_latin-regular.woff │ │ └── source-sans-pro-v14-latin-ext_latin-regular.woff2 ├── html │ ├── index.ejs │ ├── login.ejs │ └── partials │ │ ├── footer.ejs │ │ └── header.ejs ├── images ├── js │ ├── app │ │ ├── api.js │ │ ├── audit-log │ │ │ ├── list │ │ │ │ ├── item.ejs │ │ │ │ ├── item.js │ │ │ │ ├── main.ejs │ │ │ │ └── main.js │ │ │ ├── main.ejs │ │ │ ├── main.js │ │ │ ├── meta.ejs │ │ │ └── meta.js │ │ ├── cache.js │ │ ├── controller.js │ │ ├── dashboard │ │ │ ├── main.ejs │ │ │ └── main.js │ │ ├── empty │ │ │ ├── main.ejs │ │ │ └── main.js │ │ ├── error │ │ │ ├── main.ejs │ │ │ └── main.js │ │ ├── help │ │ │ ├── main.ejs │ │ │ └── main.js │ │ ├── i18n.js │ │ ├── main.js │ │ ├── nginx │ │ │ ├── access │ │ │ │ ├── delete.ejs │ │ │ │ ├── delete.js │ │ │ │ ├── form.ejs │ │ │ │ ├── form.js │ │ │ │ ├── form │ │ │ │ │ ├── client.ejs │ │ │ │ │ ├── client.js │ │ │ │ │ ├── item.ejs │ │ │ │ │ └── item.js │ │ │ │ ├── list │ │ │ │ │ ├── item.ejs │ │ │ │ │ ├── item.js │ │ │ │ │ ├── main.ejs │ │ │ │ │ └── main.js │ │ │ │ ├── main.ejs │ │ │ │ └── main.js │ │ │ ├── certificates-list-item.ejs │ │ │ ├── certificates │ │ │ │ ├── delete.ejs │ │ │ │ ├── delete.js │ │ │ │ ├── form.ejs │ │ │ │ ├── form.js │ │ │ │ ├── list │ │ │ │ │ ├── item.ejs │ │ │ │ │ ├── item.js │ │ │ │ │ ├── main.ejs │ │ │ │ │ └── main.js │ │ │ │ ├── main.ejs │ │ │ │ ├── main.js │ │ │ │ ├── renew.ejs │ │ │ │ ├── renew.js │ │ │ │ ├── test.ejs │ │ │ │ └── test.js │ │ │ ├── dead │ │ │ │ ├── delete.ejs │ │ │ │ ├── delete.js │ │ │ │ ├── form.ejs │ │ │ │ ├── form.js │ │ │ │ ├── list │ │ │ │ │ ├── item.ejs │ │ │ │ │ ├── item.js │ │ │ │ │ ├── main.ejs │ │ │ │ │ └── main.js │ │ │ │ ├── main.ejs │ │ │ │ └── main.js │ │ │ ├── proxy │ │ │ │ ├── access-list-item.ejs │ │ │ │ ├── delete.ejs │ │ │ │ ├── delete.js │ │ │ │ ├── form.ejs │ │ │ │ ├── form.js │ │ │ │ ├── list │ │ │ │ │ ├── item.ejs │ │ │ │ │ ├── item.js │ │ │ │ │ ├── main.ejs │ │ │ │ │ └── main.js │ │ │ │ ├── location-item.ejs │ │ │ │ ├── location.js │ │ │ │ ├── main.ejs │ │ │ │ └── main.js │ │ │ ├── redirection │ │ │ │ ├── delete.ejs │ │ │ │ ├── delete.js │ │ │ │ ├── form.ejs │ │ │ │ ├── form.js │ │ │ │ ├── list │ │ │ │ │ ├── item.ejs │ │ │ │ │ ├── item.js │ │ │ │ │ ├── main.ejs │ │ │ │ │ └── main.js │ │ │ │ ├── main.ejs │ │ │ │ └── main.js │ │ │ └── stream │ │ │ │ ├── delete.ejs │ │ │ │ ├── delete.js │ │ │ │ ├── form.ejs │ │ │ │ ├── form.js │ │ │ │ ├── list │ │ │ │ ├── item.ejs │ │ │ │ ├── item.js │ │ │ │ ├── main.ejs │ │ │ │ └── main.js │ │ │ │ ├── main.ejs │ │ │ │ └── main.js │ │ ├── router.js │ │ ├── settings │ │ │ ├── default-site │ │ │ │ ├── main.ejs │ │ │ │ └── main.js │ │ │ ├── list │ │ │ │ ├── item.ejs │ │ │ │ ├── item.js │ │ │ │ ├── main.ejs │ │ │ │ └── main.js │ │ │ ├── main.ejs │ │ │ └── main.js │ │ ├── tokens.js │ │ ├── ui │ │ │ ├── footer │ │ │ │ ├── main.ejs │ │ │ │ └── main.js │ │ │ ├── header │ │ │ │ ├── main.ejs │ │ │ │ └── main.js │ │ │ ├── main.ejs │ │ │ ├── main.js │ │ │ └── menu │ │ │ │ ├── main.ejs │ │ │ │ └── main.js │ │ ├── user │ │ │ ├── delete.ejs │ │ │ ├── delete.js │ │ │ ├── form.ejs │ │ │ ├── form.js │ │ │ ├── password.ejs │ │ │ ├── password.js │ │ │ ├── permissions.ejs │ │ │ └── permissions.js │ │ └── users │ │ │ ├── list │ │ │ ├── item.ejs │ │ │ ├── item.js │ │ │ ├── main.ejs │ │ │ └── main.js │ │ │ ├── main.ejs │ │ │ └── main.js │ ├── i18n │ │ └── messages.json │ ├── index.js │ ├── lib │ │ ├── helpers.js │ │ └── marionette.js │ ├── login.js │ ├── login │ │ ├── main.js │ │ └── ui │ │ │ ├── login.ejs │ │ │ └── login.js │ └── models │ │ ├── access-list.js │ │ ├── audit-log.js │ │ ├── certificate.js │ │ ├── dead-host.js │ │ ├── proxy-host-location.js │ │ ├── proxy-host.js │ │ ├── redirection-host.js │ │ ├── setting.js │ │ ├── stream.js │ │ └── user.js ├── package.json ├── scss │ ├── custom.scss │ ├── fonts.scss │ ├── selectize.scss │ ├── styles.scss │ └── tabler-extra.scss ├── webpack.config.js └── yarn.lock ├── global ├── README.md └── certbot-dns-plugins.json ├── scripts ├── .common.sh ├── build-zh ├── buildx ├── buildx-zh ├── ci │ ├── frontend-build │ ├── fulltest-cypress │ └── test-and-build ├── cypress-dev ├── destroy-dev ├── docs-build ├── docs-upload ├── start-dev ├── stop-dev └── wait-healthy └── test ├── .eslintrc.json ├── .gitignore ├── .prettierrc ├── README.md ├── cypress ├── Dockerfile ├── config │ └── ci.js ├── e2e │ └── api │ │ ├── Certificates.cy.js │ │ ├── FullCertProvision.cy.js │ │ ├── Health.cy.js │ │ ├── Ldap.cy.js │ │ ├── OAuth.cy.js │ │ ├── ProxyHosts.cy.js │ │ ├── Settings.cy.js │ │ ├── Streams.cy.js │ │ └── Users.cy.js ├── fixtures │ ├── test.example.com-key.pem │ └── test.example.com.pem ├── plugins │ ├── backendApi │ │ ├── client.js │ │ ├── logger.js │ │ └── task.js │ └── index.js └── support │ ├── commands.js │ └── e2e.js ├── jsconfig.json ├── multi-reporter.json ├── package.json └── yarn.lock /.github/ISSUE_TEMPLATE/dns_challenge_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: DNS challenge provider request 3 | about: Suggest a new provider to be available for a certificate DNS challenge 4 | title: '' 5 | labels: dns provider request 6 | assignees: '' 7 | 8 | --- 9 | 10 | **What provider would you like to see added to NPM?** 11 | 12 | 13 | 14 | **Have you checked if a certbot plugin exists?** 15 | 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | 18 | 19 | **Is your feature request related to a problem? Please describe.** 20 | 21 | 22 | 23 | **Describe the solution you'd like** 24 | 25 | 26 | 27 | **Describe alternatives you've considered** 28 | 29 | 30 | 31 | **Additional context** 32 | 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | ._* 4 | .vscode 5 | certbot-help.txt 6 | test/node_modules 7 | */node_modules 8 | docker/dev/dnsrouter-config.json.tmp 9 | docker/dev/resolv.conf 10 | -------------------------------------------------------------------------------- /.version: -------------------------------------------------------------------------------- 1 | 2.12.3 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | config/development.json 2 | data/* 3 | yarn-error.log 4 | tmp 5 | certbot.log 6 | node_modules 7 | core.* 8 | 9 | -------------------------------------------------------------------------------- /backend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 320, 3 | "tabWidth": 4, 4 | "useTabs": true, 5 | "semi": true, 6 | "singleQuote": true, 7 | "bracketSpacing": true, 8 | "jsxBracketSameLine": true, 9 | "trailingComma": "all", 10 | "proseWrap": "always" 11 | } 12 | -------------------------------------------------------------------------------- /backend/config/README.md: -------------------------------------------------------------------------------- 1 | These files are use in development and are not deployed as part of the final product. 2 | -------------------------------------------------------------------------------- /backend/config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "database": { 3 | "engine": "mysql2", 4 | "host": "db", 5 | "name": "npm", 6 | "user": "npm", 7 | "password": "npm", 8 | "port": 3306 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /backend/config/sqlite-test-db.json: -------------------------------------------------------------------------------- 1 | { 2 | "database": { 3 | "engine": "knex-native", 4 | "knex": { 5 | "client": "sqlite3", 6 | "connection": { 7 | "filename": "/app/config/mydb.sqlite" 8 | }, 9 | "pool": { 10 | "min": 0, 11 | "max": 1, 12 | "createTimeoutMillis": 3000, 13 | "acquireTimeoutMillis": 30000, 14 | "idleTimeoutMillis": 30000, 15 | "reapIntervalMillis": 1000, 16 | "createRetryIntervalMillis": 100, 17 | "propagateCreateError": false 18 | }, 19 | "migrations": { 20 | "tableName": "migrations", 21 | "stub": "src/backend/lib/migrate_template.js", 22 | "directory": "src/backend/migrations" 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /backend/db.js: -------------------------------------------------------------------------------- 1 | const config = require('./lib/config'); 2 | 3 | if (!config.has('database')) { 4 | throw new Error('Database config does not exist! Please read the instructions: https://nginxproxymanager.com/setup/'); 5 | } 6 | 7 | function generateDbConfig() { 8 | const cfg = config.get('database'); 9 | if (cfg.engine === 'knex-native') { 10 | return cfg.knex; 11 | } 12 | return { 13 | client: cfg.engine, 14 | connection: { 15 | host: cfg.host, 16 | user: cfg.user, 17 | password: cfg.password, 18 | database: cfg.name, 19 | port: cfg.port 20 | }, 21 | migrations: { 22 | tableName: 'migrations' 23 | } 24 | }; 25 | } 26 | 27 | module.exports = require('knex')(generateDbConfig()); 28 | -------------------------------------------------------------------------------- /backend/internal/report.js: -------------------------------------------------------------------------------- 1 | const internalProxyHost = require('./proxy-host'); 2 | const internalRedirectionHost = require('./redirection-host'); 3 | const internalDeadHost = require('./dead-host'); 4 | const internalStream = require('./stream'); 5 | 6 | const internalReport = { 7 | 8 | /** 9 | * @param {Access} access 10 | * @return {Promise} 11 | */ 12 | getHostsReport: (access) => { 13 | return access.can('reports:hosts', 1) 14 | .then((access_data) => { 15 | let user_id = access.token.getUserId(1); 16 | 17 | let promises = [ 18 | internalProxyHost.getCount(user_id, access_data.visibility), 19 | internalRedirectionHost.getCount(user_id, access_data.visibility), 20 | internalStream.getCount(user_id, access_data.visibility), 21 | internalDeadHost.getCount(user_id, access_data.visibility) 22 | ]; 23 | 24 | return Promise.all(promises); 25 | }) 26 | .then((counts) => { 27 | return { 28 | proxy: counts.shift(), 29 | redirection: counts.shift(), 30 | stream: counts.shift(), 31 | dead: counts.shift() 32 | }; 33 | }); 34 | 35 | } 36 | }; 37 | 38 | module.exports = internalReport; 39 | -------------------------------------------------------------------------------- /backend/knexfile.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | development: { 3 | client: 'mysql2', 4 | migrations: { 5 | tableName: 'migrations', 6 | stub: 'lib/migrate_template.js', 7 | directory: 'migrations' 8 | } 9 | }, 10 | 11 | production: { 12 | client: 'mysql2', 13 | migrations: { 14 | tableName: 'migrations', 15 | stub: 'lib/migrate_template.js', 16 | directory: 'migrations' 17 | } 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /backend/lib/access/access_lists-create.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_access_lists", "roles"], 9 | "properties": { 10 | "permission_access_lists": { 11 | "$ref": "perms#/definitions/manage" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/access_lists-delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_access_lists", "roles"], 9 | "properties": { 10 | "permission_access_lists": { 11 | "$ref": "perms#/definitions/manage" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/access_lists-get.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_access_lists", "roles"], 9 | "properties": { 10 | "permission_access_lists": { 11 | "$ref": "perms#/definitions/view" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/access_lists-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_access_lists", "roles"], 9 | "properties": { 10 | "permission_access_lists": { 11 | "$ref": "perms#/definitions/view" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/access_lists-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_access_lists", "roles"], 9 | "properties": { 10 | "permission_access_lists": { 11 | "$ref": "perms#/definitions/manage" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/auditlog-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /backend/lib/access/certificates-create.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_certificates", "roles"], 9 | "properties": { 10 | "permission_certificates": { 11 | "$ref": "perms#/definitions/manage" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/certificates-delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_certificates", "roles"], 9 | "properties": { 10 | "permission_certificates": { 11 | "$ref": "perms#/definitions/manage" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/certificates-get.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_certificates", "roles"], 9 | "properties": { 10 | "permission_certificates": { 11 | "$ref": "perms#/definitions/view" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/certificates-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_certificates", "roles"], 9 | "properties": { 10 | "permission_certificates": { 11 | "$ref": "perms#/definitions/view" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/certificates-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_certificates", "roles"], 9 | "properties": { 10 | "permission_certificates": { 11 | "$ref": "perms#/definitions/manage" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/dead_hosts-create.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_dead_hosts", "roles"], 9 | "properties": { 10 | "permission_dead_hosts": { 11 | "$ref": "perms#/definitions/manage" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/dead_hosts-delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_dead_hosts", "roles"], 9 | "properties": { 10 | "permission_dead_hosts": { 11 | "$ref": "perms#/definitions/manage" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/dead_hosts-get.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_dead_hosts", "roles"], 9 | "properties": { 10 | "permission_dead_hosts": { 11 | "$ref": "perms#/definitions/view" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/dead_hosts-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_dead_hosts", "roles"], 9 | "properties": { 10 | "permission_dead_hosts": { 11 | "$ref": "perms#/definitions/view" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/dead_hosts-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_dead_hosts", "roles"], 9 | "properties": { 10 | "permission_dead_hosts": { 11 | "$ref": "perms#/definitions/manage" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/permissions.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "perms", 3 | "definitions": { 4 | "view": { 5 | "type": "string", 6 | "pattern": "^(view|manage)$" 7 | }, 8 | "manage": { 9 | "type": "string", 10 | "pattern": "^(manage)$" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /backend/lib/access/proxy_hosts-create.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_proxy_hosts", "roles"], 9 | "properties": { 10 | "permission_proxy_hosts": { 11 | "$ref": "perms#/definitions/manage" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/proxy_hosts-delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_proxy_hosts", "roles"], 9 | "properties": { 10 | "permission_proxy_hosts": { 11 | "$ref": "perms#/definitions/manage" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/proxy_hosts-get.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_proxy_hosts", "roles"], 9 | "properties": { 10 | "permission_proxy_hosts": { 11 | "$ref": "perms#/definitions/view" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/proxy_hosts-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_proxy_hosts", "roles"], 9 | "properties": { 10 | "permission_proxy_hosts": { 11 | "$ref": "perms#/definitions/view" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/proxy_hosts-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_proxy_hosts", "roles"], 9 | "properties": { 10 | "permission_proxy_hosts": { 11 | "$ref": "perms#/definitions/manage" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/redirection_hosts-create.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_redirection_hosts", "roles"], 9 | "properties": { 10 | "permission_redirection_hosts": { 11 | "$ref": "perms#/definitions/manage" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/redirection_hosts-delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_redirection_hosts", "roles"], 9 | "properties": { 10 | "permission_redirection_hosts": { 11 | "$ref": "perms#/definitions/manage" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/redirection_hosts-get.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_redirection_hosts", "roles"], 9 | "properties": { 10 | "permission_redirection_hosts": { 11 | "$ref": "perms#/definitions/view" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/redirection_hosts-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_redirection_hosts", "roles"], 9 | "properties": { 10 | "permission_redirection_hosts": { 11 | "$ref": "perms#/definitions/view" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/redirection_hosts-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_redirection_hosts", "roles"], 9 | "properties": { 10 | "permission_redirection_hosts": { 11 | "$ref": "perms#/definitions/manage" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/reports-hosts.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/user" 5 | } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /backend/lib/access/roles.json: -------------------------------------------------------------------------------- 1 | { 2 | "$id": "roles", 3 | "definitions": { 4 | "admin": { 5 | "type": "object", 6 | "required": ["scope", "roles"], 7 | "properties": { 8 | "scope": { 9 | "type": "array", 10 | "contains": { 11 | "type": "string", 12 | "pattern": "^user$" 13 | } 14 | }, 15 | "roles": { 16 | "type": "array", 17 | "contains": { 18 | "type": "string", 19 | "pattern": "^admin$" 20 | } 21 | } 22 | } 23 | }, 24 | "user": { 25 | "type": "object", 26 | "required": ["scope"], 27 | "properties": { 28 | "scope": { 29 | "type": "array", 30 | "contains": { 31 | "type": "string", 32 | "pattern": "^user$" 33 | } 34 | } 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /backend/lib/access/settings-get.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /backend/lib/access/settings-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /backend/lib/access/settings-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /backend/lib/access/streams-create.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_streams", "roles"], 9 | "properties": { 10 | "permission_streams": { 11 | "$ref": "perms#/definitions/manage" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/streams-delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_streams", "roles"], 9 | "properties": { 10 | "permission_streams": { 11 | "$ref": "perms#/definitions/manage" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/streams-get.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_streams", "roles"], 9 | "properties": { 10 | "permission_streams": { 11 | "$ref": "perms#/definitions/view" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/streams-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_streams", "roles"], 9 | "properties": { 10 | "permission_streams": { 11 | "$ref": "perms#/definitions/view" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/streams-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["permission_streams", "roles"], 9 | "properties": { 10 | "permission_streams": { 11 | "$ref": "perms#/definitions/manage" 12 | }, 13 | "roles": { 14 | "type": "array", 15 | "items": { 16 | "type": "string", 17 | "enum": ["user"] 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/users-create.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /backend/lib/access/users-delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /backend/lib/access/users-get.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["data", "scope"], 9 | "properties": { 10 | "data": { 11 | "$ref": "objects#/properties/users" 12 | }, 13 | "scope": { 14 | "type": "array", 15 | "contains": { 16 | "type": "string", 17 | "pattern": "^user$" 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/users-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /backend/lib/access/users-loginas.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /backend/lib/access/users-password.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["data", "scope"], 9 | "properties": { 10 | "data": { 11 | "$ref": "objects#/properties/users" 12 | }, 13 | "scope": { 14 | "type": "array", 15 | "contains": { 16 | "type": "string", 17 | "pattern": "^user$" 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/access/users-permissions.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /backend/lib/access/users-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "anyOf": [ 3 | { 4 | "$ref": "roles#/definitions/admin" 5 | }, 6 | { 7 | "type": "object", 8 | "required": ["data", "scope"], 9 | "properties": { 10 | "data": { 11 | "$ref": "objects#/properties/users" 12 | }, 13 | "scope": { 14 | "type": "array", 15 | "contains": { 16 | "type": "string", 17 | "pattern": "^user$" 18 | } 19 | } 20 | } 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /backend/lib/express/cors.js: -------------------------------------------------------------------------------- 1 | module.exports = function (req, res, next) { 2 | if (req.headers.origin) { 3 | res.set({ 4 | 'Access-Control-Allow-Origin': req.headers.origin, 5 | 'Access-Control-Allow-Credentials': true, 6 | 'Access-Control-Allow-Methods': 'OPTIONS, GET, POST', 7 | 'Access-Control-Allow-Headers': 'Content-Type, Cache-Control, Pragma, Expires, Authorization, X-Dataset-Total, X-Dataset-Offset, X-Dataset-Limit', 8 | 'Access-Control-Max-Age': 5 * 60, 9 | 'Access-Control-Expose-Headers': 'X-Dataset-Total, X-Dataset-Offset, X-Dataset-Limit' 10 | }); 11 | next(); 12 | } else { 13 | // No origin 14 | next(); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /backend/lib/express/jwt-decode.js: -------------------------------------------------------------------------------- 1 | const Access = require('../access'); 2 | 3 | module.exports = () => { 4 | return function (req, res, next) { 5 | res.locals.access = null; 6 | let access = new Access(res.locals.token || null); 7 | access.load() 8 | .then(() => { 9 | res.locals.access = access; 10 | next(); 11 | }) 12 | .catch(next); 13 | }; 14 | }; 15 | 16 | -------------------------------------------------------------------------------- /backend/lib/express/jwt.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | return function (req, res, next) { 3 | if (req.headers.authorization) { 4 | let parts = req.headers.authorization.split(' '); 5 | 6 | if (parts && parts[0] === 'Bearer' && parts[1]) { 7 | res.locals.token = parts[1]; 8 | } 9 | } 10 | 11 | next(); 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /backend/lib/express/user-id-from-me.js: -------------------------------------------------------------------------------- 1 | module.exports = (req, res, next) => { 2 | if (req.params.user_id === 'me' && res.locals.access) { 3 | req.params.user_id = res.locals.access.token.get('attrs').id; 4 | } else { 5 | req.params.user_id = parseInt(req.params.user_id, 10); 6 | } 7 | 8 | next(); 9 | }; 10 | -------------------------------------------------------------------------------- /backend/lib/validator/api.js: -------------------------------------------------------------------------------- 1 | const Ajv = require('ajv/dist/2020'); 2 | const error = require('../error'); 3 | 4 | const ajv = new Ajv({ 5 | verbose: true, 6 | allErrors: true, 7 | allowUnionTypes: true, 8 | strict: false, 9 | coerceTypes: true, 10 | }); 11 | 12 | /** 13 | * @param {Object} schema 14 | * @param {Object} payload 15 | * @returns {Promise} 16 | */ 17 | function apiValidator (schema, payload/*, description*/) { 18 | return new Promise(function Promise_apiValidator (resolve, reject) { 19 | if (schema === null) { 20 | reject(new error.ValidationError('Schema is undefined')); 21 | return; 22 | } 23 | 24 | if (typeof payload === 'undefined') { 25 | reject(new error.ValidationError('Payload is undefined')); 26 | return; 27 | } 28 | 29 | const validate = ajv.compile(schema); 30 | const valid = validate(payload); 31 | 32 | if (valid && !validate.errors) { 33 | resolve(payload); 34 | } else { 35 | let message = ajv.errorsText(validate.errors); 36 | let err = new error.ValidationError(message); 37 | err.debug = [validate.errors, payload]; 38 | reject(err); 39 | } 40 | }); 41 | } 42 | 43 | module.exports = apiValidator; 44 | -------------------------------------------------------------------------------- /backend/logger.js: -------------------------------------------------------------------------------- 1 | const {Signale} = require('signale'); 2 | 3 | module.exports = { 4 | global: new Signale({scope: 'Global '}), 5 | migrate: new Signale({scope: 'Migrate '}), 6 | express: new Signale({scope: 'Express '}), 7 | access: new Signale({scope: 'Access '}), 8 | nginx: new Signale({scope: 'Nginx '}), 9 | ssl: new Signale({scope: 'SSL '}), 10 | certbot: new Signale({scope: 'Certbot '}), 11 | import: new Signale({scope: 'Importer '}), 12 | setup: new Signale({scope: 'Setup '}), 13 | ip_ranges: new Signale({scope: 'IP Ranges'}) 14 | }; 15 | -------------------------------------------------------------------------------- /backend/migrate.js: -------------------------------------------------------------------------------- 1 | const db = require('./db'); 2 | const logger = require('./logger').migrate; 3 | 4 | module.exports = { 5 | latest: function () { 6 | return db.migrate.currentVersion() 7 | .then((version) => { 8 | logger.info('Current database version:', version); 9 | return db.migrate.latest({ 10 | tableName: 'migrations', 11 | directory: 'migrations' 12 | }); 13 | }); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /backend/migrations/20180929054513_websockets.js: -------------------------------------------------------------------------------- 1 | const migrate_name = 'websockets'; 2 | const logger = require('../logger').migrate; 3 | 4 | /** 5 | * Migrate 6 | * 7 | * @see http://knexjs.org/#Schema 8 | * 9 | * @param {Object} knex 10 | * @param {Promise} Promise 11 | * @returns {Promise} 12 | */ 13 | exports.up = function (knex/*, Promise*/) { 14 | logger.info('[' + migrate_name + '] Migrating Up...'); 15 | 16 | return knex.schema.table('proxy_host', function (proxy_host) { 17 | proxy_host.integer('allow_websocket_upgrade').notNull().unsigned().defaultTo(0); 18 | }) 19 | .then(() => { 20 | logger.info('[' + migrate_name + '] proxy_host Table altered'); 21 | }); 22 | 23 | }; 24 | 25 | /** 26 | * Undo Migrate 27 | * 28 | * @param {Object} knex 29 | * @param {Promise} Promise 30 | * @returns {Promise} 31 | */ 32 | exports.down = function (knex, Promise) { 33 | logger.warn('[' + migrate_name + '] You can\'t migrate down this one.'); 34 | return Promise.resolve(true); 35 | }; -------------------------------------------------------------------------------- /backend/migrations/20181019052346_forward_host.js: -------------------------------------------------------------------------------- 1 | const migrate_name = 'forward_host'; 2 | const logger = require('../logger').migrate; 3 | 4 | /** 5 | * Migrate 6 | * 7 | * @see http://knexjs.org/#Schema 8 | * 9 | * @param {Object} knex 10 | * @param {Promise} Promise 11 | * @returns {Promise} 12 | */ 13 | exports.up = function (knex/*, Promise*/) { 14 | logger.info('[' + migrate_name + '] Migrating Up...'); 15 | 16 | return knex.schema.table('proxy_host', function (proxy_host) { 17 | proxy_host.renameColumn('forward_ip', 'forward_host'); 18 | }) 19 | .then(() => { 20 | logger.info('[' + migrate_name + '] proxy_host Table altered'); 21 | }); 22 | }; 23 | 24 | /** 25 | * Undo Migrate 26 | * 27 | * @param {Object} knex 28 | * @param {Promise} Promise 29 | * @returns {Promise} 30 | */ 31 | exports.down = function (knex, Promise) { 32 | logger.warn('[' + migrate_name + '] You can\'t migrate down this one.'); 33 | return Promise.resolve(true); 34 | }; -------------------------------------------------------------------------------- /backend/migrations/20181213013211_forward_scheme.js: -------------------------------------------------------------------------------- 1 | const migrate_name = 'forward_scheme'; 2 | const logger = require('../logger').migrate; 3 | 4 | /** 5 | * Migrate 6 | * 7 | * @see http://knexjs.org/#Schema 8 | * 9 | * @param {Object} knex 10 | * @param {Promise} Promise 11 | * @returns {Promise} 12 | */ 13 | exports.up = function (knex/*, Promise*/) { 14 | logger.info('[' + migrate_name + '] Migrating Up...'); 15 | 16 | return knex.schema.table('proxy_host', function (proxy_host) { 17 | proxy_host.string('forward_scheme').notNull().defaultTo('http'); 18 | }) 19 | .then(() => { 20 | logger.info('[' + migrate_name + '] proxy_host Table altered'); 21 | }); 22 | }; 23 | 24 | /** 25 | * Undo Migrate 26 | * 27 | * @param {Object} knex 28 | * @param {Promise} Promise 29 | * @returns {Promise} 30 | */ 31 | exports.down = function (knex, Promise) { 32 | logger.warn('[' + migrate_name + '] You can\'t migrate down this one.'); 33 | return Promise.resolve(true); 34 | }; 35 | -------------------------------------------------------------------------------- /backend/migrations/20190215115310_customlocations.js: -------------------------------------------------------------------------------- 1 | const migrate_name = 'custom_locations'; 2 | const logger = require('../logger').migrate; 3 | 4 | /** 5 | * Migrate 6 | * Extends proxy_host table with locations field 7 | * 8 | * @see http://knexjs.org/#Schema 9 | * 10 | * @param {Object} knex 11 | * @param {Promise} Promise 12 | * @returns {Promise} 13 | */ 14 | exports.up = function (knex/*, Promise*/) { 15 | logger.info('[' + migrate_name + '] Migrating Up...'); 16 | 17 | return knex.schema.table('proxy_host', function (proxy_host) { 18 | proxy_host.json('locations'); 19 | }) 20 | .then(() => { 21 | logger.info('[' + migrate_name + '] proxy_host Table altered'); 22 | }); 23 | }; 24 | 25 | /** 26 | * Undo Migrate 27 | * 28 | * @param {Object} knex 29 | * @param {Promise} Promise 30 | * @returns {Promise} 31 | */ 32 | exports.down = function (knex, Promise) { 33 | logger.warn('[' + migrate_name + '] You can\'t migrate down this one.'); 34 | return Promise.resolve(true); 35 | }; 36 | -------------------------------------------------------------------------------- /backend/migrations/20190227065017_settings.js: -------------------------------------------------------------------------------- 1 | const migrate_name = 'settings'; 2 | const logger = require('../logger').migrate; 3 | 4 | /** 5 | * Migrate 6 | * 7 | * @see http://knexjs.org/#Schema 8 | * 9 | * @param {Object} knex 10 | * @param {Promise} Promise 11 | * @returns {Promise} 12 | */ 13 | exports.up = function (knex/*, Promise*/) { 14 | logger.info('[' + migrate_name + '] Migrating Up...'); 15 | 16 | return knex.schema.createTable('setting', (table) => { 17 | table.string('id').notNull().primary(); 18 | table.string('name', 100).notNull(); 19 | table.string('description', 255).notNull(); 20 | table.string('value', 255).notNull(); 21 | table.json('meta').notNull(); 22 | }) 23 | .then(() => { 24 | logger.info('[' + migrate_name + '] setting Table created'); 25 | }); 26 | }; 27 | 28 | /** 29 | * Undo Migrate 30 | * 31 | * @param {Object} knex 32 | * @param {Promise} Promise 33 | * @returns {Promise} 34 | */ 35 | exports.down = function (knex, Promise) { 36 | logger.warn('[' + migrate_name + '] You can\'t migrate down the initial data.'); 37 | return Promise.resolve(true); 38 | }; 39 | -------------------------------------------------------------------------------- /backend/migrations/20200410143840_access_list_client_fix.js: -------------------------------------------------------------------------------- 1 | const migrate_name = 'access_list_client_fix'; 2 | const logger = require('../logger').migrate; 3 | 4 | /** 5 | * Migrate 6 | * 7 | * @see http://knexjs.org/#Schema 8 | * 9 | * @param {Object} knex 10 | * @param {Promise} Promise 11 | * @returns {Promise} 12 | */ 13 | exports.up = function (knex/*, Promise*/) { 14 | logger.info('[' + migrate_name + '] Migrating Up...'); 15 | 16 | return knex.schema.table('access_list', function (access_list) { 17 | access_list.renameColumn('satify_any', 'satisfy_any'); 18 | }) 19 | .then(() => { 20 | logger.info('[' + migrate_name + '] access_list Table altered'); 21 | }); 22 | }; 23 | 24 | /** 25 | * Undo Migrate 26 | * 27 | * @param {Object} knex 28 | * @param {Promise} Promise 29 | * @returns {Promise} 30 | */ 31 | exports.down = function (knex, Promise) { 32 | logger.warn('[' + migrate_name + '] You can\'t migrate down this one.'); 33 | return Promise.resolve(true); 34 | }; 35 | -------------------------------------------------------------------------------- /backend/migrations/20201014143841_pass_auth.js: -------------------------------------------------------------------------------- 1 | const migrate_name = 'pass_auth'; 2 | const logger = require('../logger').migrate; 3 | 4 | /** 5 | * Migrate 6 | * 7 | * @see http://knexjs.org/#Schema 8 | * 9 | * @param {Object} knex 10 | * @param {Promise} Promise 11 | * @returns {Promise} 12 | */ 13 | exports.up = function (knex/*, Promise*/) { 14 | 15 | logger.info('[' + migrate_name + '] Migrating Up...'); 16 | 17 | return knex.schema.table('access_list', function (access_list) { 18 | access_list.integer('pass_auth').notNull().defaultTo(1); 19 | }) 20 | .then(() => { 21 | logger.info('[' + migrate_name + '] access_list Table altered'); 22 | }); 23 | }; 24 | 25 | /** 26 | * Undo Migrate 27 | * 28 | * @param {Object} knex 29 | * @param {Promise} Promise 30 | * @returns {Promise} 31 | */ 32 | exports.down = function (knex/*, Promise*/) { 33 | logger.info('[' + migrate_name + '] Migrating Down...'); 34 | 35 | return knex.schema.table('access_list', function (access_list) { 36 | access_list.dropColumn('pass_auth'); 37 | }) 38 | .then(() => { 39 | logger.info('[' + migrate_name + '] access_list pass_auth Column dropped'); 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /backend/migrations/20210210154702_redirection_scheme.js: -------------------------------------------------------------------------------- 1 | const migrate_name = 'redirection_scheme'; 2 | const logger = require('../logger').migrate; 3 | 4 | /** 5 | * Migrate 6 | * 7 | * @see http://knexjs.org/#Schema 8 | * 9 | * @param {Object} knex 10 | * @param {Promise} Promise 11 | * @returns {Promise} 12 | */ 13 | exports.up = function (knex/*, Promise*/) { 14 | 15 | logger.info('[' + migrate_name + '] Migrating Up...'); 16 | 17 | return knex.schema.table('redirection_host', (table) => { 18 | table.string('forward_scheme').notNull().defaultTo('$scheme'); 19 | }) 20 | .then(function () { 21 | logger.info('[' + migrate_name + '] redirection_host Table altered'); 22 | }); 23 | }; 24 | 25 | /** 26 | * Undo Migrate 27 | * 28 | * @param {Object} knex 29 | * @param {Promise} Promise 30 | * @returns {Promise} 31 | */ 32 | exports.down = function (knex/*, Promise*/) { 33 | logger.info('[' + migrate_name + '] Migrating Down...'); 34 | 35 | return knex.schema.table('redirection_host', (table) => { 36 | table.dropColumn('forward_scheme'); 37 | }) 38 | .then(function () { 39 | logger.info('[' + migrate_name + '] redirection_host Table altered'); 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /backend/migrations/20210210154703_redirection_status_code.js: -------------------------------------------------------------------------------- 1 | const migrate_name = 'redirection_status_code'; 2 | const logger = require('../logger').migrate; 3 | 4 | /** 5 | * Migrate 6 | * 7 | * @see http://knexjs.org/#Schema 8 | * 9 | * @param {Object} knex 10 | * @param {Promise} Promise 11 | * @returns {Promise} 12 | */ 13 | exports.up = function (knex/*, Promise*/) { 14 | 15 | logger.info('[' + migrate_name + '] Migrating Up...'); 16 | 17 | return knex.schema.table('redirection_host', (table) => { 18 | table.integer('forward_http_code').notNull().unsigned().defaultTo(302); 19 | }) 20 | .then(function () { 21 | logger.info('[' + migrate_name + '] redirection_host Table altered'); 22 | }); 23 | }; 24 | 25 | /** 26 | * Undo Migrate 27 | * 28 | * @param {Object} knex 29 | * @param {Promise} Promise 30 | * @returns {Promise} 31 | */ 32 | exports.down = function (knex/*, Promise*/) { 33 | logger.info('[' + migrate_name + '] Migrating Down...'); 34 | 35 | return knex.schema.table('redirection_host', (table) => { 36 | table.dropColumn('forward_http_code'); 37 | }) 38 | .then(function () { 39 | logger.info('[' + migrate_name + '] redirection_host Table altered'); 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /backend/migrations/20210423103500_stream_domain.js: -------------------------------------------------------------------------------- 1 | const migrate_name = 'stream_domain'; 2 | const logger = require('../logger').migrate; 3 | 4 | /** 5 | * Migrate 6 | * 7 | * @see http://knexjs.org/#Schema 8 | * 9 | * @param {Object} knex 10 | * @param {Promise} Promise 11 | * @returns {Promise} 12 | */ 13 | exports.up = function (knex/*, Promise*/) { 14 | logger.info('[' + migrate_name + '] Migrating Up...'); 15 | 16 | return knex.schema.table('stream', (table) => { 17 | table.renameColumn('forward_ip', 'forwarding_host'); 18 | }) 19 | .then(function () { 20 | logger.info('[' + migrate_name + '] stream Table altered'); 21 | }); 22 | }; 23 | 24 | /** 25 | * Undo Migrate 26 | * 27 | * @param {Object} knex 28 | * @param {Promise} Promise 29 | * @returns {Promise} 30 | */ 31 | exports.down = function (knex/*, Promise*/) { 32 | logger.info('[' + migrate_name + '] Migrating Down...'); 33 | 34 | return knex.schema.table('stream', (table) => { 35 | table.renameColumn('forwarding_host', 'forward_ip'); 36 | }) 37 | .then(function () { 38 | logger.info('[' + migrate_name + '] stream Table altered'); 39 | }); 40 | }; 41 | -------------------------------------------------------------------------------- /backend/migrations/20240427161436_stream_ssl.js: -------------------------------------------------------------------------------- 1 | const migrate_name = 'stream_ssl'; 2 | const logger = require('../logger').migrate; 3 | 4 | /** 5 | * Migrate 6 | * 7 | * @see http://knexjs.org/#Schema 8 | * 9 | * @param {Object} knex 10 | * @returns {Promise} 11 | */ 12 | exports.up = function (knex) { 13 | logger.info('[' + migrate_name + '] Migrating Up...'); 14 | 15 | return knex.schema.table('stream', (table) => { 16 | table.integer('certificate_id').notNull().unsigned().defaultTo(0); 17 | }) 18 | .then(function () { 19 | logger.info('[' + migrate_name + '] stream Table altered'); 20 | }); 21 | }; 22 | 23 | /** 24 | * Undo Migrate 25 | * 26 | * @param {Object} knex 27 | * @returns {Promise} 28 | */ 29 | exports.down = function (knex) { 30 | logger.info('[' + migrate_name + '] Migrating Down...'); 31 | 32 | return knex.schema.table('stream', (table) => { 33 | table.dropColumn('certificate_id'); 34 | }) 35 | .then(function () { 36 | logger.info('[' + migrate_name + '] stream Table altered'); 37 | }); 38 | }; 39 | -------------------------------------------------------------------------------- /backend/models/audit-log.js: -------------------------------------------------------------------------------- 1 | // Objection Docs: 2 | // http://vincit.github.io/objection.js/ 3 | 4 | const db = require('../db'); 5 | const Model = require('objection').Model; 6 | const User = require('./user'); 7 | const now = require('./now_helper'); 8 | 9 | Model.knex(db); 10 | 11 | class AuditLog extends Model { 12 | $beforeInsert () { 13 | this.created_on = now(); 14 | this.modified_on = now(); 15 | 16 | // Default for meta 17 | if (typeof this.meta === 'undefined') { 18 | this.meta = {}; 19 | } 20 | } 21 | 22 | $beforeUpdate () { 23 | this.modified_on = now(); 24 | } 25 | 26 | static get name () { 27 | return 'AuditLog'; 28 | } 29 | 30 | static get tableName () { 31 | return 'audit_log'; 32 | } 33 | 34 | static get jsonAttributes () { 35 | return ['meta']; 36 | } 37 | 38 | static get relationMappings () { 39 | return { 40 | user: { 41 | relation: Model.HasOneRelation, 42 | modelClass: User, 43 | join: { 44 | from: 'audit_log.user_id', 45 | to: 'user.id' 46 | } 47 | } 48 | }; 49 | } 50 | } 51 | 52 | module.exports = AuditLog; 53 | -------------------------------------------------------------------------------- /backend/models/now_helper.js: -------------------------------------------------------------------------------- 1 | const db = require('../db'); 2 | const config = require('../lib/config'); 3 | const Model = require('objection').Model; 4 | 5 | Model.knex(db); 6 | 7 | module.exports = function () { 8 | if (config.isSqlite()) { 9 | // eslint-disable-next-line 10 | return Model.raw("datetime('now','localtime')"); 11 | } 12 | return Model.raw('NOW()'); 13 | }; 14 | -------------------------------------------------------------------------------- /backend/models/setting.js: -------------------------------------------------------------------------------- 1 | // Objection Docs: 2 | // http://vincit.github.io/objection.js/ 3 | 4 | const db = require('../db'); 5 | const Model = require('objection').Model; 6 | 7 | Model.knex(db); 8 | 9 | class Setting extends Model { 10 | $beforeInsert () { 11 | // Default for meta 12 | if (typeof this.meta === 'undefined') { 13 | this.meta = {}; 14 | } 15 | } 16 | 17 | static get name () { 18 | return 'Setting'; 19 | } 20 | 21 | static get tableName () { 22 | return 'setting'; 23 | } 24 | 25 | static get jsonAttributes () { 26 | return ['meta']; 27 | } 28 | } 29 | 30 | module.exports = Setting; 31 | -------------------------------------------------------------------------------- /backend/models/user_permission.js: -------------------------------------------------------------------------------- 1 | // Objection Docs: 2 | // http://vincit.github.io/objection.js/ 3 | 4 | const db = require('../db'); 5 | const Model = require('objection').Model; 6 | const now = require('./now_helper'); 7 | 8 | Model.knex(db); 9 | 10 | class UserPermission extends Model { 11 | $beforeInsert () { 12 | this.created_on = now(); 13 | this.modified_on = now(); 14 | } 15 | 16 | $beforeUpdate () { 17 | this.modified_on = now(); 18 | } 19 | 20 | static get name () { 21 | return 'UserPermission'; 22 | } 23 | 24 | static get tableName () { 25 | return 'user_permission'; 26 | } 27 | } 28 | 29 | module.exports = UserPermission; 30 | -------------------------------------------------------------------------------- /backend/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "verbose": false, 3 | "ignore": [ 4 | "data" 5 | ], 6 | "ext": "js json ejs" 7 | } 8 | -------------------------------------------------------------------------------- /backend/routes/reports.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const jwtdecode = require('../lib/express/jwt-decode'); 3 | const internalReport = require('../internal/report'); 4 | 5 | let router = express.Router({ 6 | caseSensitive: true, 7 | strict: true, 8 | mergeParams: true 9 | }); 10 | 11 | router 12 | .route('/hosts') 13 | .options((_, res) => { 14 | res.sendStatus(204); 15 | }) 16 | 17 | /** 18 | * GET /reports/hosts 19 | */ 20 | .get(jwtdecode(), (_, res, next) => { 21 | internalReport.getHostsReport(res.locals.access) 22 | .then((data) => { 23 | res.status(200) 24 | .send(data); 25 | }) 26 | .catch(next); 27 | }); 28 | 29 | module.exports = router; 30 | -------------------------------------------------------------------------------- /backend/routes/schema.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const schema = require('../schema'); 3 | const PACKAGE = require('../package.json'); 4 | 5 | const router = express.Router({ 6 | caseSensitive: true, 7 | strict: true, 8 | mergeParams: true 9 | }); 10 | 11 | router 12 | .route('/') 13 | .options((_, res) => { 14 | res.sendStatus(204); 15 | }) 16 | 17 | /** 18 | * GET /schema 19 | */ 20 | .get(async (req, res) => { 21 | let swaggerJSON = await schema.getCompiledSchema(); 22 | 23 | let proto = req.protocol; 24 | if (typeof req.headers['x-forwarded-proto'] !== 'undefined' && req.headers['x-forwarded-proto']) { 25 | proto = req.headers['x-forwarded-proto']; 26 | } 27 | 28 | let origin = proto + '://' + req.hostname; 29 | if (typeof req.headers.origin !== 'undefined' && req.headers.origin) { 30 | origin = req.headers.origin; 31 | } 32 | 33 | swaggerJSON.info.version = PACKAGE.version; 34 | swaggerJSON.servers[0].url = origin + '/api'; 35 | res.status(200).send(swaggerJSON); 36 | }); 37 | 38 | module.exports = router; 39 | -------------------------------------------------------------------------------- /backend/schema/components/audit-log-object.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "description": "Audit Log object", 4 | "required": ["id", "created_on", "modified_on", "user_id", "object_type", "object_id", "action", "meta"], 5 | "additionalProperties": false, 6 | "properties": { 7 | "id": { 8 | "$ref": "../common.json#/properties/id" 9 | }, 10 | "created_on": { 11 | "$ref": "../common.json#/properties/created_on" 12 | }, 13 | "modified_on": { 14 | "$ref": "../common.json#/properties/modified_on" 15 | }, 16 | "user_id": { 17 | "$ref": "../common.json#/properties/user_id" 18 | }, 19 | "object_type": { 20 | "type": "string" 21 | }, 22 | "object_id": { 23 | "$ref": "../common.json#/properties/id" 24 | }, 25 | "action": { 26 | "type": "string" 27 | }, 28 | "meta": { 29 | "type": "object" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /backend/schema/components/certificate-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "array", 3 | "description": "Certificates list", 4 | "items": { 5 | "$ref": "./certificate-object.json" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /backend/schema/components/dead-host-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "array", 3 | "description": "404 Hosts list", 4 | "items": { 5 | "$ref": "./dead-host-object.json" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /backend/schema/components/error-object.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "description": "Error object", 4 | "additionalProperties": false, 5 | "required": ["code", "message"], 6 | "properties": { 7 | "code": { 8 | "type": "integer" 9 | }, 10 | "message": { 11 | "type": "string" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /backend/schema/components/error.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "description": "Error", 4 | "properties": { 5 | "error": { 6 | "$ref": "./error-object.json" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /backend/schema/components/health-object.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "description": "Health object", 4 | "additionalProperties": false, 5 | "required": ["status", "version"], 6 | "properties": { 7 | "status": { 8 | "type": "string", 9 | "description": "Healthy", 10 | "example": "OK" 11 | }, 12 | "version": { 13 | "type": "object", 14 | "description": "The version object", 15 | "example": { 16 | "major": 2, 17 | "minor": 0, 18 | "revision": 0 19 | }, 20 | "additionalProperties": false, 21 | "required": ["major", "minor", "revision"], 22 | "properties": { 23 | "major": { 24 | "type": "integer", 25 | "minimum": 0 26 | }, 27 | "minor": { 28 | "type": "integer", 29 | "minimum": 0 30 | }, 31 | "revision": { 32 | "type": "integer", 33 | "minimum": 0 34 | } 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /backend/schema/components/permission-object.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "minProperties": 1, 4 | "properties": { 5 | "visibility": { 6 | "type": "string", 7 | "description": "Visibility Type", 8 | "enum": ["all", "user"] 9 | }, 10 | "access_lists": { 11 | "type": "string", 12 | "description": "Access Lists Permissions", 13 | "enum": ["hidden", "view", "manage"] 14 | }, 15 | "dead_hosts": { 16 | "type": "string", 17 | "description": "404 Hosts Permissions", 18 | "enum": ["hidden", "view", "manage"] 19 | }, 20 | "proxy_hosts": { 21 | "type": "string", 22 | "description": "Proxy Hosts Permissions", 23 | "enum": ["hidden", "view", "manage"] 24 | }, 25 | "redirection_hosts": { 26 | "type": "string", 27 | "description": "Redirection Permissions", 28 | "enum": ["hidden", "view", "manage"] 29 | }, 30 | "streams": { 31 | "type": "string", 32 | "description": "Streams Permissions", 33 | "enum": ["hidden", "view", "manage"] 34 | }, 35 | "certificates": { 36 | "type": "string", 37 | "description": "Certificates Permissions", 38 | "enum": ["hidden", "view", "manage"] 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /backend/schema/components/proxy-host-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "array", 3 | "description": "Proxy Hosts list", 4 | "items": { 5 | "$ref": "./proxy-host-object.json" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /backend/schema/components/redirection-host-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "array", 3 | "description": "Redirection Hosts list", 4 | "items": { 5 | "$ref": "./redirection-host-object.json" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /backend/schema/components/security-schemes.json: -------------------------------------------------------------------------------- 1 | { 2 | "BearerAuth": { 3 | "type": "http", 4 | "scheme": "bearer" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /backend/schema/components/setting-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "array", 3 | "description": "Setting list", 4 | "items": { 5 | "$ref": "./setting-object.json" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /backend/schema/components/stream-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "array", 3 | "description": "Proxy Hosts list", 4 | "items": { 5 | "$ref": "./proxy-host-object.json" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /backend/schema/components/token-object.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "description": "Token object", 4 | "required": ["expires", "token"], 5 | "additionalProperties": false, 6 | "properties": { 7 | "expires": { 8 | "description": "Token Expiry ISO Time String", 9 | "example": "2025-02-04T20:40:46.340Z", 10 | "type": "string" 11 | }, 12 | "token": { 13 | "description": "JWT Token", 14 | "example": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4", 15 | "type": "string" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /backend/schema/components/user-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "array", 3 | "description": "User list", 4 | "items": { 5 | "$ref": "./user-object.json" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /backend/schema/paths/get.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationId": "health", 3 | "summary": "Returns the API health status", 4 | "tags": ["Public"], 5 | "responses": { 6 | "200": { 7 | "description": "200 response", 8 | "content": { 9 | "application/json": { 10 | "examples": { 11 | "default": { 12 | "value": { 13 | "status": "OK", 14 | "version": { 15 | "major": 2, 16 | "minor": 1, 17 | "revision": 0 18 | } 19 | } 20 | } 21 | }, 22 | "schema": { 23 | "$ref": "../components/health-object.json" 24 | } 25 | } 26 | } 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /backend/schema/paths/nginx/access-lists/get.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationId": "getAccessLists", 3 | "summary": "Get all access lists", 4 | "tags": ["Access Lists"], 5 | "security": [ 6 | { 7 | "BearerAuth": ["access_lists"] 8 | } 9 | ], 10 | "parameters": [ 11 | { 12 | "in": "query", 13 | "name": "expand", 14 | "description": "Expansions", 15 | "schema": { 16 | "type": "string", 17 | "enum": ["owner", "items", "clients", "proxy_hosts"] 18 | } 19 | } 20 | ], 21 | "responses": { 22 | "200": { 23 | "description": "200 response", 24 | "content": { 25 | "application/json": { 26 | "examples": { 27 | "default": { 28 | "value": [ 29 | { 30 | "id": 1, 31 | "created_on": "2024-10-08T22:15:40.000Z", 32 | "modified_on": "2024-10-08T22:15:40.000Z", 33 | "owner_user_id": 1, 34 | "name": "test1234", 35 | "meta": {}, 36 | "satisfy_any": true, 37 | "pass_auth": false, 38 | "proxy_host_count": 0 39 | } 40 | ] 41 | } 42 | }, 43 | "schema": { 44 | "$ref": "../../../components/access-list-object.json" 45 | } 46 | } 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /backend/schema/paths/nginx/access-lists/listID/delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationId": "deleteAccessList", 3 | "summary": "Delete a Access List", 4 | "tags": ["Access Lists"], 5 | "security": [ 6 | { 7 | "BearerAuth": ["access_lists"] 8 | } 9 | ], 10 | "parameters": [ 11 | { 12 | "in": "path", 13 | "name": "listID", 14 | "schema": { 15 | "type": "integer", 16 | "minimum": 1 17 | }, 18 | "required": true, 19 | "example": 2 20 | } 21 | ], 22 | "responses": { 23 | "200": { 24 | "description": "200 response", 25 | "content": { 26 | "application/json": { 27 | "examples": { 28 | "default": { 29 | "value": true 30 | } 31 | }, 32 | "schema": { 33 | "type": "boolean" 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /backend/schema/paths/nginx/access-lists/listID/get.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationId": "getAccessList", 3 | "summary": "Get a access List", 4 | "tags": ["Access Lists"], 5 | "security": [ 6 | { 7 | "BearerAuth": ["access_lists"] 8 | } 9 | ], 10 | "parameters": [ 11 | { 12 | "in": "path", 13 | "name": "listID", 14 | "schema": { 15 | "type": "integer", 16 | "minimum": 1 17 | }, 18 | "required": true, 19 | "example": 1 20 | } 21 | ], 22 | "responses": { 23 | "200": { 24 | "description": "200 response", 25 | "content": { 26 | "application/json": { 27 | "examples": { 28 | "default": { 29 | "value": { 30 | "id": 1, 31 | "created_on": "2020-01-30T09:36:08.000Z", 32 | "modified_on": "2020-01-30T09:41:04.000Z", 33 | "is_disabled": false, 34 | "email": "jc@jc21.com", 35 | "name": "Jamie Curnow", 36 | "nickname": "James", 37 | "avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm", 38 | "roles": ["admin"] 39 | } 40 | } 41 | }, 42 | "schema": { 43 | "$ref": "../../../../components/access-list-object.json" 44 | } 45 | } 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /backend/schema/paths/nginx/certificates/certID/delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationId": "deleteCertificate", 3 | "summary": "Delete a Certificate", 4 | "tags": ["Certificates"], 5 | "security": [ 6 | { 7 | "BearerAuth": ["certificates"] 8 | } 9 | ], 10 | "parameters": [ 11 | { 12 | "in": "path", 13 | "name": "certID", 14 | "schema": { 15 | "type": "integer", 16 | "minimum": 1 17 | }, 18 | "required": true, 19 | "example": 2 20 | } 21 | ], 22 | "responses": { 23 | "200": { 24 | "description": "200 response", 25 | "content": { 26 | "application/json": { 27 | "examples": { 28 | "default": { 29 | "value": true 30 | } 31 | }, 32 | "schema": { 33 | "type": "boolean" 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /backend/schema/paths/nginx/certificates/certID/download/get.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationId": "downloadCertificate", 3 | "summary": "Downloads a Certificate", 4 | "tags": ["Certificates"], 5 | "security": [ 6 | { 7 | "BearerAuth": ["certificates"] 8 | } 9 | ], 10 | "parameters": [ 11 | { 12 | "in": "path", 13 | "name": "certID", 14 | "schema": { 15 | "type": "integer", 16 | "minimum": 1 17 | }, 18 | "required": true, 19 | "example": 1 20 | } 21 | ], 22 | "responses": { 23 | "200": { 24 | "description": "200 response", 25 | "content": { 26 | "application/zip": { 27 | "schema": { 28 | "type": "string", 29 | "format": "binary" 30 | } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /backend/schema/paths/nginx/certificates/test-http/get.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationId": "testHttpReach", 3 | "summary": "Test HTTP Reachability", 4 | "tags": ["Certificates"], 5 | "security": [ 6 | { 7 | "BearerAuth": ["certificates"] 8 | } 9 | ], 10 | "parameters": [ 11 | { 12 | "in": "query", 13 | "name": "domains", 14 | "description": "Expansions", 15 | "required": true, 16 | "schema": { 17 | "type": "string", 18 | "example": "[\"test.example.ord\",\"test.example.com\",\"nonexistent.example.com\"]" 19 | } 20 | } 21 | ], 22 | "responses": { 23 | "200": { 24 | "description": "200 response", 25 | "content": { 26 | "application/json": { 27 | "examples": { 28 | "default": { 29 | "value": { 30 | "test.example.org": "ok", 31 | "test.example.com": "other:Invalid domain or IP", 32 | "nonexistent.example.com": "404" 33 | } 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /backend/schema/paths/nginx/dead-hosts/hostID/delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationId": "deleteDeadHost", 3 | "summary": "Delete a 404 Host", 4 | "tags": ["404 Hosts"], 5 | "security": [ 6 | { 7 | "BearerAuth": ["dead_hosts"] 8 | } 9 | ], 10 | "parameters": [ 11 | { 12 | "in": "path", 13 | "name": "hostID", 14 | "schema": { 15 | "type": "integer", 16 | "minimum": 1 17 | }, 18 | "required": true, 19 | "example": 2 20 | } 21 | ], 22 | "responses": { 23 | "200": { 24 | "description": "200 response", 25 | "content": { 26 | "application/json": { 27 | "examples": { 28 | "default": { 29 | "value": true 30 | } 31 | }, 32 | "schema": { 33 | "type": "boolean" 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /backend/schema/paths/nginx/dead-hosts/hostID/disable/post.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationId": "disableDeadHost", 3 | "summary": "Disable a 404 Host", 4 | "tags": ["404 Hosts"], 5 | "security": [ 6 | { 7 | "BearerAuth": ["dead_hosts"] 8 | } 9 | ], 10 | "parameters": [ 11 | { 12 | "in": "path", 13 | "name": "hostID", 14 | "schema": { 15 | "type": "integer", 16 | "minimum": 1 17 | }, 18 | "required": true, 19 | "example": 2 20 | } 21 | ], 22 | "responses": { 23 | "200": { 24 | "description": "200 response", 25 | "content": { 26 | "application/json": { 27 | "examples": { 28 | "default": { 29 | "value": true 30 | } 31 | }, 32 | "schema": { 33 | "type": "boolean" 34 | } 35 | } 36 | } 37 | }, 38 | "400": { 39 | "description": "400 response", 40 | "content": { 41 | "application/json": { 42 | "examples": { 43 | "default": { 44 | "value": { 45 | "error": { 46 | "code": 400, 47 | "message": "Host is already disabled" 48 | } 49 | } 50 | } 51 | }, 52 | "schema": { 53 | "$ref": "../../../../../components/error.json" 54 | } 55 | } 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /backend/schema/paths/nginx/dead-hosts/hostID/enable/post.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationId": "enableDeadHost", 3 | "summary": "Enable a 404 Host", 4 | "tags": ["404 Hosts"], 5 | "security": [ 6 | { 7 | "BearerAuth": ["dead_hosts"] 8 | } 9 | ], 10 | "parameters": [ 11 | { 12 | "in": "path", 13 | "name": "hostID", 14 | "schema": { 15 | "type": "integer", 16 | "minimum": 1 17 | }, 18 | "required": true, 19 | "example": 2 20 | } 21 | ], 22 | "responses": { 23 | "200": { 24 | "description": "200 response", 25 | "content": { 26 | "application/json": { 27 | "examples": { 28 | "default": { 29 | "value": true 30 | } 31 | }, 32 | "schema": { 33 | "type": "boolean" 34 | } 35 | } 36 | } 37 | }, 38 | "400": { 39 | "description": "400 response", 40 | "content": { 41 | "application/json": { 42 | "examples": { 43 | "default": { 44 | "value": { 45 | "error": { 46 | "code": 400, 47 | "message": "Host is already enabled" 48 | } 49 | } 50 | } 51 | }, 52 | "schema": { 53 | "$ref": "../../../../../components/error.json" 54 | } 55 | } 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /backend/schema/paths/nginx/proxy-hosts/hostID/delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationId": "deleteProxyHost", 3 | "summary": "Delete a Proxy Host", 4 | "tags": ["Proxy Hosts"], 5 | "security": [ 6 | { 7 | "BearerAuth": ["proxy_hosts"] 8 | } 9 | ], 10 | "parameters": [ 11 | { 12 | "in": "path", 13 | "name": "hostID", 14 | "schema": { 15 | "type": "integer", 16 | "minimum": 1 17 | }, 18 | "required": true, 19 | "example": 2 20 | } 21 | ], 22 | "responses": { 23 | "200": { 24 | "description": "200 response", 25 | "content": { 26 | "application/json": { 27 | "examples": { 28 | "default": { 29 | "value": true 30 | } 31 | }, 32 | "schema": { 33 | "type": "boolean" 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /backend/schema/paths/nginx/redirection-hosts/hostID/delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationId": "deleteRedirectionHost", 3 | "summary": "Delete a Redirection Host", 4 | "tags": ["Redirection Hosts"], 5 | "security": [ 6 | { 7 | "BearerAuth": ["redirection_hosts"] 8 | } 9 | ], 10 | "parameters": [ 11 | { 12 | "in": "path", 13 | "name": "hostID", 14 | "schema": { 15 | "type": "integer", 16 | "minimum": 1 17 | }, 18 | "required": true, 19 | "example": 2 20 | } 21 | ], 22 | "responses": { 23 | "200": { 24 | "description": "200 response", 25 | "content": { 26 | "application/json": { 27 | "examples": { 28 | "default": { 29 | "value": true 30 | } 31 | }, 32 | "schema": { 33 | "type": "boolean" 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /backend/schema/paths/nginx/streams/streamID/delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationId": "deleteStream", 3 | "summary": "Delete a Stream", 4 | "tags": ["Streams"], 5 | "security": [ 6 | { 7 | "BearerAuth": ["streams"] 8 | } 9 | ], 10 | "parameters": [ 11 | { 12 | "in": "path", 13 | "name": "streamID", 14 | "schema": { 15 | "type": "integer", 16 | "minimum": 1 17 | }, 18 | "required": true, 19 | "example": 2 20 | } 21 | ], 22 | "responses": { 23 | "200": { 24 | "description": "200 response", 25 | "content": { 26 | "application/json": { 27 | "examples": { 28 | "default": { 29 | "value": true 30 | } 31 | }, 32 | "schema": { 33 | "type": "boolean" 34 | } 35 | } 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /backend/schema/paths/nginx/streams/streamID/disable/post.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationId": "disableStream", 3 | "summary": "Disable a Stream", 4 | "tags": ["Streams"], 5 | "security": [ 6 | { 7 | "BearerAuth": ["streams"] 8 | } 9 | ], 10 | "parameters": [ 11 | { 12 | "in": "path", 13 | "name": "streamID", 14 | "schema": { 15 | "type": "integer", 16 | "minimum": 1 17 | }, 18 | "required": true, 19 | "example": 2 20 | } 21 | ], 22 | "responses": { 23 | "200": { 24 | "description": "200 response", 25 | "content": { 26 | "application/json": { 27 | "examples": { 28 | "default": { 29 | "value": true 30 | } 31 | }, 32 | "schema": { 33 | "type": "boolean" 34 | } 35 | } 36 | } 37 | }, 38 | "400": { 39 | "description": "400 response", 40 | "content": { 41 | "application/json": { 42 | "examples": { 43 | "default": { 44 | "value": { 45 | "error": { 46 | "code": 400, 47 | "message": "Host is already disabled" 48 | } 49 | } 50 | } 51 | }, 52 | "schema": { 53 | "$ref": "../../../../../components/error.json" 54 | } 55 | } 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /backend/schema/paths/nginx/streams/streamID/enable/post.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationId": "enableStream", 3 | "summary": "Enable a Stream", 4 | "tags": ["Streams"], 5 | "security": [ 6 | { 7 | "BearerAuth": ["streams"] 8 | } 9 | ], 10 | "parameters": [ 11 | { 12 | "in": "path", 13 | "name": "streamID", 14 | "schema": { 15 | "type": "integer", 16 | "minimum": 1 17 | }, 18 | "required": true, 19 | "example": 2 20 | } 21 | ], 22 | "responses": { 23 | "200": { 24 | "description": "200 response", 25 | "content": { 26 | "application/json": { 27 | "examples": { 28 | "default": { 29 | "value": true 30 | } 31 | }, 32 | "schema": { 33 | "type": "boolean" 34 | } 35 | } 36 | } 37 | }, 38 | "400": { 39 | "description": "400 response", 40 | "content": { 41 | "application/json": { 42 | "examples": { 43 | "default": { 44 | "value": { 45 | "error": { 46 | "code": 400, 47 | "message": "Host is already enabled" 48 | } 49 | } 50 | } 51 | }, 52 | "schema": { 53 | "$ref": "../../../../../components/error.json" 54 | } 55 | } 56 | } 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /backend/schema/paths/reports/hosts/get.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationId": "reportsHosts", 3 | "summary": "Report on Host Statistics", 4 | "tags": ["Reports"], 5 | "security": [ 6 | { 7 | "BearerAuth": ["reports"] 8 | } 9 | ], 10 | "responses": { 11 | "200": { 12 | "description": "200 response", 13 | "content": { 14 | "application/json": { 15 | "examples": { 16 | "default": { 17 | "value": { 18 | "proxy": 20, 19 | "redirection": 1, 20 | "stream": 0, 21 | "dead": 1 22 | } 23 | } 24 | }, 25 | "schema": { 26 | "type": "object", 27 | "properties": { 28 | "proxy": { 29 | "type": "integer", 30 | "description": "Proxy Hosts Count" 31 | }, 32 | "redirection": { 33 | "type": "integer", 34 | "description": "Redirection Hosts Count" 35 | }, 36 | "stream": { 37 | "type": "integer", 38 | "description": "Streams Count" 39 | }, 40 | "dead": { 41 | "type": "integer", 42 | "description": "404 Hosts Count" 43 | } 44 | } 45 | } 46 | } 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /backend/schema/paths/schema/get.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationId": "schema", 3 | "summary": "Returns this swagger API schema", 4 | "tags": ["Public"], 5 | "responses": { 6 | "200": { 7 | "description": "200 response" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /backend/schema/paths/settings/get.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationId": "getSettings", 3 | "summary": "Get all settings", 4 | "tags": ["Settings"], 5 | "security": [ 6 | { 7 | "BearerAuth": ["settings"] 8 | } 9 | ], 10 | "responses": { 11 | "200": { 12 | "description": "200 response", 13 | "content": { 14 | "application/json": { 15 | "examples": { 16 | "default": { 17 | "value": [ 18 | { 19 | "id": "default-site", 20 | "name": "Default Site", 21 | "description": "What to show when Nginx is hit with an unknown Host", 22 | "value": "congratulations", 23 | "meta": {} 24 | } 25 | ] 26 | } 27 | }, 28 | "schema": { 29 | "$ref": "../../components/setting-list.json" 30 | } 31 | } 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /backend/schema/paths/settings/settingID/get.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationId": "getSetting", 3 | "summary": "Get a setting", 4 | "tags": ["Settings"], 5 | "security": [ 6 | { 7 | "BearerAuth": ["settings"] 8 | } 9 | ], 10 | "parameters": [ 11 | { 12 | "in": "path", 13 | "name": "settingID", 14 | "schema": { 15 | "type": "string", 16 | "minLength": 1 17 | }, 18 | "required": true, 19 | "description": "Setting ID", 20 | "example": "default-site" 21 | } 22 | ], 23 | "responses": { 24 | "200": { 25 | "description": "200 response", 26 | "content": { 27 | "application/json": { 28 | "examples": { 29 | "default": { 30 | "value": { 31 | "id": "default-site", 32 | "name": "Default Site", 33 | "description": "What to show when Nginx is hit with an unknown Host", 34 | "value": "congratulations", 35 | "meta": {} 36 | } 37 | } 38 | }, 39 | "schema": { 40 | "$ref": "../../../components/setting-object.json" 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /backend/schema/paths/tokens/get.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationId": "refreshToken", 3 | "summary": "Refresh your access token", 4 | "tags": ["Tokens"], 5 | "security": [ 6 | { 7 | "BearerAuth": ["tokens"] 8 | } 9 | ], 10 | "responses": { 11 | "200": { 12 | "description": "200 response", 13 | "content": { 14 | "application/json": { 15 | "examples": { 16 | "default": { 17 | "value": { 18 | "expires": "2025-02-04T20:40:46.340Z", 19 | "token": "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.ey...xaHKYr3Kk6MvkUjcC4" 20 | } 21 | } 22 | }, 23 | "schema": { 24 | "$ref": "../../components/token-object.json" 25 | } 26 | } 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /backend/schema/paths/users/userID/delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationId": "deleteUser", 3 | "summary": "Delete a User", 4 | "tags": ["Users"], 5 | "security": [ 6 | { 7 | "BearerAuth": ["users"] 8 | } 9 | ], 10 | "parameters": [ 11 | { 12 | "in": "path", 13 | "name": "userID", 14 | "schema": { 15 | "type": "integer", 16 | "minimum": 1 17 | }, 18 | "required": true, 19 | "description": "User ID", 20 | "example": 2 21 | } 22 | ], 23 | "responses": { 24 | "200": { 25 | "description": "200 response", 26 | "content": { 27 | "application/json": { 28 | "examples": { 29 | "default": { 30 | "value": true 31 | } 32 | }, 33 | "schema": { 34 | "type": "boolean" 35 | } 36 | } 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /backend/schema/paths/users/userID/permissions/put.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationId": "updateUserPermissions", 3 | "summary": "Update a User's Permissions", 4 | "tags": ["Users"], 5 | "security": [ 6 | { 7 | "BearerAuth": ["users"] 8 | } 9 | ], 10 | "parameters": [ 11 | { 12 | "in": "path", 13 | "name": "userID", 14 | "schema": { 15 | "type": "integer", 16 | "minimum": 1 17 | }, 18 | "required": true, 19 | "description": "User ID", 20 | "example": 2 21 | } 22 | ], 23 | "requestBody": { 24 | "description": "Permissions Payload", 25 | "required": true, 26 | "content": { 27 | "application/json": { 28 | "schema": { 29 | "$ref": "../../../../components/permission-object.json" 30 | } 31 | } 32 | } 33 | }, 34 | "responses": { 35 | "200": { 36 | "description": "200 response", 37 | "content": { 38 | "application/json": { 39 | "examples": { 40 | "default": { 41 | "value": true 42 | } 43 | }, 44 | "schema": { 45 | "type": "boolean" 46 | } 47 | } 48 | } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /backend/templates/_access.conf: -------------------------------------------------------------------------------- 1 | {% if access_list_id > 0 %} 2 | {% if access_list.items.length > 0 %} 3 | # Authorization 4 | auth_basic "Authorization required"; 5 | auth_basic_user_file /data/access/{{ access_list_id }}; 6 | 7 | {% if access_list.pass_auth == 0 or access_list.pass_auth == true %} 8 | proxy_set_header Authorization ""; 9 | {% endif %} 10 | 11 | {% endif %} 12 | 13 | # Access Rules: {{ access_list.clients | size }} total 14 | {% for client in access_list.clients %} 15 | {{client | nginxAccessRule}} 16 | {% endfor %} 17 | deny all; 18 | 19 | # Access checks must... 20 | {% if access_list.satisfy_any == 1 or access_list.satisfy_any == true %} 21 | satisfy any; 22 | {% else %} 23 | satisfy all; 24 | {% endif %} 25 | {% endif %} 26 | -------------------------------------------------------------------------------- /backend/templates/_assets.conf: -------------------------------------------------------------------------------- 1 | {% if caching_enabled == 1 or caching_enabled == true -%} 2 | # Asset Caching 3 | include conf.d/include/assets.conf; 4 | {% endif %} -------------------------------------------------------------------------------- /backend/templates/_certificates.conf: -------------------------------------------------------------------------------- 1 | {% if certificate and certificate_id > 0 -%} 2 | {% if certificate.provider == "letsencrypt" %} 3 | # Let's Encrypt SSL 4 | include conf.d/include/letsencrypt-acme-challenge.conf; 5 | include conf.d/include/ssl-cache.conf; 6 | include conf.d/include/ssl-ciphers.conf; 7 | ssl_certificate /etc/letsencrypt/live/npm-{{ certificate_id }}/fullchain.pem; 8 | ssl_certificate_key /etc/letsencrypt/live/npm-{{ certificate_id }}/privkey.pem; 9 | {% else %} 10 | # Custom SSL 11 | ssl_certificate /data/custom_ssl/npm-{{ certificate_id }}/fullchain.pem; 12 | ssl_certificate_key /data/custom_ssl/npm-{{ certificate_id }}/privkey.pem; 13 | {% endif %} 14 | {% endif %} 15 | 16 | -------------------------------------------------------------------------------- /backend/templates/_certificates_stream.conf: -------------------------------------------------------------------------------- 1 | {% if certificate and certificate_id > 0 %} 2 | {% if certificate.provider == "letsencrypt" %} 3 | # Let's Encrypt SSL 4 | include conf.d/include/ssl-cache-stream.conf; 5 | include conf.d/include/ssl-ciphers.conf; 6 | ssl_certificate /etc/letsencrypt/live/npm-{{ certificate_id }}/fullchain.pem; 7 | ssl_certificate_key /etc/letsencrypt/live/npm-{{ certificate_id }}/privkey.pem; 8 | {%- else %} 9 | # Custom SSL 10 | ssl_certificate /data/custom_ssl/npm-{{ certificate_id }}/fullchain.pem; 11 | ssl_certificate_key /data/custom_ssl/npm-{{ certificate_id }}/privkey.pem; 12 | {%- endif -%} 13 | {%- endif -%} 14 | -------------------------------------------------------------------------------- /backend/templates/_exploits.conf: -------------------------------------------------------------------------------- 1 | {% if block_exploits == 1 or block_exploits == true %} 2 | # Block Exploits 3 | include conf.d/include/block-exploits.conf; 4 | {% endif %} -------------------------------------------------------------------------------- /backend/templates/_forced_ssl.conf: -------------------------------------------------------------------------------- 1 | {% if certificate and certificate_id > 0 -%} 2 | {% if ssl_forced == 1 or ssl_forced == true %} 3 | # Force SSL 4 | include conf.d/include/force-ssl.conf; 5 | {% endif %} 6 | {% endif %} -------------------------------------------------------------------------------- /backend/templates/_header_comment.conf: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------ 2 | # {{ domain_names | join: ", " }} 3 | # ------------------------------------------------------------ -------------------------------------------------------------------------------- /backend/templates/_hsts.conf: -------------------------------------------------------------------------------- 1 | {% if certificate and certificate_id > 0 -%} 2 | {% if ssl_forced == 1 or ssl_forced == true %} 3 | {% if hsts_enabled == 1 or hsts_enabled == true %} 4 | # HSTS (ngx_http_headers_module is required) (63072000 seconds = 2 years) 5 | add_header Strict-Transport-Security $hsts_header always; 6 | {% endif %} 7 | {% endif %} 8 | {% endif %} 9 | -------------------------------------------------------------------------------- /backend/templates/_hsts_map.conf: -------------------------------------------------------------------------------- 1 | map $scheme $hsts_header { 2 | https "max-age=63072000;{% if hsts_subdomains == 1 or hsts_subdomains == true -%} includeSubDomains;{% endif %} preload"; 3 | } -------------------------------------------------------------------------------- /backend/templates/_listen.conf: -------------------------------------------------------------------------------- 1 | listen 80; 2 | {% if ipv6 -%} 3 | listen [::]:80; 4 | {% else -%} 5 | #listen [::]:80; 6 | {% endif %} 7 | {% if certificate -%} 8 | listen 443 ssl; 9 | {% if ipv6 -%} 10 | listen [::]:443 ssl; 11 | {% else -%} 12 | #listen [::]:443; 13 | {% endif %} 14 | {% endif %} 15 | server_name {{ domain_names | join: " " }}; 16 | {% if http2_support == 1 or http2_support == true %} 17 | http2 on; 18 | {% else -%} 19 | http2 off; 20 | {% endif %} -------------------------------------------------------------------------------- /backend/templates/_location.conf: -------------------------------------------------------------------------------- 1 | location {{ path }} { 2 | {{ advanced_config }} 3 | 4 | proxy_set_header Host $host; 5 | proxy_set_header X-Forwarded-Scheme $scheme; 6 | proxy_set_header X-Forwarded-Proto $scheme; 7 | proxy_set_header X-Forwarded-For $remote_addr; 8 | proxy_set_header X-Real-IP $remote_addr; 9 | 10 | proxy_pass {{ forward_scheme }}://{{ forward_host }}:{{ forward_port }}{{ forward_path }}; 11 | 12 | {% include "_access.conf" %} 13 | {% include "_assets.conf" %} 14 | {% include "_exploits.conf" %} 15 | {% include "_forced_ssl.conf" %} 16 | {% include "_hsts.conf" %} 17 | 18 | {% if allow_websocket_upgrade == 1 or allow_websocket_upgrade == true %} 19 | proxy_set_header Upgrade $http_upgrade; 20 | proxy_set_header Connection $http_connection; 21 | proxy_http_version 1.1; 22 | {% endif %} 23 | } 24 | 25 | -------------------------------------------------------------------------------- /backend/templates/dead_host.conf: -------------------------------------------------------------------------------- 1 | {% include "_header_comment.conf" %} 2 | 3 | {% if enabled %} 4 | 5 | {% include "_hsts_map.conf" %} 6 | 7 | server { 8 | {% include "_listen.conf" %} 9 | {% include "_certificates.conf" %} 10 | {% include "_hsts.conf" %} 11 | {% include "_forced_ssl.conf" %} 12 | 13 | access_log /data/logs/dead-host-{{ id }}_access.log standard; 14 | error_log /data/logs/dead-host-{{ id }}_error.log warn; 15 | 16 | {{ advanced_config }} 17 | 18 | {% if use_default_location %} 19 | location / { 20 | {% include "_hsts.conf" %} 21 | return 404; 22 | } 23 | {% endif %} 24 | 25 | # Custom 26 | include /data/nginx/custom/server_dead[.]conf; 27 | } 28 | {% endif %} 29 | -------------------------------------------------------------------------------- /backend/templates/default.conf: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------ 2 | # Default Site 3 | # ------------------------------------------------------------ 4 | {% if value == "congratulations" %} 5 | # Skipping output, congratulations page configration is baked in. 6 | {%- else %} 7 | server { 8 | listen 80 default; 9 | {% if ipv6 -%} 10 | listen [::]:80 default; 11 | {% else -%} 12 | #listen [::]:80 default; 13 | {% endif %} 14 | server_name default-host.localhost; 15 | access_log /data/logs/default-host_access.log combined; 16 | error_log /data/logs/default-host_error.log warn; 17 | {% include "_exploits.conf" %} 18 | 19 | include conf.d/include/letsencrypt-acme-challenge.conf; 20 | 21 | {%- if value == "404" %} 22 | location / { 23 | return 404; 24 | } 25 | {% endif %} 26 | 27 | {%- if value == "444" %} 28 | location / { 29 | return 444; 30 | } 31 | {% endif %} 32 | 33 | {%- if value == "redirect" %} 34 | location / { 35 | return 301 {{ meta.redirect }}; 36 | } 37 | {%- endif %} 38 | 39 | {%- if value == "html" %} 40 | root /data/nginx/default_www; 41 | location / { 42 | try_files $uri /index.html; 43 | } 44 | {%- endif %} 45 | } 46 | {% endif %} 47 | -------------------------------------------------------------------------------- /backend/templates/ip_ranges.conf: -------------------------------------------------------------------------------- 1 | {% for range in ip_ranges %} 2 | set_real_ip_from {{ range }}; 3 | {% endfor %} -------------------------------------------------------------------------------- /backend/templates/letsencrypt-request.conf: -------------------------------------------------------------------------------- 1 | {% include "_header_comment.conf" %} 2 | 3 | server { 4 | listen 80; 5 | {% if ipv6 -%} 6 | listen [::]:80; 7 | {% endif %} 8 | 9 | server_name {{ domain_names | join: " " }}; 10 | 11 | access_log /data/logs/letsencrypt-requests_access.log standard; 12 | error_log /data/logs/letsencrypt-requests_error.log warn; 13 | 14 | include conf.d/include/letsencrypt-acme-challenge.conf; 15 | 16 | location / { 17 | return 404; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /backend/templates/redirection_host.conf: -------------------------------------------------------------------------------- 1 | {% include "_header_comment.conf" %} 2 | 3 | {% if enabled %} 4 | 5 | {% include "_hsts_map.conf" %} 6 | 7 | server { 8 | {% include "_listen.conf" %} 9 | {% include "_certificates.conf" %} 10 | {% include "_assets.conf" %} 11 | {% include "_exploits.conf" %} 12 | {% include "_hsts.conf" %} 13 | {% include "_forced_ssl.conf" %} 14 | 15 | access_log /data/logs/redirection-host-{{ id }}_access.log standard; 16 | error_log /data/logs/redirection-host-{{ id }}_error.log warn; 17 | 18 | {{ advanced_config }} 19 | 20 | {% if use_default_location %} 21 | location / { 22 | {% include "_hsts.conf" %} 23 | 24 | {% if preserve_path == 1 or preserve_path == true %} 25 | return {{ forward_http_code }} {{ forward_scheme }}://{{ forward_domain_name }}$request_uri; 26 | {% else %} 27 | return {{ forward_http_code }} {{ forward_scheme }}://{{ forward_domain_name }}; 28 | {% endif %} 29 | } 30 | {% endif %} 31 | 32 | # Custom 33 | include /data/nginx/custom/server_redirect[.]conf; 34 | } 35 | {% endif %} 36 | -------------------------------------------------------------------------------- /backend/templates/stream.conf: -------------------------------------------------------------------------------- 1 | # ------------------------------------------------------------ 2 | # {{ incoming_port }} TCP: {{ tcp_forwarding }} UDP: {{ udp_forwarding }} 3 | # ------------------------------------------------------------ 4 | 5 | {% if enabled %} 6 | {% if tcp_forwarding == 1 or tcp_forwarding == true -%} 7 | server { 8 | listen {{ incoming_port }} {%- if certificate %} ssl {%- endif %}; 9 | {% unless ipv6 -%} # {%- endunless -%} listen [::]:{{ incoming_port }} {%- if certificate %} ssl {%- endif %}; 10 | 11 | {%- include "_certificates_stream.conf" %} 12 | 13 | proxy_pass {{ forwarding_host }}:{{ forwarding_port }}; 14 | 15 | # Custom 16 | include /data/nginx/custom/server_stream[.]conf; 17 | include /data/nginx/custom/server_stream_tcp[.]conf; 18 | } 19 | {% endif %} 20 | 21 | {% if udp_forwarding == 1 or udp_forwarding == true -%} 22 | server { 23 | listen {{ incoming_port }} udp; 24 | {% unless ipv6 -%} # {%- endunless -%} listen [::]:{{ incoming_port }} udp; 25 | 26 | proxy_pass {{ forwarding_host }}:{{ forwarding_port }}; 27 | 28 | # Custom 29 | include /data/nginx/custom/server_stream[.]conf; 30 | include /data/nginx/custom/server_stream_udp[.]conf; 31 | } 32 | {% endif %} 33 | {% endif %} -------------------------------------------------------------------------------- /backend/validate-schema.js: -------------------------------------------------------------------------------- 1 | const SwaggerParser = require('@apidevtools/swagger-parser'); 2 | const chalk = require('chalk'); 3 | const schema = require('./schema'); 4 | const log = console.log; 5 | 6 | schema.getCompiledSchema().then(async (swaggerJSON) => { 7 | try { 8 | const api = await SwaggerParser.validate(swaggerJSON); 9 | console.log('API name: %s, Version: %s', api.info.title, api.info.version); 10 | log(chalk.green('❯ Schema is valid')); 11 | } catch (e) { 12 | console.error(e); 13 | log(chalk.red('❯', e.message), '\n'); 14 | process.exit(1); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /docker/.dive-ci: -------------------------------------------------------------------------------- 1 | rules: 2 | # If the efficiency is measured below X%, mark as failed. 3 | # Expressed as a ratio between 0-1. 4 | lowestEfficiency: 0.99 5 | 6 | # If the amount of wasted space is at least X or larger than X, mark as failed. 7 | # Expressed in B, KB, MB, and GB. 8 | highestWastedBytes: 15MB 9 | 10 | # If the amount of wasted space makes up for X% or more of the image, mark as failed. 11 | # Note: the base image layer is NOT included in the total image size. 12 | # Expressed as a ratio between 0-1; fails if the threshold is met or crossed. 13 | highestUserWastedPercent: 0.02 14 | 15 | -------------------------------------------------------------------------------- /docker/Dockerfile-zh: -------------------------------------------------------------------------------- 1 | FROM jc21/nginx-proxy-manager:2.12.3 2 | 3 | ENV NPM_LANGUAGE="zh" 4 | 5 | EXPOSE 80 81 443 6 | 7 | RUN rm -rf /app/frontend /var/www/html/index.html 8 | COPY frontend/dist /app/frontend 9 | COPY docker/rootfs/var/www/html/index.html /var/www/html/index.html 10 | 11 | WORKDIR /app 12 | 13 | VOLUME [ "/data", "/etc/letsencrypt" ] 14 | ENTRYPOINT [ "/init" ] 15 | 16 | LABEL org.label-schema.schema-version="1.0" \ 17 | org.label-schema.license="MIT" \ 18 | org.label-schema.name="nginx-proxy-manager-zh" \ 19 | org.label-schema.description="Docker container for managing Nginx proxy hosts with a simple, powerful interface " \ 20 | org.label-schema.url="https://github.com/xiaoxinpro/nginx-proxy-manager-zh" \ 21 | org.label-schema.vcs-url="https://github.com/xiaoxinpro/nginx-proxy-manager-zh.git" \ 22 | org.label-schema.cmd="docker run --rm -ti chishin/nginx-proxy-manager-zh:latest" 23 | -------------------------------------------------------------------------------- /docker/ci.env: -------------------------------------------------------------------------------- 1 | AUTHENTIK_SECRET_KEY=gl8woZe8L6IIX8SC0c5Ocsj0xPkX5uJo5DVZCFl+L/QGbzuplfutYuua2ODNLEiDD3aFd9H2ylJmrke0 2 | AUTHENTIK_REDIS__HOST=authentik-redis 3 | AUTHENTIK_POSTGRESQL__HOST=db-postgres 4 | AUTHENTIK_POSTGRESQL__USER=authentik 5 | AUTHENTIK_POSTGRESQL__NAME=authentik 6 | AUTHENTIK_POSTGRESQL__PASSWORD=07EKS5NLI6Tpv68tbdvrxfvj 7 | AUTHENTIK_BOOTSTRAP_PASSWORD=admin 8 | AUTHENTIK_BOOTSTRAP_EMAIL=admin@example.com 9 | -------------------------------------------------------------------------------- /docker/ci/postgres/authentik.sql.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/docker/ci/postgres/authentik.sql.gz -------------------------------------------------------------------------------- /docker/dev/dnsrouter-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "log": { 3 | "format": "nice", 4 | "level": "debug" 5 | }, 6 | "servers": [ 7 | { 8 | "host": "0.0.0.0", 9 | "port": 53, 10 | "upstreams": [ 11 | { 12 | "regex": "website[0-9]+.example\\.com", 13 | "upstream": "127.0.0.11" 14 | }, 15 | { 16 | "regex": ".*\\.example\\.com", 17 | "upstream": "1.1.1.1" 18 | }, 19 | { 20 | "regex": "local", 21 | "nxdomain": true 22 | } 23 | ], 24 | "internal": null, 25 | "default_upstream": "127.0.0.11" 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /docker/dev/letsencrypt.ini: -------------------------------------------------------------------------------- 1 | text = True 2 | non-interactive = True 3 | webroot-path = /data/letsencrypt-acme-challenge 4 | key-type = ecdsa 5 | elliptic-curve = secp384r1 6 | preferred-chain = ISRG Root X1 7 | server = 8 | -------------------------------------------------------------------------------- /docker/dev/pebble-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "pebble": { 3 | "listenAddress": "0.0.0.0:443", 4 | "managementListenAddress": "0.0.0.0:15000", 5 | "certificate": "test/certs/localhost/cert.pem", 6 | "privateKey": "test/certs/localhost/key.pem", 7 | "httpPort": 80, 8 | "tlsPort": 443, 9 | "ocspResponderURL": "", 10 | "externalAccountBindingRequired": false 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /docker/docker-compose.ci.mysql.yml: -------------------------------------------------------------------------------- 1 | # WARNING: This is a CI docker-compose file used for building and testing of the entire app, it should not be used for production. 2 | services: 3 | 4 | fullstack: 5 | environment: 6 | DB_MYSQL_HOST: 'db-mysql' 7 | DB_MYSQL_PORT: '3306' 8 | DB_MYSQL_USER: 'npm' 9 | DB_MYSQL_PASSWORD: 'npmpass' 10 | DB_MYSQL_NAME: 'npm' 11 | depends_on: 12 | - db-mysql 13 | 14 | db-mysql: 15 | image: jc21/mariadb-aria 16 | environment: 17 | MYSQL_ROOT_PASSWORD: 'npm' 18 | MYSQL_DATABASE: 'npm' 19 | MYSQL_USER: 'npm' 20 | MYSQL_PASSWORD: 'npmpass' 21 | volumes: 22 | - mysql_vol:/var/lib/mysql 23 | networks: 24 | - fulltest 25 | 26 | volumes: 27 | mysql_vol: 28 | -------------------------------------------------------------------------------- /docker/docker-compose.ci.sqlite.yml: -------------------------------------------------------------------------------- 1 | # WARNING: This is a CI docker-compose file used for building and testing of the entire app, it should not be used for production. 2 | services: 3 | 4 | fullstack: 5 | environment: 6 | DB_SQLITE_FILE: '/data/mydb.sqlite' 7 | PUID: 1000 8 | PGID: 1000 9 | DISABLE_IPV6: 'true' 10 | -------------------------------------------------------------------------------- /docker/rootfs/etc/letsencrypt.ini: -------------------------------------------------------------------------------- 1 | text = True 2 | non-interactive = True 3 | webroot-path = /data/letsencrypt-acme-challenge 4 | key-type = ecdsa 5 | elliptic-curve = secp384r1 6 | preferred-chain = ISRG Root X1 7 | -------------------------------------------------------------------------------- /docker/rootfs/etc/logrotate.d/nginx-proxy-manager: -------------------------------------------------------------------------------- 1 | /data/logs/*_access.log /data/logs/*/access.log { 2 | su npm npm 3 | create 0644 4 | weekly 5 | rotate 4 6 | missingok 7 | notifempty 8 | compress 9 | sharedscripts 10 | postrotate 11 | kill -USR1 `cat /run/nginx/nginx.pid 2>/dev/null` 2>/dev/null || true 12 | endscript 13 | } 14 | 15 | /data/logs/*_error.log /data/logs/*/error.log { 16 | su npm npm 17 | create 0644 18 | weekly 19 | rotate 10 20 | missingok 21 | notifempty 22 | compress 23 | sharedscripts 24 | postrotate 25 | kill -USR1 `cat /run/nginx/nginx.pid 2>/dev/null` 2>/dev/null || true 26 | endscript 27 | } 28 | -------------------------------------------------------------------------------- /docker/rootfs/etc/nginx/conf.d/default.conf: -------------------------------------------------------------------------------- 1 | # "You are not configured" page, which is the default if another default doesn't exist 2 | server { 3 | listen 80; 4 | listen [::]:80; 5 | 6 | set $forward_scheme "http"; 7 | set $server "127.0.0.1"; 8 | set $port "80"; 9 | 10 | server_name localhost-nginx-proxy-manager; 11 | access_log /data/logs/fallback_access.log standard; 12 | error_log /data/logs/fallback_error.log warn; 13 | include conf.d/include/assets.conf; 14 | include conf.d/include/block-exploits.conf; 15 | include conf.d/include/letsencrypt-acme-challenge.conf; 16 | 17 | location / { 18 | index index.html; 19 | root /var/www/html; 20 | } 21 | } 22 | 23 | # First 443 Host, which is the default if another default doesn't exist 24 | server { 25 | listen 443 ssl; 26 | listen [::]:443 ssl; 27 | 28 | set $forward_scheme "https"; 29 | set $server "127.0.0.1"; 30 | set $port "443"; 31 | 32 | server_name localhost; 33 | access_log /data/logs/fallback_access.log standard; 34 | error_log /dev/null crit; 35 | include conf.d/include/ssl-ciphers.conf; 36 | ssl_reject_handshake on; 37 | 38 | return 444; 39 | } 40 | -------------------------------------------------------------------------------- /docker/rootfs/etc/nginx/conf.d/dev.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 81 default; 3 | listen [::]:81 default; 4 | 5 | server_name nginxproxymanager-dev; 6 | root /app/frontend/dist; 7 | access_log /dev/null; 8 | 9 | location /api { 10 | return 302 /api/; 11 | } 12 | 13 | location /api/ { 14 | add_header X-Served-By $host; 15 | proxy_set_header Host $host; 16 | proxy_set_header X-Forwarded-Scheme $scheme; 17 | proxy_set_header X-Forwarded-Proto $scheme; 18 | proxy_set_header X-Forwarded-For $remote_addr; 19 | proxy_pass http://127.0.0.1:3000/; 20 | 21 | proxy_read_timeout 15m; 22 | proxy_send_timeout 15m; 23 | } 24 | 25 | location / { 26 | index index.html; 27 | try_files $uri $uri.html $uri/ /index.html; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /docker/rootfs/etc/nginx/conf.d/include/.gitignore: -------------------------------------------------------------------------------- 1 | resolvers.conf 2 | -------------------------------------------------------------------------------- /docker/rootfs/etc/nginx/conf.d/include/assets.conf: -------------------------------------------------------------------------------- 1 | location ~* ^.*\.(css|js|jpe?g|gif|png|webp|woff|woff2|eot|ttf|svg|ico|css\.map|js\.map)$ { 2 | if_modified_since off; 3 | 4 | # use the public cache 5 | proxy_cache public-cache; 6 | proxy_cache_key $host$request_uri; 7 | 8 | # ignore these headers for media 9 | proxy_ignore_headers Set-Cookie Cache-Control Expires X-Accel-Expires; 10 | 11 | # cache 200s and also 404s (not ideal but there are a few 404 images for some reason) 12 | proxy_cache_valid any 30m; 13 | proxy_cache_valid 404 1m; 14 | 15 | # strip this header to avoid If-Modified-Since requests 16 | proxy_hide_header Last-Modified; 17 | proxy_hide_header Cache-Control; 18 | proxy_hide_header Vary; 19 | 20 | proxy_cache_bypass 0; 21 | proxy_no_cache 0; 22 | 23 | proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504 http_404; 24 | proxy_connect_timeout 5s; 25 | proxy_read_timeout 45s; 26 | 27 | expires @30m; 28 | access_log off; 29 | 30 | include conf.d/include/proxy.conf; 31 | } 32 | -------------------------------------------------------------------------------- /docker/rootfs/etc/nginx/conf.d/include/force-ssl.conf: -------------------------------------------------------------------------------- 1 | set $test ""; 2 | if ($scheme = "http") { 3 | set $test "H"; 4 | } 5 | if ($request_uri = /.well-known/acme-challenge/test-challenge) { 6 | set $test "${test}T"; 7 | } 8 | if ($test = H) { 9 | return 301 https://$host$request_uri; 10 | } 11 | -------------------------------------------------------------------------------- /docker/rootfs/etc/nginx/conf.d/include/ip_ranges.conf: -------------------------------------------------------------------------------- 1 | # This should be left blank is it is populated programatically 2 | # by the application backend. 3 | -------------------------------------------------------------------------------- /docker/rootfs/etc/nginx/conf.d/include/log.conf: -------------------------------------------------------------------------------- 1 | log_format proxy '[$time_local] $upstream_cache_status $upstream_status $status - $request_method $scheme $host "$request_uri" [Client $remote_addr] [Length $body_bytes_sent] [Gzip $gzip_ratio] [Sent-to $server] "$http_user_agent" "$http_referer"'; 2 | log_format standard '[$time_local] $status - $request_method $scheme $host "$request_uri" [Client $remote_addr] [Length $body_bytes_sent] [Gzip $gzip_ratio] "$http_user_agent" "$http_referer"'; 3 | 4 | access_log /data/logs/fallback_access.log proxy; 5 | -------------------------------------------------------------------------------- /docker/rootfs/etc/nginx/conf.d/include/proxy.conf: -------------------------------------------------------------------------------- 1 | add_header X-Served-By $host; 2 | proxy_set_header Host $host; 3 | proxy_set_header X-Forwarded-Scheme $scheme; 4 | proxy_set_header X-Forwarded-Proto $scheme; 5 | proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 6 | proxy_set_header X-Real-IP $remote_addr; 7 | proxy_pass $forward_scheme://$server:$port$request_uri; 8 | 9 | -------------------------------------------------------------------------------- /docker/rootfs/etc/nginx/conf.d/include/ssl-cache-stream.conf: -------------------------------------------------------------------------------- 1 | ssl_session_timeout 5m; 2 | ssl_session_cache shared:SSL_stream:50m; 3 | -------------------------------------------------------------------------------- /docker/rootfs/etc/nginx/conf.d/include/ssl-cache.conf: -------------------------------------------------------------------------------- 1 | ssl_session_timeout 5m; 2 | ssl_session_cache shared:SSL:50m; 3 | -------------------------------------------------------------------------------- /docker/rootfs/etc/nginx/conf.d/include/ssl-ciphers.conf: -------------------------------------------------------------------------------- 1 | # intermediate configuration. tweak to your needs. 2 | ssl_protocols TLSv1.2 TLSv1.3; 3 | ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384'; 4 | ssl_prefer_server_ciphers off; 5 | -------------------------------------------------------------------------------- /docker/rootfs/etc/nginx/conf.d/production.conf: -------------------------------------------------------------------------------- 1 | # Admin Interface 2 | server { 3 | listen 81 default; 4 | listen [::]:81 default; 5 | 6 | server_name nginxproxymanager; 7 | root /app/frontend; 8 | access_log /dev/null; 9 | 10 | location /api { 11 | return 302 /api/; 12 | } 13 | 14 | location /api/ { 15 | add_header X-Served-By $host; 16 | proxy_set_header Host $host; 17 | proxy_set_header X-Forwarded-Scheme $scheme; 18 | proxy_set_header X-Forwarded-Proto $scheme; 19 | proxy_set_header X-Forwarded-For $remote_addr; 20 | proxy_pass http://127.0.0.1:3000/; 21 | 22 | proxy_read_timeout 15m; 23 | proxy_send_timeout 15m; 24 | } 25 | 26 | location / { 27 | index index.html; 28 | if ($request_uri ~ ^/(.*)\.html$) { 29 | return 302 /$1; 30 | } 31 | try_files $uri $uri.html $uri/ /index.html; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/backend/dependencies.d/prepare: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/docker/rootfs/etc/s6-overlay/s6-rc.d/backend/dependencies.d/prepare -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/backend/run: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bash 2 | # shellcheck shell=bash 3 | 4 | set -e 5 | 6 | . /usr/bin/common.sh 7 | 8 | cd /app || exit 1 9 | 10 | log_info 'Starting backend ...' 11 | 12 | if [ "${DEVELOPMENT:-}" = 'true' ]; then 13 | s6-setuidgid "$PUID:$PGID" yarn install 14 | exec s6-setuidgid "$PUID:$PGID" bash -c "export HOME=$NPMHOME;node --max_old_space_size=250 --abort_on_uncaught_exception node_modules/nodemon/bin/nodemon.js" 15 | else 16 | while : 17 | do 18 | s6-setuidgid "$PUID:$PGID" bash -c "export HOME=$NPMHOME;node --abort_on_uncaught_exception --max_old_space_size=250 index.js" 19 | sleep 1 20 | done 21 | fi 22 | -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/backend/type: -------------------------------------------------------------------------------- 1 | longrun 2 | -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/dependencies.d/prepare: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/dependencies.d/prepare -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/run: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bash 2 | # shellcheck shell=bash 3 | 4 | set -e 5 | 6 | # This service is DEVELOPMENT only. 7 | 8 | if [ "$DEVELOPMENT" = 'true' ]; then 9 | . /usr/bin/common.sh 10 | cd /app/frontend || exit 1 11 | HOME=$NPMHOME 12 | export HOME 13 | mkdir -p /app/frontend/dist 14 | chown -R "$PUID:$PGID" /app/frontend/dist 15 | 16 | log_info 'Starting frontend ...' 17 | s6-setuidgid "$PUID:$PGID" yarn install 18 | exec s6-setuidgid "$PUID:$PGID" yarn watch 19 | else 20 | exit 0 21 | fi 22 | -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/type: -------------------------------------------------------------------------------- 1 | longrun 2 | -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/nginx/dependencies.d/prepare: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/docker/rootfs/etc/s6-overlay/s6-rc.d/nginx/dependencies.d/prepare -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/nginx/run: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bash 2 | # shellcheck shell=bash 3 | 4 | set -e 5 | 6 | . /usr/bin/common.sh 7 | 8 | log_info 'Starting nginx ...' 9 | exec s6-setuidgid "$PUID:$PGID" nginx 10 | -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/nginx/type: -------------------------------------------------------------------------------- 1 | longrun 2 | -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/00-all.sh: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bash 2 | # shellcheck shell=bash 3 | 4 | set -e 5 | 6 | . /usr/bin/common.sh 7 | 8 | if [ "$(id -u)" != "0" ]; then 9 | log_fatal "This docker container must be run as root, do not specify a user.\nYou can specify PUID and PGID env vars to run processes as that user and group after initialization." 10 | fi 11 | 12 | if [ "$DEBUG" = "true" ]; then 13 | set -x 14 | fi 15 | 16 | . /etc/s6-overlay/s6-rc.d/prepare/10-usergroup.sh 17 | . /etc/s6-overlay/s6-rc.d/prepare/20-paths.sh 18 | . /etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh 19 | . /etc/s6-overlay/s6-rc.d/prepare/40-dynamic.sh 20 | . /etc/s6-overlay/s6-rc.d/prepare/50-ipv6.sh 21 | . /etc/s6-overlay/s6-rc.d/prepare/60-secrets.sh 22 | . /etc/s6-overlay/s6-rc.d/prepare/90-banner.sh 23 | -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/10-usergroup.sh: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bash 2 | # shellcheck shell=bash 3 | 4 | set -e 5 | 6 | log_info "Configuring $NPMUSER user ..." 7 | 8 | if id -u "$NPMUSER" 2>/dev/null; then 9 | # user already exists 10 | usermod -u "$PUID" "$NPMUSER" 11 | else 12 | # Add user 13 | useradd -o -u "$PUID" -U -d "$NPMHOME" -s /bin/false "$NPMUSER" 14 | fi 15 | 16 | log_info "Configuring $NPMGROUP group ..." 17 | if [ "$(get_group_id "$NPMGROUP")" = '' ]; then 18 | # Add group. This will not set the id properly if it's already taken 19 | groupadd -f -g "$PGID" "$NPMGROUP" 20 | else 21 | groupmod -o -g "$PGID" "$NPMGROUP" 22 | fi 23 | 24 | # Set the group ID and check it 25 | groupmod -o -g "$PGID" "$NPMGROUP" 26 | if [ "$(get_group_id "$NPMGROUP")" != "$PGID" ]; then 27 | echo "ERROR: Unable to set group id properly" 28 | exit 1 29 | fi 30 | 31 | # Set the group against the user and check it 32 | usermod -G "$PGID" "$NPMGROUP" 33 | if [ "$(id -g "$NPMUSER")" != "$PGID" ] ; then 34 | echo "ERROR: Unable to set group against the user properly" 35 | exit 1 36 | fi 37 | 38 | # Home for user 39 | mkdir -p "$NPMHOME" 40 | chown -R "$PUID:$PGID" "$NPMHOME" 41 | -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/20-paths.sh: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bash 2 | # shellcheck shell=bash 3 | 4 | set -e 5 | 6 | log_info 'Checking paths ...' 7 | 8 | # Ensure /data is mounted 9 | if [ ! -d '/data' ]; then 10 | log_fatal '/data is not mounted! Check your docker configuration.' 11 | fi 12 | # Ensure /etc/letsencrypt is mounted 13 | if [ ! -d '/etc/letsencrypt' ]; then 14 | log_fatal '/etc/letsencrypt is not mounted! Check your docker configuration.' 15 | fi 16 | 17 | # Create required folders 18 | mkdir -p \ 19 | /data/nginx \ 20 | /data/custom_ssl \ 21 | /data/logs \ 22 | /data/access \ 23 | /data/nginx/default_host \ 24 | /data/nginx/default_www \ 25 | /data/nginx/proxy_host \ 26 | /data/nginx/redirection_host \ 27 | /data/nginx/stream \ 28 | /data/nginx/dead_host \ 29 | /data/nginx/temp \ 30 | /data/letsencrypt-acme-challenge \ 31 | /run/nginx \ 32 | /tmp/nginx/body \ 33 | /var/log/nginx \ 34 | /var/lib/nginx/cache/public \ 35 | /var/lib/nginx/cache/private \ 36 | /var/cache/nginx/proxy_temp 37 | 38 | touch /var/log/nginx/error.log || true 39 | chmod 777 /var/log/nginx/error.log || true 40 | chmod -R 777 /var/cache/nginx || true 41 | chmod 644 /etc/logrotate.d/nginx-proxy-manager 42 | -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bash 2 | # shellcheck shell=bash 3 | 4 | set -e 5 | 6 | log_info 'Setting ownership ...' 7 | 8 | # root 9 | chown root /tmp/nginx 10 | 11 | # npm user and group 12 | chown -R "$PUID:$PGID" /data 13 | chown -R "$PUID:$PGID" /etc/letsencrypt 14 | chown -R "$PUID:$PGID" /run/nginx 15 | chown -R "$PUID:$PGID" /tmp/nginx 16 | chown -R "$PUID:$PGID" /var/cache/nginx 17 | chown -R "$PUID:$PGID" /var/lib/logrotate 18 | chown -R "$PUID:$PGID" /var/lib/nginx 19 | chown -R "$PUID:$PGID" /var/log/nginx 20 | 21 | # Don't chown entire /etc/nginx folder as this causes crashes on some systems 22 | chown -R "$PUID:$PGID" /etc/nginx/nginx 23 | chown -R "$PUID:$PGID" /etc/nginx/nginx.conf 24 | chown -R "$PUID:$PGID" /etc/nginx/conf.d 25 | 26 | # Prevents errors when installing python certbot plugins when non-root 27 | chown "$PUID:$PGID" /opt/certbot /opt/certbot/bin 28 | find /opt/certbot/lib/python*/site-packages -not -user "$PUID" -execdir chown "$PUID:$PGID" {} \+ 29 | -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/40-dynamic.sh: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bash 2 | # shellcheck shell=bash 3 | 4 | set -e 5 | 6 | log_info 'Dynamic resolvers ...' 7 | 8 | DISABLE_IPV6=$(echo "${DISABLE_IPV6:-}" | tr '[:upper:]' '[:lower:]') 9 | 10 | # Dynamically generate resolvers file, if resolver is IPv6, enclose in `[]` 11 | # thanks @tfmm 12 | if [ "$DISABLE_IPV6" == "true" ] || [ "$DISABLE_IPV6" == "on" ] || [ "$DISABLE_IPV6" == "1" ] || [ "$DISABLE_IPV6" == "yes" ]; 13 | then 14 | echo resolver "$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf) ipv6=off valid=10s;" > /etc/nginx/conf.d/include/resolvers.conf 15 | else 16 | echo resolver "$(awk 'BEGIN{ORS=" "} $1=="nameserver" { sub(/%.*$/,"",$2); print ($2 ~ ":")? "["$2"]": $2}' /etc/resolv.conf) valid=10s;" > /etc/nginx/conf.d/include/resolvers.conf 17 | fi 18 | -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/50-ipv6.sh: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bash 2 | # shellcheck shell=bash 3 | 4 | # This command reads the `DISABLE_IPV6` env var and will either enable 5 | # or disable ipv6 in all nginx configs based on this setting. 6 | 7 | set -e 8 | 9 | log_info 'IPv6 ...' 10 | 11 | # Lowercase 12 | DISABLE_IPV6=$(echo "${DISABLE_IPV6:-}" | tr '[:upper:]' '[:lower:]') 13 | 14 | process_folder () { 15 | FILES=$(find "$1" -type f -name "*.conf") 16 | SED_REGEX= 17 | 18 | if [ "$DISABLE_IPV6" == "true" ] || [ "$DISABLE_IPV6" == "on" ] || [ "$DISABLE_IPV6" == "1" ] || [ "$DISABLE_IPV6" == "yes" ]; then 19 | # IPV6 is disabled 20 | echo "Disabling IPV6 in hosts in: $1" 21 | SED_REGEX='s/^([^#]*)listen \[::\]/\1#listen [::]/g' 22 | else 23 | # IPV6 is enabled 24 | echo "Enabling IPV6 in hosts in: $1" 25 | SED_REGEX='s/^(\s*)#listen \[::\]/\1listen [::]/g' 26 | fi 27 | 28 | for FILE in $FILES 29 | do 30 | echo "- ${FILE}" 31 | echo "$(sed -E "$SED_REGEX" "$FILE")" > $FILE 32 | done 33 | 34 | # ensure the files are still owned by the npm user 35 | chown -R "$PUID:$PGID" "$1" 36 | } 37 | 38 | process_folder /etc/nginx/conf.d 39 | process_folder /data/nginx 40 | -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/90-banner.sh: -------------------------------------------------------------------------------- 1 | #!/command/with-contenv bash 2 | # shellcheck shell=bash 3 | 4 | set -e 5 | set +x 6 | 7 | echo " 8 | ------------------------------------- 9 | _ _ ____ __ __ 10 | | \ | | _ \| \/ | 11 | | \| | |_) | |\/| | 12 | | |\ | __/| | | | 13 | |_| \_|_| |_| |_| 14 | ------------------------------------- 15 | User: $NPMUSER PUID:$PUID ID:$(id -u "$NPMUSER") GROUP:$(id -g "$NPMUSER") 16 | Group: $NPMGROUP PGID:$PGID ID:$(get_group_id "$NPMGROUP") 17 | ------------------------------------- 18 | " 19 | -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/dependencies.d/base: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/dependencies.d/base -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/type: -------------------------------------------------------------------------------- 1 | oneshot 2 | -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/up: -------------------------------------------------------------------------------- 1 | # shellcheck shell=bash 2 | /etc/s6-overlay/s6-rc.d/prepare/00-all.sh 3 | -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/backend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/backend -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/frontend: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/frontend -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/nginx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/nginx -------------------------------------------------------------------------------- /docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/prepare: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/docker/rootfs/etc/s6-overlay/s6-rc.d/user/contents.d/prepare -------------------------------------------------------------------------------- /docker/rootfs/root/.bashrc: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -t 1 ]; then 4 | export PS1="\e[1;34m[\e[1;33m\u@\e[1;32mdocker-\h\e[1;37m:\w\[\e[1;34m]\e[1;36m\\$ \e[0m" 5 | fi 6 | 7 | # Aliases 8 | alias l='ls -lAsh --color' 9 | alias ls='ls -C1 --color' 10 | alias cp='cp -ip' 11 | alias rm='rm -i' 12 | alias mv='mv -i' 13 | alias h='cd ~;clear;' 14 | 15 | . /etc/os-release 16 | 17 | echo -e -n '\E[1;34m' 18 | figlet -w 120 "NginxProxyManager" 19 | echo -e "\E[1;36mVersion \E[1;32m${NPM_BUILD_VERSION:-2.0.0-dev} (${NPM_BUILD_COMMIT:-dev}) ${NPM_BUILD_DATE:-0000-00-00}\E[1;36m, OpenResty \E[1;32m${OPENRESTY_VERSION:-unknown}\E[1;36m, ${ID:-debian} \E[1;32m${VERSION:-unknown}\E[1;36m, Certbot \E[1;32m$(certbot --version)\E[0m" 20 | echo -e -n '\E[1;34m' 21 | cat /built-for-arch 22 | echo -e '\E[0m' 23 | -------------------------------------------------------------------------------- /docker/rootfs/usr/bin/check-health: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | OK=$(curl --silent http://127.0.0.1:81/api/ | jq --raw-output '.status') 4 | 5 | if [ "$OK" == "OK" ]; then 6 | echo "OK" 7 | exit 0 8 | else 9 | echo "NOT OK" 10 | exit 1 11 | fi 12 | -------------------------------------------------------------------------------- /docker/rootfs/var/www/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Default Site 8 | 9 | 12 | 13 | 14 |
15 |
16 |

恭喜!

17 |

您已成功启动 Nginx 代理管理器。

18 |

如果您看到此站点,则说明您正在尝试访问尚未设置的主机。

19 |

登录管理面板开始使用。

20 |
21 |

Powered by Nginx Proxy Manager

22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | ts 4 | .temp 5 | .cache 6 | .vitepress/cache 7 | 8 | .yarn/* 9 | !.yarn/releases 10 | !.yarn/plugins 11 | !.yarn/sdks 12 | !.yarn/versions 13 | *.gz 14 | *.tgz 15 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/custom.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --vp-home-hero-name-color: transparent; 3 | --vp-home-hero-name-background: -webkit-linear-gradient(120deg, #f15833 30%, #FAA42F); 4 | 5 | --vp-home-hero-image-background-image: linear-gradient(-45deg, #aaaaaa 50%, #777777 50%); 6 | --vp-home-hero-image-filter: blur(44px); 7 | 8 | --vp-c-brand-1: #f15833; 9 | --vp-c-brand-2: #FAA42F; 10 | --vp-c-brand-3: #f15833; 11 | } 12 | 13 | @media (min-width: 640px) { 14 | :root { 15 | --vp-home-hero-image-filter: blur(56px); 16 | } 17 | } 18 | 19 | @media (min-width: 960px) { 20 | :root { 21 | --vp-home-hero-image-filter: blur(68px); 22 | } 23 | } 24 | 25 | .inline-img img { 26 | display: inline; 27 | } 28 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.ts: -------------------------------------------------------------------------------- 1 | import DefaultTheme from 'vitepress/theme' 2 | import './custom.css' 3 | 4 | export default DefaultTheme 5 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "dev": "vitepress dev --host", 4 | "build": "vitepress build", 5 | "preview": "vitepress preview" 6 | }, 7 | "devDependencies": { 8 | "vitepress": "^1.4.0" 9 | }, 10 | "dependencies": {} 11 | } 12 | -------------------------------------------------------------------------------- /docs/src/public/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/docs/src/public/github.png -------------------------------------------------------------------------------- /docs/src/public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/docs/src/public/icon.png -------------------------------------------------------------------------------- /docs/src/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /docs/src/public/screenshots/access-lists.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/docs/src/public/screenshots/access-lists.png -------------------------------------------------------------------------------- /docs/src/public/screenshots/audit-log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/docs/src/public/screenshots/audit-log.png -------------------------------------------------------------------------------- /docs/src/public/screenshots/certificates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/docs/src/public/screenshots/certificates.png -------------------------------------------------------------------------------- /docs/src/public/screenshots/custom-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/docs/src/public/screenshots/custom-settings.png -------------------------------------------------------------------------------- /docs/src/public/screenshots/dashboard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/docs/src/public/screenshots/dashboard.png -------------------------------------------------------------------------------- /docs/src/public/screenshots/dead-hosts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/docs/src/public/screenshots/dead-hosts.png -------------------------------------------------------------------------------- /docs/src/public/screenshots/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/docs/src/public/screenshots/login.png -------------------------------------------------------------------------------- /docs/src/public/screenshots/permissions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/docs/src/public/screenshots/permissions.png -------------------------------------------------------------------------------- /docs/src/public/screenshots/proxy-hosts-add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/docs/src/public/screenshots/proxy-hosts-add.png -------------------------------------------------------------------------------- /docs/src/public/screenshots/proxy-hosts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/docs/src/public/screenshots/proxy-hosts.png -------------------------------------------------------------------------------- /docs/src/public/screenshots/redirection-hosts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/docs/src/public/screenshots/redirection-hosts.png -------------------------------------------------------------------------------- /docs/src/third-party/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # Third Party 6 | 7 | As this software gains popularity it's common to see it integrated with other platforms. Please be aware that unless specifically mentioned in the documentation of those 8 | integrations, they are *not supported* by me. 9 | 10 | Known integrations: 11 | 12 | - [HomeAssistant Hass.io plugin](https://github.com/hassio-addons/addon-nginx-proxy-manager) 13 | - [UnRaid / Synology](https://github.com/jlesage/docker-nginx-proxy-manager) 14 | - [Proxmox Scripts](https://github.com/ej52/proxmox-scripts/tree/main/apps/nginx-proxy-manager) 15 | - [Proxmox VE Helper-Scripts](https://community-scripts.github.io/ProxmoxVE/scripts?id=nginxproxymanager) 16 | - [nginxproxymanagerGraf](https://github.com/ma-karai/nginxproxymanagerGraf) 17 | 18 | 19 | If you would like your integration of NPM listed, please open a 20 | [Github issue](https://github.com/NginxProxyManager/nginx-proxy-manager/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=) 21 | -------------------------------------------------------------------------------- /docs/src/upgrading/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # Upgrading 6 | 7 | ```bash 8 | docker compose pull 9 | docker compose up -d 10 | ``` 11 | 12 | This project will automatically update any databases or other requirements so you don't have to follow 13 | any crazy instructions. These steps above will pull the latest updates and recreate the docker 14 | containers. 15 | 16 | See the [list of releases](https://github.com/NginxProxyManager/nginx-proxy-manager/releases) for any upgrade steps specific to each release. 17 | -------------------------------------------------------------------------------- /frontend/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "targets": { 7 | "browsers": [ 8 | "Chrome >= 65" 9 | ] 10 | }, 11 | "debug": false, 12 | "modules": false, 13 | "useBuiltIns": "usage" 14 | } 15 | ] 16 | ] 17 | } -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | webpack_stats.html 4 | yarn-error.log 5 | -------------------------------------------------------------------------------- /frontend/app-images/default-avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/frontend/app-images/default-avatar.jpg -------------------------------------------------------------------------------- /frontend/app-images/favicons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/frontend/app-images/favicons/android-chrome-192x192.png -------------------------------------------------------------------------------- /frontend/app-images/favicons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/frontend/app-images/favicons/android-chrome-512x512.png -------------------------------------------------------------------------------- /frontend/app-images/favicons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/frontend/app-images/favicons/apple-touch-icon.png -------------------------------------------------------------------------------- /frontend/app-images/favicons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #333333 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /frontend/app-images/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/frontend/app-images/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /frontend/app-images/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/frontend/app-images/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /frontend/app-images/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/frontend/app-images/favicons/favicon.ico -------------------------------------------------------------------------------- /frontend/app-images/favicons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/frontend/app-images/favicons/mstile-150x150.png -------------------------------------------------------------------------------- /frontend/app-images/favicons/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "/images/favicons/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/images/favicons/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /frontend/app-images/logo-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/frontend/app-images/logo-256.png -------------------------------------------------------------------------------- /frontend/app-images/logo-text-vertical-grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/frontend/app-images/logo-text-vertical-grey.png -------------------------------------------------------------------------------- /frontend/fonts/feather: -------------------------------------------------------------------------------- 1 | ../node_modules/tabler-ui/dist/assets/fonts/feather -------------------------------------------------------------------------------- /frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff -------------------------------------------------------------------------------- /frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700.woff2 -------------------------------------------------------------------------------- /frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff -------------------------------------------------------------------------------- /frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-700italic.woff2 -------------------------------------------------------------------------------- /frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff -------------------------------------------------------------------------------- /frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-italic.woff2 -------------------------------------------------------------------------------- /frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff -------------------------------------------------------------------------------- /frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiaoxinpro/nginx-proxy-manager-zh/fcf8fce1e026a0e003ac2fe9120108484ca860ca/frontend/fonts/source-sans-pro/source-sans-pro-v14-latin-ext_latin-regular.woff2 -------------------------------------------------------------------------------- /frontend/html/index.ejs: -------------------------------------------------------------------------------- 1 | <% var title = 'Nginx 代理管理器' %> 2 | <%- include partials/header.ejs %> 3 | 4 |
5 | 6 |
7 | 8 | 9 | <%- include partials/footer.ejs %> 10 | -------------------------------------------------------------------------------- /frontend/html/login.ejs: -------------------------------------------------------------------------------- 1 | <% var title = '登录 – Nginx 代理管理器' %> 2 | <%- include partials/header.ejs %> 3 | 4 |
5 | 6 |
7 | 8 | 9 | <%- include partials/footer.ejs %> 10 | -------------------------------------------------------------------------------- /frontend/html/partials/footer.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /frontend/images: -------------------------------------------------------------------------------- 1 | ./node_modules/tabler-ui/dist/assets/images -------------------------------------------------------------------------------- /frontend/js/app/audit-log/list/item.js: -------------------------------------------------------------------------------- 1 | const Mn = require('backbone.marionette'); 2 | const Controller = require('../../controller'); 3 | const template = require('./item.ejs'); 4 | 5 | module.exports = Mn.View.extend({ 6 | template: template, 7 | tagName: 'tr', 8 | 9 | ui: { 10 | meta: 'a.meta' 11 | }, 12 | 13 | events: { 14 | 'click @ui.meta': function (e) { 15 | e.preventDefault(); 16 | Controller.showAuditMeta(this.model); 17 | } 18 | }, 19 | 20 | templateContext: { 21 | more: function() { 22 | switch (this.object_type) { 23 | case 'redirection-host': 24 | case 'stream': 25 | case 'proxy-host': 26 | return this.meta.domain_names.join(', '); 27 | } 28 | 29 | return '#' + (this.object_id || '?'); 30 | } 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /frontend/js/app/audit-log/list/main.ejs: -------------------------------------------------------------------------------- 1 | 2 |   3 | User 4 | Event 5 |   6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /frontend/js/app/audit-log/list/main.js: -------------------------------------------------------------------------------- 1 | const Mn = require('backbone.marionette'); 2 | const ItemView = require('./item'); 3 | const template = require('./main.ejs'); 4 | 5 | const TableBody = Mn.CollectionView.extend({ 6 | tagName: 'tbody', 7 | childView: ItemView 8 | }); 9 | 10 | module.exports = Mn.View.extend({ 11 | tagName: 'table', 12 | className: 'table table-hover table-outline table-vcenter card-table', 13 | template: template, 14 | 15 | regions: { 16 | body: { 17 | el: 'tbody', 18 | replaceElement: true 19 | } 20 | }, 21 | 22 | onRender: function () { 23 | this.showChildView('body', new TableBody({ 24 | collection: this.collection 25 | })); 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /frontend/js/app/audit-log/main.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

<%- i18n('audit-log', 'title') %>

5 |
6 | 14 |
15 |
16 |
17 |
18 |
19 |
20 | 21 |
22 |
23 | 24 |
25 |
26 | -------------------------------------------------------------------------------- /frontend/js/app/audit-log/meta.js: -------------------------------------------------------------------------------- 1 | const Mn = require('backbone.marionette'); 2 | const template = require('./meta.ejs'); 3 | 4 | module.exports = Mn.View.extend({ 5 | template: template, 6 | className: 'modal-dialog wide' 7 | }); 8 | -------------------------------------------------------------------------------- /frontend/js/app/cache.js: -------------------------------------------------------------------------------- 1 | const UserModel = require('../models/user'); 2 | 3 | let cache = { 4 | User: new UserModel.Model(), 5 | locale: 'zh', 6 | version: null 7 | }; 8 | 9 | module.exports = cache; 10 | 11 | -------------------------------------------------------------------------------- /frontend/js/app/empty/main.ejs: -------------------------------------------------------------------------------- 1 | <% if (title) { %> 2 |

<%- title %>

3 | <% } 4 | 5 | if (subtitle) { %> 6 |

<%- subtitle %>

7 | <% } 8 | 9 | if (link) { %> 10 | <%- link %> 11 | <% } %> 12 | -------------------------------------------------------------------------------- /frontend/js/app/empty/main.js: -------------------------------------------------------------------------------- 1 | const Mn = require('backbone.marionette'); 2 | const template = require('./main.ejs'); 3 | 4 | module.exports = Mn.View.extend({ 5 | className: 'text-center m-7', 6 | template: template, 7 | 8 | options: { 9 | btn_color: 'teal' 10 | }, 11 | 12 | ui: { 13 | action: 'a' 14 | }, 15 | 16 | events: { 17 | 'click @ui.action': function (e) { 18 | e.preventDefault(); 19 | this.getOption('action')(); 20 | } 21 | }, 22 | 23 | templateContext: function () { 24 | return { 25 | title: this.getOption('title'), 26 | subtitle: this.getOption('subtitle'), 27 | link: this.getOption('link'), 28 | action: typeof this.getOption('action') === 'function', 29 | btn_color: this.getOption('btn_color') 30 | }; 31 | } 32 | 33 | }); 34 | -------------------------------------------------------------------------------- /frontend/js/app/error/main.ejs: -------------------------------------------------------------------------------- 1 | 2 | <%= code ? '' + code + ' — ' : '' %> 3 | <%- message %> 4 | 5 | <% if (retry) { %> 6 |

<%- i18n('str', 'try-again') %> 7 | <% } %> 8 | -------------------------------------------------------------------------------- /frontend/js/app/error/main.js: -------------------------------------------------------------------------------- 1 | const Mn = require('backbone.marionette'); 2 | const template = require('./main.ejs'); 3 | 4 | module.exports = Mn.View.extend({ 5 | template: template, 6 | className: 'alert alert-icon alert-warning m-5', 7 | 8 | ui: { 9 | retry: 'a.retry' 10 | }, 11 | 12 | events: { 13 | 'click @ui.retry': function (e) { 14 | e.preventDefault(); 15 | this.getOption('retry')(); 16 | } 17 | }, 18 | 19 | templateContext: function () { 20 | return { 21 | message: this.getOption('message'), 22 | code: this.getOption('code'), 23 | retry: typeof this.getOption('retry') === 'function' 24 | }; 25 | } 26 | 27 | }); 28 | -------------------------------------------------------------------------------- /frontend/js/app/help/main.ejs: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /frontend/js/app/help/main.js: -------------------------------------------------------------------------------- 1 | const Mn = require('backbone.marionette'); 2 | const template = require('./main.ejs'); 3 | 4 | module.exports = Mn.View.extend({ 5 | template: template, 6 | className: 'modal-dialog wide', 7 | 8 | templateContext: function () { 9 | let content = this.getOption('content').split("\n"); 10 | 11 | return { 12 | title: this.getOption('title'), 13 | content: '

' + content.join('

') + '

' 14 | }; 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /frontend/js/app/i18n.js: -------------------------------------------------------------------------------- 1 | const Cache = ('./cache'); 2 | const messages = require('../i18n/messages.json'); 3 | 4 | /** 5 | * @param {String} namespace 6 | * @param {String} key 7 | * @param {Object} [data] 8 | */ 9 | module.exports = function (namespace, key, data) { 10 | let locale = Cache.locale; 11 | // check that the locale exists 12 | if (typeof messages[locale] === 'undefined') { 13 | locale = 'zh'; 14 | } 15 | 16 | if (typeof messages[locale][namespace] !== 'undefined' && typeof messages[locale][namespace][key] !== 'undefined') { 17 | return messages[locale][namespace][key](data); 18 | } else if (locale !== 'zh' && typeof messages['zh'][namespace] !== 'undefined' && typeof messages['zh'][namespace][key] !== 'undefined') { 19 | return messages['zh'][namespace][key](data); 20 | } 21 | 22 | return '(MISSING: ' + namespace + '/' + key + ')'; 23 | }; 24 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/access/delete.ejs: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/access/delete.js: -------------------------------------------------------------------------------- 1 | const Mn = require('backbone.marionette'); 2 | const App = require('../../main'); 3 | const template = require('./delete.ejs'); 4 | 5 | module.exports = Mn.View.extend({ 6 | template: template, 7 | className: 'modal-dialog', 8 | 9 | ui: { 10 | form: 'form', 11 | buttons: '.modal-footer button', 12 | cancel: 'button.cancel', 13 | save: 'button.save' 14 | }, 15 | 16 | events: { 17 | 18 | 'click @ui.save': function (e) { 19 | e.preventDefault(); 20 | 21 | App.Api.Nginx.AccessLists.delete(this.model.get('id')) 22 | .then(() => { 23 | App.Controller.showNginxAccess(); 24 | App.UI.closeModal(); 25 | }) 26 | .catch(err => { 27 | alert(err.message); 28 | this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); 29 | }); 30 | } 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/access/form/client.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 | 7 |
8 |
9 |
10 |
11 | 12 |
13 |
14 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/access/form/client.js: -------------------------------------------------------------------------------- 1 | const Mn = require('backbone.marionette'); 2 | const template = require('./client.ejs'); 3 | 4 | module.exports = Mn.View.extend({ 5 | template: template, 6 | className: 'row' 7 | }); 8 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/access/form/item.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 |
7 |
8 | 9 |
10 |
11 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/access/form/item.js: -------------------------------------------------------------------------------- 1 | const Mn = require('backbone.marionette'); 2 | const template = require('./item.ejs'); 3 | 4 | module.exports = Mn.View.extend({ 5 | template: template, 6 | className: 'row' 7 | }); 8 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/access/list/item.js: -------------------------------------------------------------------------------- 1 | const Mn = require('backbone.marionette'); 2 | const App = require('../../../main'); 3 | const template = require('./item.ejs'); 4 | 5 | module.exports = Mn.View.extend({ 6 | template: template, 7 | tagName: 'tr', 8 | 9 | ui: { 10 | edit: 'a.edit', 11 | delete: 'a.delete' 12 | }, 13 | 14 | events: { 15 | 'click @ui.edit': function (e) { 16 | e.preventDefault(); 17 | App.Controller.showNginxAccessListForm(this.model); 18 | }, 19 | 20 | 'click @ui.delete': function (e) { 21 | e.preventDefault(); 22 | App.Controller.showNginxAccessListDeleteConfirm(this.model); 23 | } 24 | }, 25 | 26 | templateContext: { 27 | canManage: App.Cache.User.canManage('access_lists') 28 | }, 29 | 30 | initialize: function () { 31 | this.listenTo(this.model, 'change', this.render); 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/access/list/main.ejs: -------------------------------------------------------------------------------- 1 | 2 |   3 | <%- i18n('str', 'name') %> 4 | <%- i18n('access-lists', 'authorization') %> 5 | <%- i18n('access-lists', 'access') %> 6 | <%- i18n('access-lists', 'satisfy') %> 7 | <%- i18n('proxy-hosts', 'title') %> 8 | <% if (canManage) { %> 9 |   10 | <% } %> 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/access/list/main.js: -------------------------------------------------------------------------------- 1 | const Mn = require('backbone.marionette'); 2 | const App = require('../../../main'); 3 | const ItemView = require('./item'); 4 | const template = require('./main.ejs'); 5 | 6 | const TableBody = Mn.CollectionView.extend({ 7 | tagName: 'tbody', 8 | childView: ItemView 9 | }); 10 | 11 | module.exports = Mn.View.extend({ 12 | tagName: 'table', 13 | className: 'table table-hover table-outline table-vcenter card-table', 14 | template: template, 15 | 16 | regions: { 17 | body: { 18 | el: 'tbody', 19 | replaceElement: true 20 | } 21 | }, 22 | 23 | templateContext: { 24 | canManage: App.Cache.User.canManage('access_lists') 25 | }, 26 | 27 | onRender: function () { 28 | this.showChildView('body', new TableBody({ 29 | collection: this.collection 30 | })); 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/certificates-list-item.ejs: -------------------------------------------------------------------------------- 1 |
2 | <% if (id === 'new') { %> 3 |
4 | <%- i18n('all-hosts', 'new-cert') %> 5 |
6 | <%- i18n('all-hosts', 'with-le') %> 7 | <% } else if (id > 0) { %> 8 |
9 | <%- provider === 'other' ? nice_name : domain_names.join(', ') %> 10 |
11 | <%- i18n('ssl', provider) %> – Expires: <%- formatDbDate(expires_on, 'Do MMMM YYYY, h:mm a') %> 12 | <% } else { %> 13 |
14 | <%- i18n('all-hosts', 'none') %> 15 |
16 | <%- i18n('all-hosts', 'no-ssl') %> 17 | <% } %> 18 |
19 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/certificates/delete.ejs: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/certificates/list/main.ejs: -------------------------------------------------------------------------------- 1 | 2 |   3 | <%- i18n('str', 'name') %> 4 | <%- i18n('all-hosts', 'cert-provider') %> 5 | <%- i18n('str', 'expires') %> 6 | <%- i18n('str', 'status') %> 7 | <% if (canManage) { %> 8 |   9 | <% } %> 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/certificates/list/main.js: -------------------------------------------------------------------------------- 1 | const Mn = require('backbone.marionette'); 2 | const App = require('../../../main'); 3 | const ItemView = require('./item'); 4 | const template = require('./main.ejs'); 5 | 6 | const TableBody = Mn.CollectionView.extend({ 7 | tagName: 'tbody', 8 | childView: ItemView 9 | }); 10 | 11 | module.exports = Mn.View.extend({ 12 | tagName: 'table', 13 | className: 'table table-hover table-outline table-vcenter card-table', 14 | template: template, 15 | 16 | regions: { 17 | body: { 18 | el: 'tbody', 19 | replaceElement: true 20 | } 21 | }, 22 | 23 | templateContext: { 24 | canManage: App.Cache.User.canManage('certificates') 25 | }, 26 | 27 | onRender: function () { 28 | this.showChildView('body', new TableBody({ 29 | collection: this.collection 30 | })); 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/certificates/renew.ejs: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/certificates/renew.js: -------------------------------------------------------------------------------- 1 | const Mn = require('backbone.marionette'); 2 | const App = require('../../main'); 3 | const template = require('./renew.ejs'); 4 | 5 | module.exports = Mn.View.extend({ 6 | template: template, 7 | className: 'modal-dialog', 8 | 9 | ui: { 10 | waiting: '.waiting', 11 | error: '.error', 12 | close: 'button.cancel' 13 | }, 14 | 15 | onRender: function () { 16 | this.ui.error.hide(); 17 | 18 | App.Api.Nginx.Certificates.renew(this.model.get('id')) 19 | .then((result) => { 20 | this.model.set(result); 21 | setTimeout(() => { 22 | App.UI.closeModal(); 23 | }, 1000); 24 | }) 25 | .catch((err) => { 26 | this.ui.waiting.hide(); 27 | this.ui.error.text(err.message).show(); 28 | this.ui.close.prop('disabled', false); 29 | }); 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/certificates/test.ejs: -------------------------------------------------------------------------------- 1 | 16 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/dead/delete.ejs: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/dead/delete.js: -------------------------------------------------------------------------------- 1 | const Mn = require('backbone.marionette'); 2 | const App = require('../../main'); 3 | const template = require('./delete.ejs'); 4 | 5 | module.exports = Mn.View.extend({ 6 | template: template, 7 | className: 'modal-dialog', 8 | 9 | ui: { 10 | form: 'form', 11 | buttons: '.modal-footer button', 12 | cancel: 'button.cancel', 13 | save: 'button.save' 14 | }, 15 | 16 | events: { 17 | 18 | 'click @ui.save': function (e) { 19 | e.preventDefault(); 20 | 21 | App.Api.Nginx.DeadHosts.delete(this.model.get('id')) 22 | .then(() => { 23 | App.Controller.showNginxDead(); 24 | App.UI.closeModal(); 25 | }) 26 | .catch(err => { 27 | alert(err.message); 28 | this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); 29 | }); 30 | } 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/dead/list/main.ejs: -------------------------------------------------------------------------------- 1 | 2 |   3 | <%- i18n('str', 'source') %> 4 | <%- i18n('str', 'ssl') %> 5 | <%- i18n('str', 'status') %> 6 | <% if (canManage) { %> 7 |   8 | <% } %> 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/dead/list/main.js: -------------------------------------------------------------------------------- 1 | const Mn = require('backbone.marionette'); 2 | const App = require('../../../main'); 3 | const ItemView = require('./item'); 4 | const template = require('./main.ejs'); 5 | 6 | const TableBody = Mn.CollectionView.extend({ 7 | tagName: 'tbody', 8 | childView: ItemView 9 | }); 10 | 11 | module.exports = Mn.View.extend({ 12 | tagName: 'table', 13 | className: 'table table-hover table-outline table-vcenter card-table', 14 | template: template, 15 | 16 | regions: { 17 | body: { 18 | el: 'tbody', 19 | replaceElement: true 20 | } 21 | }, 22 | 23 | templateContext: { 24 | canManage: App.Cache.User.canManage('dead_hosts') 25 | }, 26 | 27 | onRender: function () { 28 | this.showChildView('body', new TableBody({ 29 | collection: this.collection 30 | })); 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/proxy/access-list-item.ejs: -------------------------------------------------------------------------------- 1 |
2 | <% if (id > 0) { %> 3 |
4 | <%- name %> 5 |
6 | <%- i18n('access-lists', 'item-count', {count: items.length || 0}) %>, <%- i18n('access-lists', 'client-count', {count: clients.length || 0}) %> – Created: <%- formatDbDate(created_on, 'Do MMMM YYYY, h:mm a') %> 7 | <% } else { %> 8 |
9 | <%- i18n('access-lists', 'public') %> 10 |
11 | <%- i18n('access-lists', 'public-sub') %> 12 | <% } %> 13 |
14 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/proxy/delete.ejs: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/proxy/delete.js: -------------------------------------------------------------------------------- 1 | const Mn = require('backbone.marionette'); 2 | const App = require('../../main'); 3 | const template = require('./delete.ejs'); 4 | 5 | module.exports = Mn.View.extend({ 6 | template: template, 7 | className: 'modal-dialog', 8 | 9 | ui: { 10 | form: 'form', 11 | buttons: '.modal-footer button', 12 | cancel: 'button.cancel', 13 | save: 'button.save' 14 | }, 15 | 16 | events: { 17 | 18 | 'click @ui.save': function (e) { 19 | e.preventDefault(); 20 | 21 | App.Api.Nginx.ProxyHosts.delete(this.model.get('id')) 22 | .then(() => { 23 | App.Controller.showNginxProxy(); 24 | App.UI.closeModal(); 25 | }) 26 | .catch(err => { 27 | alert(err.message); 28 | this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); 29 | }); 30 | } 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/proxy/list/main.ejs: -------------------------------------------------------------------------------- 1 | 2 |   3 | <%- i18n('str', 'source') %> 4 | <%- i18n('str', 'destination') %> 5 | <%- i18n('str', 'ssl') %> 6 | <%- i18n('str', 'access') %> 7 | <%- i18n('str', 'status') %> 8 | <% if (canManage) { %> 9 |   10 | <% } %> 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/proxy/list/main.js: -------------------------------------------------------------------------------- 1 | const Mn = require('backbone.marionette'); 2 | const App = require('../../../main'); 3 | const ItemView = require('./item'); 4 | const template = require('./main.ejs'); 5 | 6 | const TableBody = Mn.CollectionView.extend({ 7 | tagName: 'tbody', 8 | childView: ItemView 9 | }); 10 | 11 | module.exports = Mn.View.extend({ 12 | tagName: 'table', 13 | className: 'table table-hover table-outline table-vcenter card-table', 14 | template: template, 15 | 16 | regions: { 17 | body: { 18 | el: 'tbody', 19 | replaceElement: true 20 | } 21 | }, 22 | 23 | templateContext: { 24 | canManage: App.Cache.User.canManage('proxy_hosts') 25 | }, 26 | 27 | onRender: function () { 28 | this.showChildView('body', new TableBody({ 29 | collection: this.collection 30 | })); 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/redirection/delete.ejs: -------------------------------------------------------------------------------- 1 | 24 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/redirection/delete.js: -------------------------------------------------------------------------------- 1 | const Mn = require('backbone.marionette'); 2 | const App = require('../../main'); 3 | const template = require('./delete.ejs'); 4 | 5 | module.exports = Mn.View.extend({ 6 | template: template, 7 | className: 'modal-dialog', 8 | 9 | ui: { 10 | form: 'form', 11 | buttons: '.modal-footer button', 12 | cancel: 'button.cancel', 13 | save: 'button.save' 14 | }, 15 | 16 | events: { 17 | 18 | 'click @ui.save': function (e) { 19 | e.preventDefault(); 20 | 21 | App.Api.Nginx.RedirectionHosts.delete(this.model.get('id')) 22 | .then(() => { 23 | App.Controller.showNginxRedirection(); 24 | App.UI.closeModal(); 25 | }) 26 | .catch(err => { 27 | alert(err.message); 28 | this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); 29 | }); 30 | } 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/redirection/list/main.ejs: -------------------------------------------------------------------------------- 1 | 2 |   3 | <%- i18n('str', 'source') %> 4 | <%- i18n('redirection-hosts', 'forward-http-status-code') %> 5 | <%- i18n('redirection-hosts', 'forward-scheme') %> 6 | <%- i18n('str', 'destination') %> 7 | <%- i18n('str', 'ssl') %> 8 | <%- i18n('str', 'status') %> 9 | <% if (canManage) { %> 10 |   11 | <% } %> 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/redirection/list/main.js: -------------------------------------------------------------------------------- 1 | const Mn = require('backbone.marionette'); 2 | const App = require('../../../main'); 3 | const ItemView = require('./item'); 4 | const template = require('./main.ejs'); 5 | 6 | const TableBody = Mn.CollectionView.extend({ 7 | tagName: 'tbody', 8 | childView: ItemView 9 | }); 10 | 11 | module.exports = Mn.View.extend({ 12 | tagName: 'table', 13 | className: 'table table-hover table-outline table-vcenter card-table', 14 | template: template, 15 | 16 | regions: { 17 | body: { 18 | el: 'tbody', 19 | replaceElement: true 20 | } 21 | }, 22 | 23 | templateContext: { 24 | canManage: App.Cache.User.canManage('redirection_hosts') 25 | }, 26 | 27 | onRender: function () { 28 | this.showChildView('body', new TableBody({ 29 | collection: this.collection 30 | })); 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/stream/delete.ejs: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/stream/delete.js: -------------------------------------------------------------------------------- 1 | const Mn = require('backbone.marionette'); 2 | const App = require('../../main'); 3 | const template = require('./delete.ejs'); 4 | 5 | module.exports = Mn.View.extend({ 6 | template: template, 7 | className: 'modal-dialog', 8 | 9 | ui: { 10 | form: 'form', 11 | buttons: '.modal-footer button', 12 | cancel: 'button.cancel', 13 | save: 'button.save' 14 | }, 15 | 16 | events: { 17 | 18 | 'click @ui.save': function (e) { 19 | e.preventDefault(); 20 | 21 | App.Api.Nginx.Streams.delete(this.model.get('id')) 22 | .then(() => { 23 | App.Controller.showNginxStream(); 24 | App.UI.closeModal(); 25 | }) 26 | .catch(err => { 27 | alert(err.message); 28 | this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); 29 | }); 30 | } 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/stream/list/main.ejs: -------------------------------------------------------------------------------- 1 | 2 |   3 | <%- i18n('streams', 'incoming-port') %> 4 | <%- i18n('str', 'destination') %> 5 | <%- i18n('streams', 'protocol') %> 6 | <%- i18n('str', 'ssl') %> 7 | <%- i18n('str', 'status') %> 8 | <% if (canManage) { %> 9 |   10 | <% } %> 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /frontend/js/app/nginx/stream/list/main.js: -------------------------------------------------------------------------------- 1 | const Mn = require('backbone.marionette'); 2 | const App = require('../../../main'); 3 | const ItemView = require('./item'); 4 | const template = require('./main.ejs'); 5 | 6 | const TableBody = Mn.CollectionView.extend({ 7 | tagName: 'tbody', 8 | childView: ItemView 9 | }); 10 | 11 | module.exports = Mn.View.extend({ 12 | tagName: 'table', 13 | className: 'table table-hover table-outline table-vcenter card-table', 14 | template: template, 15 | 16 | regions: { 17 | body: { 18 | el: 'tbody', 19 | replaceElement: true 20 | } 21 | }, 22 | 23 | templateContext: { 24 | canManage: App.Cache.User.canManage('streams') 25 | }, 26 | 27 | onRender: function () { 28 | this.showChildView('body', new TableBody({ 29 | collection: this.collection 30 | })); 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /frontend/js/app/router.js: -------------------------------------------------------------------------------- 1 | const AppRouter = require('marionette.approuter'); 2 | const Controller = require('./controller'); 3 | 4 | module.exports = AppRouter.default.extend({ 5 | controller: Controller, 6 | appRoutes: { 7 | users: 'showUsers', 8 | logout: 'logout', 9 | 'nginx/proxy': 'showNginxProxy', 10 | 'nginx/redirection': 'showNginxRedirection', 11 | 'nginx/404': 'showNginxDead', 12 | 'nginx/stream': 'showNginxStream', 13 | 'nginx/access': 'showNginxAccess', 14 | 'nginx/certificates': 'showNginxCertificates', 15 | 'audit-log': 'showAuditLog', 16 | 'settings': 'showSettings', 17 | '*default': 'showDashboard' 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /frontend/js/app/settings/list/item.ejs: -------------------------------------------------------------------------------- 1 | 2 |
<%- i18n('settings', 'default-site') %>
3 |
4 | <%- i18n('settings', 'default-site-description') %> 5 |
6 | 7 | 8 |
9 | <% if (id === 'default-site') { %> 10 | <%- i18n('settings', 'default-site-' + value) %> 11 | <% } %> 12 |
13 | 14 | 15 | 21 | -------------------------------------------------------------------------------- /frontend/js/app/settings/list/item.js: -------------------------------------------------------------------------------- 1 | const Mn = require('backbone.marionette'); 2 | const App = require('../../main'); 3 | const template = require('./item.ejs'); 4 | 5 | module.exports = Mn.View.extend({ 6 | template: template, 7 | tagName: 'tr', 8 | 9 | ui: { 10 | edit: 'a.edit' 11 | }, 12 | 13 | events: { 14 | 'click @ui.edit': function (e) { 15 | e.preventDefault(); 16 | App.Controller.showSettingForm(this.model); 17 | } 18 | }, 19 | 20 | initialize: function () { 21 | this.listenTo(this.model, 'change', this.render); 22 | } 23 | }); 24 | -------------------------------------------------------------------------------- /frontend/js/app/settings/list/main.ejs: -------------------------------------------------------------------------------- 1 | 2 | <%- i18n('str', 'name') %> 3 | <%- i18n('str', 'value') %> 4 |   5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/js/app/settings/list/main.js: -------------------------------------------------------------------------------- 1 | const Mn = require('backbone.marionette'); 2 | const ItemView = require('./item'); 3 | const template = require('./main.ejs'); 4 | 5 | const TableBody = Mn.CollectionView.extend({ 6 | tagName: 'tbody', 7 | childView: ItemView 8 | }); 9 | 10 | module.exports = Mn.View.extend({ 11 | tagName: 'table', 12 | className: 'table table-hover table-outline table-vcenter card-table', 13 | template: template, 14 | 15 | regions: { 16 | body: { 17 | el: 'tbody', 18 | replaceElement: true 19 | } 20 | }, 21 | 22 | onRender: function () { 23 | this.showChildView('body', new TableBody({ 24 | collection: this.collection 25 | })); 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /frontend/js/app/settings/main.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

<%- i18n('settings', 'title') %>

5 |
6 |
7 |
8 |
9 |
10 | 11 |
12 |
13 |
14 |
15 | -------------------------------------------------------------------------------- /frontend/js/app/ui/footer/main.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 9 |
10 |
11 |
12 | <%- i18n('main', 'version', {version: getVersion()}) %> 13 | <%= i18n('footer', 'copy', {url: 'https://jc21.com?utm_source=nginx-proxy-manager'}) %> 14 | <%= i18n('footer', 'theme', {url: 'https://tabler.github.io/?utm_source=nginx-proxy-manager'}) %> 15 | <%= i18n('footer', 'translate', {url: 'https://github.com/xiaoxinpro/nginx-proxy-manager-zh'}) %> 16 |
17 |
18 | -------------------------------------------------------------------------------- /frontend/js/app/ui/footer/main.js: -------------------------------------------------------------------------------- 1 | const Mn = require('backbone.marionette'); 2 | const template = require('./main.ejs'); 3 | const Cache = require('../../cache'); 4 | 5 | module.exports = Mn.View.extend({ 6 | className: 'container', 7 | template: template, 8 | 9 | templateContext: { 10 | getVersion: function () { 11 | return Cache.version || '0.0.0'; 12 | } 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /frontend/js/app/ui/main.ejs: -------------------------------------------------------------------------------- 1 |
2 | 10 |
11 |
12 | 13 |
14 |
15 |
16 | 17 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /frontend/js/app/ui/menu/main.js: -------------------------------------------------------------------------------- 1 | const $ = require('jquery'); 2 | const Mn = require('backbone.marionette'); 3 | const Controller = require('../../controller'); 4 | const Cache = require('../../cache'); 5 | const template = require('./main.ejs'); 6 | 7 | module.exports = Mn.View.extend({ 8 | id: 'menu', 9 | className: 'header collapse d-lg-flex p-0', 10 | template: template, 11 | 12 | ui: { 13 | links: 'a' 14 | }, 15 | 16 | events: { 17 | 'click @ui.links': function (e) { 18 | let href = $(e.currentTarget).attr('href'); 19 | if (href !== '#') { 20 | e.preventDefault(); 21 | Controller.navigate(href, true); 22 | } 23 | } 24 | }, 25 | 26 | templateContext: { 27 | isAdmin: function () { 28 | return Cache.User.isAdmin(); 29 | }, 30 | 31 | canShow: function (perm) { 32 | return Cache.User.isAdmin() || Cache.User.canView(perm); 33 | } 34 | }, 35 | 36 | initialize: function () { 37 | this.listenTo(Cache.User, 'change', this.render); 38 | } 39 | }); 40 | -------------------------------------------------------------------------------- /frontend/js/app/user/delete.ejs: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /frontend/js/app/user/delete.js: -------------------------------------------------------------------------------- 1 | const Mn = require('backbone.marionette'); 2 | const template = require('./delete.ejs'); 3 | const App = require('../main'); 4 | 5 | require('jquery-serializejson'); 6 | 7 | module.exports = Mn.View.extend({ 8 | template: template, 9 | className: 'modal-dialog', 10 | 11 | ui: { 12 | form: 'form', 13 | buttons: '.modal-footer button', 14 | cancel: 'button.cancel', 15 | save: 'button.save' 16 | }, 17 | 18 | events: { 19 | 20 | 'click @ui.save': function (e) { 21 | e.preventDefault(); 22 | 23 | App.Api.Users.delete(this.model.get('id')) 24 | .then(() => { 25 | App.Controller.showUsers(); 26 | App.UI.closeModal(); 27 | }) 28 | .catch(err => { 29 | alert(err.message); 30 | this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); 31 | }); 32 | } 33 | } 34 | }); 35 | -------------------------------------------------------------------------------- /frontend/js/app/users/list/main.ejs: -------------------------------------------------------------------------------- 1 | 2 |   3 | <%- i18n('str', 'name') %> 4 | <%- i18n('str', 'email') %> 5 | <%- i18n('str', 'roles') %> 6 |   7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /frontend/js/app/users/list/main.js: -------------------------------------------------------------------------------- 1 | const Mn = require('backbone.marionette'); 2 | const ItemView = require('./item'); 3 | const template = require('./main.ejs'); 4 | 5 | const TableBody = Mn.CollectionView.extend({ 6 | tagName: 'tbody', 7 | childView: ItemView 8 | }); 9 | 10 | module.exports = Mn.View.extend({ 11 | tagName: 'table', 12 | className: 'table table-hover table-outline table-vcenter card-table', 13 | template: template, 14 | 15 | regions: { 16 | body: { 17 | el: 'tbody', 18 | replaceElement: true 19 | } 20 | }, 21 | 22 | onRender: function () { 23 | this.showChildView('body', new TableBody({ 24 | collection: this.collection 25 | })); 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /frontend/js/app/users/main.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

<%- i18n('users', 'title') %>

5 |
6 | 14 | <%- i18n('users', 'add') %> 15 |
16 |
17 |
18 |
19 |
20 |
21 | 22 |
23 |
24 | 25 |
26 |
27 | -------------------------------------------------------------------------------- /frontend/js/lib/helpers.js: -------------------------------------------------------------------------------- 1 | const numeral = require('numeral'); 2 | const moment = require('moment'); 3 | 4 | module.exports = { 5 | 6 | /** 7 | * @param {Integer} number 8 | * @returns {String} 9 | */ 10 | niceNumber: function (number) { 11 | return numeral(number).format('0,0'); 12 | }, 13 | 14 | /** 15 | * @param {String|Number} date 16 | * @param {String} format 17 | * @returns {String} 18 | */ 19 | formatDbDate: function (date, format) { 20 | if (typeof date === 'number') { 21 | return moment.unix(date).format(format); 22 | } 23 | 24 | return moment(date).format(format); 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /frontend/js/lib/marionette.js: -------------------------------------------------------------------------------- 1 | const _ = require('underscore'); 2 | const Mn = require('backbone.marionette'); 3 | const i18n = require('../app/i18n'); 4 | const Helpers = require('./helpers'); 5 | const TemplateCache = require('marionette.templatecache'); 6 | 7 | Mn.setRenderer(function (template, data, view) { 8 | data = _.clone(data); 9 | data.i18n = i18n; 10 | data.formatDbDate = Helpers.formatDbDate; 11 | 12 | return TemplateCache.default.render.call(this, template, data, view); 13 | }); 14 | 15 | module.exports = Mn; 16 | -------------------------------------------------------------------------------- /frontend/js/login.js: -------------------------------------------------------------------------------- 1 | const App = require('./login/main'); 2 | 3 | $(document).ready(() => { 4 | App.start(); 5 | }); 6 | -------------------------------------------------------------------------------- /frontend/js/login/main.js: -------------------------------------------------------------------------------- 1 | const Mn = require('backbone.marionette'); 2 | const LoginView = require('./ui/login'); 3 | 4 | const App = Mn.Application.extend({ 5 | region: '#login', 6 | UI: null, 7 | 8 | onStart: function (/*app, options*/) { 9 | this.getRegion().show(new LoginView()); 10 | } 11 | }); 12 | 13 | const app = new App(); 14 | module.exports = app; 15 | -------------------------------------------------------------------------------- /frontend/js/models/access-list.js: -------------------------------------------------------------------------------- 1 | const Backbone = require('backbone'); 2 | 3 | const model = Backbone.Model.extend({ 4 | idAttribute: 'id', 5 | 6 | defaults: function () { 7 | return { 8 | id: undefined, 9 | created_on: null, 10 | modified_on: null, 11 | name: '', 12 | items: [], 13 | clients: [], 14 | // The following are expansions: 15 | owner: null 16 | }; 17 | } 18 | }); 19 | 20 | module.exports = { 21 | Model: model, 22 | Collection: Backbone.Collection.extend({ 23 | model: model 24 | }) 25 | }; 26 | -------------------------------------------------------------------------------- /frontend/js/models/audit-log.js: -------------------------------------------------------------------------------- 1 | const Backbone = require('backbone'); 2 | 3 | const model = Backbone.Model.extend({ 4 | idAttribute: 'id', 5 | 6 | defaults: function () { 7 | return { 8 | name: '' 9 | }; 10 | } 11 | }); 12 | 13 | module.exports = { 14 | Model: model, 15 | Collection: Backbone.Collection.extend({ 16 | model: model 17 | }) 18 | }; 19 | -------------------------------------------------------------------------------- /frontend/js/models/certificate.js: -------------------------------------------------------------------------------- 1 | const Backbone = require('backbone'); 2 | 3 | const model = Backbone.Model.extend({ 4 | idAttribute: 'id', 5 | 6 | defaults: function () { 7 | return { 8 | id: undefined, 9 | created_on: null, 10 | modified_on: null, 11 | provider: '', 12 | nice_name: '', 13 | domain_names: [], 14 | expires_on: null, 15 | meta: {}, 16 | // The following are expansions: 17 | owner: null, 18 | proxy_hosts: [], 19 | redirection_hosts: [], 20 | dead_hosts: [] 21 | }; 22 | }, 23 | 24 | /** 25 | * @returns {Boolean} 26 | */ 27 | hasSslFiles: function () { 28 | let meta = this.get('meta'); 29 | return typeof meta['certificate'] !== 'undefined' && meta['certificate'] && typeof meta['certificate_key'] !== 'undefined' && meta['certificate_key']; 30 | } 31 | }); 32 | 33 | module.exports = { 34 | Model: model, 35 | Collection: Backbone.Collection.extend({ 36 | model: model 37 | }) 38 | }; 39 | -------------------------------------------------------------------------------- /frontend/js/models/dead-host.js: -------------------------------------------------------------------------------- 1 | const Backbone = require('backbone'); 2 | 3 | const model = Backbone.Model.extend({ 4 | idAttribute: 'id', 5 | 6 | defaults: function () { 7 | return { 8 | id: undefined, 9 | created_on: null, 10 | modified_on: null, 11 | domain_names: [], 12 | certificate_id: 0, 13 | ssl_forced: false, 14 | http2_support: false, 15 | hsts_enabled: false, 16 | hsts_subdomains: false, 17 | enabled: true, 18 | meta: {}, 19 | advanced_config: '', 20 | // The following are expansions: 21 | owner: null, 22 | certificate: null 23 | }; 24 | } 25 | }); 26 | 27 | module.exports = { 28 | Model: model, 29 | Collection: Backbone.Collection.extend({ 30 | model: model 31 | }) 32 | }; 33 | -------------------------------------------------------------------------------- /frontend/js/models/proxy-host-location.js: -------------------------------------------------------------------------------- 1 | const Backbone = require('backbone'); 2 | 3 | const model = Backbone.Model.extend({ 4 | idAttribute: 'id', 5 | 6 | defaults: function() { 7 | return { 8 | opened: false, 9 | path: '', 10 | advanced_config: '', 11 | forward_scheme: 'http', 12 | forward_host: '', 13 | forward_port: '80' 14 | } 15 | }, 16 | 17 | toJSON() { 18 | const r = Object.assign({}, this.attributes); 19 | delete r.opened; 20 | return r; 21 | }, 22 | 23 | toggleVisibility: function () { 24 | this.save({ 25 | opened: !this.get('opened') 26 | }); 27 | } 28 | }) 29 | 30 | module.exports = { 31 | Model: model, 32 | Collection: Backbone.Collection.extend({ 33 | model 34 | }) 35 | } -------------------------------------------------------------------------------- /frontend/js/models/setting.js: -------------------------------------------------------------------------------- 1 | const Backbone = require('backbone'); 2 | 3 | const model = Backbone.Model.extend({ 4 | idAttribute: 'id', 5 | 6 | defaults: function () { 7 | return { 8 | id: undefined, 9 | name: '', 10 | description: '', 11 | value: null, 12 | meta: [] 13 | }; 14 | } 15 | }); 16 | 17 | module.exports = { 18 | Model: model, 19 | Collection: Backbone.Collection.extend({ 20 | model: model 21 | }) 22 | }; 23 | -------------------------------------------------------------------------------- /frontend/js/models/stream.js: -------------------------------------------------------------------------------- 1 | const Backbone = require('backbone'); 2 | 3 | const model = Backbone.Model.extend({ 4 | idAttribute: 'id', 5 | 6 | defaults: function () { 7 | return { 8 | id: undefined, 9 | created_on: null, 10 | modified_on: null, 11 | incoming_port: null, 12 | forwarding_host: null, 13 | forwarding_port: null, 14 | tcp_forwarding: true, 15 | udp_forwarding: false, 16 | enabled: true, 17 | meta: {}, 18 | certificate_id: 0, 19 | domain_names: [], 20 | // The following are expansions: 21 | owner: null, 22 | certificate: null 23 | }; 24 | } 25 | }); 26 | 27 | module.exports = { 28 | Model: model, 29 | Collection: Backbone.Collection.extend({ 30 | model: model 31 | }) 32 | }; 33 | -------------------------------------------------------------------------------- /frontend/scss/custom.scss: -------------------------------------------------------------------------------- 1 | $primary-color: #2bcbba; 2 | 3 | .loader { 4 | color: $primary-color; 5 | } 6 | 7 | a { 8 | color: $primary-color; 9 | } 10 | 11 | a:hover { 12 | color: darken($primary-color, 10%); 13 | } 14 | 15 | .dropdown-header { 16 | padding-left: 1rem; 17 | } 18 | 19 | .dropdown-item.active, .dropdown-item:active { 20 | background-color: $primary-color; 21 | } 22 | 23 | .custom-switch-input:checked ~ .custom-switch-indicator { 24 | background: $primary-color; 25 | } 26 | 27 | .min-100 { 28 | min-height: 100px; 29 | } 30 | 31 | .card-options .dropdown-menu a:not(.btn) { 32 | margin-left: 0; 33 | } 34 | 35 | .wrap { 36 | display: flex; 37 | flex-wrap: wrap; 38 | } 39 | 40 | .col-login { 41 | max-width: 48rem; 42 | } -------------------------------------------------------------------------------- /frontend/scss/styles.scss: -------------------------------------------------------------------------------- 1 | @import "~tabler-ui/dist/assets/css/dashboard"; 2 | @import "tabler-extra"; 3 | @import "fonts"; 4 | @import "selectize"; 5 | @import "custom"; 6 | 7 | /* Before any JS content is loaded */ 8 | #app > .loader, #login > .loader, .container > .loader { 9 | position: absolute; 10 | left: 49%; 11 | top: 40%; 12 | display: block; 13 | } 14 | 15 | .no-js-warning { 16 | margin-top: 100px; 17 | } 18 | -------------------------------------------------------------------------------- /global/README.md: -------------------------------------------------------------------------------- 1 | # certbot-dns-plugins 2 | 3 | This file contains info about available Certbot DNS plugins. 4 | This only works for plugins which use the standard argument structure, so: 5 | --authenticator ---credentials ---propagation-seconds 6 | 7 | File Structure: 8 | 9 | ```json 10 | { 11 | "cloudflare": { 12 | "display_name": "Name displayed to the user", 13 | "package_name": "Package name in PyPi repo", 14 | "version_requirement": "Optional package version requirements (e.g. ==1.3 or >=1.2,<2.0, see https://www.python.org/dev/peps/pep-0440/#version-specifiers)", 15 | "dependencies": "Additional dependencies, space separated (as you would pass it to pip install)", 16 | "credentials": "Template of the credentials file", 17 | "full_plugin_name": "The full plugin name as used in the commandline with certbot, e.g. 'dns-njalla'" 18 | }, 19 | ... 20 | } 21 | ``` 22 | -------------------------------------------------------------------------------- /scripts/.common.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Colors 4 | BLUE='\E[1;34m' 5 | CYAN='\E[1;36m' 6 | GREEN='\E[1;32m' 7 | RED='\E[1;31m' 8 | RESET='\E[0m' 9 | YELLOW='\E[1;33m' 10 | 11 | export BLUE CYAN GREEN RED RESET YELLOW 12 | 13 | # Docker Compose 14 | COMPOSE_PROJECT_NAME="npm2dev" 15 | COMPOSE_FILE="docker/docker-compose.dev.yml" 16 | 17 | export COMPOSE_FILE COMPOSE_PROJECT_NAME 18 | 19 | # $1: container_name 20 | get_container_ip () { 21 | local container_name=$1 22 | local container 23 | local ip 24 | container=$(docker-compose ps --all -q "${container_name}" | tail -n1) 25 | ip=$(docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$container") 26 | echo "$ip" 27 | } 28 | -------------------------------------------------------------------------------- /scripts/build-zh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 4 | . "$DIR/ci/frontend-build" 5 | 6 | cd "${DIR}/../.." 7 | 8 | docker build -t chishin/nginx-proxy-manager-zh:2.12.3 -f docker/Dockerfile-zh . 9 | -------------------------------------------------------------------------------- /scripts/buildx: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 4 | . "$DIR/.common.sh" 5 | 6 | echo -e "${BLUE}❯ ${CYAN}Building docker multiarch: ${YELLOW}${*}${RESET}" 7 | 8 | DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 9 | cd "${DIR}/.." || exit 1 10 | 11 | # determine commit if not already set 12 | if [ "$BUILD_COMMIT" == "" ]; then 13 | BUILD_COMMIT=$(git log -n 1 --format=%h) 14 | fi 15 | 16 | # Buildx Builder 17 | docker buildx create --name "${BUILDX_NAME:-npm}" || echo 18 | docker buildx use "${BUILDX_NAME:-npm}" 19 | 20 | docker buildx build \ 21 | --build-arg BUILD_VERSION="${BUILD_VERSION:-dev}" \ 22 | --build-arg BUILD_COMMIT="${BUILD_COMMIT:-notset}" \ 23 | --build-arg BUILD_DATE="$(date '+%Y-%m-%d %T %Z')" \ 24 | --build-arg GOPROXY="${GOPROXY:-}" \ 25 | --build-arg GOPRIVATE="${GOPRIVATE:-}" \ 26 | --platform linux/amd64,linux/arm64,linux/arm/7 \ 27 | --progress plain \ 28 | --pull \ 29 | -f docker/Dockerfile \ 30 | $@ \ 31 | . 32 | 33 | rc=$? 34 | docker buildx rm "${BUILDX_NAME:-npm}" 35 | echo -e "${BLUE}❯ ${GREEN}Multiarch build Complete${RESET}" 36 | exit $rc 37 | -------------------------------------------------------------------------------- /scripts/buildx-zh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 4 | . "$DIR/ci/frontend-build" 5 | 6 | cd "${DIR}/../.." 7 | 8 | # Buildx Builder 9 | docker buildx create --name "Buildx-NPM" || echo 10 | docker buildx use "Buildx-NPM" 11 | 12 | if [ "${BUILD_TAG:-0}" != 0 ]; then 13 | docker buildx build -f docker/Dockerfile-zh $BUILD_TAG --platform $BUILD_PLATFORM . --push 14 | else 15 | docker buildx build -f docker/Dockerfile-zh -t "chishin/nginx-proxy-manager-zh:dev" --platform linux/amd64,linux/arm64,linux/arm/7 . --push 16 | fi 17 | 18 | docker buildx rm "Buildx-NPM" 19 | 20 | echo "Multiarch build Complete" -------------------------------------------------------------------------------- /scripts/ci/frontend-build: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 4 | . "$DIR/../.common.sh" 5 | 6 | DOCKER_IMAGE=nginxproxymanager/nginx-full:certbot-node 7 | 8 | # Ensure docker exists 9 | if hash docker 2>/dev/null; then 10 | docker pull "${DOCKER_IMAGE}" 11 | cd "${DIR}/../.." 12 | echo -e "${BLUE}❯ ${CYAN}Building Frontend ...${RESET}" 13 | 14 | docker run --rm \ 15 | -e CI=true \ 16 | -e NODE_OPTIONS=--openssl-legacy-provider \ 17 | -v "$(pwd)/frontend:/app/frontend" \ 18 | -v "$(pwd)/global:/app/global" \ 19 | -w /app/frontend "${DOCKER_IMAGE}" \ 20 | sh -c "yarn install && yarn build && yarn build && chown -R $(id -u):$(id -g) /app/frontend" 21 | 22 | echo -e "${BLUE}❯ ${GREEN}Building Frontend Complete${RESET}" 23 | else 24 | echo -e "${RED}❯ docker command is not available${RESET}" 25 | fi 26 | -------------------------------------------------------------------------------- /scripts/ci/test-and-build: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 4 | . "$DIR/../.common.sh" 5 | 6 | TESTING_IMAGE=nginxproxymanager/nginx-full:certbot-node 7 | docker pull "${TESTING_IMAGE}" 8 | 9 | # Test 10 | echo -e "${BLUE}❯ ${CYAN}Testing backend ...${RESET}" 11 | docker run --rm \ 12 | -v "$(pwd)/backend:/app" \ 13 | -v "$(pwd)/global:/app/global" \ 14 | -w /app \ 15 | "${TESTING_IMAGE}" \ 16 | sh -c 'yarn install && yarn eslint . && rm -rf node_modules' 17 | echo -e "${BLUE}❯ ${GREEN}Testing Complete${RESET}" 18 | 19 | # Build 20 | echo -e "${BLUE}❯ ${CYAN}Building ...${RESET}" 21 | docker build --pull --no-cache --compress \ 22 | -t "${IMAGE:-nginx-proxy-manager}:${BRANCH_LOWER:-unknown}-ci-${BUILD_NUMBER:-0000}" \ 23 | -f docker/Dockerfile \ 24 | --progress=plain \ 25 | --build-arg TARGETPLATFORM=linux/amd64 \ 26 | --build-arg BUILDPLATFORM=linux/amd64 \ 27 | --build-arg BUILD_VERSION="${BUILD_VERSION:-unknown}" \ 28 | --build-arg BUILD_COMMIT="${BUILD_COMMIT:-unknown}" \ 29 | --build-arg BUILD_DATE="$(date '+%Y-%m-%d %T %Z')" \ 30 | . 31 | echo -e "${BLUE}❯ ${GREEN}Building Complete${RESET}" 32 | -------------------------------------------------------------------------------- /scripts/cypress-dev: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 4 | . "$DIR/.common.sh" 5 | 6 | # Ensure docker-compose exists 7 | if hash docker-compose 2>/dev/null; then 8 | cd "${DIR}/.." 9 | rm -rf "$DIR/../test/results" 10 | docker-compose up --build cypress 11 | else 12 | echo -e "${RED}❯ docker-compose command is not available${RESET}" 13 | fi 14 | -------------------------------------------------------------------------------- /scripts/destroy-dev: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 4 | . "$DIR/.common.sh" 5 | 6 | # Ensure docker-compose exists 7 | # Make sure docker exists 8 | if hash docker-compose 2>/dev/null; then 9 | cd "${DIR}/.." 10 | echo -e "${BLUE}❯ ${CYAN}Destroying Dev Stack ...${RESET}" 11 | docker-compose down --remove-orphans --volumes 12 | else 13 | echo -e "${RED}❯ docker-compose command is not available${RESET}" 14 | fi 15 | -------------------------------------------------------------------------------- /scripts/docs-build: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 4 | . "$DIR/.common.sh" 5 | 6 | # Ensure docker-compose exists 7 | if hash docker 2>/dev/null; then 8 | cd "${DIR}/.." 9 | echo -e "${BLUE}❯ ${CYAN}Building Docs ...${RESET}" 10 | docker run --rm -e CI=true -v "$(pwd)/docs:/app/docs" -w /app/docs node:alpine sh -c "yarn set version berry && yarn install && yarn build && chown -R $(id -u):$(id -g) /app/docs" 11 | echo -e "${BLUE}❯ ${GREEN}Building Docs Complete${RESET}" 12 | else 13 | echo -e "${RED}❯ docker command is not available${RESET}" 14 | fi 15 | -------------------------------------------------------------------------------- /scripts/stop-dev: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 4 | . "$DIR/.common.sh" 5 | 6 | # Ensure docker-compose exists 7 | # Make sure docker exists 8 | if hash docker-compose 2>/dev/null; then 9 | cd "${DIR}/.." 10 | echo -e "${BLUE}❯ ${CYAN}Stopping Dev Stack ...${RESET}" 11 | docker-compose down --remove-orphans 12 | else 13 | echo -e "${RED}❯ docker-compose command is not available${RESET}" 14 | fi 15 | -------------------------------------------------------------------------------- /scripts/wait-healthy: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" 4 | . "$DIR/.common.sh" 5 | 6 | if [ "$1" == "" ]; then 7 | echo "Waits for a docker container to be healthy." 8 | echo "Usage: $0 docker-container" 9 | exit 1 10 | fi 11 | 12 | SERVICE=$1 13 | LOOPCOUNT=0 14 | HEALTHY= 15 | LIMIT=${2:-90} 16 | 17 | echo -e "${BLUE}❯ ${CYAN}Waiting for healthy: ${YELLOW}${SERVICE}${RESET}" 18 | 19 | until [ "${HEALTHY}" = "healthy" ]; do 20 | echo -n "." 21 | sleep 1 22 | HEALTHY="$(docker inspect -f '{{.State.Health.Status}}' $SERVICE)" 23 | ((LOOPCOUNT++)) 24 | 25 | if [ "$LOOPCOUNT" == "$LIMIT" ]; then 26 | echo -e "${BLUE}❯ ${RED}Timed out waiting for healthy${RESET}" 27 | docker logs --tail 50 "$SERVICE" 28 | exit 1 29 | fi 30 | done 31 | 32 | echo "" 33 | echo -e "${BLUE}❯ ${GREEN}Healthy!${RESET}" 34 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | results/* 2 | cypress/results/* 3 | -------------------------------------------------------------------------------- /test/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 160, 3 | "tabWidth": 4, 4 | "useTabs": true, 5 | "semi": true, 6 | "singleQuote": true, 7 | "bracketSpacing": true, 8 | "jsxBracketSameLine": true, 9 | "trailingComma": "all", 10 | "proseWrap": "always" 11 | } 12 | -------------------------------------------------------------------------------- /test/cypress/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM cypress/included:14.0.1 2 | 3 | # Disable Cypress CLI colors 4 | ENV FORCE_COLOR=0 5 | ENV NO_COLOR=1 6 | 7 | # testssl.sh and mkcert 8 | RUN wget "https://github.com/testssl/testssl.sh/archive/refs/tags/v3.2rc4.tar.gz" -O /tmp/testssl.tgz -q \ 9 | && tar -xzf /tmp/testssl.tgz -C /tmp \ 10 | && mv /tmp/testssl.sh-3.2rc4 /testssl \ 11 | && rm /tmp/testssl.tgz \ 12 | && apt-get update \ 13 | && apt-get install -y bsdmainutils curl dnsutils \ 14 | && apt-get clean \ 15 | && rm -rf /var/lib/apt/lists/* \ 16 | && wget "https://github.com/FiloSottile/mkcert/releases/download/v1.4.4/mkcert-v1.4.4-linux-amd64" -O /bin/mkcert \ 17 | && chmod +x /bin/mkcert 18 | 19 | COPY --chown=1000 ./test /test 20 | WORKDIR /test 21 | RUN yarn install && yarn cache clean 22 | ENTRYPOINT [] 23 | CMD ["cypress", "run"] 24 | -------------------------------------------------------------------------------- /test/cypress/config/ci.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress'); 2 | 3 | module.exports = defineConfig({ 4 | requestTimeout: 30000, 5 | defaultCommandTimeout: 20000, 6 | reporter: 'cypress-multi-reporters', 7 | reporterOptions: { 8 | configFile: 'multi-reporter.json' 9 | }, 10 | video: true, 11 | videosFolder: 'results/videos', 12 | screenshotsFolder: 'results/screenshots', 13 | e2e: { 14 | setupNodeEvents(on, config) { 15 | return require("../plugins/index.js")(on, config); 16 | }, 17 | env: { 18 | swaggerBase: '{{baseUrl}}/api/schema?ts=' + Date.now(), 19 | }, 20 | baseUrl: 'http://fullstack:81', 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /test/cypress/e2e/api/Health.cy.js: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | describe('Basic API checks', () => { 4 | it('Should return a valid health payload', function () { 5 | cy.task('backendApiGet', { 6 | path: '/api/', 7 | }).then((data) => { 8 | // Check the swagger schema: 9 | cy.validateSwaggerSchema('get', 200, '/', data); 10 | }); 11 | }); 12 | 13 | it('Should return a valid schema payload', function () { 14 | cy.task('backendApiGet', { 15 | path: '/api/schema?ts=' + Date.now(), 16 | }).then((data) => { 17 | expect(data.openapi).to.be.equal('3.1.0'); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/cypress/plugins/backendApi/logger.js: -------------------------------------------------------------------------------- 1 | const _ = require("lodash"); 2 | 3 | module.exports = function() { 4 | let arr = _.values(arguments); 5 | arr.unshift('[Backend API]'); 6 | console.log.apply(null, arr); 7 | }; 8 | -------------------------------------------------------------------------------- /test/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | const { SwaggerValidation } = require('@jc21/cypress-swagger-validation'); 2 | 3 | module.exports = (on, config) => { 4 | // Replace swaggerBase config var wildcard 5 | if (typeof config.env.swaggerBase !== 'undefined') { 6 | config.env.swaggerBase = config.env.swaggerBase.replace('{{baseUrl}}', config.baseUrl); 7 | } 8 | 9 | // Plugin Events 10 | on('task', SwaggerValidation(config)); 11 | on('task', require('./backendApi/task')(config)); 12 | on('task', { 13 | log(message) { 14 | console.log(message); 15 | return null; 16 | } 17 | }); 18 | 19 | return config; 20 | }; 21 | -------------------------------------------------------------------------------- /test/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | import './commands'; 2 | 3 | Cypress.on('uncaught:exception', (/*err, runnable*/) => { 4 | // returning false here prevents Cypress from 5 | // failing the test 6 | return false; 7 | }); 8 | -------------------------------------------------------------------------------- /test/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ 3 | "./node_modules/cypress", 4 | "cypress/**/*.js" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /test/multi-reporter.json: -------------------------------------------------------------------------------- 1 | { 2 | "reporterEnabled": "spec, mocha-junit-reporter", 3 | "mochaJunitReporterReporterOptions": { 4 | "jenkinsMode": true, 5 | "rootSuiteTitle": "Cypress.npm", 6 | "jenkinsClassnamePrefix": "Cypress.npm.", 7 | "mochaFile": "results/junit/cypress.npm.[hash].xml" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "npm-test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "dependencies": { 7 | "@jc21/cypress-swagger-validation": "^0.3.2", 8 | "axios": "^1.7.9", 9 | "cypress": "^14.0.1", 10 | "cypress-multi-reporters": "^2.0.5", 11 | "cypress-wait-until": "^3.0.2", 12 | "eslint": "^9.19.0", 13 | "eslint-plugin-align-assignments": "^1.1.2", 14 | "eslint-plugin-chai-friendly": "^1.0.1", 15 | "eslint-plugin-cypress": "^4.1.0", 16 | "form-data": "^4.0.1", 17 | "lodash": "^4.17.21", 18 | "mocha": "^11.1.0", 19 | "mocha-junit-reporter": "^2.2.1" 20 | }, 21 | "scripts": { 22 | "cypress": "HTTP_PROXY=127.0.0.1:8128 HTTPS_PROXY=127.0.0.1:8128 cypress open --config-file=cypress/config/ci.js", 23 | "cypress:headless": "HTTP_PROXY=127.0.0.1:8128 HTTPS_PROXY=127.0.0.1:8128 cypress run --config-file=cypress/config/ci.js" 24 | }, 25 | "author": "", 26 | "license": "ISC" 27 | } 28 | --------------------------------------------------------------------------------