Readable Nginx configs

Nginx allows you to include files inline in your configs to make re-using code simple. An example would be all your ssl proxy settings as per generated using the Mozilla ssl-config generator.

simply add this config to a file like /etc/nginx/include.d/include.ssl_sec with your cert paths modified and include it in your config:

upstream example_service {
  server 127.0.0.1:8080;
  keepalive 32;
}

server {
  server_name example.tld;

  #Mozilla modern tls config
  include /etc/nginx/include.d/include.ssl_sec;

  location / {
    #Common Proxy settings
    include /etc/nginx/include.d/include.proxy_settings;

    proxy_pass http://example_service/;
  }
}

Now you have a nice easy config file that can be easily used as a template for new services. Adding additional configurations to files really makes it quick and easy to deploy new services without needing complicated projects like Nginx Proxy Manager


Eking out some Nextcloud performance

Tweeking my linux server Nextcloud is notorious in the selfhosted community of being difficult for some people to achieve a decent level of performance. After enabling the basic caching with both APCu and Redis there are several options to trim some fat. Once all the easy stuff is taken care of the hidden bottlenecks is where I am focusing my efforts. So far I have had some success by switching to UNIX sockets in my dockerised Nextcloud deployment.

Generally I've found:

  • Shipping file logging off to syslog made a noticeable visual difference over logging to the nextcloud.log file.
  • Using postgresql has been often touted as a decent option for easy performance gains.
  • Using the preview generator app alongside using Imaginary makes images less of an issue for general browsing.

But what else can you do after that? Trying to find bottlenecks in your setup. Be it spinning rust vs SSD vs M.2 drives there are usually some form of low hanging fruit you can find that is causing issues. A big potential issue is of course your abstraction layer, in my case docker. Docker adds some minor overheads to any service, a trade off for simplifying deployment and replication, one of these overheads is the networking stack. My understanding is that Docker's networking when not in host mode acts as a NAT, even when one container is talking to another. One method of bypass networking overhead between local services is the use of unix sockets.

In researching how to achieve this I found @jonbaldie's post on How to Connect to Redis with Unix Sockets in Docker. A few modifications and I was ready to test and verify that this made a difference.

Setup

These are the modifications done to my docker-compose file. Note that I have made a few modifications to avoid the need to set the folders and sockets permissions as 777. This is mainly handled by modifying the container user group id to the www-data group from the Nextcloud app container.

version: '2'

services:
    #Temporary busybox container to set correct permissions to shared socket folder
    tmp:
      image: busybox
      command: sh -c "chown -R 33:33 /tmp/docker/ && chmod -R 770 /tmp/docker/"
      volumes:
        - /tmp/docker/

    db:
      container_name: nextcloud_db
      image: postgres:14-alpine
      restart: always
      volumes:
        - ./volumes/postgresql:/var/lib/postgresql/data
        - /etc/localtime:/etc/localtime:ro
        - /etc/timezone:/etc/timezone:ro
      env_file:
        - db.env
      # Unix socket modifications
      # Run as a member of the www-data GID 33 group but keep postgres uid as 70
      user: "70:33"
      # Add the /tmp/docker/ socket folder to postgres
      command: postgres -c unix_socket_directories='/var/run/postgresql/,/tmp/docker/'
      depends_on:
        - tmp
      # Add shared volume from Temporary busybox container
      volumes_from:
        - tmp

    redis:
      container_name: nextcloud_redis
      image: redis:alpine
      restart: always
      volumes:
        - /etc/localtime:/etc/localtime:ro
        - /etc/timezone:/etc/timezone:ro
      # Unix socket modifications
        - ./volumes/redis.conf:/etc/redis.conf
      # Run redis with custom config
      command: redis-server /etc/redis.conf
      # Run as a member of the www-data GID 33 group but keep redis uid as 999
      user: "999:33"
      depends_on:
        - tmp
      # Add shared volume from Temporary busybox container
      volumes_from:
        - tmp

    app:
      container_name: nextcloud_app
      image: nextcloud:apache
      restart: always
      ports:
        - 127.0.0.1:9001:80
      volumes:
        - ./volumes/nextcloud:/var/www/html
        - ./volumes/php.ini:/usr/local/etc/php/conf.d/zzz-custom.ini
        - /etc/localtime:/etc/localtime:ro
        - /etc/timezone:/etc/timezone:ro
      depends_on:
        - db
        - redis
      # Unix socket modifications
      # Add shared volume from Temporary busybox container
      volumes_from:
        - tmp

