From fd9b10f1f5454bc61bdd6ca5d8ed3345917782cd Mon Sep 17 00:00:00 2001 From: Bifeldy Date: Thu, 20 Apr 2023 10:08:21 +0700 Subject: [PATCH 1/6] Location prefix behind reverse proxy --- README.md | 57 ++++++++++++++++++++++++++++++++++++++ app/__init__.py | 7 +++++ app/static/custom.js | 10 +++---- app/static/custom.min.js | 2 +- docker-compose.windows.yml | 30 ++++++++++++++++++++ 5 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 docker-compose.windows.yml diff --git a/README.md b/README.md index e04e7a4..370b251 100644 --- a/README.md +++ b/README.md @@ -114,3 +114,60 @@ server { 2. Run `nginx -t` to make sure, that your config is valid 3. Run `systemctl restart nginx` (or equivalent) to restart your nginx and apply the new settings 4. Your nginx ui is now accessible at nginx.mydomain.com and will correctly prompt for basic auth + +### Example NginX-UI + NginX (run in container) behind path '/some-location' + +docker-compose.yml +```yaml +version: '3' +services: + nginx-ui: + container_name: nginx-ui + build: . + image: schenkd/nginx-ui:latest + # ports: + # - 8080:8080 + volumes: + - D:/_data/_docker/nginx:/etc/nginx + networks: + - my-custom-network + nginx: + container_name: nginx + image: nginx:latest + ports: + - 80:80 + volumes: + - D:/_data/_docker/nginx:/etc/nginx + networks: + - my-custom-network +networks: + my-custom-network: + name: my-custom-network + external: true +``` + +default.conf +```none +server { + + listen 80; + listen [::]:80; + server_name localhost; + + # ... + + location /nginx-ui/ { + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Need to pass this header for backend procesing route '/nginx-ui' + '/api' + proxy_set_header X-Forwarded-Prefix /nginx-ui; + + # With docker custom network, we can use container name with port + # We cannot access port 8080 directly from outside host server + proxy_pass http://nginx-ui:8080/; + } + +} +``` \ No newline at end of file diff --git a/app/__init__.py b/app/__init__.py index 7d8039c..6f56994 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -2,12 +2,19 @@ from config import config from flask_moment import Moment +from werkzeug.middleware.proxy_fix import ProxyFix +import os moment = Moment() def create_app(config_name): app = Flask(__name__) + + app.wsgi_app = ProxyFix( + app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1 + ) + app.config.from_object(config[config_name]) config[config_name].init_app(app) diff --git a/app/static/custom.js b/app/static/custom.js index 3e55e1d..af4d816 100644 --- a/app/static/custom.js +++ b/app/static/custom.js @@ -25,7 +25,7 @@ function add_domain() { $.ajax({ type: 'POST', - url: '/api/domain/' + name, + url: 'api/domain/' + name, statusCode: { 201: function() { fetch_domain(name) } } @@ -37,7 +37,7 @@ function enable_domain(name, enable) { $.ajax({ type: 'POST', - url: '/api/domain/' + name + '/enable', + url: 'api/domain/' + name + '/enable', contentType: 'application/json; charset=utf-8', dataType: 'json', data: JSON.stringify({ @@ -56,7 +56,7 @@ function update_domain(name) { $.ajax({ type: 'PUT', - url: '/api/domain/' + name, + url: 'api/domain/' + name, contentType: 'application/json; charset=utf-8', dataType: 'json', data: JSON.stringify({ @@ -88,7 +88,7 @@ function remove_domain(name) { $.ajax({ type: 'DELETE', - url: '/api/domain/' + name, + url: 'api/domain/' + name, statusCode: { 200: function() { load_domains(); @@ -121,7 +121,7 @@ function update_config(name) { $.ajax({ type: 'POST', - url: '/api/config/' + name, + url: 'api/config/' + name, contentType: 'application/json; charset=utf-8', dataType: 'json', data: JSON.stringify({ diff --git a/app/static/custom.min.js b/app/static/custom.min.js index da19fda..f712f59 100644 --- a/app/static/custom.min.js +++ b/app/static/custom.min.js @@ -1 +1 @@ -function load_domains(){$.when(fetch_html("api/domains")).then(function(){$("#domain").hide(),$("#domain_cards").fadeIn()})}function add_domain(){var n=$("#add_domain").val();$("#add_domain").val(""),$.ajax({type:"POST",url:"/api/domain/"+n,statusCode:{201:function(){fetch_domain(n)}}})}function enable_domain(n,t){$.ajax({type:"POST",url:"/api/domain/"+n+"/enable",contentType:"application/json; charset=utf-8",dataType:"json",data:JSON.stringify({enable:t}),statusCode:{200:function(){fetch_domain(n)}}})}function update_domain(n){var t=$("#file-content").val();$("#dimmer").addClass("active"),$.ajax({type:"PUT",url:"/api/domain/"+n,contentType:"application/json; charset=utf-8",dataType:"json",data:JSON.stringify({file:t}),statusCode:{200:function(){setTimeout(function(){fetch_domain(n)},400)}}})}function fetch_domain(n){fetch("api/domain/"+n).then(function(n){n.text().then(function(n){$("#domain").html(n).fadeIn(),$("#domain_cards").hide()})}).catch(function(n){console.error(n)})}function remove_domain(n){$.ajax({type:"DELETE",url:"/api/domain/"+n,statusCode:{200:function(){load_domains()},400:function(){alert("Deleting not possible")}}})}function fetch_html(n){fetch(n).then(function(n){n.text().then(function(n){$("#content").html(n)})}).catch(function(n){console.error(n)})}function update_config(n){var t=$("#file-content").val();$("#dimmer").addClass("active"),$.ajax({type:"POST",url:"/api/config/"+n,contentType:"application/json; charset=utf-8",dataType:"json",data:JSON.stringify({file:t}),statusCode:{200:function(){setTimeout(function(){$("#dimmer").removeClass("active")},450)}}})}function load_config(n){fetch("api/config/"+n).then(function(n){n.text().then(function(n){$("#content").html(n)})}).catch(function(n){console.error(n)})}$(document).ready(function(){$(".ui.dropdown").dropdown(),$(".config.item").click(function(){load_config($(this).html())}),$("#domains").click(function(){load_domains()}),load_domains()}); \ No newline at end of file +function load_domains(){$.when(fetch_html("api/domains")).then(function(){$("#domain").hide(),$("#domain_cards").fadeIn()})}function add_domain(){var n=$("#add_domain").val();$("#add_domain").val(""),$.ajax({type:"POST",url:"api/domain/"+n,statusCode:{201:function(){fetch_domain(n)}}})}function enable_domain(n,t){$.ajax({type:"POST",url:"api/domain/"+n+"/enable",contentType:"application/json; charset=utf-8",dataType:"json",data:JSON.stringify({enable:t}),statusCode:{200:function(){fetch_domain(n)}}})}function update_domain(n){var t=$("#file-content").val();$("#dimmer").addClass("active"),$.ajax({type:"PUT",url:"api/domain/"+n,contentType:"application/json; charset=utf-8",dataType:"json",data:JSON.stringify({file:t}),statusCode:{200:function(){setTimeout(function(){fetch_domain(n)},400)}}})}function fetch_domain(n){fetch("api/domain/"+n).then(function(n){n.text().then(function(n){$("#domain").html(n).fadeIn(),$("#domain_cards").hide()})}).catch(function(n){console.error(n)})}function remove_domain(n){$.ajax({type:"DELETE",url:"api/domain/"+n,statusCode:{200:function(){load_domains()},400:function(){alert("Deleting not possible")}}})}function fetch_html(n){fetch(n).then(function(n){n.text().then(function(n){$("#content").html(n)})}).catch(function(n){console.error(n)})}function update_config(n){var t=$("#file-content").val();$("#dimmer").addClass("active"),$.ajax({type:"POST",url:"api/config/"+n,contentType:"application/json; charset=utf-8",dataType:"json",data:JSON.stringify({file:t}),statusCode:{200:function(){setTimeout(function(){$("#dimmer").removeClass("active")},450)}}})}function load_config(n){fetch("api/config/"+n).then(function(n){n.text().then(function(n){$("#content").html(n)})}).catch(function(n){console.error(n)})}$(document).ready(function(){$(".ui.dropdown").dropdown(),$(".config.item").click(function(){load_config($(this).html())}),$("#domains").click(function(){load_domains()}),load_domains()}); \ No newline at end of file diff --git a/docker-compose.windows.yml b/docker-compose.windows.yml new file mode 100644 index 0000000..717a1fa --- /dev/null +++ b/docker-compose.windows.yml @@ -0,0 +1,30 @@ +version: '3' + +services: + + nginx-ui: + container_name: nginx-ui + build: . + image: bifeldy/nginx-ui:latest + # ports: + # - 8080:8080 + volumes: + - D:/_data/_docker/nginx:/etc/nginx + networks: + - bifeldy-net + + nginx: + container_name: nginx + image: nginx:latest + ports: + - 8888:80 + volumes: + - D:/_data/_docker/nginx:/etc/nginx + networks: + - bifeldy-net + +networks: + + bifeldy-net: + name: bifeldy-net + external: true \ No newline at end of file From e8b1275f81ef830bece8ee71f98a238a86405cd9 Mon Sep 17 00:00:00 2001 From: Basilius Bias Astho Christyono Date: Thu, 20 Apr 2023 18:03:16 +0900 Subject: [PATCH 2/6] Remove unnecessary import --- app/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/__init__.py b/app/__init__.py index 6f56994..d202e06 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -3,7 +3,6 @@ from flask_moment import Moment from werkzeug.middleware.proxy_fix import ProxyFix -import os moment = Moment() From 7ec74727574696deef67520be7cb2560b47f57ad Mon Sep 17 00:00:00 2001 From: Bifeldy Date: Wed, 3 May 2023 17:15:57 +0900 Subject: [PATCH 3/6] Remove windows test --- docker-compose.windows.yml | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 docker-compose.windows.yml diff --git a/docker-compose.windows.yml b/docker-compose.windows.yml deleted file mode 100644 index 717a1fa..0000000 --- a/docker-compose.windows.yml +++ /dev/null @@ -1,30 +0,0 @@ -version: '3' - -services: - - nginx-ui: - container_name: nginx-ui - build: . - image: bifeldy/nginx-ui:latest - # ports: - # - 8080:8080 - volumes: - - D:/_data/_docker/nginx:/etc/nginx - networks: - - bifeldy-net - - nginx: - container_name: nginx - image: nginx:latest - ports: - - 8888:80 - volumes: - - D:/_data/_docker/nginx:/etc/nginx - networks: - - bifeldy-net - -networks: - - bifeldy-net: - name: bifeldy-net - external: true \ No newline at end of file From 8e1175107ab17c2692513fb44e67930fa149adab Mon Sep 17 00:00:00 2001 From: Bifeldy Date: Wed, 3 May 2023 17:16:57 +0900 Subject: [PATCH 4/6] Ignore personal test file --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 9ba829a..b8ab2c3 100644 --- a/.gitignore +++ b/.gitignore @@ -467,3 +467,5 @@ typings/ # Linux trash folder which might appear on any partition or disk # .nfs files are created when an open file is removed but is still being accessed + +docker-compose.windows.yml From fdce4a4438499ef93568cc5ea443d5d0dad8094b Mon Sep 17 00:00:00 2001 From: Bifeldy Date: Wed, 3 May 2023 17:23:05 +0900 Subject: [PATCH 5/6] Universal relative path --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 370b251..99914a2 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ services: # ports: # - 8080:8080 volumes: - - D:/_data/_docker/nginx:/etc/nginx + - ./nginx/etc-nginx:/etc/nginx networks: - my-custom-network nginx: @@ -137,7 +137,7 @@ services: ports: - 80:80 volumes: - - D:/_data/_docker/nginx:/etc/nginx + - ./nginx/etc-nginx:/etc/nginx networks: - my-custom-network networks: From c301de7d226a52ba589b0b723a298bda52843c27 Mon Sep 17 00:00:00 2001 From: Bifeldy Date: Wed, 10 May 2023 17:44:43 +0700 Subject: [PATCH 6/6] Reload Nginx (Container) Configuration --- Dockerfile | 11 ++++++++++- README.md | 7 +++++++ app/api/endpoints.py | 28 ++++++++++++++++++++++++++++ app/static/custom.js | 15 +++++++++++++++ app/static/custom.min.js | 2 +- app/templates/index.html | 5 +++++ app/ui/views.py | 9 ++++++++- 7 files changed, 74 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1173d01..ae095ab 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,15 @@ FROM python:3.7-alpine ADD requirements.txt . -RUN apk add python3-dev build-base linux-headers pcre-dev && pip install --no-cache-dir -r requirements.txt +RUN apk add curl python3-dev build-base linux-headers pcre-dev && pip install --no-cache-dir -r requirements.txt + +RUN cd /tmp/ \ + && curl -sSL -O https://download.docker.com/linux/static/stable/x86_64/docker-17.06.2-ce.tgz \ + && tar zxf docker-17.06.2-ce.tgz \ + && mkdir -p /usr/local/bin \ + && mv ./docker/docker /usr/local/bin \ + && chmod +x /usr/local/bin/docker \ + && rm -rf /tmp/* # adding application files ADD . /webapp @@ -10,6 +18,7 @@ ADD . /webapp # configure path /webapp to HOME-dir ENV HOME /webapp WORKDIR /webapp +EXPOSE 8080 ENTRYPOINT ["uwsgi"] CMD ["--http", "0.0.0.0:8080", "--wsgi-file", "wsgi.py", "--callable", "app", "--processes", "1", "--threads", "8"] \ No newline at end of file diff --git a/README.md b/README.md index 99914a2..cf336b3 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,10 @@ server { ### Example NginX-UI + NginX (run in container) behind path '/some-location' +![Image of Nginx UI](https://i.ibb.co/59myNSf/nginx-ui.png) + +> If Nginx running as a container separatedly, '$ docker exec {nginx-container-name} nginx -s reload' button will be visible after docker socket and container name provided + docker-compose.yml ```yaml version: '3' @@ -129,6 +133,9 @@ services: # - 8080:8080 volumes: - ./nginx/etc-nginx:/etc/nginx + - /var/run/docker.sock:/var/run/docker.sock + environment: + NGINX_CONTAINER_NAME: nginx networks: - my-custom-network nginx: diff --git a/app/api/endpoints.py b/app/api/endpoints.py index df8f9e1..4e15e06 100644 --- a/app/api/endpoints.py +++ b/app/api/endpoints.py @@ -2,10 +2,38 @@ import io import os import flask +import subprocess +from pathlib import Path from app.api import api +@api.route('/reload-nginx', methods=['GET']) +def get_reload_nginx(): + """ + Runs the command to reload the nginx configuration. + + :return: Returns a status from terminal output. + :rtype: str + """ + exec_cmd = 'nginx -s reload' + res = 'No NginX docker container provided' + code = 400 + + docker_sock = Path('/var/run/docker.sock') + if docker_sock.is_socket() and 'NGINX_CONTAINER_NAME' in os.environ: + exec_cmd = f'docker exec {os.environ.get("NGINX_CONTAINER_NAME")} {exec_cmd}' + proc = subprocess.Popen(exec_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = proc.communicate() + if err: + res = str(err.decode('utf-8')) + else: + res = str(out.decode('utf-8')) + code = 200 + + return flask.make_response({'script': exec_cmd, 'result': res}), code + + @api.route('/config/', methods=['GET']) def get_config(name: str): """ diff --git a/app/static/custom.js b/app/static/custom.js index af4d816..f9b91b7 100644 --- a/app/static/custom.js +++ b/app/static/custom.js @@ -12,6 +12,21 @@ $(document).ready(function() { }); +function reload_nginx() { + $.ajax({ + type: 'GET', + url: 'api/reload-nginx', + statusCode: { + 200: function() { + alert('NginX reloaded successfully'); + }, + 400: function() { + alert(`Failed to reload NginX`); + } + } + }); +} + function load_domains() { $.when(fetch_html('api/domains')).then(function() { $('#domain').hide(); diff --git a/app/static/custom.min.js b/app/static/custom.min.js index f712f59..6648591 100644 --- a/app/static/custom.min.js +++ b/app/static/custom.min.js @@ -1 +1 @@ -function load_domains(){$.when(fetch_html("api/domains")).then(function(){$("#domain").hide(),$("#domain_cards").fadeIn()})}function add_domain(){var n=$("#add_domain").val();$("#add_domain").val(""),$.ajax({type:"POST",url:"api/domain/"+n,statusCode:{201:function(){fetch_domain(n)}}})}function enable_domain(n,t){$.ajax({type:"POST",url:"api/domain/"+n+"/enable",contentType:"application/json; charset=utf-8",dataType:"json",data:JSON.stringify({enable:t}),statusCode:{200:function(){fetch_domain(n)}}})}function update_domain(n){var t=$("#file-content").val();$("#dimmer").addClass("active"),$.ajax({type:"PUT",url:"api/domain/"+n,contentType:"application/json; charset=utf-8",dataType:"json",data:JSON.stringify({file:t}),statusCode:{200:function(){setTimeout(function(){fetch_domain(n)},400)}}})}function fetch_domain(n){fetch("api/domain/"+n).then(function(n){n.text().then(function(n){$("#domain").html(n).fadeIn(),$("#domain_cards").hide()})}).catch(function(n){console.error(n)})}function remove_domain(n){$.ajax({type:"DELETE",url:"api/domain/"+n,statusCode:{200:function(){load_domains()},400:function(){alert("Deleting not possible")}}})}function fetch_html(n){fetch(n).then(function(n){n.text().then(function(n){$("#content").html(n)})}).catch(function(n){console.error(n)})}function update_config(n){var t=$("#file-content").val();$("#dimmer").addClass("active"),$.ajax({type:"POST",url:"api/config/"+n,contentType:"application/json; charset=utf-8",dataType:"json",data:JSON.stringify({file:t}),statusCode:{200:function(){setTimeout(function(){$("#dimmer").removeClass("active")},450)}}})}function load_config(n){fetch("api/config/"+n).then(function(n){n.text().then(function(n){$("#content").html(n)})}).catch(function(n){console.error(n)})}$(document).ready(function(){$(".ui.dropdown").dropdown(),$(".config.item").click(function(){load_config($(this).html())}),$("#domains").click(function(){load_domains()}),load_domains()}); \ No newline at end of file +function reload_nginx(){$.ajax({type:"GET",url:"api/reload-nginx",statusCode:{200:function(){alert("NginX reloaded successfully")},400:function(){alert("Failed to reload NginX")}}})}function load_domains(){$.when(fetch_html("api/domains")).then(function(){$("#domain").hide(),$("#domain_cards").fadeIn()})}function add_domain(){var n=$("#add_domain").val();$("#add_domain").val(""),$.ajax({type:"POST",url:"api/domain/"+n,statusCode:{201:function(){fetch_domain(n)}}})}function enable_domain(n,t){$.ajax({type:"POST",url:"api/domain/"+n+"/enable",contentType:"application/json; charset=utf-8",dataType:"json",data:JSON.stringify({enable:t}),statusCode:{200:function(){fetch_domain(n)}}})}function update_domain(n){var t=$("#file-content").val();$("#dimmer").addClass("active"),$.ajax({type:"PUT",url:"api/domain/"+n,contentType:"application/json; charset=utf-8",dataType:"json",data:JSON.stringify({file:t}),statusCode:{200:function(){setTimeout(function(){fetch_domain(n)},400)}}})}function fetch_domain(n){fetch("api/domain/"+n).then(function(n){n.text().then(function(n){$("#domain").html(n).fadeIn(),$("#domain_cards").hide()})}).catch(function(n){console.error(n)})}function remove_domain(n){$.ajax({type:"DELETE",url:"api/domain/"+n,statusCode:{200:function(){load_domains()},400:function(){alert("Deleting not possible")}}})}function fetch_html(n){fetch(n).then(function(n){n.text().then(function(n){$("#content").html(n)})}).catch(function(n){console.error(n)})}function update_config(n){var t=$("#file-content").val();$("#dimmer").addClass("active"),$.ajax({type:"POST",url:"api/config/"+n,contentType:"application/json; charset=utf-8",dataType:"json",data:JSON.stringify({file:t}),statusCode:{200:function(){setTimeout(function(){$("#dimmer").removeClass("active")},450)}}})}function load_config(n){fetch("api/config/"+n).then(function(n){n.text().then(function(n){$("#content").html(n)})}).catch(function(n){console.error(n)})}$(document).ready(function(){$(".ui.dropdown").dropdown(),$(".config.item").click(function(){load_config($(this).html())}),$("#domains").click(function(){load_domains()}),load_domains()}); \ No newline at end of file diff --git a/app/templates/index.html b/app/templates/index.html index 9109245..c74929a 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -41,6 +41,11 @@