Przeglądaj źródła

Merge pull request #2783 from NginxProxyManager/uidgid

Make PUID and PGID optional
jc21 2 lat temu
rodzic
commit
5f2576946d

+ 4 - 0
Jenkinsfile

@@ -91,6 +91,10 @@ pipeline {
 				// Bring up a stack
 				sh 'docker-compose up -d fullstack-sqlite'
 				sh './scripts/wait-healthy $(docker-compose ps --all -q fullstack-sqlite) 120'
+				// Stop and Start it, as this will test it's ability to restart with existing data
+				sh 'docker-compose stop fullstack-sqlite'
+				sh 'docker-compose start fullstack-sqlite'
+				sh './scripts/wait-healthy $(docker-compose ps --all -q fullstack-sqlite) 120'
 
 				// Run tests
 				sh 'rm -rf test/results'

+ 394 - 192
backend/doc/api.swagger.json

@@ -40,6 +40,210 @@
 				}
 			}
 		},
+		"/nginx/proxy-hosts": {
+			"get": {
+				"operationId": "getProxyHosts",
+				"summary": "Get all proxy hosts",
+				"tags": ["Proxy Hosts"],
+				"security": [
+					{
+						"BearerAuth": ["users"]
+					}
+				],
+				"parameters": [
+					{
+						"in": "query",
+						"name": "expand",
+						"description": "Expansions",
+						"schema": {
+							"type": "string",
+							"enum": ["access_list", "owner", "certificate"]
+						}
+					}
+				],
+				"responses": {
+					"200": {
+						"description": "200 response",
+						"content": {
+							"application/json": {
+								"examples": {
+									"default": {
+										"value": [
+											{
+												"id": 1,
+												"created_on": "2023-03-30T01:12:23.000Z",
+												"modified_on": "2023-03-30T02:15:40.000Z",
+												"owner_user_id": 1,
+												"domain_names": ["aasdasdad"],
+												"forward_host": "asdasd",
+												"forward_port": 80,
+												"access_list_id": 0,
+												"certificate_id": 0,
+												"ssl_forced": 0,
+												"caching_enabled": 0,
+												"block_exploits": 0,
+												"advanced_config": "sdfsdfsdf",
+												"meta": {
+													"letsencrypt_agree": false,
+													"dns_challenge": false,
+													"nginx_online": false,
+													"nginx_err": "Command failed: /usr/sbin/nginx -t -g \"error_log off;\"\nnginx: [emerg] unknown directive \"sdfsdfsdf\" in /data/nginx/proxy_host/1.conf:37\nnginx: configuration file /etc/nginx/nginx.conf test failed\n"
+												},
+												"allow_websocket_upgrade": 0,
+												"http2_support": 0,
+												"forward_scheme": "http",
+												"enabled": 1,
+												"locations": [],
+												"hsts_enabled": 0,
+												"hsts_subdomains": 0,
+												"owner": {
+													"id": 1,
+													"created_on": "2023-03-30T01:11:50.000Z",
+													"modified_on": "2023-03-30T01:11:50.000Z",
+													"is_deleted": 0,
+													"is_disabled": 0,
+													"email": "[email protected]",
+													"name": "Administrator",
+													"nickname": "Admin",
+													"avatar": "",
+													"roles": ["admin"]
+												},
+												"access_list": null,
+												"certificate": null
+											},
+											{
+												"id": 2,
+												"created_on": "2023-03-30T02:11:49.000Z",
+												"modified_on": "2023-03-30T02:11:49.000Z",
+												"owner_user_id": 1,
+												"domain_names": ["test.example.com"],
+												"forward_host": "1.1.1.1",
+												"forward_port": 80,
+												"access_list_id": 0,
+												"certificate_id": 0,
+												"ssl_forced": 0,
+												"caching_enabled": 0,
+												"block_exploits": 0,
+												"advanced_config": "",
+												"meta": {
+													"letsencrypt_agree": false,
+													"dns_challenge": false,
+													"nginx_online": true,
+													"nginx_err": null
+												},
+												"allow_websocket_upgrade": 0,
+												"http2_support": 0,
+												"forward_scheme": "http",
+												"enabled": 1,
+												"locations": [],
+												"hsts_enabled": 0,
+												"hsts_subdomains": 0,
+												"owner": {
+													"id": 1,
+													"created_on": "2023-03-30T01:11:50.000Z",
+													"modified_on": "2023-03-30T01:11:50.000Z",
+													"is_deleted": 0,
+													"is_disabled": 0,
+													"email": "[email protected]",
+													"name": "Administrator",
+													"nickname": "Admin",
+													"avatar": "",
+													"roles": ["admin"]
+												},
+												"access_list": null,
+												"certificate": null
+											}
+										]
+									}
+								},
+								"schema": {
+									"$ref": "#/components/schemas/ProxyHostsList"
+								}
+							}
+						}
+					}
+				}
+			},
+			"post": {
+				"operationId": "createProxyHost",
+				"summary": "Create a Proxy Host",
+				"tags": ["Proxy Hosts"],
+				"security": [
+					{
+						"BearerAuth": ["users"]
+					}
+				],
+				"parameters": [
+					{
+						"in": "body",
+						"name": "proxyhost",
+						"description": "Proxy Host Payload",
+						"required": true,
+						"schema": {
+							"$ref": "#/components/schemas/ProxyHostObject"
+						}
+					}
+				],
+				"responses": {
+					"201": {
+						"description": "201 response",
+						"content": {
+							"application/json": {
+								"examples": {
+									"default": {
+										"value": {
+											"id": 3,
+											"created_on": "2023-03-30T02:31:27.000Z",
+											"modified_on": "2023-03-30T02:31:27.000Z",
+											"owner_user_id": 1,
+											"domain_names": ["test2.example.com"],
+											"forward_host": "1.1.1.1",
+											"forward_port": 80,
+											"access_list_id": 0,
+											"certificate_id": 0,
+											"ssl_forced": 0,
+											"caching_enabled": 0,
+											"block_exploits": 0,
+											"advanced_config": "",
+											"meta": {
+												"letsencrypt_agree": false,
+												"dns_challenge": false
+											},
+											"allow_websocket_upgrade": 0,
+											"http2_support": 0,
+											"forward_scheme": "http",
+											"enabled": 1,
+											"locations": [],
+											"hsts_enabled": 0,
+											"hsts_subdomains": 0,
+											"certificate": null,
+											"owner": {
+												"id": 1,
+												"created_on": "2023-03-30T01:11:50.000Z",
+												"modified_on": "2023-03-30T01:11:50.000Z",
+												"is_deleted": 0,
+												"is_disabled": 0,
+												"email": "[email protected]",
+												"name": "Administrator",
+												"nickname": "Admin",
+												"avatar": "",
+												"roles": ["admin"]
+											},
+											"access_list": null,
+											"use_default_location": true,
+											"ipv6": true
+										}
+									}
+								},
+								"schema": {
+									"$ref": "#/components/schemas/ProxyHostObject"
+								}
+							}
+						}
+					}
+				}
+			}
+		},
 		"/schema": {
 			"get": {
 				"operationId": "schema",
@@ -55,14 +259,10 @@
 			"get": {
 				"operationId": "refreshToken",
 				"summary": "Refresh your access token",
-				"tags": [
-					"Tokens"
-				],
+				"tags": ["Tokens"],
 				"security": [
 					{
-						"BearerAuth": [
-							"tokens"
-						]
+						"BearerAuth": ["tokens"]
 					}
 				],
 				"responses": {
@@ -104,19 +304,14 @@
 								"scope": {
 									"minLength": 1,
 									"type": "string",
-									"enum": [
-										"user"
-									]
+									"enum": ["user"]
 								},
 								"secret": {
 									"minLength": 1,
 									"type": "string"
 								}
 							},
-							"required": [
-								"identity",
-								"secret"
-							],
+							"required": ["identity", "secret"],
 							"type": "object"
 						}
 					}
@@ -144,23 +339,17 @@
 					}
 				},
 				"summary": "Request a new access token from credentials",