This is the redis.conf file that tells it to only listen to the unix socket, and what permissions to use on said socket. Note I have a password enabled here, this is not really need it if not exposed publicly but I've used it just for best practice.

# 0 = do not listen on a port
port 0

# listen on localhost only
bind 127.0.0.1

# create a unix domain socket to listen on
unixsocket /tmp/docker/redis.sock

# set permissions for the socket
unixsocketperm 770

requirepass [password]

Finally the Nextcloud config I updated to reflect the connection changes

'dbtype' => 'pgsql',
'dbhost' => '/tmp/docker/',
'dbname' => 'nextcloud',
'dbuser' => 'nextcloud',
'dbpassword' => '{password}',

'memcache.local' => '\\OC\\Memcache\\APCu',
'memcache.distributed' => '\\OC\\Memcache\\Redis',
'memcache.locking' => '\\OC\\Memcache\\Redis',
'redis' =>
array (
  'host' => '/tmp/docker/redis.sock',
  'port' => 0,
  'dbindex' => 0,
  'password' => '{password}',
  'timeout' => 1.5,
),

Verifying the changes made a difference.

There is not much point in doing this without verification, otherwise we are all just participating in a cargo cult seeking performance enlightenment. With that in mind I set out to do some very basic benchmarks to ensure the performance gain I felt when navigating my Nextcloud install was in fact happening.

I did all my testing inside my Nextcloud container to better simulate a real-world result. I modified the redis.conf temporarily to allow both socket connections and TCP IP connections, then I had to install the redis-tools and postgresql-contrib packages to get the tools required.

# 0 = do not listen on a port
# port 0
port 6379

# listen on localhost only
# bind 127.0.0.1
bind 0.0.0.0
sudo docker exec -it nextcloud_app bash

apt update && apt install redis-tools && apt install postgresql-contrib

I then performed the same tests as @jonbaldie's using the commands time redis-benchmark -a [password] -h redis -p 6379 and time redis-benchmark -a [password] -s /tmp/docker/redis.sock

REDIS TCP (s) UNIX (s) % Diff
Real 242.8 165.5 32%
User 63.4 60.9 4%
Sys 132.1 70.6 47%
Total 438.4 297.1 32%

As you can see on my system I saw a staggering 32% difference compared to @jonbaldie's 13%. Clearly the Redis socket is a very worthwhile modification.

Using some of what I learned from reading this article I now wanted to test my Postgres database using it's benchmarking tool pgbench. I did a quick database backup just in case, but it shouldn't harm the Nextcloud db as it's only adding the tables pgbench_accounts, pgbench_branches, pgbench_tellers and pgbench_history to perform the tests.

First test the testing tables initialisation

pgbench -h db -i -p 5432 -U nextcloud -d nextcloud

...

done in 1.85 s (drop tables 0.00 s, create tables 0.13 s, client-side generate 0.60 s, vacuum 0.60 s, primary keys 0.51 s)

Then I Ran 3 tests using the command pgbench -h /tmp/docker/ -c 10 -U nextcloud -d nextcloud simulating 10 clients.

Postgres TCP 1 2 3 Average
latency average 265.887 333.644 280.873 293.468
tps (including connections establishing) 37.60993 29.972067 35.603308 34.3951016666667
tps (excluding connections establishing) 38.089613 30.24576 35.997626 34.7776663333333

Clean up inbeteween tests

psql -h /tmp/docker/ -i -U nextcloud -d nextcloud

