Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76f09db610 | ||
|
|
d51602b150 | ||
|
|
af2de98c41 | ||
|
|
0d832092a5 | ||
|
|
52a7b7dca1 | ||
|
|
8e042ba79f | ||
|
|
79a4128b78 | ||
|
|
fc1adb64a3 | ||
|
|
a6b7a7dd9d | ||
|
|
dc2c05aeb3 | ||
|
|
0c15125830 | ||
|
|
e48695401e | ||
|
|
7211bf3500 | ||
|
|
f7516304cb | ||
|
|
a89410ddd7 | ||
|
|
d08f1498c7 | ||
|
|
e703a9adc4 | ||
|
|
5fd8a30508 | ||
|
|
b7ca25a574 | ||
|
|
063950cbb4 | ||
|
|
f7d72e7cc5 | ||
|
|
f2e8ab5202 | ||
|
|
6bb9a90103 | ||
|
|
79a0deedf7 | ||
|
|
a47d401d86 | ||
|
|
33bf6d412d | ||
|
|
c94cbb235b | ||
|
|
553a69a9ef | ||
|
|
7e9cc32f97 | ||
|
|
03c70e3902 | ||
|
|
c354238c35 | ||
|
|
dff978a63c | ||
|
|
11955e5dff | ||
|
|
f53bf88f4d | ||
|
|
ee1f7ba551 | ||
|
|
586dfd36a9 | ||
|
|
8b256c33e9 | ||
|
|
2bf9e9b213 | ||
|
|
7330d0441c | ||
|
|
84886383a7 | ||
|
|
1e22574000 | ||
|
|
32a74d9781 | ||
|
|
aa66badcd8 | ||
|
|
840a720d09 | ||
|
|
ef0ec0b188 | ||
|
|
387f8d9424 | ||
|
|
6008a666dd | ||
|
|
52c32b473d | ||
|
|
43e0911ce4 | ||
|
|
0242187db0 | ||
|
|
f6a9c5865b | ||
|
|
ba2f714b55 | ||
|
|
cd32dbdc4d | ||
|
|
ebcd2a209c | ||
|
|
2a6d0d8b32 | ||
|
|
661b1005ee | ||
|
|
d099b00463 | ||
|
|
2da9e0330b | ||
|
|
9875a7650d | ||
|
|
6694df668e | ||
|
|
6aa629864f | ||
|
|
d3ba40e066 | ||
|
|
868d0b08ad | ||
|
|
31500c225d | ||
|
|
13cfa340de | ||
|
|
673082ab5b | ||
|
|
70538af311 | ||
|
|
a38fb8d311 | ||
|
|
c8e6010ac3 | ||
|
|
5fafeff7c0 | ||
|
|
edb56c285f | ||
|
|
ce55f8ebcd | ||
|
|
f4e37dff1f | ||
|
|
982c16a86d | ||
|
|
5dbfd63477 | ||
|
|
f2e3a3dd81 | ||
|
|
5af25c2d9c | ||
|
|
f8fca5bd19 | ||
|
|
0703c6589b | ||
|
|
b29bc1a66e | ||
|
|
9bb4476366 | ||
|
|
a68f993571 | ||
|
|
ea4604ffa4 | ||
|
|
9cb9512bfa | ||
|
|
7bb34a7273 | ||
|
|
6719cfc6a0 | ||
|
|
b5a26574ec | ||
|
|
a0fb4510f7 | ||
|
|
dc5e71f28c | ||
|
|
0173dd6b49 | ||
|
|
980097683f | ||
|
|
e49af3cc87 | ||
|
|
2fe460557a | ||
|
|
f06c53330a | ||
|
|
6ed6210a7b | ||
|
|
dad08083c6 | ||
|
|
8b24059e3b | ||
|
|
6537fb248e | ||
|
|
738afde96e | ||
|
|
dc84b4b94b | ||
|
|
41c32ebf0a | ||
|
|
04b43d072c | ||
|
|
7d58c579fa | ||
|
|
c17475968f | ||
|
|
18b3404f46 | ||
|
|
b66fd9d3ae | ||
|
|
c869d5a42b | ||
|
|
ff066d5626 | ||
|
|
a6f8b7dd1d | ||
|
|
f7c2219680 | ||
|
|
1ad78af5a0 | ||
|
|
26a2bcab42 | ||
|
|
cea69e4ba5 | ||
|
|
69d94b4a53 | ||
|
|
218e32bf15 | ||
|
|
a03c47932e | ||
|
|
871787ac95 | ||
|
|
d161b944b1 | ||
|
|
82a326ea0b | ||
|
|
986c4cae6c | ||
|
|
65b905789f | ||
|
|
7cfac7354d | ||
|
|
34374cbe09 | ||
|
|
1635e512c7 | ||
|
|
40f39a7f19 | ||
|
|
1b782997ef | ||
|
|
cafb614b80 | ||
|
|
9e4c92a066 | ||
|
|
10e33b39d7 | ||
|
|
d88f0b2618 | ||
|
|
4a9ad2baf7 | ||
|
|
15896132ff | ||
|
|
604b32ffbf | ||
|
|
eb67b3bfb6 | ||
|
|
f1e95c9c52 | ||
|
|
d6e1d15996 | ||
|
|
99886f3111 | ||
|
|
6b2d9a77a3 | ||
|
|
d37ab95ed8 | ||
|
|
e869518dd1 | ||
|
|
37712ba2a4 | ||
|
|
a714b41fd6 | ||
|
|
b87bedc347 | ||
|
|
a62068275d | ||
|
|
cbcbd95812 | ||
|
|
278b1c92b3 | ||
|
|
c1d09eaceb | ||
|
|
9c509f30de | ||
|
|
c85b11ee33 | ||
|
|
cd5ef390b9 | ||
|
|
d49cab1c0e | ||
|
|
dea7007802 | ||
|
|
7e5f8d824b | ||
|
|
33b1a993ec | ||
|
|
67d40e186f | ||
|
|
52be66c43e | ||
|
|
ec46cabcd4 | ||
|
|
a7a9cc3acb | ||
|
|
020b3ebb33 | ||
|
|
c1c4baf389 | ||
|
|
672b5d6dd9 | ||
|
|
cd230b5878 | ||
|
|
a8f35062af | ||
|
|
da5955412d | ||
|
|
adb27fe67d | ||
|
|
d874af8692 | ||
|
|
0844dade98 | ||
|
|
71d59516e8 | ||
|
|
06e220e184 | ||
|
|
dc53647e76 | ||
|
|
4c04e89483 | ||
|
|
7241869a9e | ||
|
|
94f6191a21 | ||
|
|
cac52dd0ff | ||
|
|
906f177960 | ||
|
|
f52afced5d | ||
|
|
e8224ff0af | ||
|
|
a4fa83d0ce | ||
|
|
770716ebf8 | ||
|
|
f1067d3308 | ||
|
|
85c1a935ea | ||
|
|
51ef7f3b86 | ||
|
|
846b94f7e8 | ||
|
|
19e24c7e7e | ||
|
|
c1bc471dac | ||
|
|
608dc0b6bf | ||
|
|
0dbf268f37 | ||
|
|
84fb2729af | ||
|
|
c7437ddf8f | ||
|
|
627f43c729 | ||
|
|
fc4c5aac86 | ||
|
|
aff390f35d | ||
|
|
5f5a3870e4 | ||
|
|
40f363bd4f | ||
|
|
678fdd22c6 | ||
|
|
6c3cc83d66 | ||
|
|
5916fd5bee | ||
|
|
f105673904 | ||
|
|
a37d0b88d6 | ||
|
|
43bc2a743e | ||
|
|
269545256a | ||
|
|
e5df45e9ef | ||
|
|
5601dd14fc | ||
|
|
3e5655cfcd | ||
|
|
a90af83270 | ||
|
|
619a8e5acc | ||
|
|
6dcdefb57e | ||
|
|
787616010b | ||
|
|
5891c291d2 | ||
|
|
41a2a41e67 | ||
|
|
379099d7ed | ||
|
|
dbeab93c02 | ||
|
|
010cb562a0 | ||
|
|
7ff2fc1900 | ||
|
|
1c189a1888 | ||
|
|
f3c46487f6 | ||
|
|
fcca481d1b | ||
|
|
c59c237000 | ||
|
|
a62b6de9f2 | ||
|
|
d92cc953e1 | ||
|
|
1b6412688b | ||
|
|
1d14f72ba5 | ||
|
|
099243aff7 | ||
|
|
5fe12f69ba | ||
|
|
011191f645 | ||
|
|
eeab425ea4 | ||
|
|
13fbc53591 | ||
|
|
3f2aec7b86 | ||
|
|
09a3d65aa1 | ||
|
|
c910cf9512 | ||
|
|
304c51aae8 | ||
|
|
b552eb90ed | ||
|
|
b78ef9bcd3 | ||
|
|
7c67fafedf | ||
|
|
b7402d47a0 | ||
|
|
21f63e3db3 | ||
|
|
232b5b759a | ||
|
|
054742539f | ||
|
|
2b6a617599 | ||
|
|
187d21a0d5 |
@@ -0,0 +1,31 @@
|
||||
<!-- WARNING: Any PR without a description will be closed. The title is not enough! -->
|
||||
|
||||
<!-- ANOTHER WARNING: Don't go creating a duplicate PR! Check that someone hasn't already created something that tackles your fix and if so, help them first -->
|
||||
|
||||
## Why
|
||||
|
||||
<!-- Provide a brief description of WHY you are making your changes -->
|
||||
|
||||
<!-- Consider if you are changing the API, then go in to detail why and/or if
|
||||
you change will break API for existing users -->
|
||||
|
||||
## Type of Change
|
||||
|
||||
<!-- Mark the relevant options with an "x" -->
|
||||
|
||||
- [ ] Bug fix (non-breaking change that fixes an issue)
|
||||
- [ ] New feature (non-breaking change that adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
||||
- [ ] Documentation update
|
||||
- [ ] Code refactoring
|
||||
- [ ] API changes
|
||||
- [ ] Performance improvement
|
||||
- [ ] Test addition or update
|
||||
|
||||
## AI Usage
|
||||
|
||||
<!-- Mark the relevant options with an "x" -->
|
||||
|
||||
- [ ] AI was used to write this
|
||||
- [ ] AI was used to review this
|
||||
|
||||
@@ -8,3 +8,5 @@ test/node_modules
|
||||
*/node_modules
|
||||
docker/dev/dnsrouter-config.json.tmp
|
||||
docker/dev/resolv.conf
|
||||
.claude
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<p align="center">
|
||||
<img src="https://nginxproxymanager.com/github.png">
|
||||
<br><br>
|
||||
<img src="https://img.shields.io/badge/version-2.13.7-green.svg?style=for-the-badge">
|
||||
<img src="https://img.shields.io/badge/version-2.15.1-green.svg?style=for-the-badge">
|
||||
<a href="https://hub.docker.com/repository/docker/jc21/nginx-proxy-manager">
|
||||
<img src="https://img.shields.io/docker/stars/jc21/nginx-proxy-manager.svg?style=for-the-badge">
|
||||
</a>
|
||||
@@ -10,7 +10,7 @@
|
||||
</a>
|
||||
</p>
|
||||
|
||||
This project comes as a pre-built docker image that enables you to easily forward to your websites
|
||||
This project comes as a pre-built Docker image that enables you to easily forward to your websites
|
||||
running at home or otherwise, including free SSL, without having to know too much about Nginx or Letsencrypt.
|
||||
|
||||
- [Quick Setup](#quick-setup)
|
||||
@@ -20,9 +20,9 @@ running at home or otherwise, including free SSL, without having to know too muc
|
||||
## Project Goal
|
||||
|
||||
I created this project to fill a personal need to provide users with an easy way to accomplish reverse
|
||||
proxying hosts with SSL termination and it had to be so easy that a monkey could do it. This goal hasn't changed.
|
||||
While there might be advanced options they are optional and the project should be as simple as possible
|
||||
so that the barrier for entry here is low.
|
||||
proxying hosts with SSL termination, and it had to be so easy that a monkey could do it. This goal hasn't changed.
|
||||
While there might be advanced options, they are optional, and the project should be as simple as possible
|
||||
so that the barrier to entry here is low.
|
||||
|
||||
<a href="https://www.buymeacoffee.com/jc21" target="_blank"><img src="http://public.jc21.com/github/by-me-a-coffee.png" alt="Buy Me A Coffee" style="height: 51px !important;width: 217px !important;" ></a>
|
||||
|
||||
@@ -30,29 +30,32 @@ so that the barrier for entry here is low.
|
||||
## Features
|
||||
|
||||
- Beautiful and Secure Admin Interface based on [Tabler](https://tabler.github.io/)
|
||||
- Easily create forwarding domains, redirections, streams and 404 hosts without knowing anything about Nginx
|
||||
- Easily create forwarding domains, redirections, streams, and 404 hosts without knowing anything about Nginx
|
||||
- Free SSL using Let's Encrypt or provide your own custom SSL certificates
|
||||
- Access Lists and basic HTTP Authentication for your hosts
|
||||
- Advanced Nginx configuration available for super users
|
||||
- User management, permissions and audit log
|
||||
- User management, permissions, and audit log
|
||||
|
||||
::: warning
|
||||
`armv7` is no longer supported in version 2.14+. This is due to Nodejs dropping support for armhf. Please
|
||||
use the `2.13.7` image tag if this applies to you.
|
||||
:::
|
||||
|
||||
## Hosting your home network
|
||||
|
||||
I won't go in to too much detail here but here are the basics for someone new to this self-hosted world.
|
||||
I won't go into too much detail here, but here are the basics for someone new to this self-hosted world.
|
||||
|
||||
1. Your home router will have a Port Forwarding section somewhere. Log in and find it
|
||||
2. Add port forwarding for port 80 and 443 to the server hosting this project
|
||||
3. Configure your domain name details to point to your home, either with a static ip or a service like DuckDNS or [Amazon Route53](https://github.com/jc21/route53-ddns)
|
||||
4. Use the Nginx Proxy Manager as your gateway to forward to your other web based services
|
||||
2. Add port forwarding for ports 80 and 443 to the server hosting this project
|
||||
3. Configure your domain name details to point to your home, either with a static ip or a service like
|
||||
- DuckDNS
|
||||
- [Amazon Route53](https://github.com/jc21/route53-ddns)
|
||||
- [Cloudflare](https://github.com/jc21/cloudflare-ddns)
|
||||
4. Use the Nginx Proxy Manager as your gateway to forward to your other web-based services
|
||||
|
||||
## Quick Setup
|
||||
|
||||
1. Install Docker and Docker-Compose
|
||||
|
||||
- [Docker Install documentation](https://docs.docker.com/install/)
|
||||
- [Docker-Compose Install documentation](https://docs.docker.com/compose/install/)
|
||||
|
||||
1. [Install Docker](https://docs.docker.com/install/)
|
||||
2. Create a docker-compose.yml file similar to this:
|
||||
|
||||
```yml
|
||||
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
# Security Policy
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Only the latest stable release receives security updates.
|
||||
Older versions are not actively maintained.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | --------- |
|
||||
| 2.15.x (latest) | :white_check_mark: |
|
||||
| < 2.15.0 | :x: |
|
||||
|
||||
Docker images: `jc21/nginx-proxy-manager:latest`, `jc21/nginx-proxy-manager:2`
|
||||
|
||||
See all releases: https://github.com/NginxProxyManager/nginx-proxy-manager/releases
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
**Do NOT open a public GitHub Issue to report a security vulnerability.**
|
||||
|
||||
Use GitHub's private vulnerability reporting:
|
||||
https://github.com/NginxProxyManager/nginx-proxy-manager/security/advisories/new
|
||||
|
||||
Please include:
|
||||
- Affected version (Docker image tag or release)
|
||||
- Description of the vulnerability
|
||||
- Steps to reproduce
|
||||
- Potential impact
|
||||
|
||||
Once a fix is available, a public GitHub Security Advisory will be published.
|
||||
+1
-1
@@ -62,7 +62,7 @@ app.use("/", mainRoutes);
|
||||
app.use((err, req, res, _) => {
|
||||
const payload = {
|
||||
error: {
|
||||
code: err.status,
|
||||
code: err.status || 500,
|
||||
message: err.public ? err.message : "Internal Error",
|
||||
},
|
||||
};
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.3.14/schema.json",
|
||||
"$schema": "https://biomejs.dev/schemas/2.4.15/schema.json",
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
|
||||
+575
-535
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
"database": {
|
||||
"engine": "knex-native",
|
||||
"knex": {
|
||||
"client": "sqlite3",
|
||||
"client": "better-sqlite3",
|
||||
"connection": {
|
||||
"filename": "/app/config/mydb.sqlite"
|
||||
},
|
||||
|
||||
@@ -161,9 +161,12 @@ const internal2fa = {
|
||||
}
|
||||
|
||||
const result = await verify({
|
||||
token: code,
|
||||
secret: auth.meta.totp_secret,
|
||||
});
|
||||
token: code,
|
||||
secret: auth.meta.totp_secret,
|
||||
guardrails: createGuardrails({
|
||||
MIN_SECRET_BYTES: 10,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!result.valid) {
|
||||
throw new errs.AuthError("Invalid verification code");
|
||||
|
||||
@@ -48,20 +48,17 @@ const internalAccessList = {
|
||||
return true;
|
||||
});
|
||||
|
||||
// Clients
|
||||
data.clients?.map((client) => {
|
||||
promises.push(
|
||||
accessListClientModel.query().insert({
|
||||
access_list_id: row.id,
|
||||
address: client.address,
|
||||
directive: client.directive,
|
||||
}),
|
||||
);
|
||||
return true;
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
// Clients
|
||||
for (const client of data.clients ?? []) {
|
||||
await accessListClientModel.query().insert({
|
||||
access_list_id: row.id,
|
||||
address: client.address,
|
||||
directive: client.directive,
|
||||
});
|
||||
}
|
||||
|
||||
// re-fetch with expansions
|
||||
const freshRow = await internalAccessList.get(
|
||||
access,
|
||||
@@ -154,25 +151,17 @@ const internalAccessList = {
|
||||
|
||||
// Check for clients and add/update/remove them
|
||||
if (typeof data.clients !== "undefined" && data.clients) {
|
||||
const clientPromises = [];
|
||||
data.clients.map((client) => {
|
||||
if (client.address) {
|
||||
clientPromises.push(
|
||||
accessListClientModel.query().insert({
|
||||
access_list_id: data.id,
|
||||
address: client.address,
|
||||
directive: client.directive,
|
||||
}),
|
||||
);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
const query = accessListClientModel.query().delete().where("access_list_id", data.id);
|
||||
await query;
|
||||
// Add new clitens
|
||||
if (clientPromises.length) {
|
||||
await Promise.all(clientPromises);
|
||||
|
||||
for (const client of data.clients) {
|
||||
if (client.address) {
|
||||
await accessListClientModel.query().insert({
|
||||
access_list_id: data.id,
|
||||
address: client.address,
|
||||
directive: client.directive,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +230,7 @@ const internalAccessList = {
|
||||
|
||||
let row = await query.then(utils.omitRow(omissions()));
|
||||
|
||||
if (!row || !row.id) {
|
||||
if (!row?.id) {
|
||||
throw new errs.ItemNotFoundError(thisData.id);
|
||||
}
|
||||
if (!skipMasking && typeof row.items !== "undefined" && row.items) {
|
||||
@@ -268,7 +257,7 @@ const internalAccessList = {
|
||||
expand: ["proxy_hosts", "items", "clients"],
|
||||
});
|
||||
|
||||
if (!row || !row.id) {
|
||||
if (!row?.id) {
|
||||
throw new errs.ItemNotFoundError(data.id);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import fs from "node:fs";
|
||||
import https from "node:https";
|
||||
import path from "path";
|
||||
import archiver from "archiver";
|
||||
import { ZipArchive } from "archiver";
|
||||
import _ from "lodash";
|
||||
import moment from "moment";
|
||||
import { ProxyAgent } from "proxy-agent";
|
||||
@@ -66,7 +66,7 @@ const internalCertificate = {
|
||||
.andWhere("provider", "letsencrypt")
|
||||
.andWhere("expires_on", "<", expirationThreshold)
|
||||
.then((certificates) => {
|
||||
if (!certificates || !certificates.length) {
|
||||
if (!certificates?.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ const internalCertificate = {
|
||||
await internalCertificate.disableInUseHosts(inUseResult);
|
||||
|
||||
const user = await userModel.query().where("is_deleted", 0).andWhere("id", data.owner_user_id).first();
|
||||
if (!user || !user.email) {
|
||||
if (!user?.email) {
|
||||
throw new error.ValidationError(
|
||||
"A valid email address must be set on your user account to use Let's Encrypt",
|
||||
);
|
||||
@@ -305,7 +305,7 @@ const internalCertificate = {
|
||||
}
|
||||
|
||||
const row = await query.then(utils.omitRow(omissions()));
|
||||
if (!row || !row.id) {
|
||||
if (!row?.id) {
|
||||
throw new error.ItemNotFoundError(data.id);
|
||||
}
|
||||
// Custom omissions
|
||||
@@ -370,7 +370,7 @@ const internalCertificate = {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
zipFiles: async (source, out) => {
|
||||
const archive = archiver("zip", { zlib: { level: 9 } });
|
||||
const archive = new ZipArchive({ zlib: { level: 9 } });
|
||||
const stream = fs.createWriteStream(out);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -397,7 +397,7 @@ const internalCertificate = {
|
||||
await access.can("certificates:delete", data.id);
|
||||
const row = await internalCertificate.get(access, { id: data.id });
|
||||
|
||||
if (!row || !row.id) {
|
||||
if (!row?.id) {
|
||||
throw new error.ItemNotFoundError(data.id);
|
||||
}
|
||||
|
||||
@@ -614,7 +614,7 @@ const internalCertificate = {
|
||||
const certificate = await internalCertificate.update(access, {
|
||||
id: data.id,
|
||||
expires_on: moment(validations.certificate.dates.to, "X").format("YYYY-MM-DD HH:mm:ss"),
|
||||
domain_names: [validations.certificate.cn],
|
||||
domain_names: validations.certificate.cn ? [validations.certificate.cn] : [],
|
||||
meta: _.clone(row.meta), // Prevent the update method from changing this value that we'll use later
|
||||
});
|
||||
|
||||
@@ -630,7 +630,7 @@ const internalCertificate = {
|
||||
* @param {String} privateKey This is the entire key contents as a string
|
||||
*/
|
||||
checkPrivateKey: async (privateKey) => {
|
||||
const filepath = await tempWrite(privateKey, "/tmp");
|
||||
const filepath = await tempWrite(privateKey);
|
||||
const failTimeout = setTimeout(() => {
|
||||
throw new error.ValidationError(
|
||||
"Result Validation Error: Validation timed out. This could be due to the key being passphrase-protected.",
|
||||
@@ -660,8 +660,8 @@ const internalCertificate = {
|
||||
* @param {Boolean} [throwExpired] Throw when the certificate is out of date
|
||||
*/
|
||||
getCertificateInfo: async (certificate, throwExpired) => {
|
||||
const filepath = await tempWrite(certificate);
|
||||
try {
|
||||
const filepath = await tempWrite(certificate, "/tmp");
|
||||
const certData = await internalCertificate.getCertificateInfoFromFile(filepath, throwExpired);
|
||||
fs.unlinkSync(filepath);
|
||||
return certData;
|
||||
@@ -683,13 +683,15 @@ const internalCertificate = {
|
||||
|
||||
try {
|
||||
const result = await utils.execFile("openssl", ["x509", "-in", certificateFile, "-subject", "-noout"]);
|
||||
|
||||
// Examples:
|
||||
// subject=CN = *.jc21.com
|
||||
// subject=CN = something.example.com
|
||||
const regex = /(?:subject=)?[^=]+=\s+(\S+)/gim;
|
||||
// subject=CN=*.jc21.com
|
||||
const regex = /(?:subject=)?[^=]+=\s*(\S+)/gim;
|
||||
const match = regex.exec(result);
|
||||
if (match && typeof match[1] !== "undefined") {
|
||||
certData.cn = match[1];
|
||||
certData.cn = match[1].trim();
|
||||
}
|
||||
|
||||
const result2 = await utils.execFile("openssl", ["x509", "-in", certificateFile, "-issuer", "-noout"]);
|
||||
@@ -779,6 +781,7 @@ const internalCertificate = {
|
||||
|
||||
const args = [
|
||||
"certonly",
|
||||
"-n", // non-interactive
|
||||
"--config",
|
||||
letsencryptConfig,
|
||||
"--work-dir",
|
||||
@@ -834,6 +837,7 @@ const internalCertificate = {
|
||||
|
||||
const args = [
|
||||
"certonly",
|
||||
"-n", // non-interactive
|
||||
"--config",
|
||||
letsencryptConfig,
|
||||
"--work-dir",
|
||||
|
||||
@@ -194,7 +194,7 @@ const internalDeadHost = {
|
||||
.query()
|
||||
.where("is_deleted", 0)
|
||||
.andWhere("id", data.id)
|
||||
.allowGraph("[owner,certificate]")
|
||||
.allowGraph(deadHostModel.defaultAllowGraph)
|
||||
.first();
|
||||
|
||||
if (accessData.permission_visibility !== "all") {
|
||||
@@ -206,7 +206,7 @@ const internalDeadHost = {
|
||||
}
|
||||
|
||||
const row = await query.then(utils.omitRow(omissions()));
|
||||
if (!row || !row.id) {
|
||||
if (!row?.id) {
|
||||
throw new errs.ItemNotFoundError(data.id);
|
||||
}
|
||||
// Custom omissions
|
||||
@@ -226,7 +226,7 @@ const internalDeadHost = {
|
||||
delete: async (access, data) => {
|
||||
await access.can("dead_hosts:delete", data.id)
|
||||
const row = await internalDeadHost.get(access, { id: data.id });
|
||||
if (!row || !row.id) {
|
||||
if (!row?.id) {
|
||||
throw new errs.ItemNotFoundError(data.id);
|
||||
}
|
||||
|
||||
@@ -264,7 +264,7 @@ const internalDeadHost = {
|
||||
id: data.id,
|
||||
expand: ["certificate", "owner"],
|
||||
});
|
||||
if (!row || !row.id) {
|
||||
if (!row?.id) {
|
||||
throw new errs.ItemNotFoundError(data.id);
|
||||
}
|
||||
if (row.enabled) {
|
||||
@@ -303,7 +303,7 @@ const internalDeadHost = {
|
||||
disable: async (access, data) => {
|
||||
await access.can("dead_hosts:update", data.id)
|
||||
const row = await internalDeadHost.get(access, { id: data.id });
|
||||
if (!row || !row.id) {
|
||||
if (!row?.id) {
|
||||
throw new errs.ItemNotFoundError(data.id);
|
||||
}
|
||||
if (!row.enabled) {
|
||||
@@ -347,7 +347,7 @@ const internalDeadHost = {
|
||||
.query()
|
||||
.where("is_deleted", 0)
|
||||
.groupBy("id")
|
||||
.allowGraph("[owner,certificate]")
|
||||
.allowGraph(deadHostModel.defaultAllowGraph)
|
||||
.orderBy(castJsonIfNeed("domain_names"), "ASC");
|
||||
|
||||
if (accessData.permission_visibility !== "all") {
|
||||
|
||||
@@ -115,9 +115,9 @@ const internalProxyHost = {
|
||||
*/
|
||||
update: (access, data) => {
|
||||
let thisData = data;
|
||||
const create_certificate = thisData.certificate_id === "new";
|
||||
const createCertificate = thisData.certificate_id === "new";
|
||||
|
||||
if (create_certificate) {
|
||||
if (createCertificate) {
|
||||
delete thisData.certificate_id;
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ const internalProxyHost = {
|
||||
);
|
||||
}
|
||||
|
||||
if (create_certificate) {
|
||||
if (createCertificate) {
|
||||
return internalCertificate
|
||||
.createQuickCertificate(access, {
|
||||
domain_names: thisData.domain_names || row.domain_names,
|
||||
@@ -232,7 +232,6 @@ const internalProxyHost = {
|
||||
*/
|
||||
get: (access, data) => {
|
||||
const thisData = data || {};
|
||||
|
||||
return access
|
||||
.can("proxy_hosts:get", thisData.id)
|
||||
.then((access_data) => {
|
||||
@@ -240,7 +239,7 @@ const internalProxyHost = {
|
||||
.query()
|
||||
.where("is_deleted", 0)
|
||||
.andWhere("id", thisData.id)
|
||||
.allowGraph("[owner,access_list.[clients,items],certificate]")
|
||||
.allowGraph(proxyHostModel.defaultAllowGraph)
|
||||
.first();
|
||||
|
||||
if (access_data.permission_visibility !== "all") {
|
||||
@@ -254,7 +253,7 @@ const internalProxyHost = {
|
||||
return query.then(utils.omitRow(omissions()));
|
||||
})
|
||||
.then((row) => {
|
||||
if (!row || !row.id) {
|
||||
if (!row?.id) {
|
||||
throw new errs.ItemNotFoundError(thisData.id);
|
||||
}
|
||||
const thisRow = internalHost.cleanRowCertificateMeta(row);
|
||||
@@ -280,7 +279,7 @@ const internalProxyHost = {
|
||||
return internalProxyHost.get(access, { id: data.id });
|
||||
})
|
||||
.then((row) => {
|
||||
if (!row || !row.id) {
|
||||
if (!row?.id) {
|
||||
throw new errs.ItemNotFoundError(data.id);
|
||||
}
|
||||
|
||||
@@ -328,7 +327,7 @@ const internalProxyHost = {
|
||||
});
|
||||
})
|
||||
.then((row) => {
|
||||
if (!row || !row.id) {
|
||||
if (!row?.id) {
|
||||
throw new errs.ItemNotFoundError(data.id);
|
||||
}
|
||||
if (row.enabled) {
|
||||
@@ -376,7 +375,7 @@ const internalProxyHost = {
|
||||
return internalProxyHost.get(access, { id: data.id });
|
||||
})
|
||||
.then((row) => {
|
||||
if (!row || !row.id) {
|
||||
if (!row?.id) {
|
||||
throw new errs.ItemNotFoundError(data.id);
|
||||
}
|
||||
if (!row.enabled) {
|
||||
@@ -422,11 +421,12 @@ const internalProxyHost = {
|
||||
*/
|
||||
getAll: async (access, expand, searchQuery) => {
|
||||
const accessData = await access.can("proxy_hosts:list");
|
||||
|
||||
const query = proxyHostModel
|
||||
.query()
|
||||
.where("is_deleted", 0)
|
||||
.groupBy("id")
|
||||
.allowGraph("[owner,access_list,certificate]")
|
||||
.allowGraph(proxyHostModel.defaultAllowGraph)
|
||||
.orderBy(castJsonIfNeed("domain_names"), "ASC");
|
||||
|
||||
if (accessData.permission_visibility !== "all") {
|
||||
|
||||
@@ -229,7 +229,6 @@ const internalRedirectionHost = {
|
||||
*/
|
||||
get: (access, data) => {
|
||||
const thisData = data || {};
|
||||
|
||||
return access
|
||||
.can("redirection_hosts:get", thisData.id)
|
||||
.then((access_data) => {
|
||||
@@ -237,7 +236,7 @@ const internalRedirectionHost = {
|
||||
.query()
|
||||
.where("is_deleted", 0)
|
||||
.andWhere("id", thisData.id)
|
||||
.allowGraph("[owner,certificate]")
|
||||
.allowGraph(redirectionHostModel.defaultAllowGraph)
|
||||
.first();
|
||||
|
||||
if (access_data.permission_visibility !== "all") {
|
||||
@@ -252,7 +251,7 @@ const internalRedirectionHost = {
|
||||
})
|
||||
.then((row) => {
|
||||
let thisRow = row;
|
||||
if (!thisRow || !thisRow.id) {
|
||||
if (!thisRow?.id) {
|
||||
throw new errs.ItemNotFoundError(thisData.id);
|
||||
}
|
||||
thisRow = internalHost.cleanRowCertificateMeta(thisRow);
|
||||
@@ -278,7 +277,7 @@ const internalRedirectionHost = {
|
||||
return internalRedirectionHost.get(access, { id: data.id });
|
||||
})
|
||||
.then((row) => {
|
||||
if (!row || !row.id) {
|
||||
if (!row?.id) {
|
||||
throw new errs.ItemNotFoundError(data.id);
|
||||
}
|
||||
|
||||
@@ -326,7 +325,7 @@ const internalRedirectionHost = {
|
||||
});
|
||||
})
|
||||
.then((row) => {
|
||||
if (!row || !row.id) {
|
||||
if (!row?.id) {
|
||||
throw new errs.ItemNotFoundError(data.id);
|
||||
}
|
||||
if (row.enabled) {
|
||||
@@ -374,7 +373,7 @@ const internalRedirectionHost = {
|
||||
return internalRedirectionHost.get(access, { id: data.id });
|
||||
})
|
||||
.then((row) => {
|
||||
if (!row || !row.id) {
|
||||
if (!row?.id) {
|
||||
throw new errs.ItemNotFoundError(data.id);
|
||||
}
|
||||
if (!row.enabled) {
|
||||
@@ -426,7 +425,7 @@ const internalRedirectionHost = {
|
||||
.query()
|
||||
.where("is_deleted", 0)
|
||||
.groupBy("id")
|
||||
.allowGraph("[owner,certificate]")
|
||||
.allowGraph(redirectionHostModel.defaultAllowGraph)
|
||||
.orderBy(castJsonIfNeed("domain_names"), "ASC");
|
||||
|
||||
if (access_data.permission_visibility !== "all") {
|
||||
|
||||
@@ -178,7 +178,6 @@ const internalStream = {
|
||||
*/
|
||||
get: (access, data) => {
|
||||
const thisData = data || {};
|
||||
|
||||
return access
|
||||
.can("streams:get", thisData.id)
|
||||
.then((access_data) => {
|
||||
@@ -186,7 +185,7 @@ const internalStream = {
|
||||
.query()
|
||||
.where("is_deleted", 0)
|
||||
.andWhere("id", thisData.id)
|
||||
.allowGraph("[owner,certificate]")
|
||||
.allowGraph(streamModel.defaultAllowGraph)
|
||||
.first();
|
||||
|
||||
if (access_data.permission_visibility !== "all") {
|
||||
@@ -201,7 +200,7 @@ const internalStream = {
|
||||
})
|
||||
.then((row) => {
|
||||
let thisRow = row;
|
||||
if (!thisRow || !thisRow.id) {
|
||||
if (!thisRow?.id) {
|
||||
throw new errs.ItemNotFoundError(thisData.id);
|
||||
}
|
||||
thisRow = internalHost.cleanRowCertificateMeta(thisRow);
|
||||
@@ -227,7 +226,7 @@ const internalStream = {
|
||||
return internalStream.get(access, { id: data.id });
|
||||
})
|
||||
.then((row) => {
|
||||
if (!row || !row.id) {
|
||||
if (!row?.id) {
|
||||
throw new errs.ItemNotFoundError(data.id);
|
||||
}
|
||||
|
||||
@@ -275,7 +274,7 @@ const internalStream = {
|
||||
});
|
||||
})
|
||||
.then((row) => {
|
||||
if (!row || !row.id) {
|
||||
if (!row?.id) {
|
||||
throw new errs.ItemNotFoundError(data.id);
|
||||
}
|
||||
if (row.enabled) {
|
||||
@@ -323,7 +322,7 @@ const internalStream = {
|
||||
return internalStream.get(access, { id: data.id });
|
||||
})
|
||||
.then((row) => {
|
||||
if (!row || !row.id) {
|
||||
if (!row?.id) {
|
||||
throw new errs.ItemNotFoundError(data.id);
|
||||
}
|
||||
if (!row.enabled) {
|
||||
@@ -375,7 +374,7 @@ const internalStream = {
|
||||
.query()
|
||||
.where("is_deleted", 0)
|
||||
.groupBy("id")
|
||||
.allowGraph("[owner,certificate]")
|
||||
.allowGraph(streamModel.defaultAllowGraph)
|
||||
.orderBy("incoming_port", "ASC");
|
||||
|
||||
if (access_data.permission_visibility !== "all") {
|
||||
|
||||
@@ -87,7 +87,13 @@ const internalUser = {
|
||||
}
|
||||
|
||||
return access
|
||||
.can("users:update", data.id)
|
||||
.can("users:permissions", data.id)
|
||||
.catch(() => {
|
||||
delete data.roles;
|
||||
})
|
||||
.then(() => {
|
||||
return access.can("users:update", data.id);
|
||||
})
|
||||
.then(() => {
|
||||
// Make sure that the user being updated doesn't change their email to another user that is already using it
|
||||
// 1. get user we want to update
|
||||
@@ -171,7 +177,7 @@ const internalUser = {
|
||||
return query.then(utils.omitRow(omissions()));
|
||||
})
|
||||
.then((row) => {
|
||||
if (!row || !row.id) {
|
||||
if (!row?.id) {
|
||||
throw new errs.ItemNotFoundError(thisData.id);
|
||||
}
|
||||
// Custom omissions
|
||||
|
||||
+17
-13
@@ -4,8 +4,6 @@ import { certbot as logger } from "../logger.js";
|
||||
import errs from "./error.js";
|
||||
import utils from "./utils.js";
|
||||
|
||||
const CERTBOT_VERSION_REPLACEMENT = "$(certbot --version | grep -Eo '[0-9](\\.[0-9]+)+')";
|
||||
|
||||
/**
|
||||
* Installs a cerbot plugin given the key for the object from
|
||||
* ../certbot/dns-plugins.json
|
||||
@@ -15,24 +13,32 @@ const CERTBOT_VERSION_REPLACEMENT = "$(certbot --version | grep -Eo '[0-9](\\.[0
|
||||
*/
|
||||
const installPlugin = async (pluginKey) => {
|
||||
if (typeof dnsPlugins[pluginKey] === "undefined") {
|
||||
// throw Error(`Certbot plugin ${pluginKey} not found`);
|
||||
throw new errs.ItemNotFoundError(pluginKey);
|
||||
}
|
||||
|
||||
const plugin = dnsPlugins[pluginKey];
|
||||
logger.start(`Installing ${pluginKey}...`);
|
||||
|
||||
plugin.version = plugin.version.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT);
|
||||
plugin.dependencies = plugin.dependencies.replace(/{{certbot-version}}/g, CERTBOT_VERSION_REPLACEMENT);
|
||||
plugin.version = plugin.version.replace(/{{certbot-version}}/g, process.env.CERTBOT_VERSION);
|
||||
plugin.dependencies = plugin.dependencies.replace(/{{certbot-version}}/g, process.env.CERTBOT_VERSION);
|
||||
|
||||
// SETUPTOOLS_USE_DISTUTILS is required for certbot plugins to install correctly
|
||||
// in new versions of Python
|
||||
let env = Object.assign({}, process.env, { SETUPTOOLS_USE_DISTUTILS: "stdlib" });
|
||||
// SETUPTOOLS_USE_DISTUTILS=local uses setuptools' own bundled distutils.
|
||||
// "stdlib" breaks Python 3.13+ where distutils was removed from the standard library.
|
||||
let env = Object.assign({}, process.env, { SETUPTOOLS_USE_DISTUTILS: "local" });
|
||||
if (typeof plugin.env === "object") {
|
||||
env = Object.assign(env, plugin.env);
|
||||
}
|
||||
|
||||
const cmd = `. /opt/certbot/bin/activate && pip install --no-cache-dir ${plugin.dependencies} ${plugin.package_name}${plugin.version} && deactivate`;
|
||||
const quotedDeps = plugin.dependencies.trim()
|
||||
? plugin.dependencies
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
.filter(Boolean)
|
||||
.map((d) => `'${d}'`)
|
||||
.join(" ")
|
||||
: "";
|
||||
|
||||
const cmd = `. /opt/certbot/bin/activate && pip install --no-cache-dir ${quotedDeps} '${plugin.package_name}${plugin.version}' && deactivate`;
|
||||
return utils
|
||||
.exec(cmd, { env })
|
||||
.then((result) => {
|
||||
@@ -73,9 +79,7 @@ const installPlugins = async (pluginKeys) => {
|
||||
})
|
||||
.end(() => {
|
||||
if (hasErrors) {
|
||||
reject(
|
||||
new errs.CommandError("Some plugins failed to install. Please check the logs above", 1),
|
||||
);
|
||||
reject(new errs.CommandError("Some plugins failed to install. Please check the logs above", 1));
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
@@ -83,4 +87,4 @@ const installPlugins = async (pluginKeys) => {
|
||||
});
|
||||
};
|
||||
|
||||
export { installPlugins, installPlugin };
|
||||
export { installPlugin, installPlugins };
|
||||
|
||||
@@ -2,10 +2,13 @@ import fs from "node:fs";
|
||||
import NodeRSA from "node-rsa";
|
||||
import { global as logger } from "../logger.js";
|
||||
|
||||
const keysFile = '/data/keys.json';
|
||||
const mysqlEngine = 'mysql2';
|
||||
const postgresEngine = 'pg';
|
||||
const sqliteClientName = 'better-sqlite3';
|
||||
const keysFile = '/data/keys.json';
|
||||
const mysqlEngine = 'mysql2';
|
||||
const postgresEngine = 'pg';
|
||||
const sqliteClientName = 'better-sqlite3';
|
||||
|
||||
// Not used for new setups anymore but may exist in legacy setups
|
||||
const legacySqliteClientName = 'sqlite3';
|
||||
|
||||
let instance = null;
|
||||
|
||||
@@ -184,7 +187,7 @@ const configGet = (key) => {
|
||||
*/
|
||||
const isSqlite = () => {
|
||||
instance === null && configure();
|
||||
return instance.database.knex && instance.database.knex.client === sqliteClientName;
|
||||
return instance.database.knex && [sqliteClientName, legacySqliteClientName].includes(instance.database.knex.client);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -208,7 +211,7 @@ const isPostgres = () => {
|
||||
};
|
||||
|
||||
/**
|
||||
* Are we running in debug mdoe?
|
||||
* Are we running in debug mode?
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
|
||||
@@ -3,7 +3,7 @@ export default (req, res, next) => {
|
||||
res.set({
|
||||
"Access-Control-Allow-Origin": req.headers.origin,
|
||||
"Access-Control-Allow-Credentials": true,
|
||||
"Access-Control-Allow-Methods": "OPTIONS, GET, POST",
|
||||
"Access-Control-Allow-Methods": "OPTIONS, GET, POST, PUT, DELETE",
|
||||
"Access-Control-Allow-Headers":
|
||||
"Content-Type, Cache-Control, Pragma, Expires, Authorization, X-Dataset-Total, X-Dataset-Offset, X-Dataset-Limit",
|
||||
"Access-Control-Max-Age": 5 * 60,
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import chalk from "chalk";
|
||||
import { debug, express as logger } from "../../logger.js";
|
||||
|
||||
export default (req, _res, next) => {
|
||||
debug(logger, `[${chalk.yellow(req.method.toUpperCase())}] ${chalk.green(req.path)}`);
|
||||
next();
|
||||
};
|
||||
@@ -3,14 +3,12 @@ import { dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { Liquid } from "liquidjs";
|
||||
import _ from "lodash";
|
||||
import { debug, global as logger } from "../logger.js";
|
||||
import errs from "./error.js";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const exec = async (cmd, options = {}) => {
|
||||
debug(logger, "CMD:", cmd);
|
||||
const { stdout, stderr } = await new Promise((resolve, reject) => {
|
||||
const child = nodeExec(cmd, options, (isError, stdout, stderr) => {
|
||||
if (isError) {
|
||||
@@ -34,7 +32,6 @@ const exec = async (cmd, options = {}) => {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
const execFile = (cmd, args, options) => {
|
||||
debug(logger, `CMD: ${cmd} ${args ? args.join(" ") : ""}`);
|
||||
const opts = options || {};
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import { migrate as logger } from "../logger.js";
|
||||
|
||||
const migrateName = "trust_forwarded_proto";
|
||||
|
||||
/**
|
||||
* Migrate
|
||||
*
|
||||
* @see http://knexjs.org/#Schema
|
||||
*
|
||||
* @param {Object} knex
|
||||
* @returns {Promise}
|
||||
*/
|
||||
const up = (knex) => {
|
||||
logger.info(`[${migrateName}] Migrating Up...`);
|
||||
|
||||
return knex.schema
|
||||
.alterTable('proxy_host', (table) => {
|
||||
table.tinyint('trust_forwarded_proto').notNullable().defaultTo(0);
|
||||
})
|
||||
.then(() => {
|
||||
logger.info(`[${migrateName}] proxy_host Table altered`);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Undo Migrate
|
||||
*
|
||||
* @param {Object} knex
|
||||
* @returns {Promise}
|
||||
*/
|
||||
const down = (knex) => {
|
||||
logger.info(`[${migrateName}] Migrating Down...`);
|
||||
|
||||
return knex.schema
|
||||
.alterTable('proxy_host', (table) => {
|
||||
table.dropColumn('trust_forwarded_proto');
|
||||
})
|
||||
.then(() => {
|
||||
logger.info(`[${migrateName}] proxy_host Table altered`);
|
||||
});
|
||||
};
|
||||
|
||||
export { up, down };
|
||||
@@ -15,6 +15,16 @@ Model.knex(db());
|
||||
|
||||
const boolFields = ["is_deleted"];
|
||||
|
||||
const cleanDomainNames = (domainNames) => {
|
||||
// Sort domain_names
|
||||
if (typeof domainNames !== "undefined") {
|
||||
const newDomainNames = domainNames.filter((name) => name != null);
|
||||
newDomainNames.sort();
|
||||
return newDomainNames;
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
class Certificate extends Model {
|
||||
$beforeInsert() {
|
||||
this.created_on = now();
|
||||
@@ -26,7 +36,9 @@ class Certificate extends Model {
|
||||
}
|
||||
|
||||
// Default for domain_names
|
||||
if (typeof this.domain_names === "undefined") {
|
||||
if (typeof this.domain_names !== "undefined") {
|
||||
this.domain_names = cleanDomainNames(this.domain_names);
|
||||
} else {
|
||||
this.domain_names = [];
|
||||
}
|
||||
|
||||
@@ -34,16 +46,12 @@ class Certificate extends Model {
|
||||
if (typeof this.meta === "undefined") {
|
||||
this.meta = {};
|
||||
}
|
||||
|
||||
this.domain_names.sort();
|
||||
}
|
||||
|
||||
$beforeUpdate() {
|
||||
this.modified_on = now();
|
||||
|
||||
// Sort domain_names
|
||||
if (typeof this.domain_names !== "undefined") {
|
||||
this.domain_names.sort();
|
||||
this.domain_names = cleanDomainNames(this.domain_names);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import { Model } from "objection";
|
||||
import db from "../db.js";
|
||||
import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js";
|
||||
import { castJsonIfNeed, convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js";
|
||||
import Certificate from "./certificate.js";
|
||||
import now from "./now_helper.js";
|
||||
import User from "./user.js";
|
||||
@@ -61,6 +61,18 @@ class DeadHost extends Model {
|
||||
return ["domain_names", "meta"];
|
||||
}
|
||||
|
||||
static get defaultAllowGraph() {
|
||||
return "[owner,certificate]";
|
||||
}
|
||||
|
||||
static get defaultExpand() {
|
||||
return ["certificate", "owner"];
|
||||
}
|
||||
|
||||
static get defaultOrder() {
|
||||
return [castJsonIfNeed("domain_names"), "ASC"];
|
||||
}
|
||||
|
||||
static get relationMappings() {
|
||||
return {
|
||||
owner: {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import { Model } from "objection";
|
||||
import db from "../db.js";
|
||||
import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js";
|
||||
import { castJsonIfNeed, convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js";
|
||||
import AccessList from "./access_list.js";
|
||||
import Certificate from "./certificate.js";
|
||||
import now from "./now_helper.js";
|
||||
@@ -21,6 +21,7 @@ const boolFields = [
|
||||
"enabled",
|
||||
"hsts_enabled",
|
||||
"hsts_subdomains",
|
||||
"trust_forwarded_proto",
|
||||
];
|
||||
|
||||
class ProxyHost extends Model {
|
||||
@@ -72,6 +73,18 @@ class ProxyHost extends Model {
|
||||
return ["domain_names", "meta", "locations"];
|
||||
}
|
||||
|
||||
static get defaultAllowGraph() {
|
||||
return "[owner,access_list.[clients,items],certificate]";
|
||||
}
|
||||
|
||||
static get defaultExpand() {
|
||||
return ["owner", "certificate", "access_list.[clients,items]"];
|
||||
}
|
||||
|
||||
static get defaultOrder() {
|
||||
return [castJsonIfNeed("domain_names"), "ASC"];
|
||||
}
|
||||
|
||||
static get relationMappings() {
|
||||
return {
|
||||
owner: {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import { Model } from "objection";
|
||||
import db from "../db.js";
|
||||
import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js";
|
||||
import { castJsonIfNeed, convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js";
|
||||
import Certificate from "./certificate.js";
|
||||
import now from "./now_helper.js";
|
||||
import User from "./user.js";
|
||||
@@ -70,6 +70,18 @@ class RedirectionHost extends Model {
|
||||
return ["domain_names", "meta"];
|
||||
}
|
||||
|
||||
static get defaultAllowGraph() {
|
||||
return "[owner,certificate]";
|
||||
}
|
||||
|
||||
static get defaultExpand() {
|
||||
return ["certificate", "owner"];
|
||||
}
|
||||
|
||||
static get defaultOrder() {
|
||||
return [castJsonIfNeed("domain_names"), "ASC"];
|
||||
}
|
||||
|
||||
static get relationMappings() {
|
||||
return {
|
||||
owner: {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Model } from "objection";
|
||||
import db from "../db.js";
|
||||
import { convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js";
|
||||
import { castJsonIfNeed, convertBoolFieldsToInt, convertIntFieldsToBool } from "../lib/helpers.js";
|
||||
import Certificate from "./certificate.js";
|
||||
import now from "./now_helper.js";
|
||||
import User from "./user.js";
|
||||
@@ -46,6 +46,18 @@ class Stream extends Model {
|
||||
return ["meta"];
|
||||
}
|
||||
|
||||
static get defaultAllowGraph() {
|
||||
return "[owner,certificate]";
|
||||
}
|
||||
|
||||
static get defaultExpand() {
|
||||
return ["certificate", "owner"];
|
||||
}
|
||||
|
||||
static get defaultOrder() {
|
||||
return [castJsonIfNeed("incoming_port"), "ASC"];
|
||||
}
|
||||
|
||||
static get relationMappings() {
|
||||
return {
|
||||
owner: {
|
||||
|
||||
+19
-18
@@ -9,41 +9,42 @@
|
||||
"scripts": {
|
||||
"lint": "biome lint",
|
||||
"prettier": "biome format --write .",
|
||||
"validate-schema": "node validate-schema.js"
|
||||
"validate-schema": "node validate-schema.js",
|
||||
"regenerate-config": "node scripts/regenerate-config"
|
||||
},
|
||||
"dependencies": {
|
||||
"@apidevtools/json-schema-ref-parser": "^14.1.1",
|
||||
"ajv": "^8.17.1",
|
||||
"archiver": "^7.0.1",
|
||||
"@apidevtools/json-schema-ref-parser": "^15.3.5",
|
||||
"ajv": "^8.20.0",
|
||||
"archiver": "^8.0.0",
|
||||
"batchflow": "^0.4.0",
|
||||
"bcrypt": "^6.0.0",
|
||||
"better-sqlite3": "^12.6.2",
|
||||
"better-sqlite3": "^12.10.0",
|
||||
"body-parser": "^2.2.2",
|
||||
"compression": "^1.7.4",
|
||||
"chalk": "5.6.2",
|
||||
"compression": "^1.8.1",
|
||||
"express": "^5.2.1",
|
||||
"express-fileupload": "^1.5.2",
|
||||
"gravatar": "^1.8.2",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"knex": "3.1.0",
|
||||
"liquidjs": "10.24.0",
|
||||
"lodash": "^4.17.23",
|
||||
"knex": "3.2.10",
|
||||
"liquidjs": "10.27.0",
|
||||
"lodash": "^4.18.1",
|
||||
"moment": "^2.30.1",
|
||||
"mysql2": "^3.16.3",
|
||||
"mysql2": "^3.22.3",
|
||||
"node-rsa": "^1.1.1",
|
||||
"objection": "3.1.5",
|
||||
"otplib": "^13.2.1",
|
||||
"otplib": "^13.4.0",
|
||||
"path": "^0.12.7",
|
||||
"pg": "^8.18.0",
|
||||
"proxy-agent": "^6.5.0",
|
||||
"pg": "^8.21.0",
|
||||
"proxy-agent": "^8.0.1",
|
||||
"signale": "1.4.0",
|
||||
"sqlite3": "^5.1.7",
|
||||
"temp-write": "^4.0.0"
|
||||
"sqlite3": "^6.0.1",
|
||||
"temp-write": "^6.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@apidevtools/swagger-parser": "^12.1.0",
|
||||
"@biomejs/biome": "^2.3.14",
|
||||
"chalk": "5.6.2",
|
||||
"nodemon": "^3.1.11"
|
||||
"@biomejs/biome": "^2.4.15",
|
||||
"nodemon": "^3.1.14"
|
||||
},
|
||||
"signale": {
|
||||
"displayDate": true,
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import express from "express";
|
||||
import dnsPlugins from "../certbot/dns-plugins.json" with { type: "json" };
|
||||
import { installPlugin } from "../lib/certbot.js";
|
||||
import { debug, express as logger } from "../logger.js";
|
||||
|
||||
const router = express.Router({
|
||||
caseSensitive: true,
|
||||
strict: true,
|
||||
mergeParams: true,
|
||||
});
|
||||
|
||||
/**
|
||||
* ONLY AVAILABLE IN CI ENVIRONMENT!
|
||||
*/
|
||||
|
||||
/**
|
||||
* /api/ci/certbot-plugins
|
||||
*/
|
||||
router
|
||||
.route("/certbot-plugins")
|
||||
.options((_, res) => {
|
||||
res.sendStatus(204);
|
||||
})
|
||||
|
||||
// Return all certbot plugins
|
||||
.get(async (_req, res, _next) => {
|
||||
res.status(200).send(dnsPlugins);
|
||||
});
|
||||
|
||||
/**
|
||||
* /api/ci/certbot-plugins/{plugin}
|
||||
*/
|
||||
router
|
||||
.route("/certbot-plugins/:plugin")
|
||||
.options((_, res) => {
|
||||
res.sendStatus(204);
|
||||
})
|
||||
|
||||
// Install a certbot plugin
|
||||
.post(async (req, res, next) => {
|
||||
try {
|
||||
const pluginName = req.params.plugin;
|
||||
// check if plugin exists
|
||||
if (!dnsPlugins[pluginName]) {
|
||||
return res.status(404).send({
|
||||
error: "Plugin not found",
|
||||
});
|
||||
}
|
||||
|
||||
await installPlugin(pluginName);
|
||||
res.status(200).send(true);
|
||||
} catch (err) {
|
||||
debug(logger, `${req.method.toUpperCase()} ${req.path}: ${err}`);
|
||||
next(err);
|
||||
}
|
||||
return;
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -1,8 +1,11 @@
|
||||
import express from "express";
|
||||
import { isCI } from "../lib/config.js";
|
||||
import errs from "../lib/error.js";
|
||||
import logRequest from "../lib/express/log-request.js";
|
||||
import pjson from "../package.json" with { type: "json" };
|
||||
import { isSetup } from "../setup.js";
|
||||
import auditLogRoutes from "./audit-log.js";
|
||||
import ciRoutes from "./ci.js";
|
||||
import accessListsRoutes from "./nginx/access_lists.js";
|
||||
import certificatesHostsRoutes from "./nginx/certificates.js";
|
||||
import deadHostsRoutes from "./nginx/dead_hosts.js";
|
||||
@@ -22,6 +25,8 @@ const router = express.Router({
|
||||
mergeParams: true,
|
||||
});
|
||||
|
||||
router.use(logRequest);
|
||||
|
||||
/**
|
||||
* Health Check
|
||||
* GET /api
|
||||
@@ -55,6 +60,11 @@ router.use("/nginx/streams", streamsRoutes);
|
||||
router.use("/nginx/access-lists", accessListsRoutes);
|
||||
router.use("/nginx/certificates", certificatesHostsRoutes);
|
||||
|
||||
// Only include CI routes if we're in a CI environment
|
||||
if (isCI()) {
|
||||
router.use("/ci", ciRoutes);
|
||||
}
|
||||
|
||||
/**
|
||||
* API 404 for all other routes
|
||||
*
|
||||
|
||||
@@ -18,7 +18,7 @@ router
|
||||
/**
|
||||
* GET /schema
|
||||
*/
|
||||
.get(async (req, res) => {
|
||||
.get(async (req, res, next) => {
|
||||
try {
|
||||
const swaggerJSON = await getCompiledSchema();
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
"enabled",
|
||||
"locations",
|
||||
"hsts_enabled",
|
||||
"hsts_subdomains"
|
||||
"hsts_subdomains",
|
||||
"trust_forwarded_proto"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
@@ -141,6 +142,11 @@
|
||||
"hsts_subdomains": {
|
||||
"$ref": "../common.json#/properties/hsts_subdomains"
|
||||
},
|
||||
"trust_forwarded_proto":{
|
||||
"type": "boolean",
|
||||
"description": "Trust the forwarded headers",
|
||||
"example": false
|
||||
},
|
||||
"certificate": {
|
||||
"oneOf": [
|
||||
{
|
||||
|
||||
@@ -58,7 +58,8 @@
|
||||
"enabled": true,
|
||||
"locations": [],
|
||||
"hsts_enabled": false,
|
||||
"hsts_subdomains": false
|
||||
"hsts_subdomains": false,
|
||||
"trust_forwarded_proto": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@
|
||||
"locations": [],
|
||||
"hsts_enabled": false,
|
||||
"hsts_subdomains": false,
|
||||
"trust_forwarded_proto": false,
|
||||
"owner": {
|
||||
"id": 1,
|
||||
"created_on": "2025-10-28T00:50:24.000Z",
|
||||
|
||||
@@ -56,6 +56,9 @@
|
||||
"hsts_subdomains": {
|
||||
"$ref": "../../../../components/proxy-host-object.json#/properties/hsts_subdomains"
|
||||
},
|
||||
"trust_forwarded_proto": {
|
||||
"$ref": "../../../../components/proxy-host-object.json#/properties/trust_forwarded_proto"
|
||||
},
|
||||
"http2_support": {
|
||||
"$ref": "../../../../components/proxy-host-object.json#/properties/http2_support"
|
||||
},
|
||||
@@ -122,6 +125,7 @@
|
||||
"locations": [],
|
||||
"hsts_enabled": false,
|
||||
"hsts_subdomains": false,
|
||||
"trust_forwarded_proto": false,
|
||||
"owner": {
|
||||
"id": 1,
|
||||
"created_on": "2025-10-28T00:50:24.000Z",
|
||||
|
||||
@@ -48,6 +48,9 @@
|
||||
"hsts_subdomains": {
|
||||
"$ref": "../../../components/proxy-host-object.json#/properties/hsts_subdomains"
|
||||
},
|
||||
"trust_forwarded_proto": {
|
||||
"$ref": "../../../components/proxy-host-object.json#/properties/trust_forwarded_proto"
|
||||
},
|
||||
"http2_support": {
|
||||
"$ref": "../../../components/proxy-host-object.json#/properties/http2_support"
|
||||
},
|
||||
@@ -119,6 +122,7 @@
|
||||
"locations": [],
|
||||
"hsts_enabled": false,
|
||||
"hsts_subdomains": false,
|
||||
"trust_forwarded_proto": false,
|
||||
"certificate": null,
|
||||
"owner": {
|
||||
"id": 1,
|
||||
|
||||
Executable
+76
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import * as process from "node:process"; // Use the node: protocol for built-ins
|
||||
import internalNginx from "../internal/nginx.js";
|
||||
import { global as logger } from "../logger.js";
|
||||
import deadHostModel from "../models/dead_host.js";
|
||||
import proxyHostModel from "../models/proxy_host.js";
|
||||
import redirectionHostModel from "../models/redirection_host.js";
|
||||
import streamModel from "../models/stream.js";
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const UNATTENDED = args.includes("-y") || args.includes("--yes");
|
||||
const DRY_RUN = args.includes("--dry-run");
|
||||
|
||||
if (args.includes("--help") || args.includes("-h")) {
|
||||
console.log("\nThis will iterate over all Hosts and regnerate their Nginx configs.\n")
|
||||
console.log("Usage: ./regenerate-config [-h|--help] [-y|--yes] [--dry-run]\n");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// ask for the user to confirm the action if not in unattended mode
|
||||
if (!UNATTENDED && !DRY_RUN) {
|
||||
const readline = await import("node:readline");
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
const question = (query) =>
|
||||
new Promise((resolve) => rl.question(query, resolve));
|
||||
|
||||
const answer = await question(
|
||||
"This will iterate over all Hosts and regnerate their Nginx configs.\n\nAre you sure you want to proceed? (y/N) ",
|
||||
);
|
||||
rl.close();
|
||||
|
||||
if (answer.toLowerCase() !== "y") {
|
||||
console.log("Aborting.");
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
const logIt = (msg, type = "info") => logger[type](
|
||||
`${DRY_RUN ? '[DRY RUN] ' : ''}${msg}`,
|
||||
);
|
||||
|
||||
// Let's do it.
|
||||
|
||||
const processItems = async (model, type) => {
|
||||
const rows = await model
|
||||
.query()
|
||||
.where("is_deleted", 0)
|
||||
.andWhere("enabled", 1)
|
||||
.groupBy("id")
|
||||
.allowGraph(model.defaultAllowGraph)
|
||||
.withGraphFetched(`[${model.defaultExpand.join(", ")}]`)
|
||||
.orderBy(...model.defaultOrder);
|
||||
|
||||
logIt(`[${type}] Found ${rows.length} rows to process...`);
|
||||
for (const row of rows) {
|
||||
if (!DRY_RUN) {
|
||||
logIt(`[${type}] Regenerating config #${row.id}: ${row.domain_names ? row.domain_names.join(", ") : 'port ' + row.incoming_port}`);
|
||||
await internalNginx.configure(proxyHostModel, "proxy_host", row);
|
||||
} else {
|
||||
logIt(`[${type}] Skipping generation of config #${row.id}: ${row.domain_names ? row.domain_names.join(", ") : 'port ' + row.incoming_port}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await processItems(proxyHostModel, "Proxy Host");
|
||||
await processItems(redirectionHostModel, "Redirection Host");
|
||||
await processItems(deadHostModel, "404 Host");
|
||||
await processItems(streamModel, "Stream");
|
||||
|
||||
logIt("Completed", "success");
|
||||
process.exit(0);
|
||||
@@ -10,11 +10,13 @@
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% if access_list.clients.length > 0 %}
|
||||
# Access Rules: {{ access_list.clients | size }} total
|
||||
{% for client in access_list.clients %}
|
||||
{{client | nginxAccessRule}}
|
||||
{% endfor %}
|
||||
deny all;
|
||||
{% endif %}
|
||||
|
||||
# Access checks must...
|
||||
{% if access_list.satisfy_any == 1 or access_list.satisfy_any == true %}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
{% if certificate and certificate_id > 0 -%}
|
||||
{% if ssl_forced == 1 or ssl_forced == true %}
|
||||
# Force SSL
|
||||
{% if trust_forwarded_proto == true %}
|
||||
set $trust_forwarded_proto "T";
|
||||
{% else %}
|
||||
set $trust_forwarded_proto "F";
|
||||
{% endif %}
|
||||
include conf.d/include/force-ssl.conf;
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
+469
-1048
File diff suppressed because it is too large
Load Diff
+8
-5
@@ -3,8 +3,9 @@
|
||||
|
||||
# This file assumes that the frontend has been built using ./scripts/frontend-build
|
||||
|
||||
ARG BASE_IMAGE=nginxproxymanager/nginx-full:certbot-node
|
||||
FROM nginxproxymanager/testca AS testca
|
||||
FROM nginxproxymanager/nginx-full:certbot-node
|
||||
FROM $BASE_IMAGE
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
ARG BUILD_VERSION
|
||||
@@ -38,15 +39,17 @@ EXPOSE 80 81 443
|
||||
|
||||
COPY backend /app
|
||||
COPY frontend/dist /app/frontend
|
||||
COPY docker/rootfs /
|
||||
|
||||
# Copy test CA cert for use in container and add to trusted certs from a self-signed CA
|
||||
COPY --from=testca /home/step/certs/root_ca.crt /etc/ssl/certs/NginxProxyManager.crt
|
||||
WORKDIR /etc/ssl/certs
|
||||
RUN ln -s NginxProxyManager.crt 1d0e3f10.0 && update-ca-certificates
|
||||
|
||||
WORKDIR /app
|
||||
RUN yarn install \
|
||||
&& yarn cache clean
|
||||
|
||||
# add late to limit cache-busting by modifications
|
||||
COPY docker/rootfs /
|
||||
COPY --from=testca /home/step/certs/root_ca.crt /etc/ssl/certs/NginxProxyManager.crt
|
||||
|
||||
# Remove frontend service not required for prod, dev nginx config as well
|
||||
RUN rm -rf /etc/s6-overlay/s6-rc.d/user/contents.d/frontend /etc/nginx/conf.d/dev.conf \
|
||||
&& chmod 644 /etc/logrotate.d/nginx-proxy-manager
|
||||
|
||||
@@ -20,10 +20,10 @@ RUN echo "fs.file-max = 65535" > /etc/sysctl.conf \
|
||||
|
||||
# Task
|
||||
WORKDIR /usr
|
||||
RUN curl -sL https://taskfile.dev/install.sh | sh
|
||||
WORKDIR /root
|
||||
RUN curl -sL 'https://taskfile.dev/install.sh' | sh
|
||||
|
||||
COPY rootfs /
|
||||
|
||||
COPY scripts/install-s6 /tmp/install-s6
|
||||
RUN rm -f /etc/nginx/conf.d/production.conf \
|
||||
&& chmod 644 /etc/logrotate.d/nginx-proxy-manager \
|
||||
@@ -31,8 +31,12 @@ RUN rm -f /etc/nginx/conf.d/production.conf \
|
||||
&& rm -f /tmp/install-s6 \
|
||||
&& chmod 644 -R /root/.cache
|
||||
|
||||
# Certs for testing purposes
|
||||
# Copy test CA cert for use in container and add to trusted certs from a self-signed CA
|
||||
COPY --from=testca /home/step/certs/root_ca.crt /etc/ssl/certs/NginxProxyManager.crt
|
||||
WORKDIR /etc/ssl/certs
|
||||
RUN ln -s NginxProxyManager.crt 1d0e3f10.0 && update-ca-certificates
|
||||
|
||||
WORKDIR /root
|
||||
|
||||
EXPOSE 80 81 443
|
||||
ENTRYPOINT [ "/init" ]
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# WARNING: This is a CI docker-compose file used for building and testing of the entire app, it should not be used for production.
|
||||
services:
|
||||
cypress:
|
||||
environment:
|
||||
CYPRESS_stack: "sqlite"
|
||||
command: cypress run --browser chrome --config-file=cypress/config/ci.mjs --expose grepTags="@isolated"
|
||||
|
||||
fullstack:
|
||||
environment:
|
||||
DB_SQLITE_FILE: "/data/mydb.sqlite"
|
||||
PUID: 1000
|
||||
PGID: 1000
|
||||
DISABLE_IPV6: "true"
|
||||
@@ -1,9 +1,12 @@
|
||||
# WARNING: This is a CI docker-compose file used for building and testing of the entire app, it should not be used for production.
|
||||
services:
|
||||
cypress:
|
||||
environment:
|
||||
CYPRESS_stack: "sqlite"
|
||||
|
||||
fullstack:
|
||||
environment:
|
||||
DB_SQLITE_FILE: '/data/mydb.sqlite'
|
||||
DB_SQLITE_FILE: "/data/mydb.sqlite"
|
||||
PUID: 1000
|
||||
PGID: 1000
|
||||
DISABLE_IPV6: 'true'
|
||||
DISABLE_IPV6: "true"
|
||||
|
||||
@@ -39,7 +39,7 @@ services:
|
||||
- website3.example.com
|
||||
|
||||
stepca:
|
||||
image: jc21/testca
|
||||
image: nginxproxymanager/testca
|
||||
volumes:
|
||||
- "./dev/resolv.conf:/etc/resolv.conf:ro"
|
||||
- "/etc/localtime:/etc/localtime:ro"
|
||||
@@ -109,7 +109,7 @@ services:
|
||||
- "cypress_logs:/test/results"
|
||||
- "./dev/resolv.conf:/etc/resolv.conf:ro"
|
||||
- "/etc/localtime:/etc/localtime:ro"
|
||||
command: cypress run --browser chrome --config-file=cypress/config/ci.mjs
|
||||
command: cypress run --browser chrome --config-file=cypress/config/ci.mjs --expose grepTags="-@isolated"
|
||||
networks:
|
||||
- fulltest
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ services:
|
||||
- pgdb.internal
|
||||
|
||||
stepca:
|
||||
image: jc21/testca
|
||||
image: nginxproxymanager/testca
|
||||
container_name: npm2dev.stepca
|
||||
volumes:
|
||||
- "./dev/resolv.conf:/etc/resolv.conf:ro"
|
||||
|
||||
@@ -5,9 +5,28 @@ if ($scheme = "http") {
|
||||
if ($request_uri = /.well-known/acme-challenge/test-challenge) {
|
||||
set $test "${test}T";
|
||||
}
|
||||
|
||||
# Check if the ssl staff has been handled
|
||||
set $test_ssl_handled "";
|
||||
if ($trust_forwarded_proto = "") {
|
||||
set $trust_forwarded_proto "F";
|
||||
}
|
||||
if ($trust_forwarded_proto = "T") {
|
||||
set $test_ssl_handled "${test_ssl_handled}T";
|
||||
}
|
||||
if ($http_x_forwarded_proto = "https") {
|
||||
set $test_ssl_handled "${test_ssl_handled}S";
|
||||
}
|
||||
if ($http_x_forwarded_scheme = "https") {
|
||||
set $test_ssl_handled "${test_ssl_handled}S";
|
||||
}
|
||||
if ($test_ssl_handled = "TSS") {
|
||||
set $test_ssl_handled "TS";
|
||||
}
|
||||
if ($test_ssl_handled = "TS") {
|
||||
set $test "${test}S";
|
||||
}
|
||||
|
||||
if ($test = H) {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
add_header X-Served-By $host;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-Scheme $scheme;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header X-Forwarded-Scheme $x_forwarded_scheme;
|
||||
proxy_set_header X-Forwarded-Proto $x_forwarded_proto;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_pass $forward_scheme://$server:$port$request_uri;
|
||||
|
||||
@@ -47,16 +47,28 @@ http {
|
||||
proxy_cache_path /var/lib/nginx/cache/private levels=1:2 keys_zone=private-cache:5m max_size=1024m;
|
||||
|
||||
# Log format and fallback log file
|
||||
include /etc/nginx/conf.d/include/log-proxy.conf;
|
||||
include /etc/nginx/conf.d/include/log-proxy[.]conf;
|
||||
|
||||
# Dynamically generated resolvers file
|
||||
include /etc/nginx/conf.d/include/resolvers.conf;
|
||||
include /etc/nginx/conf.d/include/resolvers[.]conf;
|
||||
|
||||
# Default upstream scheme
|
||||
map $host $forward_scheme {
|
||||
default http;
|
||||
}
|
||||
|
||||
# Handle upstream X-Forwarded-Proto and X-Forwarded-Scheme header
|
||||
map $http_x_forwarded_proto $x_forwarded_proto {
|
||||
"http" "http";
|
||||
"https" "https";
|
||||
default $scheme;
|
||||
}
|
||||
map $http_x_forwarded_scheme $x_forwarded_scheme {
|
||||
"http" "http";
|
||||
"https" "https";
|
||||
default $scheme;
|
||||
}
|
||||
|
||||
# Real IP Determination
|
||||
|
||||
# Local subnets:
|
||||
@@ -64,7 +76,7 @@ http {
|
||||
set_real_ip_from 172.16.0.0/12; # Includes Docker subnet
|
||||
set_real_ip_from 192.168.0.0/16;
|
||||
# NPM generated CDN ip ranges:
|
||||
include conf.d/include/ip_ranges.conf;
|
||||
include conf.d/include/ip_ranges[.]conf;
|
||||
# always put the following 2 lines after ip subnets:
|
||||
real_ip_header X-Real-IP;
|
||||
real_ip_recursive on;
|
||||
@@ -86,7 +98,7 @@ http {
|
||||
|
||||
stream {
|
||||
# Log format and fallback log file
|
||||
include /etc/nginx/conf.d/include/log-stream.conf;
|
||||
include /etc/nginx/conf.d/include/log-stream[.]conf;
|
||||
|
||||
# Files generated by NPM
|
||||
include /data/nginx/stream/*.conf;
|
||||
|
||||
@@ -11,11 +11,11 @@ log_info 'Starting backend ...'
|
||||
|
||||
if [ "${DEVELOPMENT:-}" = 'true' ]; then
|
||||
s6-setuidgid "$PUID:$PGID" yarn install
|
||||
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"
|
||||
exec s6-setuidgid "$PUID:$PGID" bash -c "export HOME=$NPMHOME;export CERTBOT_VERSION=$CERTBOT_VERSION;node --max_old_space_size=250 --abort_on_uncaught_exception node_modules/nodemon/bin/nodemon.js"
|
||||
else
|
||||
while :
|
||||
do
|
||||
s6-setuidgid "$PUID:$PGID" bash -c "export HOME=$NPMHOME;node --abort_on_uncaught_exception --max_old_space_size=250 index.js"
|
||||
s6-setuidgid "$PUID:$PGID" bash -c "export HOME=$NPMHOME;export CERTBOT_VERSION=$CERTBOT_VERSION;node --abort_on_uncaught_exception --max_old_space_size=250 index.js"
|
||||
sleep 1
|
||||
done
|
||||
fi
|
||||
|
||||
@@ -7,8 +7,10 @@ log_info 'Dynamic resolvers ...'
|
||||
|
||||
# Dynamically generate resolvers file, if resolver is IPv6, enclose in `[]`
|
||||
# thanks @tfmm
|
||||
if [ "$(is_true "$DISABLE_IPV6")" = '1' ]; then
|
||||
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
|
||||
else
|
||||
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
|
||||
if [ "$(is_true "${DISABLE_RESOLVER:-}")" = '0' ]; then
|
||||
if [ "$(is_true "${DISABLE_IPV6:-}")" = '1' ]; then
|
||||
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
|
||||
else
|
||||
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
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -8,11 +8,15 @@ set -e
|
||||
|
||||
log_info 'IPv6 ...'
|
||||
|
||||
is_mounted() {
|
||||
awk -v p="$1" '$5 == p { found=1 } END { exit !found }' /proc/self/mountinfo
|
||||
}
|
||||
|
||||
process_folder () {
|
||||
FILES=$(find "$1" -type f -name "*.conf")
|
||||
SED_REGEX=
|
||||
|
||||
if [ "$(is_true "$DISABLE_IPV6")" = '1' ]; then
|
||||
if [ "$(is_true "${DISABLE_IPV6:-}")" = '1' ]; then
|
||||
# IPV6 is disabled
|
||||
echo "Disabling IPV6 in hosts in: $1"
|
||||
SED_REGEX='s/^([^#]*)listen \[::\]/\1#listen [::]/g'
|
||||
@@ -25,7 +29,16 @@ process_folder () {
|
||||
for FILE in $FILES
|
||||
do
|
||||
echo "- ${FILE}"
|
||||
echo "$(sed -E "$SED_REGEX" "$FILE")" > $FILE
|
||||
TMPFILE="${FILE}.tmp"
|
||||
|
||||
if is_mounted "${FILE}"; then
|
||||
echo "WARNING: skipping ${FILE} — mounted file" >&2
|
||||
elif sed -E "$SED_REGEX" "$FILE" > "$TMPFILE" && [ -s "$TMPFILE" ]; then
|
||||
mv "$TMPFILE" "$FILE"
|
||||
else
|
||||
echo "WARNING: skipping ${FILE} — sed produced empty output" >&2
|
||||
rm -f "$TMPFILE"
|
||||
fi
|
||||
done
|
||||
|
||||
# ensure the files are still owned by the npm user
|
||||
|
||||
@@ -21,6 +21,10 @@ NPMGROUP=npm
|
||||
NPMHOME=/tmp/npmuserhome
|
||||
export NPMUSER NPMGROUP NPMHOME
|
||||
|
||||
# Query the certbot version just once and use it elsewhere
|
||||
CERTBOT_VERSION="$(certbot --version | grep -Eo '[0-9](\.[0-9]+)+')"
|
||||
export CERTBOT_VERSION
|
||||
|
||||
if [[ "$PUID" -ne '0' ]] && [ "$PGID" = '0' ]; then
|
||||
# set group id to same as user id,
|
||||
# the user probably forgot to specify the group id and
|
||||
|
||||
@@ -17,10 +17,6 @@ case $TARGETPLATFORM in
|
||||
S6_ARCH=aarch64
|
||||
;;
|
||||
|
||||
linux/arm/v7)
|
||||
S6_ARCH=armhf
|
||||
;;
|
||||
|
||||
*)
|
||||
S6_ARCH=x86_64
|
||||
;;
|
||||
|
||||
+78
-33
@@ -1,4 +1,4 @@
|
||||
import { defineConfig, type DefaultTheme } from 'vitepress';
|
||||
import { defineConfig } from "vitepress";
|
||||
|
||||
// https://vitepress.dev/reference/site-config
|
||||
export default defineConfig({
|
||||
@@ -6,56 +6,101 @@ export default defineConfig({
|
||||
description: "Expose your services easily and securely",
|
||||
head: [
|
||||
["link", { rel: "icon", href: "/icon.png" }],
|
||||
["meta", { name: "description", content: "Docker container and built in Web Application for managing Nginx proxy hosts with a simple, powerful interface, providing free SSL support via Let's Encrypt" }],
|
||||
[
|
||||
"meta",
|
||||
{
|
||||
name: "description",
|
||||
content:
|
||||
"Docker container and built in Web Application for managing Nginx proxy hosts with a simple, powerful interface, providing free SSL support via Let's Encrypt",
|
||||
},
|
||||
],
|
||||
["meta", { property: "og:title", content: "Nginx Proxy Manager" }],
|
||||
["meta", { property: "og:description", content: "Docker container and built in Web Application for managing Nginx proxy hosts with a simple, powerful interface, providing free SSL support via Let's Encrypt"}],
|
||||
[
|
||||
"meta",
|
||||
{
|
||||
property: "og:description",
|
||||
content:
|
||||
"Docker container and built in Web Application for managing Nginx proxy hosts with a simple, powerful interface, providing free SSL support via Let's Encrypt",
|
||||
},
|
||||
],
|
||||
["meta", { property: "og:type", content: "website" }],
|
||||
["meta", { property: "og:url", content: "https://nginxproxymanager.com/" }],
|
||||
["meta", { property: "og:image", content: "https://nginxproxymanager.com/icon.png" }],
|
||||
["meta", { name: "twitter:card", content: "summary"}],
|
||||
["meta", { name: "twitter:title", content: "Nginx Proxy Manager"}],
|
||||
["meta", { name: "twitter:description", content: "Docker container and built in Web Application for managing Nginx proxy hosts with a simple, powerful interface, providing free SSL support via Let's Encrypt"}],
|
||||
["meta", { name: "twitter:image", content: "https://nginxproxymanager.com/icon.png"}],
|
||||
["meta", { name: "twitter:alt", content: "Nginx Proxy Manager"}],
|
||||
[
|
||||
"meta",
|
||||
{
|
||||
property: "og:image",
|
||||
content: "https://nginxproxymanager.com/icon.png",
|
||||
},
|
||||
],
|
||||
["meta", { name: "twitter:card", content: "summary" }],
|
||||
["meta", { name: "twitter:title", content: "Nginx Proxy Manager" }],
|
||||
[
|
||||
"meta",
|
||||
{
|
||||
name: "twitter:description",
|
||||
content:
|
||||
"Docker container and built in Web Application for managing Nginx proxy hosts with a simple, powerful interface, providing free SSL support via Let's Encrypt",
|
||||
},
|
||||
],
|
||||
[
|
||||
"meta",
|
||||
{
|
||||
name: "twitter:image",
|
||||
content: "https://nginxproxymanager.com/icon.png",
|
||||
},
|
||||
],
|
||||
["meta", { name: "twitter:alt", content: "Nginx Proxy Manager" }],
|
||||
// GA
|
||||
['script', { async: 'true', src: 'https://www.googletagmanager.com/gtag/js?id=G-TXT8F5WY5B'}],
|
||||
['script', {}, "window.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\ngtag('js', new Date());\ngtag('config', 'G-TXT8F5WY5B');"],
|
||||
[
|
||||
"script",
|
||||
{
|
||||
async: "true",
|
||||
src: "https://www.googletagmanager.com/gtag/js?id=G-TXT8F5WY5B",
|
||||
},
|
||||
],
|
||||
[
|
||||
"script",
|
||||
{},
|
||||
"window.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\ngtag('js', new Date());\ngtag('config', 'G-TXT8F5WY5B');",
|
||||
],
|
||||
],
|
||||
sitemap: {
|
||||
hostname: 'https://nginxproxymanager.com'
|
||||
hostname: "https://nginxproxymanager.com",
|
||||
},
|
||||
metaChunk: true,
|
||||
srcDir: './src',
|
||||
outDir: './dist',
|
||||
srcDir: "./src",
|
||||
outDir: "./dist",
|
||||
themeConfig: {
|
||||
// https://vitepress.dev/reference/default-theme-config
|
||||
logo: { src: '/logo.svg', width: 24, height: 24 },
|
||||
nav: [
|
||||
{ text: 'Setup', link: '/setup/' },
|
||||
],
|
||||
logo: { src: "/logo.svg", width: 24, height: 24 },
|
||||
nav: [{ text: "Setup", link: "/setup/" }],
|
||||
sidebar: [
|
||||
{
|
||||
items: [
|
||||
// { text: 'Home', link: '/' },
|
||||
{ text: 'Guide', link: '/guide/' },
|
||||
{ text: 'Screenshots', link: '/screenshots/' },
|
||||
{ text: 'Setup Instructions', link: '/setup/' },
|
||||
{ text: 'Advanced Configuration', link: '/advanced-config/' },
|
||||
{ text: 'Upgrading', link: '/upgrading/' },
|
||||
{ text: 'Frequently Asked Questions', link: '/faq/' },
|
||||
{ text: 'Third Party', link: '/third-party/' },
|
||||
]
|
||||
}
|
||||
{ text: "Guide", link: "/guide/" },
|
||||
{ text: "Screenshots", link: "/screenshots/" },
|
||||
{ text: "Setup Instructions", link: "/setup/" },
|
||||
{ text: "Advanced Configuration", link: "/advanced-config/" },
|
||||
{ text: "Upgrading", link: "/upgrading/" },
|
||||
{ text: "Frequently Asked Questions", link: "/faq/" },
|
||||
{ text: "Certbot", link: "/certbot/" },
|
||||
{ text: "Third Party", link: "/third-party/" },
|
||||
],
|
||||
},
|
||||
],
|
||||
socialLinks: [
|
||||
{ icon: 'github', link: 'https://github.com/NginxProxyManager/nginx-proxy-manager' }
|
||||
{
|
||||
icon: "github",
|
||||
link: "https://github.com/NginxProxyManager/nginx-proxy-manager",
|
||||
},
|
||||
],
|
||||
search: {
|
||||
provider: 'local'
|
||||
provider: "local",
|
||||
},
|
||||
footer: {
|
||||
message: 'Released under the MIT License.',
|
||||
copyright: 'Copyright © 2016-present jc21.com'
|
||||
}
|
||||
}
|
||||
message: "Released under the MIT License.",
|
||||
copyright: "Copyright © 2016-present jc21.com",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
+2
-1
@@ -2,7 +2,8 @@
|
||||
"scripts": {
|
||||
"dev": "vitepress dev --host",
|
||||
"build": "vitepress build",
|
||||
"preview": "vitepress preview"
|
||||
"preview": "vitepress preview",
|
||||
"set-version": "./scripts/set-version.sh"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitepress": "^1.6.4"
|
||||
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
set -euf
|
||||
|
||||
# this script accepts a version number as an argument
|
||||
# and replaces {{VERSION}} in src/*.md with the provided version number.
|
||||
|
||||
if [ "$#" -ne 1 ]; then
|
||||
echo "Usage: $0 <version>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
cd "$DIR/.." || exit 1
|
||||
|
||||
VERSION="$1"
|
||||
# find all .md files in src/ and replace {{VERSION}} with the provided version number
|
||||
find src/ -type f -name "*.md" -exec sed -i "s/{{VERSION}}/$VERSION/g" {} \;
|
||||
@@ -14,7 +14,7 @@ on the `data` and `letsencrypt` folders at startup.
|
||||
```yml
|
||||
services:
|
||||
app:
|
||||
image: 'jc21/nginx-proxy-manager:latest'
|
||||
image: 'jc21/nginx-proxy-manager:{{VERSION}}'
|
||||
environment:
|
||||
PUID: 1000
|
||||
PGID: 1000
|
||||
@@ -101,7 +101,7 @@ secrets:
|
||||
|
||||
services:
|
||||
app:
|
||||
image: 'jc21/nginx-proxy-manager:latest'
|
||||
image: 'jc21/nginx-proxy-manager:{{VERSION}}'
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
# Public HTTP Port:
|
||||
@@ -130,18 +130,16 @@ services:
|
||||
- db
|
||||
|
||||
db:
|
||||
image: jc21/mariadb-aria
|
||||
image: 'linuxserver/mariadb'
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
# MYSQL_ROOT_PASSWORD: "npm" # use secret instead
|
||||
MYSQL_ROOT_PASSWORD__FILE: /run/secrets/DB_ROOT_PWD
|
||||
MYSQL_DATABASE: "npm"
|
||||
MYSQL_USER: "npm"
|
||||
# MYSQL_PASSWORD: "npm" # use secret instead
|
||||
MYSQL_DATABASE: 'npm'
|
||||
MYSQL_USER: 'npm'
|
||||
MYSQL_PASSWORD__FILE: /run/secrets/MYSQL_PWD
|
||||
MARIADB_AUTO_UPGRADE: '1'
|
||||
TZ: 'Australia/Brisbane'
|
||||
volumes:
|
||||
- ./mysql:/var/lib/mysql
|
||||
- ./mariadb:/config
|
||||
secrets:
|
||||
- DB_ROOT_PWD
|
||||
- MYSQL_PWD
|
||||
@@ -233,8 +231,20 @@ load_module /usr/lib/nginx/modules/ngx_stream_geoip2_module.so;
|
||||
|
||||
Setting these environment variables will create the default user on startup, skipping the UI first user setup screen:
|
||||
|
||||
```
|
||||
```yml
|
||||
environment:
|
||||
INITIAL_ADMIN_EMAIL: my@example.com
|
||||
INITIAL_ADMIN_PASSWORD: mypassword1
|
||||
```
|
||||
|
||||
## Disable Nginx Resolver
|
||||
|
||||
On startup, we generate a resolvers directive for Nginx unless this is defined:
|
||||
|
||||
```yml
|
||||
environment:
|
||||
DISABLE_RESOLVER: true
|
||||
```
|
||||
|
||||
In this configuration, all DNS queries performed by Nginx will fall to the `/etc/hosts` file
|
||||
and then the `/etc/resolv.conf`.
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
## Certbot DNS plugins in Nginx Proxy Manager
|
||||
|
||||
Nginx Proxy Manager uses Certbot to issue and renew Let’s Encrypt certificates.
|
||||
|
||||
When you request a certificate using a DNS challenge, Nginx Proxy Manager installs
|
||||
the corresponding Certbot DNS plugin for the provider you selected. The available
|
||||
providers and package versions are defined in
|
||||
[certbot-dns-plugins.json](https://github.com/NginxProxyManager/nginx-proxy-manager/blob/develop/backend/certbot/dns-plugins.json).
|
||||
|
||||
## Important limitations
|
||||
|
||||
DNS plugins are maintained independently from Certbot and from Nginx Proxy Manager. As a result:
|
||||
- Some plugins may lag behind current Certbot or dependency versions.
|
||||
- A plugin may install package versions that conflict with other Certbot components.
|
||||
- Support quality varies between providers, and not every plugin is regularly tested in Nginx Proxy Manager.
|
||||
|
||||
::: warning
|
||||
Using more than one DNS provider in the same Nginx Proxy Manager instance may introduce Python
|
||||
dependency conflicts between Certbot plugins.
|
||||
:::
|
||||
|
||||
## If a DNS plugin does not work
|
||||
|
||||
1. Check the Nginx Proxy Manager container logs for Certbot or Python package installation errors.
|
||||
2. Identify the plugin package and version defined for your provider in
|
||||
[certbot-dns-plugins.json](https://github.com/NginxProxyManager/nginx-proxy-manager/blob/develop/backend/certbot/dns-plugins.json).
|
||||
3. Check [PyPI](https://pypi.org/) or the plugin project for a newer compatible release.
|
||||
4. If needed, update the plugin definition, including any required dependency pins.
|
||||
5. Submitting a pull request and CI will build a testable docker image for you.
|
||||
|
||||
## File reference
|
||||
|
||||
Use this file when reviewing or updating provider definitions:
|
||||
- [certbot-dns-plugins.json](https://github.com/NginxProxyManager/nginx-proxy-manager/blob/develop/backend/certbot/dns-plugins.json)
|
||||
@@ -64,7 +64,7 @@ I won't go in to too much detail here but here are the basics for someone new to
|
||||
```yml
|
||||
services:
|
||||
app:
|
||||
image: 'jc21/nginx-proxy-manager:latest'
|
||||
image: 'jc21/nginx-proxy-manager:{{VERSION}}'
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
TZ: "Australia/Brisbane"
|
||||
|
||||
+16
-19
@@ -11,7 +11,7 @@ Create a `docker-compose.yml` file:
|
||||
```yml
|
||||
services:
|
||||
app:
|
||||
image: 'jc21/nginx-proxy-manager:latest'
|
||||
image: 'jc21/nginx-proxy-manager:{{VERSION}}'
|
||||
restart: unless-stopped
|
||||
|
||||
ports:
|
||||
@@ -45,10 +45,7 @@ docker compose up -d
|
||||
|
||||
## Using MySQL / MariaDB Database
|
||||
|
||||
If you opt for the MySQL configuration you will have to provide the database server yourself. You can also use MariaDB. Here are the minimum supported versions:
|
||||
|
||||
- MySQL v5.7.8+
|
||||
- MariaDB v10.2.7+
|
||||
If you opt for the MySQL configuration you will have to provide the database server yourself.
|
||||
|
||||
It's easy to use another docker container for your database also and link it as part of the docker stack, so that's what the following examples
|
||||
are going to use.
|
||||
@@ -58,7 +55,7 @@ Here is an example of what your `docker-compose.yml` will look like when using a
|
||||
```yml
|
||||
services:
|
||||
app:
|
||||
image: 'jc21/nginx-proxy-manager:latest'
|
||||
image: 'jc21/nginx-proxy-manager:{{VERSION}}'
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
# These ports are in format <host-port>:<container-port>
|
||||
@@ -88,31 +85,29 @@ services:
|
||||
- db
|
||||
|
||||
db:
|
||||
image: 'jc21/mariadb-aria:latest'
|
||||
image: 'linuxserver/mariadb'
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: 'npm'
|
||||
MYSQL_DATABASE: 'npm'
|
||||
MYSQL_USER: 'npm'
|
||||
MYSQL_PASSWORD: 'npm'
|
||||
MARIADB_AUTO_UPGRADE: '1'
|
||||
TZ: 'Australia/Brisbane'
|
||||
volumes:
|
||||
- ./mysql:/var/lib/mysql
|
||||
- ./mariadb:/config
|
||||
```
|
||||
|
||||
::: warning
|
||||
|
||||
Please note, that `DB_MYSQL_*` environment variables will take precedent over `DB_SQLITE_*` variables. So if you keep the MySQL variables, you will not be able to use SQLite.
|
||||
|
||||
:::
|
||||
|
||||
### Optional: MySQL / MariaDB SSL
|
||||
|
||||
You can enable TLS for the MySQL/MariaDB connection with these environment variables:
|
||||
|
||||
- DB_MYSQL_SSL: Enable SSL when set to true. If unset or false, SSL disabled (previous default behaviour).
|
||||
- DB_MYSQL_SSL_REJECT_UNAUTHORIZED: (default: true) Validate the server certificate chain. Set to false to allow self‑signed/unknown CA.
|
||||
- DB_MYSQL_SSL_VERIFY_IDENTITY: (default: true) Performs host name / identity verification.
|
||||
- `DB_MYSQL_SSL`: Enable SSL when set to true. If unset or false, SSL disabled (previous default behaviour).
|
||||
- `DB_MYSQL_SSL_REJECT_UNAUTHORIZED`: (default: true) Validate the server certificate chain. Set to false to allow self‑signed/unknown CA.
|
||||
- `DB_MYSQL_SSL_VERIFY_IDENTITY`: (default: true) Performs host name / identity verification.
|
||||
|
||||
Enabling SSL using a self-signed cert (not recommended for production).
|
||||
|
||||
@@ -123,7 +118,7 @@ Similar to the MySQL server setup:
|
||||
```yml
|
||||
services:
|
||||
app:
|
||||
image: 'jc21/nginx-proxy-manager:latest'
|
||||
image: 'jc21/nginx-proxy-manager:{{VERSION}}'
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
# These ports are in format <host-port>:<container-port>
|
||||
@@ -155,7 +150,7 @@ services:
|
||||
POSTGRES_PASSWORD: 'npmpass'
|
||||
POSTGRES_DB: 'npm'
|
||||
volumes:
|
||||
- ./postgresql:/var/lib/postgresql
|
||||
- ./postgres_data:/var/lib/postgresql/data
|
||||
```
|
||||
|
||||
::: warning
|
||||
@@ -169,7 +164,11 @@ Custom Postgres schema is not supported, as such `public` will be used.
|
||||
The docker images support the following architectures:
|
||||
- amd64
|
||||
- arm64
|
||||
- armv7
|
||||
|
||||
::: warning
|
||||
`armv7` is no longer supported in version 2.14+. This is due to Nodejs dropping support for armhf. Please
|
||||
use the `2.13.7` image tag if this applies to you.
|
||||
:::
|
||||
|
||||
The docker images are a manifest of all the architecture docker builds supported, so this means
|
||||
you don't have to worry about doing anything special and you can follow the common instructions above.
|
||||
@@ -181,8 +180,6 @@ for a list of supported architectures and if you want one that doesn't exist,
|
||||
Also, if you don't know how to already, follow [this guide to install docker and docker-compose](https://manre-universe.net/how-to-run-docker-and-docker-compose-on-raspbian/)
|
||||
on Raspbian.
|
||||
|
||||
Please note that the `jc21/mariadb-aria:latest` image might have some problems on some ARM devices, if you want a separate database container, use the `yobasystems/alpine-mariadb:latest` image.
|
||||
|
||||
## Initial Run
|
||||
|
||||
After the app is running for the first time, the following will happen:
|
||||
|
||||
Vendored
+3
-1
@@ -14,7 +14,9 @@ Known integrations:
|
||||
- [Proxmox Scripts](https://github.com/ej52/proxmox-scripts/tree/main/apps/nginx-proxy-manager)
|
||||
- [Proxmox VE Helper-Scripts](https://community-scripts.github.io/ProxmoxVE/scripts?id=nginxproxymanager)
|
||||
- [nginxproxymanagerGraf](https://github.com/ma-karai/nginxproxymanagerGraf)
|
||||
- [selfhosting.sh Nginx Proxy Manager Guide](https://selfhosting.sh/apps/nginx-proxy-manager/) - Complete Docker Compose setup guide with SSL configuration, access lists, and proxy host management.
|
||||
- [NPM Auth Gateway](https://github.com/Mark0025/npm-auth-gateway) — User-level access control with auto IP whitelisting via auth providers. [Details](/third-party/npm-auth-gateway)
|
||||
|
||||
|
||||
If you would like your integration of NPM listed, please open a
|
||||
[Github issue](https://github.com/NginxProxyManager/nginx-proxy-manager/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=)
|
||||
[Github issue](https://github.com/NginxProxyManager/nginx-proxy-manager/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=)
|
||||
+83
@@ -0,0 +1,83 @@
|
||||
---
|
||||
outline: deep
|
||||
---
|
||||
|
||||
# NPM Auth Gateway
|
||||
|
||||
User-level access control for Nginx Proxy Manager with auto IP whitelisting.
|
||||
|
||||
**Repository:** [github.com/Mark0025/npm-auth-gateway](https://github.com/Mark0025/npm-auth-gateway)
|
||||
|
||||
## What It Does
|
||||
|
||||
NPM Auth Gateway is a companion app that adds user management on top of NPM's access list system. Instead of manually adding IPs to access lists, users log in through an auth provider and their IP is automatically whitelisted on the access lists they've been assigned to.
|
||||
|
||||
NPM remains fully in control — the gateway only reads and writes through NPM's REST API. All access enforcement stays in NPM's nginx config.
|
||||
|
||||
## The Problem It Solves
|
||||
|
||||
- **Manual IP management** — every time a user needs access, an admin manually adds their IP to an access list
|
||||
- **IP changes** — mobile users, VPNs, and travel mean IPs change constantly
|
||||
- **No user visibility** — access lists contain IPs, but there's no record of who each IP belongs to
|
||||
- **Scaling** — managing 10+ users across multiple access lists gets tedious
|
||||
|
||||
## How It Works
|
||||
|
||||
```
|
||||
Browser → NPM (SSL) → Auth Gateway → Auth Provider
|
||||
↓
|
||||
NPM REST API (:81)
|
||||
auto-add IP to access lists
|
||||
```
|
||||
|
||||
1. Admin creates a user by email
|
||||
2. Admin assigns access — a table of proxy hosts with checkboxes showing which hosts each access list protects
|
||||
3. User logs in → IP detected → automatically added to their assigned NPM access lists
|
||||
4. User's IP changes → they log in again → new IP auto-added
|
||||
5. Admin revokes access → user's IPs removed from all access lists
|
||||
|
||||
## Key Features
|
||||
|
||||
- **Auto IP whitelisting** on login
|
||||
- **Per-host access control** with checkboxes (not abstract groups)
|
||||
- **Admin/user roles** — admin sees everything, users see only their assigned hosts
|
||||
- **Personalized dashboard** — users see their services as clickable cards
|
||||
- **Login logging** with IP history per user
|
||||
- **One-click revoke** — removes user's IPs from all access lists
|
||||
- **Searchable tables** for proxy hosts and users
|
||||
- **Survives gateway failure** — NPM keeps enforcing existing whitelists
|
||||
|
||||
## Architecture
|
||||
|
||||
| Responsibility | Who Handles It |
|
||||
|---|---|
|
||||
| SSL termination | **NPM** |
|
||||
| Proxy host configuration | **NPM** |
|
||||
| Access list enforcement | **NPM** |
|
||||
| IP whitelisting | **NPM** |
|
||||
| User identity | **Auth Provider** |
|
||||
| User → access list mapping | **Gateway** |
|
||||
| Auto IP whitelisting | **Gateway** |
|
||||
|
||||
**No database required.** NPM stores all proxy/ACL config. User metadata lives in the auth provider. Zero state duplication.
|
||||
|
||||
## NPM API Endpoints Used
|
||||
|
||||
| Endpoint | Purpose |
|
||||
|---|---|
|
||||
| `POST /api/tokens` | Authentication |
|
||||
| `GET /api/nginx/proxy-hosts` | List proxy hosts |
|
||||
| `GET /api/nginx/access-lists` | List access lists |
|
||||
| `PUT /api/nginx/access-lists/:id` | Update access list IPs |
|
||||
| `POST /api/nginx/access-lists` | Create access list |
|
||||
| `GET /api/nginx/certificates` | List SSL certificates |
|
||||
|
||||
## Setup
|
||||
|
||||
See the [repository README](https://github.com/Mark0025/npm-auth-gateway) for Docker deployment instructions.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
Next.js / React / TypeScript / Docker
|
||||
|
||||
The auth provider is swappable — the proof of concept uses Clerk, but any OIDC provider works (Auth0, Keycloak, Authentik, etc.).
|
||||
+146
-111
@@ -335,85 +335,130 @@
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba"
|
||||
integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==
|
||||
|
||||
"@rollup/rollup-android-arm-eabi@4.24.0":
|
||||
version "4.24.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz#1661ff5ea9beb362795304cb916049aba7ac9c54"
|
||||
integrity sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==
|
||||
"@rollup/rollup-android-arm-eabi@4.59.0":
|
||||
version "4.59.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz#a6742c74c7d9d6d604ef8a48f99326b4ecda3d82"
|
||||
integrity sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==
|
||||
|
||||
"@rollup/rollup-android-arm64@4.24.0":
|
||||
version "4.24.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz#2ffaa91f1b55a0082b8a722525741aadcbd3971e"
|
||||
integrity sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==
|
||||
"@rollup/rollup-android-arm64@4.59.0":
|
||||
version "4.59.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz#97247be098de4df0c11971089fd2edf80a5da8cf"
|
||||
integrity sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==
|
||||
|
||||
"@rollup/rollup-darwin-arm64@4.24.0":
|
||||
version "4.24.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz#627007221b24b8cc3063703eee0b9177edf49c1f"
|
||||
integrity sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==
|
||||
"@rollup/rollup-darwin-arm64@4.59.0":
|
||||
version "4.59.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz#674852cf14cf11b8056e0b1a2f4e872b523576cf"
|
||||
integrity sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==
|
||||
|
||||
"@rollup/rollup-darwin-x64@4.24.0":
|
||||
version "4.24.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz#0605506142b9e796c370d59c5984ae95b9758724"
|
||||
integrity sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==
|
||||
"@rollup/rollup-darwin-x64@4.59.0":
|
||||
version "4.59.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz#36dfd7ed0aaf4d9d89d9ef983af72632455b0246"
|
||||
integrity sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==
|
||||
|
||||
"@rollup/rollup-linux-arm-gnueabihf@4.24.0":
|
||||
version "4.24.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz#62dfd196d4b10c0c2db833897164d2d319ee0cbb"
|
||||
integrity sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==
|
||||
"@rollup/rollup-freebsd-arm64@4.59.0":
|
||||
version "4.59.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz#2f87c2074b4220260fdb52a9996246edfc633c22"
|
||||
integrity sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==
|
||||
|
||||
"@rollup/rollup-linux-arm-musleabihf@4.24.0":
|
||||
version "4.24.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz#53ce72aeb982f1f34b58b380baafaf6a240fddb3"
|
||||
integrity sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==
|
||||
"@rollup/rollup-freebsd-x64@4.59.0":
|
||||
version "4.59.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz#9b5a26522a38a95dc06616d1939d4d9a76937803"
|
||||
integrity sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==
|
||||
|
||||
"@rollup/rollup-linux-arm64-gnu@4.24.0":
|
||||
version "4.24.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz#1632990f62a75c74f43e4b14ab3597d7ed416496"
|
||||
integrity sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==
|
||||
"@rollup/rollup-linux-arm-gnueabihf@4.59.0":
|
||||
version "4.59.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz#86aa4859385a8734235b5e40a48e52d770758c3a"
|
||||
integrity sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==
|
||||
|
||||
"@rollup/rollup-linux-arm64-musl@4.24.0":
|
||||
version "4.24.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz#8c03a996efb41e257b414b2e0560b7a21f2d9065"
|
||||
integrity sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==
|
||||
"@rollup/rollup-linux-arm-musleabihf@4.59.0":
|
||||
version "4.59.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz#cbe70e56e6ece8dac83eb773b624fc9e5a460976"
|
||||
integrity sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==
|
||||
|
||||
"@rollup/rollup-linux-powerpc64le-gnu@4.24.0":
|
||||
version "4.24.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz#5b98729628d5bcc8f7f37b58b04d6845f85c7b5d"
|
||||
integrity sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==
|
||||
"@rollup/rollup-linux-arm64-gnu@4.59.0":
|
||||
version "4.59.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz#d14992a2e653bc3263d284bc6579b7a2890e1c45"
|
||||
integrity sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==
|
||||
|
||||
"@rollup/rollup-linux-riscv64-gnu@4.24.0":
|
||||
version "4.24.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz#48e42e41f4cabf3573cfefcb448599c512e22983"
|
||||
integrity sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==
|
||||
"@rollup/rollup-linux-arm64-musl@4.59.0":
|
||||
version "4.59.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz#2fdd1ddc434ea90aeaa0851d2044789b4d07f6da"
|
||||
integrity sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==
|
||||
|
||||
"@rollup/rollup-linux-s390x-gnu@4.24.0":
|
||||
version "4.24.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz#e0b4f9a966872cb7d3e21b9e412a4b7efd7f0b58"
|
||||
integrity sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==
|
||||
"@rollup/rollup-linux-loong64-gnu@4.59.0":
|
||||
version "4.59.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz#8a181e6f89f969f21666a743cd411416c80099e7"
|
||||
integrity sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==
|
||||
|
||||
"@rollup/rollup-linux-x64-gnu@4.24.0":
|
||||
version "4.24.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz#78144741993100f47bd3da72fce215e077ae036b"
|
||||
integrity sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==
|
||||
"@rollup/rollup-linux-loong64-musl@4.59.0":
|
||||
version "4.59.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz#904125af2babc395f8061daa27b5af1f4e3f2f78"
|
||||
integrity sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==
|
||||
|
||||
"@rollup/rollup-linux-x64-musl@4.24.0":
|
||||
version "4.24.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz#d9fe32971883cd1bd858336bd33a1c3ca6146127"
|
||||
integrity sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==
|
||||
"@rollup/rollup-linux-ppc64-gnu@4.59.0":
|
||||
version "4.59.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz#a57970ac6864c9a3447411a658224bdcf948be22"
|
||||
integrity sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==
|
||||
|
||||
"@rollup/rollup-win32-arm64-msvc@4.24.0":
|
||||
version "4.24.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz#71fa3ea369316db703a909c790743972e98afae5"
|
||||
integrity sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==
|
||||
"@rollup/rollup-linux-ppc64-musl@4.59.0":
|
||||
version "4.59.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz#bb84de5b26870567a4267666e08891e80bb56a63"
|
||||
integrity sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==
|
||||
|
||||
"@rollup/rollup-win32-ia32-msvc@4.24.0":
|
||||
version "4.24.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz#653f5989a60658e17d7576a3996deb3902e342e2"
|
||||
integrity sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==
|
||||
"@rollup/rollup-linux-riscv64-gnu@4.59.0":
|
||||
version "4.59.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz#72d00d2c7fb375ce3564e759db33f17a35bffab9"
|
||||
integrity sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc@4.24.0":
|
||||
version "4.24.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz#0574d7e87b44ee8511d08cc7f914bcb802b70818"
|
||||
integrity sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==
|
||||
"@rollup/rollup-linux-riscv64-musl@4.59.0":
|
||||
version "4.59.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz#4c166ef58e718f9245bd31873384ba15a5c1a883"
|
||||
integrity sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==
|
||||
|
||||
"@rollup/rollup-linux-s390x-gnu@4.59.0":
|
||||
version "4.59.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz#bb5025cde9a61db478c2ca7215808ad3bce73a09"
|
||||
integrity sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==
|
||||
|
||||
"@rollup/rollup-linux-x64-gnu@4.59.0":
|
||||
version "4.59.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz#9b66b1f9cd95c6624c788f021c756269ffed1552"
|
||||
integrity sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==
|
||||
|
||||
"@rollup/rollup-linux-x64-musl@4.59.0":
|
||||
version "4.59.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz#b007ca255dc7166017d57d7d2451963f0bd23fd9"
|
||||
integrity sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==
|
||||
|
||||
"@rollup/rollup-openbsd-x64@4.59.0":
|
||||
version "4.59.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz#e8b357b2d1aa2c8d76a98f5f0d889eabe93f4ef9"
|
||||
integrity sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==
|
||||
|
||||
"@rollup/rollup-openharmony-arm64@4.59.0":
|
||||
version "4.59.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz#96c2e3f4aacd3d921981329831ff8dde492204dc"
|
||||
integrity sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==
|
||||
|
||||
"@rollup/rollup-win32-arm64-msvc@4.59.0":
|
||||
version "4.59.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz#2d865149d706d938df8b4b8f117e69a77646d581"
|
||||
integrity sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==
|
||||
|
||||
"@rollup/rollup-win32-ia32-msvc@4.59.0":
|
||||
version "4.59.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz#abe1593be0fa92325e9971c8da429c5e05b92c36"
|
||||
integrity sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==
|
||||
|
||||
"@rollup/rollup-win32-x64-gnu@4.59.0":
|
||||
version "4.59.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz#c4af3e9518c9a5cd4b1c163dc81d0ad4d82e7eab"
|
||||
integrity sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc@4.59.0":
|
||||
version "4.59.0"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz#4584a8a87b29188a4c1fe987a9fcf701e256d86c"
|
||||
integrity sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==
|
||||
|
||||
"@shikijs/core@2.5.0", "@shikijs/core@^2.1.0":
|
||||
version "2.5.0"
|
||||
@@ -479,10 +524,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz#a90ab31d0cc1dfb54c66a69e515bf624fa7b2224"
|
||||
integrity sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==
|
||||
|
||||
"@types/estree@1.0.6":
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50"
|
||||
integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==
|
||||
"@types/estree@1.0.8":
|
||||
version "1.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e"
|
||||
integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
|
||||
|
||||
"@types/hast@^3.0.0", "@types/hast@^3.0.4":
|
||||
version "3.0.4"
|
||||
@@ -912,11 +957,6 @@ nanoid@^3.3.11:
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.11.tgz#4f4f112cefbe303202f2199838128936266d185b"
|
||||
integrity sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==
|
||||
|
||||
nanoid@^3.3.7:
|
||||
version "3.3.8"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf"
|
||||
integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==
|
||||
|
||||
oniguruma-to-es@^3.1.0:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz#480e4bac4d3bc9439ac0d2124f0725e7a0d76d17"
|
||||
@@ -931,29 +971,15 @@ perfect-debounce@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz#9c2e8bc30b169cc984a58b7d5b28049839591d2a"
|
||||
integrity sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==
|
||||
|
||||
picocolors@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59"
|
||||
integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==
|
||||
|
||||
picocolors@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b"
|
||||
integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==
|
||||
|
||||
postcss@^8.4.43:
|
||||
version "8.4.47"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.47.tgz#5bf6c9a010f3e724c503bf03ef7947dcb0fea365"
|
||||
integrity sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==
|
||||
dependencies:
|
||||
nanoid "^3.3.7"
|
||||
picocolors "^1.1.0"
|
||||
source-map-js "^1.2.1"
|
||||
|
||||
postcss@^8.5.6:
|
||||
version "8.5.6"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c"
|
||||
integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==
|
||||
postcss@^8.4.43, postcss@^8.5.6:
|
||||
version "8.5.14"
|
||||
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.14.tgz#a66c2d7808fadf69ebb5b84a03f8bafd76c4919c"
|
||||
integrity sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==
|
||||
dependencies:
|
||||
nanoid "^3.3.11"
|
||||
picocolors "^1.1.1"
|
||||
@@ -994,28 +1020,37 @@ rfdc@^1.4.1:
|
||||
integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==
|
||||
|
||||
rollup@^4.20.0:
|
||||
version "4.24.0"
|
||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.24.0.tgz#c14a3576f20622ea6a5c9cad7caca5e6e9555d05"
|
||||
integrity sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==
|
||||
version "4.59.0"
|
||||
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.59.0.tgz#cf74edac17c1486f562d728a4d923a694abdf06f"
|
||||
integrity sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==
|
||||
dependencies:
|
||||
"@types/estree" "1.0.6"
|
||||
"@types/estree" "1.0.8"
|
||||
optionalDependencies:
|
||||
"@rollup/rollup-android-arm-eabi" "4.24.0"
|
||||
"@rollup/rollup-android-arm64" "4.24.0"
|
||||
"@rollup/rollup-darwin-arm64" "4.24.0"
|
||||
"@rollup/rollup-darwin-x64" "4.24.0"
|
||||
"@rollup/rollup-linux-arm-gnueabihf" "4.24.0"
|
||||
"@rollup/rollup-linux-arm-musleabihf" "4.24.0"
|
||||
"@rollup/rollup-linux-arm64-gnu" "4.24.0"
|
||||
"@rollup/rollup-linux-arm64-musl" "4.24.0"
|
||||
"@rollup/rollup-linux-powerpc64le-gnu" "4.24.0"
|
||||
"@rollup/rollup-linux-riscv64-gnu" "4.24.0"
|
||||
"@rollup/rollup-linux-s390x-gnu" "4.24.0"
|
||||
"@rollup/rollup-linux-x64-gnu" "4.24.0"
|
||||
"@rollup/rollup-linux-x64-musl" "4.24.0"
|
||||
"@rollup/rollup-win32-arm64-msvc" "4.24.0"
|
||||
"@rollup/rollup-win32-ia32-msvc" "4.24.0"
|
||||
"@rollup/rollup-win32-x64-msvc" "4.24.0"
|
||||
"@rollup/rollup-android-arm-eabi" "4.59.0"
|
||||
"@rollup/rollup-android-arm64" "4.59.0"
|
||||
"@rollup/rollup-darwin-arm64" "4.59.0"
|
||||
"@rollup/rollup-darwin-x64" "4.59.0"
|
||||
"@rollup/rollup-freebsd-arm64" "4.59.0"
|
||||
"@rollup/rollup-freebsd-x64" "4.59.0"
|
||||
"@rollup/rollup-linux-arm-gnueabihf" "4.59.0"
|
||||
"@rollup/rollup-linux-arm-musleabihf" "4.59.0"
|
||||
"@rollup/rollup-linux-arm64-gnu" "4.59.0"
|
||||
"@rollup/rollup-linux-arm64-musl" "4.59.0"
|
||||
"@rollup/rollup-linux-loong64-gnu" "4.59.0"
|
||||
"@rollup/rollup-linux-loong64-musl" "4.59.0"
|
||||
"@rollup/rollup-linux-ppc64-gnu" "4.59.0"
|
||||
"@rollup/rollup-linux-ppc64-musl" "4.59.0"
|
||||
"@rollup/rollup-linux-riscv64-gnu" "4.59.0"
|
||||
"@rollup/rollup-linux-riscv64-musl" "4.59.0"
|
||||
"@rollup/rollup-linux-s390x-gnu" "4.59.0"
|
||||
"@rollup/rollup-linux-x64-gnu" "4.59.0"
|
||||
"@rollup/rollup-linux-x64-musl" "4.59.0"
|
||||
"@rollup/rollup-openbsd-x64" "4.59.0"
|
||||
"@rollup/rollup-openharmony-arm64" "4.59.0"
|
||||
"@rollup/rollup-win32-arm64-msvc" "4.59.0"
|
||||
"@rollup/rollup-win32-ia32-msvc" "4.59.0"
|
||||
"@rollup/rollup-win32-x64-gnu" "4.59.0"
|
||||
"@rollup/rollup-win32-x64-msvc" "4.59.0"
|
||||
fsevents "~2.3.2"
|
||||
|
||||
shiki@^2.1.0:
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.3.14/schema.json",
|
||||
"$schema": "https://biomejs.dev/schemas/2.4.15/schema.json",
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
|
||||
@@ -11,13 +11,16 @@ const allLocales = [
|
||||
["de", "de-DE"],
|
||||
["pt", "pt-PT"],
|
||||
["es", "es-ES"],
|
||||
["et", "et-EE"],
|
||||
["fr", "fr-FR"],
|
||||
["ga", "ga-IE"],
|
||||
["it", "it-IT"],
|
||||
["ja", "ja-JP"],
|
||||
["nl", "nl-NL"],
|
||||
["pl", "pl-PL"],
|
||||
["ru", "ru-RU"],
|
||||
["sk", "sk-SK"],
|
||||
["cs", "cs-CZ"],
|
||||
["vi", "vi-VN"],
|
||||
["zh", "zh-CN"],
|
||||
["ko", "ko-KR"],
|
||||
@@ -25,6 +28,7 @@ const allLocales = [
|
||||
["id", "id-ID"],
|
||||
["tr", "tr-TR"],
|
||||
["hu", "hu-HU"],
|
||||
["no", "no-NO"],
|
||||
];
|
||||
|
||||
const ignoreUnused = [/^.*$/];
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Nginx Proxy Manager</title>
|
||||
<meta name="description" content="In The Office Planner" />
|
||||
<meta name="description" content="Manage Nginx hosts with a simple, powerful interface" />
|
||||
<link rel="preload" href="/images/logo-no-text.svg" as="image" type="image/svg+xml" fetchPriority="high">
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
|
||||
+24
-24
@@ -17,50 +17,50 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@tabler/core": "^1.4.0",
|
||||
"@tabler/icons-react": "^3.36.1",
|
||||
"@tanstack/react-query": "^5.90.20",
|
||||
"@tabler/icons-react": "^3.44.0",
|
||||
"@tanstack/react-query": "^5.100.14",
|
||||
"@tanstack/react-table": "^8.21.3",
|
||||
"@uiw/react-textarea-code-editor": "^3.1.1",
|
||||
"classnames": "^2.5.1",
|
||||
"country-flag-icons": "^1.6.12",
|
||||
"date-fns": "^4.1.0",
|
||||
"country-flag-icons": "^1.6.17",
|
||||
"date-fns": "^4.3.0",
|
||||
"ez-modal-react": "^1.0.5",
|
||||
"formik": "^2.4.9",
|
||||
"generate-password-browser": "^1.1.0",
|
||||
"humps": "^2.0.1",
|
||||
"query-string": "^9.3.1",
|
||||
"react": "^19.2.4",
|
||||
"react": "^19.2.6",
|
||||
"react-bootstrap": "^2.10.10",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-intl": "^8.1.3",
|
||||
"react-dom": "^19.2.6",
|
||||
"react-intl": "^10.1.9",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-router-dom": "^7.13.0",
|
||||
"react-router-dom": "^7.15.1",
|
||||
"react-select": "^5.10.2",
|
||||
"react-toastify": "^11.0.5",
|
||||
"rooks": "^9.5.0"
|
||||
"react-toastify": "^11.1.0",
|
||||
"rooks": "^9.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.3.14",
|
||||
"@formatjs/cli": "^6.12.2",
|
||||
"@tanstack/react-query-devtools": "^5.91.3",
|
||||
"@biomejs/biome": "^2.4.15",
|
||||
"@formatjs/cli": "^6.16.3",
|
||||
"@tanstack/react-query-devtools": "^5.100.14",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
"@testing-library/react": "^16.3.2",
|
||||
"@types/country-flag-icons": "^1.2.2",
|
||||
"@types/humps": "^2.0.6",
|
||||
"@types/react": "^19.2.10",
|
||||
"@types/node": "^25.9.1",
|
||||
"@types/react": "^19.2.15",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@types/react-table": "^7.7.20",
|
||||
"@vitejs/plugin-react": "^5.1.3",
|
||||
"happy-dom": "^20.5.0",
|
||||
"postcss": "^8.5.6",
|
||||
"@vitejs/plugin-react": "^6.0.2",
|
||||
"happy-dom": "^20.9.0",
|
||||
"postcss": "^8.5.15",
|
||||
"postcss-simple-vars": "^7.0.1",
|
||||
"sass": "^1.97.3",
|
||||
"tmp": "^0.2.5",
|
||||
"typescript": "5.9.3",
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-checker": "^0.12.0",
|
||||
"vite-tsconfig-paths": "^6.0.5",
|
||||
"vitest": "^4.0.18"
|
||||
"sass": "^1.100.0",
|
||||
"tmp": "^0.2.6",
|
||||
"typescript": "6.0.3",
|
||||
"vite": "^8.0.14",
|
||||
"vite-plugin-checker": "^0.14.1",
|
||||
"vitest": "^4.1.7"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,6 +127,7 @@ export interface ProxyHost {
|
||||
locations?: ProxyLocation[];
|
||||
hstsEnabled: boolean;
|
||||
hstsSubdomains: boolean;
|
||||
trustForwardedProto: boolean;
|
||||
// Expansions:
|
||||
owner?: User;
|
||||
accessList?: AccessList;
|
||||
|
||||
@@ -5,17 +5,18 @@ import { T } from "src/locale";
|
||||
|
||||
interface Props {
|
||||
forHttp?: boolean; // the sslForced, http2Support, hstsEnabled, hstsSubdomains fields
|
||||
forProxyHost?: boolean; // the advanced fields
|
||||
forceDNSForNew?: boolean;
|
||||
requireDomainNames?: boolean; // used for streams
|
||||
color?: string;
|
||||
}
|
||||
export function SSLOptionsFields({ forHttp = true, forceDNSForNew, requireDomainNames, color = "bg-cyan" }: Props) {
|
||||
export function SSLOptionsFields({ forHttp = true, forProxyHost = false, forceDNSForNew, requireDomainNames, color = "bg-cyan" }: Props) {
|
||||
const { values, setFieldValue } = useFormikContext();
|
||||
const v: any = values || {};
|
||||
|
||||
const newCertificate = v?.certificateId === "new";
|
||||
const hasCertificate = newCertificate || (v?.certificateId && v?.certificateId > 0);
|
||||
const { sslForced, http2Support, hstsEnabled, hstsSubdomains, meta } = v;
|
||||
const { sslForced, http2Support, hstsEnabled, hstsSubdomains, trustForwardedProto, meta } = v;
|
||||
const { dnsChallenge } = meta || {};
|
||||
|
||||
if (forceDNSForNew && newCertificate && !dnsChallenge) {
|
||||
@@ -115,6 +116,34 @@ export function SSLOptionsFields({ forHttp = true, forceDNSForNew, requireDomain
|
||||
</div>
|
||||
);
|
||||
|
||||
const getHttpAdvancedOptions = () =>(
|
||||
<div>
|
||||
<details>
|
||||
<summary className="mb-1"><T id="domains.advanced" /></summary>
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<Field name="trustForwardedProto">
|
||||
{({ field }: any) => (
|
||||
<label className="form-check form-switch mt-1">
|
||||
<input
|
||||
className={trustForwardedProto ? toggleEnabled : toggleClasses}
|
||||
type="checkbox"
|
||||
checked={!!trustForwardedProto}
|
||||
onChange={(e) => handleToggleChange(e, field.name)}
|
||||
disabled={!hasCertificate || !sslForced}
|
||||
/>
|
||||
<span className="form-check-label">
|
||||
<T id="domains.trust-forwarded-proto" />
|
||||
</span>
|
||||
</label>
|
||||
)}
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{forHttp ? getHttpOptions() : null}
|
||||
@@ -140,6 +169,7 @@ export function SSLOptionsFields({ forHttp = true, forceDNSForNew, requireDomain
|
||||
{dnsChallenge ? <DNSProviderFields showBoundaryBox /> : null}
|
||||
</>
|
||||
) : null}
|
||||
{forProxyHost && forHttp ? getHttpAdvancedOptions() : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ export function SiteFooter() {
|
||||
<div className="col-12 col-lg-auto mt-3 mt-lg-0">
|
||||
<ul className="list-inline list-inline-dots mb-0">
|
||||
<li className="list-inline-item">
|
||||
© 2025{" "}
|
||||
© 2026{" "}
|
||||
<a href="https://jc21.com" rel="noreferrer" target="_blank" className="link-secondary">
|
||||
jc21.com
|
||||
</a>
|
||||
|
||||
@@ -26,6 +26,7 @@ const DomainLink = ({ domain, color }: { domain?: string; color?: string }) => {
|
||||
key={domain}
|
||||
href={`http://${domain}`}
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
onClick={onClick}
|
||||
className={cn("badge", color ? `bg-${color}-lt` : null, "domain-name", "me-2")}
|
||||
>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { IconArrowsSort, IconChevronDown, IconChevronUp } from "@tabler/icons-react";
|
||||
import { flexRender } from "@tanstack/react-table";
|
||||
import type { TableLayoutProps } from "src/components";
|
||||
|
||||
function TableHeader<T>(props: TableLayoutProps<T>) {
|
||||
@@ -11,9 +13,34 @@ function TableHeader<T>(props: TableLayoutProps<T>) {
|
||||
{headerGroup.headers.map((header: any) => {
|
||||
const { column } = header;
|
||||
const { className } = (column.columnDef.meta as any) ?? {};
|
||||
const canSort = column.getCanSort();
|
||||
const sortDir = column.getIsSorted();
|
||||
|
||||
const headerContent = header.isPlaceholder
|
||||
? null
|
||||
: typeof column.columnDef.header === "string"
|
||||
? column.columnDef.header
|
||||
: flexRender(column.columnDef.header, header.getContext());
|
||||
|
||||
const sortIcon = canSort ? (
|
||||
sortDir === "asc" ? (
|
||||
<IconChevronUp size={14} className="ms-1" />
|
||||
) : sortDir === "desc" ? (
|
||||
<IconChevronDown size={14} className="ms-1" />
|
||||
) : (
|
||||
<IconArrowsSort size={14} className="ms-1 opacity-50" />
|
||||
)
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<th key={header.id} className={className}>
|
||||
{typeof column.columnDef.header === "string" ? `${column.columnDef.header}` : null}
|
||||
<th
|
||||
key={header.id}
|
||||
className={className}
|
||||
onClick={canSort ? column.getToggleSortingHandler() : undefined}
|
||||
style={canSort ? { cursor: "pointer", userSelect: "none" } : undefined}
|
||||
>
|
||||
{headerContent}
|
||||
{sortIcon}
|
||||
</th>
|
||||
);
|
||||
})}
|
||||
|
||||
@@ -24,6 +24,7 @@ const fetchProxyHost = (id: number | "new") => {
|
||||
enabled: true,
|
||||
hstsEnabled: false,
|
||||
hstsSubdomains: false,
|
||||
trustForwardedProto: false,
|
||||
} as ProxyHost);
|
||||
}
|
||||
return getProxyHost(id, ["owner"]);
|
||||
|
||||
@@ -4,6 +4,7 @@ import langDe from "./lang/de.json";
|
||||
import langPt from "./lang/pt.json";
|
||||
import langEn from "./lang/en.json";
|
||||
import langEs from "./lang/es.json";
|
||||
import langEt from "./lang/et.json";
|
||||
import langFr from "./lang/fr.json";
|
||||
import langGa from "./lang/ga.json";
|
||||
import langId from "./lang/id.json";
|
||||
@@ -14,10 +15,12 @@ import langNl from "./lang/nl.json";
|
||||
import langPl from "./lang/pl.json";
|
||||
import langRu from "./lang/ru.json";
|
||||
import langSk from "./lang/sk.json";
|
||||
import langCs from "./lang/cs.json";
|
||||
import langVi from "./lang/vi.json";
|
||||
import langZh from "./lang/zh.json";
|
||||
import langTr from "./lang/tr.json";
|
||||
import langHu from "./lang/hu.json";
|
||||
import langNo from "./lang/no.json";
|
||||
import langList from "./lang/lang-list.json";
|
||||
|
||||
// first item of each array should be the language code,
|
||||
@@ -27,6 +30,7 @@ const localeOptions = [
|
||||
["en", "en-US", langEn],
|
||||
["de", "de-DE", langDe],
|
||||
["es", "es-ES", langEs],
|
||||
["et", "et-EE", langEt],
|
||||
["pt", "pt-PT", langPt],
|
||||
["fr", "fr-FR", langFr],
|
||||
["ga", "ga-IE", langGa],
|
||||
@@ -36,6 +40,7 @@ const localeOptions = [
|
||||
["pl", "pl-PL", langPl],
|
||||
["ru", "ru-RU", langRu],
|
||||
["sk", "sk-SK", langSk],
|
||||
["cs", "cs-CZ", langCs],
|
||||
["vi", "vi-VN", langVi],
|
||||
["zh", "zh-CN", langZh],
|
||||
["ko", "ko-KR", langKo],
|
||||
@@ -43,6 +48,7 @@ const localeOptions = [
|
||||
["id", "id-ID", langId],
|
||||
["tr", "tr-TR", langTr],
|
||||
["hu", "hu-HU", langHu],
|
||||
["no", "no-NO", langNo],
|
||||
];
|
||||
|
||||
const loadMessages = (locale?: string): typeof langList & typeof langEn => {
|
||||
@@ -65,6 +71,8 @@ const getFlagCodeForLocale = (locale?: string) => {
|
||||
zh: "cn", // China
|
||||
vi: "vn", // Vietnam
|
||||
ko: "kr", // Korea
|
||||
cs: "cz", // Czechia
|
||||
ga: "ie", // Ireland (Irish)
|
||||
};
|
||||
|
||||
if (specialCases[thisLocale]) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { formatDateTime } from "src/locale";
|
||||
import { formatDateTime, getFlagCodeForLocale } from "src/locale";
|
||||
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||
|
||||
describe("DateFormatter", () => {
|
||||
@@ -72,3 +72,28 @@ describe("DateFormatter", () => {
|
||||
expect(text).toBe("-100");
|
||||
});
|
||||
});
|
||||
|
||||
describe("getFlagCodeForLocale", () => {
|
||||
it("returns correct flag code for standard locales", () => {
|
||||
expect(getFlagCodeForLocale("en-US")).toBe("EN");
|
||||
expect(getFlagCodeForLocale("de-DE")).toBe("DE");
|
||||
expect(getFlagCodeForLocale("fr-FR")).toBe("FR");
|
||||
});
|
||||
|
||||
it("returns correct flag code for special-case locales", () => {
|
||||
expect(getFlagCodeForLocale("ja-JP")).toBe("JP");
|
||||
expect(getFlagCodeForLocale("zh-CN")).toBe("CN");
|
||||
expect(getFlagCodeForLocale("vi-VN")).toBe("VN");
|
||||
expect(getFlagCodeForLocale("ko-KR")).toBe("KR");
|
||||
expect(getFlagCodeForLocale("cs-CZ")).toBe("CZ");
|
||||
});
|
||||
|
||||
it("returns IE (Ireland) for Irish locale, not GA (Gabon)", () => {
|
||||
expect(getFlagCodeForLocale("ga-IE")).toBe("IE");
|
||||
});
|
||||
|
||||
it("falls back to EN when no locale is provided", () => {
|
||||
expect(getFlagCodeForLocale()).toBe("EN");
|
||||
expect(getFlagCodeForLocale(undefined)).toBe("EN");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
## Co je seznam přístupů?
|
||||
|
||||
Seznamy přístupů poskytují blacklist nebo whitelist konkrétních IP adres klientů spolu s ověřením pro proxy hostitele prostřednictvím základního ověřování HTTP.
|
||||
|
||||
Můžete nakonfigurovat více pravidel pro klienty, uživatelská jména a hesla pro jeden seznam přístupu a poté ho použít na jednoho nebo více proxy hostitelů.
|
||||
|
||||
Toto je nejužitečnější pro přesměrované webové služby, které nemají vestavěné ověřovací mechanismy, nebo pokud se chcete chránit před neznámými klienty.
|
||||
@@ -0,0 +1,32 @@
|
||||
## Pomoc s certifikáty
|
||||
|
||||
### Certifikát HTTP
|
||||
|
||||
Certifikát ověřený prostřednictvím protokolu HTTP znamená, že servery Let's Encrypt se
|
||||
pokusí připojit k vašim doménám přes protokol HTTP (nikoli HTTPS!) a v případě úspěchu
|
||||
vydají váš certifikát.
|
||||
|
||||
Pro tuto metodu budete muset mít pro své domény vytvořeného _Proxy Host_, který
|
||||
je přístupný přes HTTP a směruje na tuto instalaci Nginx. Po vydání certifikátu
|
||||
můžete změnit _Proxy Host_ tak, aby tento certifikát používal i pro HTTPS
|
||||
připojení. _Proxy Host_ však bude stále potřeba nakonfigurovat pro přístup přes HTTP,
|
||||
aby se certifikát mohl obnovit.
|
||||
|
||||
Tento proces _nepodporuje_ domény se zástupnými znaky.
|
||||
|
||||
### Certifikát DNS
|
||||
|
||||
Certifikát ověřený DNS vyžaduje použití pluginu DNS Provider. Tento DNS
|
||||
Provider se použije na vytvoření dočasných záznamů ve vaší doméně a poté Let's
|
||||
Encrypt ověří tyto záznamy, aby se ujistil, že jste vlastníkem, a pokud bude úspěšný,
|
||||
vydá váš certifikát.
|
||||
|
||||
Před požádáním o tento typ certifikátu není potřeba vytvořit _Proxy Host_.
|
||||
Není také potřeba mít _Proxy Host_ nakonfigurovaný pro přístup HTTP.
|
||||
|
||||
Tento proces _podporuje_ domény se zástupnými znaky.
|
||||
|
||||
### Vlastní certifikát
|
||||
|
||||
Tuto možnost použijte na nahrání vlastního SSL certifikátu, který vám poskytla vaše
|
||||
certifikační autorita.
|
||||
@@ -0,0 +1,10 @@
|
||||
## Co je to 404 Host?
|
||||
|
||||
404 Host je jednoduše nastavení hostitele, které zobrazuje stránku 404.
|
||||
|
||||
To může být užitečné, pokud je vaše doména uvedena ve vyhledávačích a chcete
|
||||
poskytnout hezčí chybovou stránku nebo konkrétně oznámit vyhledávačům, že
|
||||
stránky domény již neexistují.
|
||||
|
||||
Další výhodou tohoto hostitele je sledování protokolů o návštěvách a
|
||||
zobrazení odkazů.
|
||||
@@ -0,0 +1,7 @@
|
||||
## Co je proxy hostitel?
|
||||
|
||||
Proxy hostitel je příchozí koncový bod pro webovou službu, kterou chcete přesměrovat.
|
||||
|
||||
Poskytuje volitelné ukončení SSL pro vaši službu, která nemusí mít zabudovanou podporu SSL.
|
||||
|
||||
Proxy hostitelé jsou nejběžnějším použitím pro Nginx Proxy Manager.
|
||||
@@ -0,0 +1,7 @@
|
||||
## Co je přesměrovací hostitel?
|
||||
|
||||
Přesměrovací hostitel přesměruje požadavky z příchozí domény a přesměruje
|
||||
návštěvníka na jinou doménu.
|
||||
|
||||
Nejčastějším důvodem pro použití tohoto typu hostitele je situace, kdy vaše webová stránka změní
|
||||
doménu, ale stále máte odkazy ve vyhledávačích nebo referenční odkazy směřující na starou doménu.
|
||||
@@ -0,0 +1,6 @@
|
||||
## Co je stream?
|
||||
|
||||
Stream je relativně nová funkce pro Nginx, která slouží na přesměrování TCP/UDP
|
||||
datového toku přímo do jiného počítače v síti.
|
||||
|
||||
Pokud provozujete herní servery, FTP nebo SSH servery, tato funkce se vám může hodit.
|
||||
@@ -0,0 +1,6 @@
|
||||
export * as AccessLists from "./AccessLists.md";
|
||||
export * as Certificates from "./Certificates.md";
|
||||
export * as DeadHosts from "./DeadHosts.md";
|
||||
export * as ProxyHosts from "./ProxyHosts.md";
|
||||
export * as RedirectionHosts from "./RedirectionHosts.md";
|
||||
export * as Streams from "./Streams.md";
|
||||
@@ -0,0 +1,7 @@
|
||||
## Mis on juurdepääsuloend?
|
||||
|
||||
Ligipääsuloendid pakuvad konkreetsete klientide IP-aadresside musta või valget nimekirja koos puhverserverite autentimisega põhilise HTTP-autentimise kaudu.
|
||||
|
||||
Saate ühe juurdepääsuloendi jaoks konfigureerida mitu kliendireeglit, kasutajanime ja parooli ning seejärel rakendada neid ühele või mitmele _puhverserverile_.
|
||||
|
||||
See on kõige kasulikum edastatud veebiteenuste puhul, millel pole sisseehitatud autentimismehhanisme või kui soovite kaitsta tundmatute klientide eest.
|
||||
@@ -0,0 +1,26 @@
|
||||
## Sertifikaatide abi
|
||||
|
||||
### HTTP-sertifikaat
|
||||
|
||||
HTTP-valideeritud sertifikaat tähendab, et Let's Encrypti serverid
|
||||
|
||||
proovivad teie domeenidega ühendust luua HTTP (mitte HTTPS!) kaudu ja kui see õnnestub,
|
||||
väljastavad nad teile sertifikaadi.
|
||||
|
||||
Selle meetodi jaoks peate oma domeeni(de) jaoks looma _Proxy Host_, millele pääseb ligi HTTP kaudu ja mis osutab sellele Nginxi installile. Pärast sertifikaadi väljastamist saate muuta _Proxy Host_'i, et seda sertifikaati ka HTTPS
|
||||
ühenduste jaoks kasutada. Sertifikaadi uuendamiseks tuleb aga _Proxy Host_ ikkagi HTTP-juurdepääsu jaoks konfigureerida.
|
||||
|
||||
See protsess _ei_ toeta metamärke kasutavaid domeene.
|
||||
|
||||
### DNS-sertifikaat
|
||||
|
||||
DNS-i poolt valideeritud sertifikaadi saamiseks peate kasutama DNS-pakkuja pistikprogrammi. Seda DNS-teenuse pakkujat kasutatakse teie domeenis ajutiste kirjete loomiseks ja seejärel pärib Let's
|
||||
Encrypt nende kirjete kohta päringu, et veenduda, et olete omanik, ja kui see õnnestub, väljastavad nad teile sertifikaadi.
|
||||
|
||||
Selle tüüpi sertifikaadi taotlemiseks ei ole vaja luua _Proxy Host_'i. Samuti ei pea teie _Proxy Host_ olema HTTP-juurdepääsu jaoks konfigureeritud.
|
||||
|
||||
See protsess _toetab_ metamärke kasutavaid domeene.
|
||||
|
||||
### Kohandatud sertifikaat
|
||||
|
||||
Kasutage seda valikut oma SSL-sertifikaadi üleslaadimiseks, mille on esitanud teie enda sertifitseerimisasutus.
|
||||
@@ -0,0 +1,9 @@
|
||||
## Mis on 404 host?
|
||||
|
||||
404 host on lihtsalt hosti seadistus, mis kuvab 404 lehte.
|
||||
|
||||
See võib olla kasulik, kui teie domeen on otsingumootorites loetletud ja soovite
|
||||
esitada kenama vealehe või konkreetselt otsingu indekseerijatele öelda, et
|
||||
domeenilehed enam ei eksisteeri.
|
||||
|
||||
Selle hosti teine eelis on selle külastatavuste logide jälgimine ja suunajate vaatamine.
|
||||
@@ -0,0 +1,7 @@
|
||||
## Mis on puhverserver?
|
||||
|
||||
Puhverserver on veebiteenuse sissetuleva andmevoo lõpp-punkt, mida soovite edastada.
|
||||
|
||||
See pakub valikulist SSL-i lõpetamist teie teenusele, millel ei pruugi olla sisseehitatud SSL-tuge.
|
||||
|
||||
Puhverserverid on Nginxi puhverserveri halduri kõige levinum kasutusala.
|
||||
@@ -0,0 +1,5 @@
|
||||
## Mis on ümbersuunamishost?
|
||||
|
||||
Ümbersuunamishost suunab sissetuleva domeeni päringud ümber ja suunab vaataja teisele domeenile.
|
||||
|
||||
Kõige levinum põhjus seda tüüpi hosti kasutamiseks on see, kui teie veebisaidi domeenid muutuvad, kuid otsingumootori või suunaja lingid osutavad endiselt vanale domeenile.
|
||||
@@ -0,0 +1,5 @@
|
||||
## Mis on voog?
|
||||
|
||||
Nginxi suhteliselt uus funktsioon, voog, edastab TCP/UDP liiklust otse võrgus olevale teisele arvutile.
|
||||
|
||||
Kui sul on mänguserverid, FTP- või SSH-serverid, võib see kasuks tulla.
|
||||
@@ -0,0 +1,6 @@
|
||||
export * as AccessLists from "./AccessLists.md";
|
||||
export * as Certificates from "./Certificates.md";
|
||||
export * as DeadHosts from "./DeadHosts.md";
|
||||
export * as ProxyHosts from "./ProxyHosts.md";
|
||||
export * as RedirectionHosts from "./RedirectionHosts.md";
|
||||
export * as Streams from "./Streams.md";
|
||||
@@ -3,6 +3,7 @@ import * as de from "./de/index";
|
||||
import * as pt from "./pt/index";
|
||||
import * as en from "./en/index";
|
||||
import * as es from "./es/index";
|
||||
import * as et from "./et/index";
|
||||
import * as fr from "./fr/index";
|
||||
import * as ga from "./ga/index";
|
||||
import * as id from "./id/index";
|
||||
@@ -13,12 +14,13 @@ import * as nl from "./nl/index";
|
||||
import * as pl from "./pl/index";
|
||||
import * as ru from "./ru/index";
|
||||
import * as sk from "./sk/index";
|
||||
import * as cs from "./cs/index";
|
||||
import * as vi from "./vi/index";
|
||||
import * as zh from "./zh/index";
|
||||
import * as tr from "./tr/index";
|
||||
import * as hu from "./hu/index";
|
||||
|
||||
const items: any = { en, de, pt, es, ja, sk, zh, pl, ru, it, vi, nl, bg, ko, ga, id, fr, tr, hu };
|
||||
const items: any = { en, de, pt, es, et, ja, sk, cs, zh, pl, ru, it, vi, nl, bg, ko, ga, id, fr, tr, hu };
|
||||
|
||||
|
||||
const fallbackLang = "en";
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
## Hva er en tilgangsliste?
|
||||
|
||||
Tilgangslister gir en svarteliste eller hviteliste over spesifikke klient‑IP‑adresser, sammen med autentisering for `Proxy‑hosts` via Basic HTTP‑autentisering.
|
||||
|
||||
Du kan konfigurere flere klientregler, brukernavn og passord for én tilgangsliste og deretter bruke denne på én eller flere `Proxy‑hosts`.
|
||||
|
||||
Dette er spesielt nyttig for videresendte webtjenester som ikke har innebygd autentisering, eller når du ønsker å beskytte mot ukjente klienter.
|
||||
@@ -0,0 +1,29 @@
|
||||
## Hjelp om sertifikater
|
||||
|
||||
### HTTP‑sertifikat
|
||||
|
||||
Et HTTP‑validert sertifikat betyr at Let's Encrypt‑serverne vil forsøke å nå
|
||||
domenene dine over HTTP (ikke HTTPS!) og hvis det lykkes, vil de utstede sertifikatet.
|
||||
|
||||
For denne metoden må du ha en `Proxy‑host` opprettet for domenet/domenene dine som
|
||||
er tilgjengelig over HTTP og peker til denne Nginx‑installasjonen. Etter at et sertifikat
|
||||
er utstedt, kan du endre `Proxy‑host` til også å bruke dette sertifikatet for HTTPS‑tilkoblinger.
|
||||
Proxy‑hosten må imidlertid fortsatt være konfigurert for HTTP‑tilgang for at sertifikatet skal kunne fornyes.
|
||||
|
||||
Denne prosessen _støtter ikke_ wildcard‑domener.
|
||||
|
||||
### DNS‑sertifikat
|
||||
|
||||
Et DNS‑validert sertifikat krever at du bruker en DNS‑leverandør‑plugin. Denne leverandøren
|
||||
vil opprette midlertidige DNS‑poster på domenet ditt, og Let's Encrypt vil deretter spørre
|
||||
disse postene for å bekrefte at du eier domenet. Hvis valideringen lykkes, utstedes sertifikatet.
|
||||
|
||||
Du trenger ikke å ha en `Proxy‑host` opprettet før du ber om denne typen sertifikat. Du trenger heller
|
||||
ikke at `Proxy‑host` er konfigurert for HTTP‑tilgang.
|
||||
|
||||
Denne prosessen _støtter_ wildcard‑domener.
|
||||
|
||||
### Egendefinert sertifikat
|
||||
|
||||
Bruk dette alternativet for å laste opp ditt eget SSL‑sertifikat, levert av din
|
||||
egen sertifikatmyndighet (CA).
|
||||
@@ -0,0 +1,10 @@
|
||||
## Hva er en 404‑host?
|
||||
|
||||
En 404‑host er enkelt og greit en host‑oppsett som viser en 404‑side.
|
||||
|
||||
Dette kan være nyttig når domenet ditt er oppført i søkemotorer og du ønsker å
|
||||
vise en penere feilmelding, eller for å fortelle søkeindekser at sidene på domenet
|
||||
ikke lenger eksisterer.
|
||||
|
||||
En annen fordel med å ha denne hosten er å kunne spore treff i loggene og
|
||||
se hvilke henvisere som kommer til den.
|
||||
@@ -0,0 +1,7 @@
|
||||
## Hva er en Proxy‑host?
|
||||
|
||||
En Proxy‑host er inngangspunktet (innkommende endepunkt) for en webtjeneste du ønsker å videresende.
|
||||
|
||||
Den tilbyr valgfri SSL‑terminering for tjenesten din hvis tjenesten ikke har innebygd støtte for SSL.
|
||||
|
||||
Proxy‑hosts er den vanligste bruken av Nginx Proxy Manager.
|
||||
@@ -0,0 +1,7 @@
|
||||
## Hva er en omdirigerings‑host?
|
||||
|
||||
En omdirigerings‑host omdirigerer forespørsler fra det innkommende domenet og videresender
|
||||
brukeren til et annet domene.
|
||||
|
||||
Den vanligste årsaken til å bruke denne typen host er når nettstedet ditt har byttet
|
||||
domene, men søkemotorer eller henvisningslenker fortsatt peker til det gamle domenet.
|
||||
@@ -0,0 +1,6 @@
|
||||
## Hva er en Stream?
|
||||
|
||||
En relativt ny funksjon i Nginx. En Stream brukes til å videresende TCP/UDP‑trafikk
|
||||
direkte til en annen maskin i nettverket.
|
||||
|
||||
Dette er nyttig hvis du kjører spillservere, FTP‑ eller SSH‑servere.
|
||||
@@ -0,0 +1,6 @@
|
||||
export * as AccessLists from "./AccessLists.md";
|
||||
export * as Certificates from "./Certificates.md";
|
||||
export * as DeadHosts from "./DeadHosts.md";
|
||||
export * as ProxyHosts from "./ProxyHosts.md";
|
||||
export * as RedirectionHosts from "./RedirectionHosts.md";
|
||||
export * as Streams from "./Streams.md";
|
||||
@@ -0,0 +1,770 @@
|
||||
{
|
||||
"2fa.backup-codes-remaining": {
|
||||
"defaultMessage": "Počet zbývajících záložních kódů: {count}"
|
||||
},
|
||||
"2fa.backup-warning": {
|
||||
"defaultMessage": "Tyto záložní kódy si uložte na bezpečném místě. Každý kód lze použít pouze jednou."
|
||||
},
|
||||
"2fa.disable": {
|
||||
"defaultMessage": "Vypnout dvoufaktorové ověřování"
|
||||
},
|
||||
"2fa.disable-confirm": {
|
||||
"defaultMessage": "Vypnout 2FA"
|
||||
},
|
||||
"2fa.disable-warning": {
|
||||
"defaultMessage": "Vypnutím dvoufaktorového ověřování snížíte bezpečnost svého účtu."
|
||||
},
|
||||
"2fa.disabled": {
|
||||
"defaultMessage": "Vypnuto"
|
||||
},
|
||||
"2fa.done": {
|
||||
"defaultMessage": "Uložil jsem si své záložní kódy."
|
||||
},
|
||||
"2fa.enable": {
|
||||
"defaultMessage": "Zapnout dvoufaktorové ověřování"
|
||||
},
|
||||
"2fa.enabled": {
|
||||
"defaultMessage": "Zapnuto"
|
||||
},
|
||||
"2fa.enter-code": {
|
||||
"defaultMessage": "Zadejte ověřovací kód"
|
||||
},
|
||||
"2fa.enter-code-disable": {
|
||||
"defaultMessage": "Zadejte ověřovací kód pro vypnutí"
|
||||
},
|
||||
"2fa.regenerate": {
|
||||
"defaultMessage": "Znovu vytvořit"
|
||||
},
|
||||
"2fa.regenerate-backup": {
|
||||
"defaultMessage": "Znovu vytvořit záložní kódy"
|
||||
},
|
||||
"2fa.regenerate-instructions": {
|
||||
"defaultMessage": "Zadejte ověřovací kód pro vytvoření nových záložních kódů. Vaše staré kódy budou neplatné."
|
||||
},
|
||||
"2fa.secret-key": {
|
||||
"defaultMessage": "Tajný klíč"
|
||||
},
|
||||
"2fa.setup-instructions": {
|
||||
"defaultMessage": "Naskenujte tento QR kód pomocí své ověřovací aplikace nebo zadejte tajný klíč ručně."
|
||||
},
|
||||
"2fa.status": {
|
||||
"defaultMessage": "Stav"
|
||||
},
|
||||
"2fa.title": {
|
||||
"defaultMessage": "Dvoufaktorové ověření"
|
||||
},
|
||||
"2fa.verify-enable": {
|
||||
"defaultMessage": "Ověřit a zapnout"
|
||||
},
|
||||
"access-list": {
|
||||
"defaultMessage": "seznam přístupů"
|
||||
},
|
||||
"access-list.access-count": {
|
||||
"defaultMessage": "{count} {count, plural, one {pravidlo} few {pravidla} other {pravidel}}"
|
||||
},
|
||||
"access-list.auth-count": {
|
||||
"defaultMessage": "{count} {count, plural, one {uživatel} few {uživatelé} other {uživatelů}}"
|
||||
},
|
||||
"access-list.help-rules-last": {
|
||||
"defaultMessage": "Když existuje alespoň jedno pravidlo, toto pravidlo „zamítnout vše“ bude přidáno jako poslední"
|
||||
},
|
||||
"access-list.help.rules-order": {
|
||||
"defaultMessage": "Upozornění: pravidla povolit a zamítnout budou uplatňována v pořadí, v jakém jsou definována."
|
||||
},
|
||||
"access-list.pass-auth": {
|
||||
"defaultMessage": "Odeslat ověření na Upstream"
|
||||
},
|
||||
"access-list.public": {
|
||||
"defaultMessage": "Veřejně přístupné"
|
||||
},
|
||||
"access-list.public.subtitle": {
|
||||
"defaultMessage": "Není potřeba základní ověření"
|
||||
},
|
||||
"access-list.rule-source.placeholder": {
|
||||
"defaultMessage": "192.168.1.100 nebo 192.168.1.0/24 nebo 2001:0db8::/32"
|
||||
},
|
||||
"access-list.satisfy-any": {
|
||||
"defaultMessage": "Splnit kterékoliv"
|
||||
},
|
||||
"access-list.subtitle": {
|
||||
"defaultMessage": "{users} {users, plural, one {uživatel} few {uživatelé} other {uživatelů}}, {rules} {rules, plural, one {pravidlo} few {pravidla} other {pravidel}} - Vytvořeno: {date}"
|
||||
},
|
||||
"access-lists": {
|
||||
"defaultMessage": "Seznamy přístupů"
|
||||
},
|
||||
"action.add": {
|
||||
"defaultMessage": "Přidat"
|
||||
},
|
||||
"action.add-location": {
|
||||
"defaultMessage": "Přidat umístění"
|
||||
},
|
||||
"action.allow": {
|
||||
"defaultMessage": "Povolit"
|
||||
},
|
||||
"action.close": {
|
||||
"defaultMessage": "Zavřít"
|
||||
},
|
||||
"action.delete": {
|
||||
"defaultMessage": "Smazat"
|
||||
},
|
||||
"action.deny": {
|
||||
"defaultMessage": "Zamítnout"
|
||||
},
|
||||
"action.disable": {
|
||||
"defaultMessage": "Deaktivovat"
|
||||
},
|
||||
"action.download": {
|
||||
"defaultMessage": "Stáhnout"
|
||||
},
|
||||
"action.edit": {
|
||||
"defaultMessage": "Upravit"
|
||||
},
|
||||
"action.enable": {
|
||||
"defaultMessage": "Aktivovat"
|
||||
},
|
||||
"action.permissions": {
|
||||
"defaultMessage": "Oprávnění"
|
||||
},
|
||||
"action.renew": {
|
||||
"defaultMessage": "Obnovit"
|
||||
},
|
||||
"action.view-details": {
|
||||
"defaultMessage": "Zobrazit podrobnosti"
|
||||
},
|
||||
"auditlogs": {
|
||||
"defaultMessage": "Záznamy auditu"
|
||||
},
|
||||
"auto": {
|
||||
"defaultMessage": "Automaticky"
|
||||
},
|
||||
"cancel": {
|
||||
"defaultMessage": "Zrušit"
|
||||
},
|
||||
"certificate": {
|
||||
"defaultMessage": "certifikát"
|
||||
},
|
||||
"certificate.custom-certificate": {
|
||||
"defaultMessage": "Certifikát"
|
||||
},
|
||||
"certificate.custom-certificate-key": {
|
||||
"defaultMessage": "Klíč certifikátu"
|
||||
},
|
||||
"certificate.custom-intermediate": {
|
||||
"defaultMessage": "Zprostředkovatelský certifikát"
|
||||
},
|
||||
"certificate.in-use": {
|
||||
"defaultMessage": "Používá se"
|
||||
},
|
||||
"certificate.none.subtitle": {
|
||||
"defaultMessage": "Není přiřazen žádný certifikát"
|
||||
},
|
||||
"certificate.none.subtitle.for-http": {
|
||||
"defaultMessage": "Tento hostitel nebude používat HTTPS"
|
||||
},
|
||||
"certificate.none.title": {
|
||||
"defaultMessage": "Žádný"
|
||||
},
|
||||
"certificate.not-in-use": {
|
||||
"defaultMessage": "Nepoužívá se"
|
||||
},
|
||||
"certificate.renew": {
|
||||
"defaultMessage": "Obnovit certifikát"
|
||||
},
|
||||
"certificates": {
|
||||
"defaultMessage": "Certifikáty"
|
||||
},
|
||||
"certificates.custom": {
|
||||
"defaultMessage": "Vlastní certifikát"
|
||||
},
|
||||
"certificates.custom.warning": {
|
||||
"defaultMessage": "Soubory klíčů chráněné heslem nejsou podporovány."
|
||||
},
|
||||
"certificates.dns.credentials": {
|
||||
"defaultMessage": "Obsah souboru s přihlašovacími údaji"
|
||||
},
|
||||
"certificates.dns.credentials-note": {
|
||||
"defaultMessage": "Tento doplněk vyžaduje konfigurační soubor obsahující API token nebo jiné přihlašovací údaje vašeho poskytovatele"
|
||||
},
|
||||
"certificates.dns.credentials-warning": {
|
||||
"defaultMessage": "Tyto údaje budou uloženy v databázi a v souboru jako obyčejný text!"
|
||||
},
|
||||
"certificates.dns.propagation-seconds": {
|
||||
"defaultMessage": "Propagace v sekundách"
|
||||
},
|
||||
"certificates.dns.propagation-seconds-note": {
|
||||
"defaultMessage": "Nechte prázdné pro výchozí hodnotu doplňku. Počet sekund, po které se čeká na propagaci DNS."
|
||||
},
|
||||
"certificates.dns.provider": {
|
||||
"defaultMessage": "DNS poskytovatel"
|
||||
},
|
||||
"certificates.dns.provider.placeholder": {
|
||||
"defaultMessage": "Vyberte poskytovatele..."
|
||||
},
|
||||
"certificates.dns.warning": {
|
||||
"defaultMessage": "Tato sekce vyžaduje znalost Certbotu a jeho DNS doplňků. Prosím, podívejte se do dokumentace příslušného doplňku."
|
||||
},
|
||||
"certificates.http.reachability-404": {
|
||||
"defaultMessage": "Na této doméně byl nalezen server, ale nezdá se, že jde o Nginx Proxy Manager. Ujistěte se, že vaše doména směřuje na IP, kde běží vaše instance NPM."
|
||||
},
|
||||
"certificates.http.reachability-failed-to-check": {
|
||||
"defaultMessage": "Nepodařilo se ověřit dostupnost kvůli chybě komunikace se službou site24x7.com."
|
||||
},
|
||||
"certificates.http.reachability-not-resolved": {
|
||||
"defaultMessage": "Na této doméně není dostupný žádný server. Ujistěte se, že doména existuje a směřuje na IP adresu s NPM a pokud je to potřeba, port 80 je přesměrován ve vašem routeru."
|
||||
},
|
||||
"certificates.http.reachability-ok": {
|
||||
"defaultMessage": "Váš server je dostupný a vytvoření certifikátu by mělo být možné."
|
||||
},
|
||||
"certificates.http.reachability-other": {
|
||||
"defaultMessage": "Na této doméně byl nalezen server, ale vrátil neočekávaný stavový kód {code}. Je to NPM server? Ujistěte se prosím, že doména směřuje na IP, kde běží vaše instance NPM."
|
||||
},
|
||||
"certificates.http.reachability-wrong-data": {
|
||||
"defaultMessage": "Na této doméně byl nalezen server, ale vrátil neočekávaná data. Je to NPM server? Ujistěte se, že doména směřuje na IP, kde běží vaše instance NPM."
|
||||
},
|
||||
"certificates.http.test-results": {
|
||||
"defaultMessage": "Výsledky testu"
|
||||
},
|
||||
"certificates.http.warning": {
|
||||
"defaultMessage": "Tyto domény musí být již nakonfigurovány tak, aby směřovaly na tuto instalaci."
|
||||
},
|
||||
"certificates.key-type": {
|
||||
"defaultMessage": "Typ klíče"
|
||||
},
|
||||
"certificates.key-type-description": {
|
||||
"defaultMessage": "RSA je široce kompatibilní, ECDSA je rychlejší a bezpečnější, ale nemusí být podporován staršími systémy"
|
||||
},
|
||||
"certificates.key-type-ecdsa": {
|
||||
"defaultMessage": "ECDSA 256"
|
||||
},
|
||||
"certificates.key-type-rsa": {
|
||||
"defaultMessage": "RSA 2048"
|
||||
},
|
||||
"certificates.request.subtitle": {
|
||||
"defaultMessage": "pomocí Let's Encrypt"
|
||||
},
|
||||
"certificates.request.title": {
|
||||
"defaultMessage": "Vyžádat nový certifikát"
|
||||
},
|
||||
"column.access": {
|
||||
"defaultMessage": "Přístup"
|
||||
},
|
||||
"column.authorization": {
|
||||
"defaultMessage": "Autorizace"
|
||||
},
|
||||
"column.authorizations": {
|
||||
"defaultMessage": "Autorizace"
|
||||
},
|
||||
"column.custom-locations": {
|
||||
"defaultMessage": "Vlastní umístění"
|
||||
},
|
||||
"column.destination": {
|
||||
"defaultMessage": "Cíl"
|
||||
},
|
||||
"column.details": {
|
||||
"defaultMessage": "Podrobnosti"
|
||||
},
|
||||
"column.email": {
|
||||
"defaultMessage": "Email"
|
||||
},
|
||||
"column.event": {
|
||||
"defaultMessage": "Událost"
|
||||
},
|
||||
"column.expires": {
|
||||
"defaultMessage": "Platnost do"
|
||||
},
|
||||
"column.http-code": {
|
||||
"defaultMessage": "Přístup"
|
||||
},
|
||||
"column.incoming-port": {
|
||||
"defaultMessage": "Vstupní port"
|
||||
},
|
||||
"column.name": {
|
||||
"defaultMessage": "Název"
|
||||
},
|
||||
"column.protocol": {
|
||||
"defaultMessage": "Protokol"
|
||||
},
|
||||
"column.provider": {
|
||||
"defaultMessage": "Poskytovatel"
|
||||
},
|
||||
"column.roles": {
|
||||
"defaultMessage": "Role"
|
||||
},
|
||||
"column.rules": {
|
||||
"defaultMessage": "Pravidla"
|
||||
},
|
||||
"column.satisfy": {
|
||||
"defaultMessage": "Splnit"
|
||||
},
|
||||
"column.satisfy-all": {
|
||||
"defaultMessage": "Všechny"
|
||||
},
|
||||
"column.satisfy-any": {
|
||||
"defaultMessage": "Kterékoliv"
|
||||
},
|
||||
"column.scheme": {
|
||||
"defaultMessage": "Schéma"
|
||||
},
|
||||
"column.source": {
|
||||
"defaultMessage": "Zdroj"
|
||||
},
|
||||
"column.ssl": {
|
||||
"defaultMessage": "SSL"
|
||||
},
|
||||
"column.status": {
|
||||
"defaultMessage": "Stav"
|
||||
},
|
||||
"created-on": {
|
||||
"defaultMessage": "Vytvořeno: {date}"
|
||||
},
|
||||
"dashboard": {
|
||||
"defaultMessage": "Panel"
|
||||
},
|
||||
"dead-host": {
|
||||
"defaultMessage": "404 hostitel"
|
||||
},
|
||||
"dead-hosts": {
|
||||
"defaultMessage": "404 Hostitelé"
|
||||
},
|
||||
"dead-hosts.count": {
|
||||
"defaultMessage": "{count} {count, plural, one {404 hostitel} few {404 hostitelé} other {404 hostitelů}}"
|
||||
},
|
||||
"disabled": {
|
||||
"defaultMessage": "Deaktivováno"
|
||||
},
|
||||
"domain-names": {
|
||||
"defaultMessage": "Doménová jména"
|
||||
},
|
||||
"domain-names.max": {
|
||||
"defaultMessage": "Maximálně {count} doménových jmen"
|
||||
},
|
||||
"domain-names.placeholder": {
|
||||
"defaultMessage": "Začněte psát pro přidání domény..."
|
||||
},
|
||||
"domain-names.wildcards-not-permitted": {
|
||||
"defaultMessage": "Wildcards nejsou pro tento typ povoleny"
|
||||
},
|
||||
"domain-names.wildcards-not-supported": {
|
||||
"defaultMessage": "Wildcards nejsou podporovány pro tuto certifikační autoritu"
|
||||
},
|
||||
"domains.force-ssl": {
|
||||
"defaultMessage": "Vynutit SSL"
|
||||
},
|
||||
"domains.hsts-enabled": {
|
||||
"defaultMessage": "HSTS povoleno"
|
||||
},
|
||||
"domains.hsts-subdomains": {
|
||||
"defaultMessage": "HSTS pro subdomény"
|
||||
},
|
||||
"domains.http2-support": {
|
||||
"defaultMessage": "Podpora HTTP/2"
|
||||
},
|
||||
"domains.use-dns": {
|
||||
"defaultMessage": "Použít DNS výzvu"
|
||||
},
|
||||
"email-address": {
|
||||
"defaultMessage": "Emailová adresa"
|
||||
},
|
||||
"empty-search": {
|
||||
"defaultMessage": "Nebyly nalezeny žádné výsledky"
|
||||
},
|
||||
"empty-subtitle": {
|
||||
"defaultMessage": "Proč nevytvoříte nějaký?"
|
||||
},
|
||||
"enabled": {
|
||||
"defaultMessage": "Aktivováno"
|
||||
},
|
||||
"error.access.at-least-one": {
|
||||
"defaultMessage": "Je vyžadována alespoň jedna autorizace nebo jedno přístupové pravidlo"
|
||||
},
|
||||
"error.access.duplicate-usernames": {
|
||||
"defaultMessage": "Uživatelská jména pro autorizaci musí být jedinečná"
|
||||
},
|
||||
"error.invalid-auth": {
|
||||
"defaultMessage": "Neplatný email nebo heslo"
|
||||
},
|
||||
"error.invalid-domain": {
|
||||
"defaultMessage": "Neplatná doména: {domain}"
|
||||
},
|
||||
"error.invalid-email": {
|
||||
"defaultMessage": "Neplatná emailová adresa"
|
||||
},
|
||||
"error.max-character-length": {
|
||||
"defaultMessage": "Maximální délka je {max} znak{max, plural, one {} few {y} other {ů}}"
|
||||
},
|
||||
"error.max-domains": {
|
||||
"defaultMessage": "Příliš mnoho domén, maximum je {max}"
|
||||
},
|
||||
"error.maximum": {
|
||||
"defaultMessage": "Maximum je {max}"
|
||||
},
|
||||
"error.min-character-length": {
|
||||
"defaultMessage": "Minimální délka je {min} znak{min, plural, one {} few {y} other {ů}}"
|
||||
},
|
||||
"error.minimum": {
|
||||
"defaultMessage": "Minimum je {min}"
|
||||
},
|
||||
"error.passwords-must-match": {
|
||||
"defaultMessage": "Hesla se musí shodovat"
|
||||
},
|
||||
"error.required": {
|
||||
"defaultMessage": "Toto pole je povinné"
|
||||
},
|
||||
"expires.on": {
|
||||
"defaultMessage": "Platnost do: {date}"
|
||||
},
|
||||
"footer.github-fork": {
|
||||
"defaultMessage": "Forkněte mě na GitHubu"
|
||||
},
|
||||
"host.flags.block-exploits": {
|
||||
"defaultMessage": "Blokovat běžné exploity"
|
||||
},
|
||||
"host.flags.cache-assets": {
|
||||
"defaultMessage": "Uložit zdroje do mezipaměti"
|
||||
},
|
||||
"host.flags.preserve-path": {
|
||||
"defaultMessage": "Zachovat cestu"
|
||||
},
|
||||
"host.flags.protocols": {
|
||||
"defaultMessage": "Protokoly"
|
||||
},
|
||||
"host.flags.websockets-upgrade": {
|
||||
"defaultMessage": "Podpora WebSockets"
|
||||
},
|
||||
"host.forward-port": {
|
||||
"defaultMessage": "Port přesměrování"
|
||||
},
|
||||
"host.forward-scheme": {
|
||||
"defaultMessage": "Schéma"
|
||||
},
|
||||
"hosts": {
|
||||
"defaultMessage": "Hostitelé"
|
||||
},
|
||||
"http-only": {
|
||||
"defaultMessage": "Pouze HTTP"
|
||||
},
|
||||
"lets-encrypt": {
|
||||
"defaultMessage": "Let's Encrypt"
|
||||
},
|
||||
"lets-encrypt-via-dns": {
|
||||
"defaultMessage": "Let's Encrypt přes DNS"
|
||||
},
|
||||
"lets-encrypt-via-http": {
|
||||
"defaultMessage": "Let's Encrypt přes HTTP"
|
||||
},
|
||||
"loading": {
|
||||
"defaultMessage": "Načítá se…"
|
||||
},
|
||||
"login.2fa-code": {
|
||||
"defaultMessage": "Ověřovací kód"
|
||||
},
|
||||
"login.2fa-code-placeholder": {
|
||||
"defaultMessage": "Vložit kód"
|
||||
},
|
||||
"login.2fa-description": {
|
||||
"defaultMessage": "Vložte kód z vaší ověřovací aplikace"
|
||||
},
|
||||
"login.2fa-title": {
|
||||
"defaultMessage": "Dvoufaktorové ověření"
|
||||
},
|
||||
"login.2fa-verify": {
|
||||
"defaultMessage": "Ověřit"
|
||||
},
|
||||
"login.title": {
|
||||
"defaultMessage": "Přihlaste se ke svému účtu"
|
||||
},
|
||||
"nginx-config.label": {
|
||||
"defaultMessage": "Vlastní Nginx konfigurace"
|
||||
},
|
||||
"nginx-config.placeholder": {
|
||||
"defaultMessage": "# Zadejte vlastní Nginx konfiguraci na vlastní riziko!"
|
||||
},
|
||||
"no-permission-error": {
|
||||
"defaultMessage": "Nemáte oprávnění k zobrazení tohoto obsahu."
|
||||
},
|
||||
"notfound.action": {
|
||||
"defaultMessage": "Zpět na hlavní stránku"
|
||||
},
|
||||
"notfound.content": {
|
||||
"defaultMessage": "Omlouváme se, stránka, kterou hledáte, nebyla nalezena"
|
||||
},
|
||||
"notfound.title": {
|
||||
"defaultMessage": "Ups… Našli jste chybovou stránku"
|
||||
},
|
||||
"notification.error": {
|
||||
"defaultMessage": "Chyba"
|
||||
},
|
||||
"notification.object-deleted": {
|
||||
"defaultMessage": "{object} byl odstraněn"
|
||||
},
|
||||
"notification.object-disabled": {
|
||||
"defaultMessage": "{object} byl deaktivován"
|
||||
},
|
||||
"notification.object-enabled": {
|
||||
"defaultMessage": "{object} byl aktivován"
|
||||
},
|
||||
"notification.object-renewed": {
|
||||
"defaultMessage": "{object} byl obnoven"
|
||||
},
|
||||
"notification.object-saved": {
|
||||
"defaultMessage": "{object} byl uložen"
|
||||
},
|
||||
"notification.success": {
|
||||
"defaultMessage": "Úspěch"
|
||||
},
|
||||
"object.actions-title": {
|
||||
"defaultMessage": "{object} #{id}"
|
||||
},
|
||||
"object.add": {
|
||||
"defaultMessage": "Přidat {object}"
|
||||
},
|
||||
"object.delete": {
|
||||
"defaultMessage": "Smazat {object}"
|
||||
},
|
||||
"object.delete.content": {
|
||||
"defaultMessage": "Opravdu chcete smazat tento {object}?"
|
||||
},
|
||||
"object.edit": {
|
||||
"defaultMessage": "Upravit {object}"
|
||||
},
|
||||
"object.empty": {
|
||||
"defaultMessage": "Nejsou {objects}"
|
||||
},
|
||||
"object.event.created": {
|
||||
"defaultMessage": "Vytvořen {object}"
|
||||
},
|
||||
"object.event.deleted": {
|
||||
"defaultMessage": "Smazán {object}"
|
||||
},
|
||||
"object.event.disabled": {
|
||||
"defaultMessage": "Deaktivován {object}"
|
||||
},
|
||||
"object.event.enabled": {
|
||||
"defaultMessage": "Aktivován {object}"
|
||||
},
|
||||
"object.event.renewed": {
|
||||
"defaultMessage": "Obnoven {object}"
|
||||
},
|
||||
"object.event.updated": {
|
||||
"defaultMessage": "Aktualizován {object}"
|
||||
},
|
||||
"offline": {
|
||||
"defaultMessage": "Offline"
|
||||
},
|
||||
"online": {
|
||||
"defaultMessage": "Online"
|
||||
},
|
||||
"options": {
|
||||
"defaultMessage": "Možnosti"
|
||||
},
|
||||
"password": {
|
||||
"defaultMessage": "Heslo"
|
||||
},
|
||||
"password.generate": {
|
||||
"defaultMessage": "Vygenerovat náhodné heslo"
|
||||
},
|
||||
"password.hide": {
|
||||
"defaultMessage": "Skrýt heslo"
|
||||
},
|
||||
"password.show": {
|
||||
"defaultMessage": "Zobrazit heslo"
|
||||
},
|
||||
"permissions.hidden": {
|
||||
"defaultMessage": "Skryté"
|
||||
},
|
||||
"permissions.manage": {
|
||||
"defaultMessage": "Spravovat"
|
||||
},
|
||||
"permissions.view": {
|
||||
"defaultMessage": "Pouze pro zobrazení"
|
||||
},
|
||||
"permissions.visibility.all": {
|
||||
"defaultMessage": "Všechny položky"
|
||||
},
|
||||
"permissions.visibility.title": {
|
||||
"defaultMessage": "Viditelnost položky"
|
||||
},
|
||||
"permissions.visibility.user": {
|
||||
"defaultMessage": "Pouze vytvořené položky"
|
||||
},
|
||||
"proxy-host": {
|
||||
"defaultMessage": "proxy hostitele"
|
||||
},
|
||||
"proxy-host.forward-host": {
|
||||
"defaultMessage": "Cílový název hostitele / IP"
|
||||
},
|
||||
"proxy-hosts": {
|
||||
"defaultMessage": "Proxy hostitelé"
|
||||
},
|
||||
"proxy-hosts.count": {
|
||||
"defaultMessage": "{count} {count, plural, one {proxy hostitel} few {proxy hostitelé} other {proxy hostitelů}}"
|
||||
},
|
||||
"public": {
|
||||
"defaultMessage": "Veřejné"
|
||||
},
|
||||
"redirection-host": {
|
||||
"defaultMessage": "přesměrovacího hostitele"
|
||||
},
|
||||
"redirection-host.forward-domain": {
|
||||
"defaultMessage": "Cílová doména"
|
||||
},
|
||||
"redirection-host.forward-http-code": {
|
||||
"defaultMessage": "HTTP kód"
|
||||
},
|
||||
"redirection-hosts": {
|
||||
"defaultMessage": "Přesměrovací hostitelé"
|
||||
},
|
||||
"redirection-hosts.count": {
|
||||
"defaultMessage": "{count} {count, plural, one {přesměrovací hostitel} few {přesměrovací hostitelé} other {přesměrovacích hostitelů}}"
|
||||
},
|
||||
"redirection-hosts.http-code.300": {
|
||||
"defaultMessage": "300 Více možností"
|
||||
},
|
||||
"redirection-hosts.http-code.301": {
|
||||
"defaultMessage": "301 Trvale přesunuto"
|
||||
},
|
||||
"redirection-hosts.http-code.302": {
|
||||
"defaultMessage": "302 Dočasně přesunuto"
|
||||
},
|
||||
"redirection-hosts.http-code.303": {
|
||||
"defaultMessage": "303 Podívat se na jiné"
|
||||
},
|
||||
"redirection-hosts.http-code.307": {
|
||||
"defaultMessage": "307 Dočasné přesměrování"
|
||||
},
|
||||
"redirection-hosts.http-code.308": {
|
||||
"defaultMessage": "308 Trvalé přesměrování"
|
||||
},
|
||||
"role.admin": {
|
||||
"defaultMessage": "Administrátor"
|
||||
},
|
||||
"role.standard-user": {
|
||||
"defaultMessage": "Běžný uživatel"
|
||||
},
|
||||
"save": {
|
||||
"defaultMessage": "Uložit"
|
||||
},
|
||||
"setting": {
|
||||
"defaultMessage": "Nastavení"
|
||||
},
|
||||
"settings": {
|
||||
"defaultMessage": "Nastavení"
|
||||
},
|
||||
"settings.default-site": {
|
||||
"defaultMessage": "Výchozí stránka"
|
||||
},
|
||||
"settings.default-site.404": {
|
||||
"defaultMessage": "Stránka 404"
|
||||
},
|
||||
"settings.default-site.444": {
|
||||
"defaultMessage": "Bez odpovědi (444)"
|
||||
},
|
||||
"settings.default-site.congratulations": {
|
||||
"defaultMessage": "Gratulační stránka"
|
||||
},
|
||||
"settings.default-site.description": {
|
||||
"defaultMessage": "Co zobrazit, když Nginx zachytí neznámého hostitele"
|
||||
},
|
||||
"settings.default-site.html": {
|
||||
"defaultMessage": "Vlastní HTML"
|
||||
},
|
||||
"settings.default-site.html.placeholder": {
|
||||
"defaultMessage": "<!-- Sem zadejte vlastní HTML obsah -->"
|
||||
},
|
||||
"settings.default-site.redirect": {
|
||||
"defaultMessage": "Přesměrovat"
|
||||
},
|
||||
"setup.preamble": {
|
||||
"defaultMessage": "Začněte vytvořením administrátorského účtu."
|
||||
},
|
||||
"setup.title": {
|
||||
"defaultMessage": "Vítejte!"
|
||||
},
|
||||
"sign-in": {
|
||||
"defaultMessage": "Přihlásit se"
|
||||
},
|
||||
"ssl-certificate": {
|
||||
"defaultMessage": "SSL certifikát"
|
||||
},
|
||||
"stream": {
|
||||
"defaultMessage": "stream"
|
||||
},
|
||||
"stream.forward-host": {
|
||||
"defaultMessage": "Cílový hostitel"
|
||||
},
|
||||
"stream.forward-host.placeholder": {
|
||||
"defaultMessage": "napriklad.cz nebo 10.0.0.1 nebo 2001:db8:3333:4444:5555:6666:7777:8888"
|
||||
},
|
||||
"stream.incoming-port": {
|
||||
"defaultMessage": "Vstupní port"
|
||||
},
|
||||
"streams": {
|
||||
"defaultMessage": "Streamy"
|
||||
},
|
||||
"streams.count": {
|
||||
"defaultMessage": "{count} {count, plural, one {stream} few {streamy} other {streamů}}"
|
||||
},
|
||||
"streams.tcp": {
|
||||
"defaultMessage": "TCP"
|
||||
},
|
||||
"streams.udp": {
|
||||
"defaultMessage": "UDP"
|
||||
},
|
||||
"test": {
|
||||
"defaultMessage": "Test"
|
||||
},
|
||||
"update-available": {
|
||||
"defaultMessage": "Dostupná aktualizace: {latestVersion}"
|
||||
},
|
||||
"user": {
|
||||
"defaultMessage": "uživatele"
|
||||
},
|
||||
"user.change-password": {
|
||||
"defaultMessage": "Změnit heslo"
|
||||
},
|
||||
"user.confirm-password": {
|
||||
"defaultMessage": "Potvrdit heslo"
|
||||
},
|
||||
"user.current-password": {
|
||||
"defaultMessage": "Aktuální heslo"
|
||||
},
|
||||
"user.edit-profile": {
|
||||
"defaultMessage": "Upravit profil"
|
||||
},
|
||||
"user.full-name": {
|
||||
"defaultMessage": "Celé jméno"
|
||||
},
|
||||
"user.login-as": {
|
||||
"defaultMessage": "Přihlásit se jako {name}"
|
||||
},
|
||||
"user.logout": {
|
||||
"defaultMessage": "Odhlásit se"
|
||||
},
|
||||
"user.new-password": {
|
||||
"defaultMessage": "Nové heslo"
|
||||
},
|
||||
"user.nickname": {
|
||||
"defaultMessage": "Přezdívka"
|
||||
},
|
||||
"user.set-password": {
|
||||
"defaultMessage": "Nastavit heslo"
|
||||
},
|
||||
"user.set-permissions": {
|
||||
"defaultMessage": "Nastavit oprávnění pro {name}"
|
||||
},
|
||||
"user.switch-dark": {
|
||||
"defaultMessage": "Přepnout na tmavý režim"
|
||||
},
|
||||
"user.switch-light": {
|
||||
"defaultMessage": "Přepnout na světlý režim"
|
||||
},
|
||||
"user.two-factor": {
|
||||
"defaultMessage": "Dvoufaktorové ověření"
|
||||
},
|
||||
"username": {
|
||||
"defaultMessage": "Uživatelské jméno"
|
||||
},
|
||||
"users": {
|
||||
"defaultMessage": "Uživatelé"
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user