-				"tags": [
-					"Tokens"
-				]
+				"tags": ["Tokens"]
 			}
 		},
 		"/settings": {
 			"get": {
 				"operationId": "getSettings",
 				"summary": "Get all settings",
-				"tags": [
-					"Settings"
-				],
+				"tags": ["Settings"],
 				"security": [
 					{
-						"BearerAuth": [
-							"settings"
-						]
+						"BearerAuth": ["settings"]
 					}
 				],
 				"responses": {
@@ -194,14 +383,10 @@
 			"get": {
 				"operationId": "getSetting",
 				"summary": "Get a setting",
-				"tags": [
-					"Settings"
-				],
+				"tags": ["Settings"],
 				"security": [
 					{
-						"BearerAuth": [
-							"settings"
-						]
+						"BearerAuth": ["settings"]
 					}
 				],
 				"parameters": [
@@ -244,14 +429,10 @@
 			"put": {
 				"operationId": "updateSetting",
 				"summary": "Update a setting",
-				"tags": [
-					"Settings"
-				],
+				"tags": ["Settings"],
 				"security": [
 					{
-						"BearerAuth": [
-							"settings"
-						]
+						"BearerAuth": ["settings"]
 					}
 				],
 				"parameters": [
@@ -305,14 +486,10 @@
 			"get": {
 				"operationId": "getUsers",
 				"summary": "Get all users",
-				"tags": [
-					"Users"
-				],
+				"tags": ["Users"],
 				"security": [
 					{
-						"BearerAuth": [
-							"users"
-						]
+						"BearerAuth": ["users"]
 					}
 				],
 				"parameters": [
@@ -322,9 +499,7 @@
 						"description": "Expansions",
 						"schema": {
 							"type": "string",
-							"enum": [
-								"permissions"
-							]
+							"enum": ["permissions"]
 						}
 					}
 				],
@@ -345,9 +520,7 @@
 												"name": "Jamie Curnow",
 												"nickname": "James",
 												"avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
-												"roles": [
-													"admin"
-												]
+												"roles": ["admin"]
 											}
 										]
 									},
@@ -362,9 +535,7 @@
 												"name": "Jamie Curnow",
 												"nickname": "James",
 												"avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
-												"roles": [
-													"admin"
-												],
+												"roles": ["admin"],
 												"permissions": {
 													"visibility": "all",
 													"proxy_hosts": "manage",
@@ -389,14 +560,10 @@
 			"post": {
 				"operationId": "createUser",
 				"summary": "Create a User",
-				"tags": [
-					"Users"
-				],
+				"tags": ["Users"],
 				"security": [
 					{
-						"BearerAuth": [
-							"users"
-						]
+						"BearerAuth": ["users"]
 					}
 				],
 				"parameters": [
@@ -426,9 +593,7 @@
 											"name": "Jamie Curnow",
 											"nickname": "James",
 											"avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
-											"roles": [
-												"admin"
-											],
+											"roles": ["admin"],
 											"permissions": {
 												"visibility": "all",
 												"proxy_hosts": "manage",
@@ -454,14 +619,10 @@
 			"get": {
 				"operationId": "getUser",
 				"summary": "Get a user",
-				"tags": [
-					"Users"
-				],
+				"tags": ["Users"],
 				"security": [
 					{
-						"BearerAuth": [
-							"users"
-						]
+						"BearerAuth": ["users"]
 					}
 				],
 				"parameters": [
@@ -501,9 +662,7 @@
 											"name": "Jamie Curnow",
 											"nickname": "James",
 											"avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
-											"roles": [
-												"admin"
-											]
+											"roles": ["admin"]
 										}
 									}
 								},
@@ -518,14 +677,10 @@
 			"put": {
 				"operationId": "updateUser",
 				"summary": "Update a User",
-				"tags": [
-					"Users"
-				],
+				"tags": ["Users"],
 				"security": [
 					{
-						"BearerAuth": [
-							"users"
-						]
+						"BearerAuth": ["users"]
 					}
 				],
 				"parameters": [
@@ -574,9 +729,7 @@
 											"name": "Jamie Curnow",
 											"nickname": "James",
 											"avatar": "//www.gravatar.com/avatar/6193176330f8d38747f038c170ddb193?default=mm",
-											"roles": [
-												"admin"
-											]
+											"roles": ["admin"]
 										}
 									}
 								},
@@ -591,14 +744,10 @@
 			"delete": {
 				"operationId": "deleteUser",
 				"summary": "Delete a User",
-				"tags": [
-					"Users"
-				],
+				"tags": ["Users"],
 				"security": [
 					{
-						"BearerAuth": [
-							"users"
-						]
+						"BearerAuth": ["users"]
 					}
 				],
 				"parameters": [
@@ -637,14 +786,10 @@
 			"put": {
 				"operationId": "updateUserAuth",
 				"summary": "Update a User's Authentication",
-				"tags": [
-					"Users"
-				],
+				"tags": ["Users"],
 				"security": [
 					{
-						"BearerAuth": [
-							"users"
-						]
+						"BearerAuth": ["users"]
 					}
 				],
 				"parameters": [
@@ -700,14 +845,10 @@
 			"put": {
 				"operationId": "updateUserPermissions",
 				"summary": "Update a User's Permissions",
-				"tags": [
-					"Users"
-				],
+				"tags": ["Users"],
 				"security": [
 					{
-						"BearerAuth": [
-							"users"
-						]
+						"BearerAuth": ["users"]
 					}
 				],
 				"parameters": [
@@ -755,14 +896,10 @@
 			"put": {
 				"operationId": "loginAsUser",
 				"summary": "Login as this user",
-				"tags": [
-					"Users"
-				],
+				"tags": ["Users"],
 				"security": [
 					{
-						"BearerAuth": [
-							"users"
-						]
+						"BearerAuth": ["users"]
 					}
 				],
 				"parameters": [
@@ -797,9 +934,7 @@
 												"name": "Jamie Curnow",
 												"nickname": "James",
 												"avatar": "//www.gravatar.com/avatar/3c8d73f45fd8763f827b964c76e6032a?default=mm",
-												"roles": [
-													"admin"
-												]
+												"roles": ["admin"]
 											}
 										}
 									}
@@ -807,11 +942,7 @@
 								"schema": {
 									"type": "object",
 									"description": "Login object",
-									"required": [
-										"expires",
-										"token",
-										"user"
-									],
+									"required": ["expires", "token", "user"],
 									"additionalProperties": false,
 									"properties": {
 										"expires": {
@@ -840,14 +971,10 @@
 			"get": {
 				"operationId": "reportsHosts",
 				"summary": "Report on Host Statistics",
-				"tags": [
-					"Reports"
-				],
+				"tags": ["Reports"],
 				"security": [
 					{
-						"BearerAuth": [
-							"reports"
-						]
+						"BearerAuth": ["reports"]
 					}
 				],
 				"responses": {
@@ -878,14 +1005,10 @@
 			"get": {
 				"operationId": "getAuditLog",
 				"summary": "Get Audit Log",
-				"tags": [
-					"Audit Log"
-				],
+				"tags": ["Audit Log"],
 				"security": [
 					{
-						"BearerAuth": [
-							"audit-log"
-						]
+						"BearerAuth": ["audit-log"]
 					}
 				],
 				"responses": {
@@ -925,10 +1048,7 @@
 				"type": "object",
 				"description": "Health object",
 				"additionalProperties": false,
-				"required": [
-					"status",
-					"version"
-				],
+				"required": ["status", "version"],
 				"properties": {
 					"status": {
 						"type": "string",
@@ -944,11 +1064,7 @@
 							"revision": 0
 						},
 						"additionalProperties": false,
-						"required": [
-							"major",
-							"minor",
-							"revision"
-						],
+						"required": ["major", "minor", "revision"],
 						"properties": {
 							"major": {
 								"type": "integer",
@@ -969,10 +1085,7 @@
 			"TokenObject": {
 				"type": "object",
 				"description": "Token object",
-				"required": [
-					"expires",
-					"token"
-				],
+				"required": ["expires", "token"],
 				"additionalProperties": false,
 				"properties": {
 					"expires": {
@@ -988,17 +1101,148 @@
 					}
 				}
 			},
-			"SettingObject": {
+			"ProxyHostObject": {
 				"type": "object",
-				"description": "Setting object",
+				"description": "Proxy Host object",
 				"required": [
 					"id",
-					"name",
-					"description",
-					"value",
-					"meta"
+					"created_on",
+					"modified_on",
+					"owner_user_id",
+					"domain_names",
+					"forward_host",
+					"forward_port",
+					"access_list_id",
+					"certificate_id",
+					"ssl_forced",
+					"caching_enabled",
+					"block_exploits",
+					"advanced_config",
+					"meta",
+					"allow_websocket_upgrade",
+					"http2_support",
+					"forward_scheme",
+					"enabled",
+					"locations",
+					"hsts_enabled",
+					"hsts_subdomains",
+					"certificate",
+					"use_default_location",
+					"ipv6"
 				],
 				"additionalProperties": false,
+				"properties": {
+					"id": {
+						"type": "integer",
+						"description": "Proxy Host ID",
+						"minimum": 1,
+						"example": 1
+					},
+					"created_on": {
+						"type": "string",
+						"description": "Created Date",
+						"example": "2020-01-30T09:36:08.000Z"
+					},
+					"modified_on": {
+						"type": "string",
+						"description": "Modified Date",
+						"example": "2020-01-30T09:41:04.000Z"
+					},
+					"owner_user_id": {
+						"type": "integer",
+						"minimum": 1,
+						"example": 1
+					},
+					"domain_names": {
+						"type": "array",
+						"minItems": 1,
+						"items": {
+							"type": "string",
+							"minLength": 1
+						}
+					},
+					"forward_host": {
+						"type": "string",
+						"minLength": 1
+					},
+					"forward_port": {
+						"type": "integer",
+						"minimum": 1
+					},
+					"access_list_id": {
+						"type": "integer"
+					},
+					"certificate_id": {
+						"type": "integer"
+					},
+					"ssl_forced": {
+						"type": "integer"
+					},
+					"caching_enabled": {
+						"type": "integer"
+					},
+					"block_exploits": {
+						"type": "integer"
+					},
+					"advanced_config": {
+						"type": "string"
+					},
+					"meta": {
+						"type": "object"
+					},
+					"allow_websocket_upgrade": {
+						"type": "integer"
+					},
+					"http2_support": {
+						"type": "integer"
+					},
+					"forward_scheme": {
+						"type": "string"
+					},
+					"enabled": {
+						"type": "integer"
+					},
+					"locations": {
+						"type": "array"
+					},
+					"hsts_enabled": {
+						"type": "integer"
+					},
+					"hsts_subdomains": {
+						"type": "integer"
+					},
+					"certificate": {
+						"type": "object",
+						"nullable": true
+					},
+					"owner": {
+						"type": "object",
+						"nullable": true
+					},
+					"access_list": {
+						"type": "object",
+						"nullable": true
+					},
+					"use_default_location": {
+						"type": "boolean"
+					},
+					"ipv6": {
+						"type": "boolean"
+					}
+				}
+			},
+			"ProxyHostsList": {
+				"type": "array",
+				"description": "Proxyn Hosts list",
+				"items": {
+					"$ref": "#/components/schemas/ProxyHostObject"
+				}
+			},
+			"SettingObject": {
+				"type": "object",
+				"description": "Setting object",
+				"required": ["id", "name", "description", "value", "meta"],
+				"additionalProperties": false,
 				"properties": {
 					"id": {
 						"type": "string",
@@ -1057,17 +1301,7 @@
 			"UserObject": {
 				"type": "object",
 				"description": "User object",
-				"required": [
-					"id",
-					"created_on",
-					"modified_on",
-					"is_disabled",
-					"email",
-					"name",
-					"nickname",
-					"avatar",
-					"roles"
-				],
+				"required": ["id", "created_on", "modified_on", "is_disabled", "email", "name", "nickname", "avatar", "roles"],
 				"additionalProperties": false,
 				"properties": {
 					"id": {
@@ -1117,9 +1351,7 @@
 					},
 					"roles": {
 						"description": "Roles applied",
-						"example": [
-							"admin"
-						],
+						"example": ["admin"],
 						"type": "array",
 						"items": {
 							"type": "string"
@@ -1137,10 +1369,7 @@
 			"AuthObject": {
 				"type": "object",
 				"description": "Authentication Object",
-				"required": [
-					"type",
-					"secret"
-				],
+				"required": ["type", "secret"],
 				"properties": {
 					"type": {
 						"type": "string",
@@ -1167,64 +1396,37 @@
 					"visibility": {
 						"type": "string",
 						"description": "Visibility Type",
-						"enum": [
-							"all",
-							"user"
-						]
+						"enum": ["all", "user"]
 					},
 					"access_lists": {
 						"type": "string",
 						"description": "Access Lists Permissions",
-						"enum": [
-							"hidden",
-							"view",
-							"manage"
-						]
+						"enum": ["hidden", "view", "manage"]
 					},
 					"dead_hosts": {
 						"type": "string",
 						"description": "404 Hosts Permissions",
-						"enum": [
-							"hidden",
-							"view",
-							"manage"
-						]
+						"enum": ["hidden", "view", "manage"]
 					},
 					"proxy_hosts": {
 						"type": "string",
 						"description": "Proxy Hosts Permissions",
-						"enum": [
-							"hidden",
-							"view",
-							"manage"
-						]
+						"enum": ["hidden", "view", "manage"]
 					},
 					"redirection_hosts": {
 						"type": "string",
 						"description": "Redirection Permissions",
-						"enum": [
-							"hidden",
-							"view",
-							"manage"
-						]
+						"enum": ["hidden", "view", "manage"]
 					},
 					"streams": {
 						"type": "string",
 						"description": "Streams Permissions",
-						"enum": [
-							"hidden",
-							"view",
-							"manage"
-						]
+						"enum": ["hidden", "view", "manage"]
 					},
 					"certificates": {
 						"type": "string",
 						"description": "Certificates Permissions",
-						"enum": [
-							"hidden",
-							"view",
-							"manage"
-						]
+						"enum": ["hidden", "view", "manage"]
 					}
 				}
 			},
@@ -1251,4 +1453,4 @@
 			}
 		}
 	}
-}
+}

+ 2 - 0
docker/docker-compose.ci.yml

@@ -33,6 +33,8 @@ services:
       LE_STAGING: 'true'
       FORCE_COLOR: 1
       DB_SQLITE_FILE: '/data/mydb.sqlite'
+      PUID: 1000
+      PGID: 1000
     volumes:
       - npm_data:/data
     expose:

+ 13 - 0
docker/rootfs/bin/common.sh

@@ -9,6 +9,19 @@ RED='\E[1;31m'
 RESET='\E[0m'
 export CYAN BLUE YELLOW RED RESET
 
+PUID=${PUID:-0}
+PGID=${PGID:-0}
+
+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
+	# it would be rediculous to intentionally use the root group
+	# for a non-root user
+	PGID=$PUID
+fi
+
+export PUID PGID
+
 log_info () {
 	echo -e "${BLUE}❯ ${CYAN}$1${RESET}"
 }

+ 1 - 0
docker/rootfs/etc/nginx/nginx.conf

@@ -1,6 +1,7 @@
 # run nginx in foreground
 daemon off;
 pid /run/nginx/nginx.pid;
+user npmuser;
 
 # Set number of worker processes automatically based on number of CPU cores.
 worker_processes auto;

+ 3 - 4
docker/rootfs/etc/s6-overlay/s6-rc.d/backend/run

@@ -5,15 +5,14 @@ set -e
 
 . /bin/common.sh
 
+cd /app || exit 1
+
 log_info 'Starting backend ...'
 
-if [ "$DEVELOPMENT" == "true" ]; then
-	cd /app || exit 1
-	# If yarn install fails: add --verbose --network-concurrency 1
+if [ "${DEVELOPMENT:-}" = 'true' ]; then
 	s6-setuidgid npmuser yarn install
 	exec s6-setuidgid npmuser bash -c 'export HOME=/tmp/npmuserhome;node --max_old_space_size=250 --abort_on_uncaught_exception node_modules/nodemon/bin/nodemon.js'
 else
-	cd /app || exit 1
 	while :
 	do
 		s6-setuidgid npmuser bash -c 'export HOME=/tmp/npmuserhome;node --abort_on_uncaught_exception --max_old_space_size=250 index.js'

+ 4 - 4
docker/rootfs/etc/s6-overlay/s6-rc.d/frontend/run

@@ -5,15 +5,15 @@ set -e
 
 # This service is DEVELOPMENT only.
 
-if [ "$DEVELOPMENT" == "true" ]; then
+if [ "$DEVELOPMENT" = 'true' ]; then
 	. /bin/common.sh
 	cd /app/frontend || exit 1
-	log_info 'Starting frontend ...'
 	HOME=/tmp/npmuserhome
 	export HOME
 	mkdir -p /app/frontend/dist
-	chown -R npmuser:npmuser /app/frontend/dist
-	# If yarn install fails: add --verbose --network-concurrency 1
+	chown -R "$PUID:$PGID" /app/frontend/dist
+
+	log_info 'Starting frontend ...'
 	s6-setuidgid npmuser yarn install
 	exec s6-setuidgid npmuser yarn watch
 else

+ 0 - 1
docker/rootfs/etc/s6-overlay/s6-rc.d/nginx/run

@@ -6,5 +6,4 @@ set -e
 . /bin/common.sh
 
 log_info 'Starting nginx ...'
-
 exec s6-setuidgid npmuser nginx

+ 5 - 10
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/10-npmuser.sh

@@ -3,23 +3,18 @@
 
 set -e
 
-PUID=${PUID:-911}
-PGID=${PGID:-911}
-
 log_info 'Configuring npmuser ...'
 
-groupmod -g 1000 users || exit 1
-
 if id -u npmuser; then
 	# user already exists
-	usermod -u "${PUID}" npmuser || exit 1
+	usermod -u "$PUID" npmuser || exit 1
 else
 	# Add npmuser user
-	useradd -u "${PUID}" -U -d /tmp/npmuserhome -s /bin/false npmuser || exit 1
+	useradd -o -u "$PUID" -U -d /tmp/npmuserhome -s /bin/false npmuser || exit 1
 fi
 
-usermod -G users npmuser || exit 1
-groupmod -o -g "${PGID}" npmuser || exit 1
+usermod -G "$PGID" npmuser || exit 1
+groupmod -o -g "$PGID" npmuser || exit 1
 # Home for npmuser
 mkdir -p /tmp/npmuserhome
-chown -R npmuser:npmuser /tmp/npmuserhome
+chown -R "$PUID:$PGID" /tmp/npmuserhome

+ 11 - 11
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/30-ownership.sh

@@ -9,16 +9,16 @@ log_info 'Setting ownership ...'
 chown root /tmp/nginx
 
 # npmuser
-chown -R npmuser:npmuser /data
-chown -R npmuser:npmuser /etc/letsencrypt
-chown -R npmuser:npmuser /run/nginx
-chown -R npmuser:npmuser /tmp/nginx
-chown -R npmuser:npmuser /var/cache/nginx
-chown -R npmuser:npmuser /var/lib/logrotate
-chown -R npmuser:npmuser /var/lib/nginx
-chown -R npmuser:npmuser /var/log/nginx
+chown -R "$PUID:$PGID" /data \
+	/etc/letsencrypt \
+	/run/nginx \
+	/tmp/nginx \
+	/var/cache/nginx \
+	/var/lib/logrotate \
+	/var/lib/nginx \
+	/var/log/nginx
 
 # Don't chown entire /etc/nginx folder as this causes crashes on some systems
-chown -R npmuser:npmuser /etc/nginx/nginx
-chown -R npmuser:npmuser /etc/nginx/nginx.conf
-chown -R npmuser:npmuser /etc/nginx/conf.d
+chown -R "$PUID:$PGID" /etc/nginx/nginx \
+	/etc/nginx/nginx.conf \
+	/etc/nginx/conf.d

+ 1 - 1
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/50-ipv6.sh

@@ -29,7 +29,7 @@ process_folder () {
 	done
 
 	# ensure the files are still owned by the npmuser
-	chown -R npmuser:npmuser "$1"
+	chown -R "$PUID:$PGID" "$1"
 }
 
 process_folder /etc/nginx/conf.d

+ 4 - 4
docker/rootfs/etc/s6-overlay/s6-rc.d/prepare/90-banner.sh

@@ -3,15 +3,15 @@
 
 set -e
 
-echo
-echo "-------------------------------------
+echo "
+-------------------------------------
  _   _ ____  __  __
 | \ | |  _ \|  \/  |
 |  \| | |_) | |\/| |
 | |\  |  __/| |  | |
 |_| \_|_|   |_|  |_|
 -------------------------------------
-User UID: $(id -u npmuser)
-User GID: $(id -g npmuser)
+User ID:  $PUID
+Group ID: $PGID
 -------------------------------------
 "

+ 21 - 0
docs/advanced-config/README.md

@@ -1,5 +1,26 @@
 # Advanced Configuration
 
+## Running processes as a user/group
+
+By default, the services (nginx etc) will run as `root` user inside the docker container.
+You can change this behaviour by setting the following environment variables.
+Not only will they run the services as this user/group, they will change the ownership
+on the `data` and `letsencrypt` folders at startup.
+
+```yml
+services:
+  app:
+    image: 'jc21/nginx-proxy-manager:latest'
+    environment:
+      PUID: 1000
+      PGID: 1000
+    # ...
+```
+
+This may have the side effect of a failed container start due to permission denied trying
+to open port 80 on some systems. The only course to fix that is to remove the variables
+and run as the default root user.
+
 ## Best Practice: Use a Docker network
 
 For those who have a few of their upstream services running in Docker on the same Docker

+ 1 - 4
docs/setup/README.md

@@ -64,9 +64,6 @@ services:
       # Add any other Stream port you want to expose
       # - '21:21' # FTP
     environment:
-      # Unix user and group IDs, optional
-      PUID: 1000
-      PGID: 1000
       # Mysql/Maria connection parameters:
       DB_MYSQL_HOST: "db"
       DB_MYSQL_PORT: 3306
@@ -90,7 +87,7 @@ services:
       MYSQL_USER: 'npm'
       MYSQL_PASSWORD: 'npm'
     volumes:
-      - ./data/mysql:/var/lib/mysql
+      - ./mysql:/var/lib/mysql
 ```
 
 ::: warning

+ 48 - 0
test/cypress/integration/api/Hosts.spec.js

@@ -0,0 +1,48 @@
+/// <reference types="Cypress" />
+
+describe('Hosts endpoints', () => {
+	let token;
+
+	before(() => {
+		cy.getToken().then((tok) => {
+			token = tok;
+		});
+	});
+
+	it('Should be able to create a http host', function() {
+		cy.task('backendApiPost', {
+			token: token,
+			path:  '/api/nginx/proxy-hosts',
+			data:  {
+				domain_names:   ['test.example.com'],
+				forward_scheme: 'http',
+				forward_host:   '1.1.1.1',
+				forward_port:   80,
+				access_list_id: '0',
+				certificate_id: 0,
+				meta:           {
+					letsencrypt_agree: false,
+					dns_challenge:     false
+				},
+				advanced_config:         '',
+				locations:               [],
+				block_exploits:          false,
+				caching_enabled:         false,
+				allow_websocket_upgrade: false,
+				http2_support:           false,
+				hsts_enabled:            false,
+				hsts_subdomains:         false,
+				ssl_forced:              false
+			}
+		}).then((data) => {
+			cy.validateSwaggerSchema('post', 201, '/nginx/proxy-hosts', data);
+			expect(data).to.have.property('id');
+			expect(data.id).to.be.greaterThan(0);
+			expect(data).to.have.property('enabled');
+			expect(data.enabled).to.be.greaterThan(0);
+			expect(data).to.have.property('meta');
+			expect(typeof data.meta.nginx_online).to.be.equal('undefined');
+		});
+	});
+
+});

+ 1 - 1
test/cypress/plugins/backendApi/client.js

@@ -126,7 +126,7 @@ BackendApi.prototype._putPostJson = function(fn, path, data, returnOnError) {
 			logger('Response data:', data);
 			if (!returnOnError && data instanceof Error) {
 				reject(data);
-			} else if (!returnOnError && response.statusCode != 200) {
+			} else if (!returnOnError && (response.statusCode < 200 || response.statusCode >= 300)) {
 				if (typeof data === 'object' && typeof data.error === 'object' && typeof data.error.message !== 'undefined') {
 					reject(new Error(data.error.code + ': ' + data.error.message));
 				} else {