DROP TABLE pgbench_accounts, pgbench_branches, pgbench_tellers, pgbench_history;

First test the testing tables initialisation

pgbench -h /tmp/docker/ -i -U nextcloud -d nextcloud

...

done in 1.42 s (drop tables 0.00 s, create tables 0.11 s, client-side generate 0.68 s, vacuum 0.25 s, primary keys 0.38 s).

Then I Ran 3 tests using the command pgbench -h db -c 10 -p 5432 -U nextcloud -d nextcloud simulating 10 clients.

Postgres UNIX 1 2 3 Average
latency average 291.566 290.129 222.446 268.047
tps (including connections establishing) 34.297528 34.467479 44.954712 37.906573
tps (excluding connections establishing) 34.397523 34.570084 45.137941 38.0351826666667

My results show a much more modest performance difference with the database. But it's still an unambiguous improvement so well worth the minor amount of effort.

% Diff
latency average 9.00%
tps (including connections establishing) 10.00%
tps (excluding connections establishing) 9.00%
testing tables initialisation 23.00%

Finding, testing and minimising bottlenecks is possibly the most difficult task for any selfhosting admin. I hope you found this of use in your own bottleneck hunting journey.


Authentik Gotifiy Login Notifications

SSO all the things Continuing with my journy of utilising Authentik for my SSO. After reading a rather good comment by /u/internallogictv over on the reddit /r/selfhosted, I wanted to add a few more protections. The simplest of which is to send myself a notification whenever a login or a failed login occurs.

Step 1

First things first we create a new application in gotify in order to generate a token for authentik use. Select the Apps tab and press the Create Application button.

Gotify create an application

Step 2

Create a new gotify property mapping in the Admin Interface -> Customisation -> Property Mappings.

I've built this so a login failed is set to the maximum gotify priority level regardless of the user group. For successful logins I divide the levels based on the group gotify-users. I algo create a geo uri for mapping applications on android. You will be able to click the notification and it will open the city co-ordinates, although you may have to skip this if you don't have the geoipupdate container configured.

try:
    # Get the login failed username
    event_user = notification.event.context["username"]
except:
    # Get the login succeeded username
    event_user = notification.event.user["username"]

if notification.event.action == "login_failed":
    priority = 7
    severity = "warning"
elif ak_is_group_member( ak_user_by(username=event_user), name="gotify-users" ): # Check if the user belongs to group
    priority = 1
    severity = notification.severity
else: # default notification settings
    priority = 0
    severity = notification.severity

# Build a geo uri for opening a mapping applications from the gotify notification.
geo_uri = f"geo:{notification.event.context['geo']['lat']},{notification.event.context['geo']['long']}?q={notification.event.context['geo']['lat']},{notification.event.context['geo']['long']}"

title = f"{severity} from authentik {notification.event.action.replace('_', ' ')}".capitalize()

message = f"New {notification.event.action.replace('_', ' ')} for {event_user} was detected coming from {notification.event.context['geo']['city']} {notification.event.context['geo']['country']} from the IP address: {str(notification.event.client_ip)}".capitalize()

# Build the gotify payload
gotify_payload = {
    "title": title,
    "message": message,
    "priority": priority,
    "extras": { "client::notification": { "click": { "url": geo_uri } }},
}

return gotify_payload

Step 3

Create a new notification transport Admin Interface -> Events -> Notification Transports using Webhook (generic) your gotify message url with the token created in step one https://example.tld/gotify/message?token=yourtokenhere

Step 4

