Skip to content

Instantly share code, notes, and snippets.

@taxilian
Last active July 9, 2019 15:24
Show Gist options
  • Save taxilian/87e83fd9e4d9c1a4408f2fb67524a516 to your computer and use it in GitHub Desktop.
Save taxilian/87e83fd9e4d9c1a4408f2fb67524a516 to your computer and use it in GitHub Desktop.
Example nginx config to reverse proxy to three different octoprint servers from one domain

Purpose

I have three printers; each uses a raspberry pi that runs OctoPrint. I wanted to be able to give people a single page to see all of the webcams, what they are printing with ETL (est time left), and what the current temperatures are.

This demonstrates how I did that. Email me at richard@hamstudy.org if you'd like to see a live demo -- I don't want every bot on the web looking at my cameras and using CPU cycles.

License

I don't care. Do whatever you want. Just don't hurt me.

# This file expects to be included from nginx.conf
upstream "mk2" {
server 172.19.32.157:80;
}
upstream "mk2cam" {
server 172.19.32.157:8080;
}
upstream "mpmini" {
server 172.19.32.158:80;
}
upstream "mpminicam" {
server 172.19.32.158:8080;
}
upstream "taz5" {
server 172.19.32.118:80;
}
upstream "taz5cam" {
server 172.19.32.118:8080;
}
server {
server_name myserver.com;
listen 80 ;
return 301 https://$host$request_uri;
}
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
server_name myserver;
listen 443 ssl http2;
client_max_body_size 64M;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA;
ssl_prefer_server_ciphers on;
ssl_session_timeout 5m;
ssl_session_cache shared:SSL:50m;
ssl_certificate /etc/nginx/certs/myserver.crt;
ssl_certificate_key /etc/nginx/certs/myserver.key;
add_header Strict-Transport-Security "max-age=31536000";
error_log /var/log/nginx/3dprint_error.log;
access_log /var/log/nginx/3dprint_access.log;
root /var/www/static/3dprint;
location /mpmini/ {
proxy_pass http://mpmini;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Script-Name /mpmini;
}
location ~ /mpmini/(?<pathinfo>(downloads|static|api|plugin).*) {
proxy_pass http://mpmini/$pathinfo;
proxy_set_header Host $host;
}
location /mpmini/webcam/ {
proxy_pass http://mpminicam/;
}
location /mpmini/sockjs {
proxy_pass http://mpmini/sockjs; # NO trailing slash here!
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
}
location /taz5/ {
proxy_pass http://taz5;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Script-Name /taz5;
}
location ~ /taz5/(?<pathinfo>(downloads|static|api|plugin).*) {
proxy_pass http://taz5/$pathinfo;
proxy_set_header Host $host;
}
location /taz5/webcam/ {
proxy_pass http://taz5cam/;
}
location /taz5/sockjs {
proxy_pass http://taz5/sockjs; # NO trailing slash here!
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
}
location /mk2/ {
proxy_pass http://mk2;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Script-Name /mk2;
}
location ~ /mk2/(?<pathinfo>(downloads|static|api|plugin).*) {
proxy_pass http://mk2/$pathinfo;
proxy_set_header Host $host;
}
location /mk2/webcam/ {
proxy_pass http://mk2cam/;
}
location /mk2/sockjs {
proxy_pass http://mk2/sockjs; # NO trailing slash here!
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host:$server_port;
proxy_set_header X-Real-IP $remote_addr;
}
# redirect server error pages to the static page /50x.html
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
<html>
<head>
<title>KD7BBC's 3d printing page</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.17.1/moment.min.js"></script>
<script src="/taz5/static/webassets/packed_client.js"></script>
<script language="javascript">
var hosts = {
mk2: '/mk2',
mpmini: '/mpmini',
taz5: '/taz5'
};
var hostKeys = Object.keys(hosts);
function getUpdater(printer) {
return function() {
UpdatePrinter(printer);
};
}
function OctoPrintFor(printer) {
OctoPrint.options.apikey = APIKeys[printer];
OctoPrint.options.baseurl = hosts[printer];
return OctoPrint;
}
function formatTime(seconds) {
dur = moment.duration(seconds, 'seconds');
return dur.humanize();
}
function UpdatePrinter(printer) {
return loadingDone.then(function() {
var histDfd = OctoPrintFor(printer).printer.getFullState({history: false, exclude: ["sd"]});
var jobDfd = OctoPrintFor(printer).job.get();
$.when(histDfd, jobDfd).then(function(resp, job) {
resp = resp[0]; job = job[0];
var status = "";
if (resp.state.flags.ready) {
status = "Ready";
}
if (resp.state.flags.printing) {
status = job.progress.completion.toFixed(1) + "% done printing " + job.job.file.name + " <br/>";
if (job.progress.printTimeLeft) {
status += "(ETL " + formatTime(job.progress.printTimeLeft) + " remaining.)";
} else {
status += "(calculating estimated time left....)";
}
}
var printStatus = $("#" + printer),
webCamCont = printStatus.find(".webcam");
// Clear out the current "things" and replace them
webCamCont.find("div").remove();
// Add temperatures
Object.keys(resp.temperature).forEach(function(tool) {
webCamCont.prepend($("<div class='temp'>" + tool + ": " + resp.temperature[tool].actual.toFixed(1) + "&deg;C -> " + resp.temperature[tool].target.toFixed(1) + "&deg;C</div>"));
});
webCamCont.append("<div class='status'>Status: " + status + "</div>");
setTimeout(getUpdater(printer), 10000);
}, function(err) {
var printStatus = $("#" + printer),
webCamCont = printStatus.find(".webcam");
webCamCont.find("div.status").remove();
console.warn("Error getting state from printer:", printer, err);
webCamCont.append("<div class='status'>Error getting status: " + err.responseText + "</div>");
setTimeout(getUpdater(printer), 60000);
});
});
}
var apiRegex = /UI_API_KEY = "([0-9A-Fa-f]*)"/;
function getAPIKey(rootUrl) {
var pageDfd = $.get(rootUrl).then(function(pageData) {
pageData = pageData.substr(0,3072);
return apiRegex.exec(pageData)[1];
});
return pageDfd;
}
var APIKeys = {};
var keyDfds = hostKeys.map(function(host) { return getAPIKey(hosts[host]); });
var loadingDone = $.when.apply($, keyDfds).then(function() {
[].forEach.call(arguments, function(arg, i, arr) {
APIKeys[hostKeys[i]] = arr[i];
UpdatePrinter(hostKeys[i]);
});
});
</script>
<style>
.printer {
display: block; width: 30%; min-width: 400px; float: left; border: 1px solid lightgray; padding: 10px;
}
.printer label {
font-weight: bolder; font-size: 120%; text-align: center; display: block;
}
.printer img {
max-width: 100%; display: block;
}
</style>
</head>
<body>
<div class="printer" id="mpmini">
<label>MonoPrice MP Mini Select</label>
<div class="webcam">
<div class="temp">Hotend: N/A</div>
<div class="temp">Bed: N/A</div>
<img src="/mpmini/webcam/?action=stream&1483251353028" alt="No camera found">
<div class="status">Loading...</div>
</div>
</div>
<div class="printer" id="mk2">
<label>Original Prusa I3 mk2</label>
<div class="webcam">
<div class="temp">Hotend: N/A</div>
<div class="temp">Bed: N/A</div>
<img src="/mk2/webcam/?action=stream&1483251353028" alt="No camera found">
<div class="status">Loading...</div>
</div>
</div>
<div class="printer" id="taz5">
<label>Heavily modified Lulzbot Taz 5</label>
<div class="webcam">
<div class="temp">Hotend: N/A</div>
<div class="temp">Bed: N/A</div>
<img src="/taz5/webcam/?action=stream&1483251353028" alt="No camera found">
<div class="status">Loading...</div>
</div>
</div>
</body>
</html>
@bipsendk
Copy link

bipsendk commented Aug 21, 2017

I am trying to setup things up with 2 octoprint behind nginx - but should authentication be enabled on the octoprint servers? And I also get an error message about invalid API key. Do the API keys need to be stored somewhere on the nginx server ?

It apparently seems to be related to my desire to require basic authentication to access any of the content..

@Stady234
Copy link

Is there any way to adopt this to work in something like XAMPP?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment