Because of reasons I am living in a residence that provides internet through an external provider. I tend to be particularly wary of any kind of service that provides internet services, so I decided to set up a local network that would let me access the internet, while keeping some privacy.
I also used this as an excuse to put my Raspberry Pi to work: a media center and centralized storage for my data. In this post I’ll go over the local network setup and the configuration steps I used to get the Pi working.
The result? Now I can back up my laptop to the HDDs attached to the Pi, and stream the videos stored on those same drives to any device on the local network.
If you’re interested in how I put everything together, read on!
I used various coding agents to write most of the configuration in this post, but I also had to do a lot of debugging of those same configurations myself.
If I trusted everything that the agents did, most of what is reported below would not work.
This post is the results of combining the output of the agents, with a healthy amount of human debugging.
Requirements
While I’ll describe my own setup, you may need less depending on your goals. At the very least you should have:
- A travel router.
- A Raspberry Pi.
- Another computer to connect to the Pi.
- One (or more) external hard drive.
- All the relevant cables and chargers.
My setup
- A GL-SFT1200 travel router (chosen because it was cheap)
- A Raspberry Pi 5
- A MacBook Air as my laptop
- Two 3.5” external HDDs (one has two partitions)
- An official 5V/5A charger for the Raspberry Pi – phone chargers often can’t provide enough power
- An ethernet cable to connect the Pi to the router
- Additional devices (phones, etc.) will connect to Wi‑Fi once it’s set up
Building a local network from inside a hotel network
Note that this section can be skipped if you’re already on a private network.
The idea here is to use a travel router’s repeater feature to connect to the public Wi‑Fi, create a private Wi‑Fi network, and let other devices connect “from behind a screen.”
I followed the manufacturer’s instructions for the router. I ran into a few snags due to quirks from my provider, but eventually got full internet access on the router.
Once that was done, I was able to connect to the new private network from my various devices.
An advantage is that I bypass the limit on the number of devices on my account. The public network sees only the router, while my multiple devices get full access behind it.
I can’t go into detail because the exact steps depend on the hotel’s setup, but it wasn’t complicated – you should manage fine.
Change your passwords!
The router had a default admin password – change it. Also change the Wi‑Fi password; don’t give someone access because you didn’t bother updating defaults.
Setting up the Raspberry Pi
This part was the trickiest and involved a lot of querying LLMs for configuration snippets. I used outputs from ChatGPT and Mistral’s LeChat, but I still had to tinker and debug things myself.
Step 0: Preparing the Pi
Before doing anything else, I installed Raspberry Pi OS from the official image. I wanted a general purpose Pi, so I avoided specialized images.
The usual recommendation is to set up the Pi with a monitor, keyboard, and mouse. I did that – years ago I had to sniff a network to find a Pi’s IP so I could SSH in, and it wasn’t fun.
Besides the OS setup, I enabled the SSH server (it’s disabled by default). See the official docs: https://www.raspberrypi.com/documentation/computers/remote-access.html#ssh
Since I’ll mostly access the Pi from my Mac, I added the Pi’s IP to /etc/hosts, for example:
raspberrypi 1.1.1.1where 1.1.1.1 is the Pi’s address. This lets me just ssh raspberrypi instead of the full IP – very convenient.
Step 1: Installing Docker compose
Following some AI-provided advice, I used Docker containers for the services, so I installed Docker Compose to manage them.
I followed the official instructions and added my user to the docker group:
sudo usermod -aG docker $USERFinally, I logged out and back in to apply the changes.
I also created a folder for my containers:
mkdir ~/containers
cd ~/containersStep 2: Preparing the HDDs
Normally an external drive gets a device like /dev/sda, /dev/sdb, etc. I wanted stable mount points so services (Samba, Jellyfin) always find the data.
One disk had an exFAT partition and an NTFS partition, so I created separate mount points for each partition.
Why two partitions? I have both a Mac and a Windows laptop. macOS can’t write to NTFS without extra software, so I made an exFAT partition for files I want to share easily between machines.
In any case, I created three mounting points:
sudo mkdir -p /mnt/storage # NTFS partition
sudo mkdir -p /mnt/storage2 # exFAT partition
sudo mkdir -p /mnt/storage3 # second diskThen, I needed to find the UUID of each partition:
sudo blkidI then modified /etc/fstab with entries like:
UUID="xxxxxxxxx" /mnt/storage ntfs defaults,auto,users,rw,uid=1000,gid=1000,nofail 0 0
UUID="xxxxxxxxx" /mnt/storage2 exfat defaults,auto,users,rw,uid=1000,gid=1000,nofail 0 0
UUID="xxxxxxxxx" /mnt/storage3 ntfs defaults,auto,users,rw,uid=1000,gid=1000,nofail 0 0Explanation of the options:
- `defaults`: Uses default mount options (rw, suid, dev, exec, auto, nouser, async).
- `auto`: Allows the partition to be mounted automatically at boot.
- `users`: Allows any user (not just root) to mount/unmount the partition.
- `rw`: Mounts the filesystem as read-write.
- `uid=1000,gid=1000`: Sets the owner and group of all files to the user/group with ID 1000 (my user account).
- `nofail`: Prevents boot failures if the partition is unavailable (e.g., external drive not connected).
0 0: Disables filesystem checks (fsck) and backup by dump.
The partitions are exFAT and NTFS. Make sure you have support for them – on modern Debian/Raspbian you can use exfat-fuse or exfatprogs for exFAT and ntfs-3g for NTFS.
For example:
sudo apt install exfat-fuse ntfs-3g
Step 3: Setting up Jellyfin
I use Jellyfin as a media server. It’s open source and lets you stream media from the local network – perfect for this setup.
The set up was quite simple. First, I created the directory for the container:
cd ~/containers
mkdir jellyfin
cd jellyfinThen, I wrote this docker-compose.yml file:
services:
jellyfin:
image: jellyfin/jellyfin:latest
container_name: jellyfin
ports:
- "8096:8096"
volumes:
- /mnt/storage/media:/media
- /mnt/storage2/media:/media2
- ./config:/config
- ./cache:/cache
restart: unless-stoppedI added both storage and storage2 for the sake of the example: the point here is to provide Jellyfin with the locations of the media folders by binding the mount positions to locations inside the container. Adapt the paths to the locations and partition names that you are working with.
Then, I started the Jellyfin container in detached mode (-d):
docker compose up -dJellyfin is now available at raspberrypi:8096 (where raspberrypi is the Pi’s hostname or IP as set in /etc/hosts).
In order to access the Raspberry Pi from both Firefox and Vivaldi (the two web browsers I use) on my Mac I had to change the privacy settings for both so that they’d be allowed to find devices on the local network. This is in System Settings -> Privacy & Security -> Local Network.
A big advantage of using Jellyfin is that now I can access my media files from any device that is connected to the local network, either by using the browser or by using one of the mobile apps for Jellyfin.
Step 4: Setting up Samba
Setting up Samba was a bigger headache. I could access the Pi’s data from my Android phone and my Mac, but not from my Windows laptop – annoying, and I need to revisit it later.
I also had trouble with authentication and permissions; part of the issue was using exFAT, which doesn’t support symlinks (that broke some of my plans).
Below is the docker-compose.yml I settled on after the permission shenanigans.
services:
samba:
image: dperson/samba
container_name: samba
restart: unless-stopped
ports:
- "139:139"
- "445:445"
volumes:
- /mnt/storage:/shared
- /mnt/storage2:/shared-2
- /mnt/storage3:/shared-3
environment:
- USERID=1000
- GROUPID=1000
- USERNAME=${SAMBA_USERNAME}
- PASSWORD=${SAMBA_PASSWORD}
- PERMISSIONS=0755
command: >
-u "${SAMBA_USERNAME};${SAMBA_PASSWORD}"
-s "shared;/shared;yes;yes;no;all;none;${SAMBA_USERNAME};0775;0664"
-s "shared-2;/shared-2;yes;yes;no;all;none;${SAMBA_USERNAME};0775;0664"
-s "shared-3;/shared-3;yes;yes;no;all;none;${SAMBA_USERNAME};0775;0664"In the end, I decided to block any access to the drives behind authentication, with the SAMBA_USERNAME and SAMBA_PASSWORD variables containing the username and password I’d need to use in order to connect from my other devices.
Note: SAMBA_USERNAME and SAMBA_PASSWORD can be provided via a .env file next to the docker-compose.yml:
SAMBA_PASSWORD=mypassword
SAMBA_USERNAME=myusernameExplanation of the docker-compose command
command: >
-u "${SAMBA_USERNAME};${SAMBA_PASSWORD}"
-s "shared;/shared;yes;yes;no;all;none;${SAMBA_USERNAME};0775;0664"
-s "shared-2;/shared-2;yes;yes;no;all;none;${SAMBA_USERNAME};0775;0664"
-s "shared-3;/shared-3;yes;yes;no;all;none;${SAMBA_USERNAME};0775;0664"-u flag (User definition)
-u "${SAMBA_USERNAME};${SAMBA_PASSWORD}"Creates a Samba user with the specified username and password.
-s flag (Share definition) Each -s parameter defines a network share. The syntax is:
-s "name;path;browseable;readonly;guest;users;admins;writelist;create_mask;directory_mask"
In this example:
-s "shared;/shared;yes;yes;no;all;none;${SAMBA_USERNAME};0775;0664"Where:
shared- Share name- What users see on the network (e.g.,
\\raspberry-pi\shared)
- What users see on the network (e.g.,
/shared- Path inside the container- The actual directory being shared (maps to
/mnt/storagehere)
- The actual directory being shared (maps to
yes- Browseableyes= Share appears in network browser listingsno= Share is hidden (but still accessible if you know the name)
yes- Read-only- Set the drive to be read-only. The authenticated user can still write.
no- Guest accessno= Requires authentication (username/password)yes= Anyone can access without credentials
all- Valid usersall= All authenticated users can access- Or specify usernames:
user1,user2
none- Admin usersnone= No admin privileges- Or specify usernames who get full control
${SAMBA_USERNAME}- Write list- Users allowed to write (important when read-only=yes is overridden)
- In this case, my Samba user gets write permissions
0775- Directory mask (create mask for directories)0775= Owner: rwx, Group: rwx, Others: r-x- New directories created with these permissions
0664- File mask (create mask for files)0664= Owner: rw-, Group: rw-, Others: r–- New files created with these permissions
These masks ensure that:
- I (owner) can read/write/delete
- My group members can read/write
- Others can only read
- Directories are executable (required to enter them)
Step 5: Setting up Home Assistant
Home Assistant is an open-source platform for home automation. It lets you connect IoT devices and monitor metrics via sensors.
I’ve been looking into ways to track the temperature and humidity of my house for a long time, and in one earlier attempt I tried to set up Home Assistant with a standalone sensor. It was very complicated, did not work well, and I gave up after a while.
For this set up I decided to make my life a bit easier by purchasing a connected temperature/humidity sensor and a Zigbee antenna to connect it with.
Still, I first had to boot the Home Assistant software, and as before I used a Docker container with this docker-compose.yml file:
services:
homeassistant:
container_name: homeassistant
image: ghcr.io/home-assistant/home-assistant:stable
volumes:
- ./config:/config
environment:
- TZ=Europe/Paris
network_mode: host
restart: unless-stopped
devices:
- "/dev/ttyUSB0:/dev/ttyUSB0"Everything is pretty much default, except for the device passthrough below:
devices:
- "/dev/ttyUSB0:/dev/ttyUSB0"These lines are needed to let the Home Assistant container communicate with the antenna, which is /dev/ttyUSB0. Depending on the device, the serial code may be different: you should be able to find it with the command
ls /dev/tty*At this point, all I did was following the instructions shown by Home Assistant.
The only thing I need to mention is that I had some issues connecting the sensor to the antenna, and the solution for that particular problem was turning it off and back on by taking out the battery. Once it rebooted, I was able to connect it just fine.
Finally, I was able to access the Home Assistant dashboard by connecting to http://raspberrypi:8123.
Step 6: Monitoring with Cockpit
The final step (for this post, at any rate) consisted in finding a way to monitor the Raspberry from the Mac, to check that it was not melting itself down, and in general keep an eye on the setup.
To do so, I installed cockpit, a monitoring software that monitors the resource usage of the board, as well as its temperature. For this, no container: apt was sufficient.
sudo apt install cockpit
sudo systemctl enable --now cockpit.socketAfter this, cockpit became accessible at the address http://raspberrypi:9090.
Wrapping up
In this very long post I covered how I set up a full private network, a local file sharing service with samba, a media center with Jellyfin, a IoT dashboard with Home Assistant and a monitoring service with cockpit.
That was a long process! And I’m not done with it yet either. There are a few more additions I’d like to make, like Photoprism and pi.hole, but I’m not sure yet when I’ll get around doing that.
I hope this very long write up was useful. See you in the next posts!