Finally we create the notification rule that actually calls the Notification transport. Admin Interface -> Events -> Notification Rules Create a new rule login-notification sending to the group of your choice (This dosn't really matter but it will display an ugly json string as notification on the web UI). Select the Gotify notification transport you created and set the Severity to Notice.

Now we have to create the policies authentik-core-login and authentik-core-login-failed to the event. Expand the login-notification event and press Create Policy. Select Event Matcher Policy, name it authentik-core-login enable the Execution Logging option, select the Login action and authentik Core App. Finish and repeat for the Login Failed action.

Now you should be receiving Login and Login Failed notifications from your Authentik instance over Gotify. I Hope I'll be able to update this to pull different tokens from the user/group attributes in the future to better separate notifications to individual users/admins.


Node-Red SSO with Authentik

Node-RED is a flow-based programming tool, originally developed by IBM’s Emerging Technology Services team and now a part of the JS Foundation. Following my last post regarding SSO with Authentik I thought I should post my passportjs configuration for Node-Red and OpenidConnect. Currently User accounts work, however I haven't gotten group based permissions setup yet.

Note This guide is based off the Gitea integration guide from the Authentik docs.

Preparation

The following placeholders will be used:

authentik.company is the FQDN of authentik.

nodered.company is the FQDN of nodered.

Step 1

In authentik, create an OAuth2/OpenID Provider (under Resources/Providers) with these settings:

note

Only settings that have been modified from default have been listed.

Protocol Settings

Name: nodered
Signing Key: Select any available key

note

Take note of the Client ID and Client Secret, you'll need to give them to nodered in Step 3.

Step 2

In authentik, create an application (under Resources/Applications) which uses this provider. Optionally apply access restrictions to the application using policy bindings. note

Only settings that have been modified from default have been listed.

Name: nodered
Slug: nodered-slug
Provider: nodered

Step 3

note

We are assuming node-red is installed under docker

Navigate to the node-red data volume data/node_modules/. Alternatively enter the docker container sudo docker exec -it nodered bash and cd /data/node_modules

Use npm to install passport-openidconnect npm install passport-openidconnect

Edit the node-red settings.js file /data/settings.js

adminAuth: {
type:"strategy",
strategy: {
        name: "openidconnect",
        label: 'Sign in with authentik',
        icon:"fa-cloud",
        strategy: require("passport-openidconnect").Strategy,
        options: {
                issuer: 'https://authentik.company/application/o/<application-slug>/',
                authorizationURL: 'https://authentik.company/application/o/authorize/',
                tokenURL: 'https://authentik.company/application/o/token/',
                userInfoURL: 'https://authentik.company/application/o/userinfo/',
                clientID: '<Client ID (Key): Step 2>',
                clientSecret: '<Client Secret: Step 2>',
                callbackURL: 'https://nodered.company/auth/strategy/callback/',
                scope: ['email', 'profile', 'openid'],
                proxy: true,
        verify: function(issuer, profile, done) {
                done(null, profile)
        }
      }
    },
    users: function(user) {
        return Promise.resolve({ username: user, permissions: "*" });
    }
},

SSO with Authentik

SSO all the things A while back I wrote about minimising my attack surface by utilising default deny and whitelists in Nginx. Now I've gotten into the weeds with authentication and deployed an SSO (Signle sign-on) service on my selfhosted infrastructure.

What is Authentik?

Authentik is a SSO (Single Sign on) provider, much like with Google's services you sign in once and then you can access all your services. This has been a big bugbear with selfhosted applications, with Roundcubemail TTRSS plugin, auto authentication for Tiny Tiny RSS against an IMAP Server and Codiad External Authentication via IMAP to name a few work arounds to the issue I have hacked together over the years.

Most importantly for my use case is the single pane of glass to access my services:

A nice dashboard really brings it all together

The Issues

Introducing a SSO system introduces complexity and potential problems so it's not all smooth sailing, passwords are a thing still as they are simple and reliable and understandable.

New Project new problems, limited reviews

Authentik's first beta release was in Jan 2020 so it's very new and has had a few teething issues and quite a few bugs. I highly recommend utilising additional security methods in front of authentik (IDS/IPS, Geo Blocking and ideally using a VPN to access) until it reaches maturity.

Poor Documentation

Quite frankly the documentation isn't great if you are attempting to figure out HOW it’s supposed to work. Thankfully they have integration guides included in the docs that covers the gaps, so some reading between the lines is needed for a while yet.

Limited compatibility

Not everything has SSO support (SAML, Oauth/OpenidConnect or reverse Proxy Authentication), thankfully this isn't as hard to deal with as it once was:

The main issue I have faced is with HomeAssistant. The developers have been reluctant/resistant to adding additional authentication methods to the project. There is the hass-auth-header project created by the developer of Authentik, however the HomeAssistant Android app is frustratingly a major sticking point.


New maintainers needed for the Thunderbird Lookout Fix-version add-on

Lookout! In august 2018 I took over maintenance of the Thunderbird addon Lookout-fix-version. I soon set up a the Github Organization TB-throwback so that future development can be expanded and transferred easier if I stop work on it.

LookOut Fix-Version is a plugin which allows Thunderbird to interface with Microsoft’s mail tools by decoding metadata and attachments encapsulated/embedded in a TNEF (Transport Neutral Encapsulation Format) encoded attachment (aka winmail.dat).

Transport Neutral Encapsulation Format or TNEF is a proprietary email attachment format used by Microsoft Outlook and Microsoft Exchange Server. An attached file with TNEF encoding is most often named winmail.dat or win.dat, and has a MIME type of Application/MS-TNEF. The official (IANA) media type, however, is application/vnd.ms-tnef. Source: https://en.wikipedia.org/wiki/Transport_Neutral_Encapsulation_Format

Unfortunately I had to stop working on Lookout a while ago because my employer switched us back to Outlook leaving me with no company time to maintain the project any more. The last major release of Thunderbird I received a lot of help from the TB team's John Bieling to bootstrap a workaround to get things rolling again, now Thunderbird 91 is coming and it's another case of the old xul add-ons not working in the webextension world.

After receiving no takers to my maintainer request over on the mailing list I have decided if I am unable to locate new maintainers I will archive the project on Github. I hope the Thunderbird devs finally assign Bug 77811 to someone as I know that proper integration for decoding TNEF emails was added to the roadmap, however in the meantime you are interested in updating the project please let me know as I'll actively be monitoring the Github project and the issue calling for new maintainers


I shouldn't use sudo nano

Over on /r/linux a user going by /u/AlternOSx posted a short You should Know: YSK : Do not use 'sudo vim/nano/emacs..' to edit a file. Instead, set your $EDITOR and use sudoedit or sudo -e.

Long story short sudoedit copies the file you want to edit to /tmp/file.xxx and then opens it with an unprivileged instance of your editor of choice. It then overwrites the source file when you are finished editing, protecting from accidental privilege escalation of commands through your text editor.

Knowing this I came up with a quick way to enforce this best practice by added this function into my .bashrc file. Hopefully I can retrain myself not to use sudo nano all the time.

# Define the default editor in this case nano.
EDITOR=nano

# Catch calls to sudo.
function sudo() {
  if [[ $1 == "$EDITOR" ]]; then
    # The editor has been called

    if [ -w "$2" ]; then
      # If the file is writable by the current user just use the editor as normal.

      command $EDITOR "$2"
    else
      # The file is not writable use sudoedit.
      command sudoedit "$2"
    fi
  else
    # Use sudo as normal.
    command /usr/bin/sudo "[email protected]"
  fi
}

Caving and switching to Amazon SES

Electronic mail (email or e-mail) is a method of exchanging messages ("mail") between people using electronic devices. Email entered limited use in the 1960s, but users could only send to users of the same computer, and some early email systems required the author and the recipient to both be online simultaneously, similar to instant messaging. Ray Tomlinson is credited as the inventor of email; in 1971, he developed the first system able to send mail between users on different hosts across the ARPANET, using the @ sign to link the user name with a destination server. By the mid-1970s, this was the form recognized as email. For almost 7 years I have run my own email server sending and receiving emails on it's own IP address. A few years ago I switched to Digital Ocean as a VPS to reduce deliverability issues, recently the IP block I was in got blacklisted by zen.spamhaus. The age of my 100% independent email server is now over.

Big players make it hard for small servers

As outlined with this blog post Google is eating out email and the resulting discussion on Hacker News the big players appear to have no interest following the standard rules when it comes to emails. All the tools are designed for big users, Google and Microsofts's postmaster tools don't even register any statistics for a domain unless your sending over 200 emails a day.

Unfortunately we find ourselves in the same position when it comes to something as simple as an SMTP relay service. If you search online for what an SMTP relay is you will only see the marketing material that claims it is a tool for large marketers to deliver massive volumes of emails into the inbox of their customers. Not what it actually is, a forwarding service that allows multiple email servers to send from one IP address to outsource reputation management.

Clearly the main advantage of an SMTP relay should be small time servers can pool together to achieve the volume the big players want to see to be able to judge if your emails are "worthy" of being treated fairly.

Imagine my frustration when trying to find any service that directly caters to this and finding that they don't appear to exist. It's all pitched as a service for marketing email delivery, if you're not sending thousands of emails you are not the target market anymore.

Can I use Mailgun for my personal email address?

It’s not recommended.

There are plenty of hosted email services better suited for this than Mailgun; Rackspace Email, Gmail / Google Apps, Outlook, etc.

Mailgun is meant to be a tool for developers and their applications.

Fair enough if you don't have a personal email server but if you have a server used by a family? Not clear. Then when you do sign up for any SMTP service they ask for your Business name, website and business use.

In the end as I technically have donations listed on the about page of this blog I simply used that and the Transactional emails from my Nextcloud instance as a sample of what I would be sending.

Getting out of the sandbox was a thing

I initially tried signing up for SendGrid but they immediately lock new accounts and you have to contact support to even login, let alone send an email. So after they straight up ignored what I wrote in the support email I signed up with AWS then immediately discovered that even though I could log in and set things up I was isolated in a sandbox not allowed to play with the other children.

Even though when signing up I selected an Individual account for side projects they still wanted a business use. After I finished banging my head against the desk I submitted what info I could. After four emails where among other things I updated my Privacy policy page to include Opt-in/out language, I was denied.

We reviewed your request and determined that your use of Amazon SES could have a negative impact on our service. We are denying this request to prevent other Amazon SES customers from experiencing interruptions in service.

There was one chance. In the correspondence they asked very specifically

How do you handle bounces and complaints?

Given they appeared to be concerned with negative service impacts I discovered a method of Automating handling this based on this xenforo plugin. Using Node-red as an endpoint I can receive a notification of a bounce/complaint and shut down the server for manual review.

Informing Amazon of this change was enough to get through the bureaucratic hurdles .

Making the switch was easy at least

Thankfully this was the least of my issues. First I created an SMTP identity in AWS, added that to my postfix postfix-relaymap.cf and postfix-sasl-password.cf as outlined in docker-mailserver documentation. Add the Amazon domain key, SPF record and CNAME records for DKIM and I was off to the races.


Minimizing my selfhosted attack surface

Tweeking my linux server I'm always fairly wary of opening my selfhosted services up to the internet, just how sure am I that the developer has done the right due-diligence? Thankfully it's relatively simple to at least limit parts of a service accessible to the open internet with Nginx and allow and deny options.


Update

Note: If you want a docker container to access a protected service you will have to set the subnet in your docker-compose file as below:

networks:
  node-red-network:
    ipam:
      config:
        - subnet: "172.16.0.0/24"

Update 2

A more generic change you can do is set the default address pools in docker's /etc/docker/daemon.json file. You then just have to whitelist 172.16.0.0/16 subnets

{
  "default-address-pools":
  [
    {"base":"172.16.0.0/16","size":24}
  ]
}

First you should store this in a file, that way you can then include it multiple times, this will make it trivial to update in the future. Create the file include_whitelist in your nginx folder, adding your own allow options between satisfy any; and deny all;.

satisfy any;

# allow localhost
allow 127.0.0.1;

# allow a single address
# allow 000.000.000.000;

# allow LAN network
allow 192.168.0.0/24;

# allow WLAN network
allow 192.168.2.0/24;

# allow VPN network
allow 10.1.4.0/24;

# drop rest of the world
deny all;

You then have to include the file in your nginx config. Here I am using TT-RSS as an example, Note that I'm excluding the API and the public.php by having it in a separate location block without including the include_whitelist. This allows me to keep accessing TT-RSS on my mobile phone through the mobile application.

  location ^~ /tt-rss/ {
      include /etc/nginx/include_whitelist;

      access_log off;
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $remote_addr;
      proxy_set_header X-Forwarded-Proto $scheme;
      proxy_pass http://127.0.0.1:8280/tt-rss/;

  }

  location ^~ /tt-rss/api {

      access_log off;
      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $remote_addr;
      proxy_set_header X-Forwarded-Proto $scheme;
      proxy_pass http://127.0.0.1:8280/tt-rss/api;

  }

  location ^~ /tt-rss/public.php {

      proxy_set_header Host $host;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $remote_addr;
      proxy_set_header X-Forwarded-Proto $scheme;
      proxy_pass http://127.0.0.1:8280/tt-rss/public.php;

  }

For Node-Red I wanted an API endpoint for Tasker on my phone. Thankfully this is just as easy to define in Node-red as it is in nginx. In Node-Red open your GET node and just add another folder.

Add and extra folder to your Node-Red endpoints

An example of the Node-Red nginx configuration. Again just like the TT-RSS example above, I have excluded an api subdirectory by having a separate location block.

  location ^~ /node-red/ {
    include /etc/nginx/include_whitelist;

    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_pass http://127.0.0.1:1880/node-red/;
  }

  location ^~ /node-red/api/ {
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_pass http://127.0.0.1:1880/node-red/api/;
  }

Now only your API endpoints are globally available. If like me you use a firewall, throwing a convenient geo-block up in front you can lower the exposure a bit more.


Removing 3g modem from a Kindle Paperwhite 2

Why do I need the cloud? Do I need the cloud connectivity on my kindle everywhere I go? Probably not.

A few years ago I received an old Kindle 3g Paperwhite 2 (2013) 6th from my mother. She wasn't a fan of the screen and decided she preferred a full tablet for reading while I quickly fell in love with the e-ink screen. However my use-case involves mainly e-novels/websites I scrape and upload via Calibre. Given how privacy conscious I have become, and how much of a power drain a 3g modem can be, I've basically had it in airplane mode since I received it.

But walking to my desktop to sync books when the device has wifi capability felt wasteful. Searches on the internet proved fruitless on ways to disable the 3g.

Modular hardware

While searching for a teardown video to determine what the internals even looked like I stumbled upon this video for an older kindle model where it was pointed out how modular Kindle hardware really is. After seeing the wifi paperwhite internals and seeing this video I felt it was likely they were still utilizing this modular design method (after all, if it works), so I cracked open my kindle following this ifixit guide.

Well that was simpler than it looked

Thankfully removing the 3g modem is a non-destructive process (apart from the glue when removing the bezel).

Kindle paperwhite 2 internals
  1. Is the 3g antenna - I didn't remove this.
  2. Is the 3g modem - Held in by 4 screws and attached with a tiny connection plug.
  3. Is simcard slot - Simply push the tray lid in the direction of the arrow to open.
3g Modem removed
  1. Is the 3g antenna connector - This is a simple push connector. A firm pull up away from the board will disconnect this without damage. I used some tape to secure the wire for re-assembly
  2. A loose metal washer, I secured this back in place with the screw I removed previously.
  3. A loose metal washer, I secured this back in place with the screw I removed previously.

Re-assembly

This was simply a matter of re-placing the board, screws and lightly clamping the bezel back in place. The glue on the bezel was, thankfully, still sticky enough to re-attach without any issue. As the bezel has no mechanical stresses on it I don't see this ever being and issue.

My next steps

My next steps where to jailbreak/root my kindle to install a couple of useful tools (i.e. a firewall). I plan to go over this in detail along with my remote book managment solution in another post so Stay tuned.