It does not sound like much, but running a 24/7 home server that draws 43 watts in idle is quiet expensive, considering the latest electricity prices of about 49 euro cent/kWh here in Germany (as of October, 2022).
A simple solution to this problem is a script that automatically suspends the server, when it is not used, and another script to wake the server up again, in case there is work to do.
To my surprise I could not find any out-of-the-box solution, so I thought, it is a worthwhile effort to write about it.
Hello HN: Because so many people asked for it, the server specs are: Intel i5-11400; ASRock B560 Pro4; Crucial DDR4 2x 8GB; 5x disks (1x M.2 NVMe SSD, 3x 5400 RPM, 1x 7200 RPM). I update the UEFI and activated Active-State Power Management (ASPM), then I installed PowerTOP 2 and tuned some settings (e.g., enabled audio codec and SATA link power management). This way I reduced the consumption to 39 watts in idle and only 2 watts during sleep. I also decided to install hd-idle, a software that suspends the hard disks after 5 minutes of inactivity, which reduced the power consumption from 39 to just 23 watts during idle.
By the way: Why aren't there any good solutions out there? Why isn't this build into every major operating system by now? Why isn't this the default (or at least a configuration option)? Seriously, there is soooo much potential to save energy in IT.
Currently, the server is primarily used for two things:
So I was forced to find a way to automatically suspend the server, but prevent it from sleeping, if any of the two services are in use. Moreover, I needed a way to wake up the server remotely, so in case that I am not at home, I could still access Plex, which led to the need for the following two components:
Finally, I do have a Raspberry Pi 4 Model B 4GB at home that is running all the time. This Pi is used for hosting Home Assistant (HASS) and Pi-hole and it could become very handy for monitoring the sleep state of the server and hosting the wake up website.
The high-level idea that came to my mind looks similar to this: Macs in my local network access the server via SMB for Time Machine backups. From remote, I access Plex via HTTPS for streaming. In case the server sleeps, I need a convenient solution to wake the server remotely via my smartphone. The Raspberry Pi is running 24/7 and already hosts Home Assistant that offers a Wake on LAN and Device tracker integration.
Fast forward a number of weeks testing various options, my setup currently looks like this:
The server implements a ring buffer and checks for activities once per minute.
To monitor Plex activities, we access the local Plex API and for Time Machine we simply monitor any file access at /mnt
using lsof.
In case there has been no activity for 15 consecutive minutes, the server goes to sleep. (Nobody streams, pausing a video doesn't count as activity, and no backup is running.)
A Web server on the Raspberry Pi hosts a website that obtains the current state of the server provided via the Home Assistant REST API.
In case the server sleeps, and I like to backup or stream something, I can wake the server using a simple button press that sends a magic packet using a wakeonlan Perl script.
My backup disk is mounted under /mnt/backup
. We monitor whether there is any file access on /mnt
.
def is_someone_accessing_files_check():
try:
print("New file access check...having a look at '/mnt'")
response = subprocess.getoutput("lsof -w /mnt/* | grep /mnt/ | wc -l")
if response == '0':
return False
return True
except Exception:
return True
There are many possible solutions to this problem. The most reliable was to use the local Plex API. In contrast to simpler solutions this comes with the advantage that we can differentiate between active playbacks and people that paused the streaming (but still have the browser tab open, usually preventing the server from sleeping 😈). You can find your account authentication token for Plex here.
PLEX_AUTH_TOKEN = 'your-plex-auth-token'
def is_someone_streaming_check():
"""Query the Plex API and obtain the number and state of current sessions."""
try:
print("New streaming check...having a look at Plex sessions")
my_headers = {}
my_headers['X-Plex-Token'] = PLEX_AUTH_TOKEN
my_headers['accept'] = 'application/json'
url = 'http://127.0.0.1:32400/status/sessions'
data = requests.get(url, headers=my_headers).json()
size = None
if 'MediaContainer' in data:
if 'size' in data['MediaContainer']:
size = data['MediaContainer']['size']
print(f"Currently there are {size} users online ...")
users = []
if 'MediaContainer' in data:
if 'Metadata' in data['MediaContainer']:
for session in data['MediaContainer']['Metadata']:
user = {'User': None, 'State': None, 'Platform': None, 'IP': None, 'Started': None}
if 'User' in session:
if 'title' in session['User']:
user['User'] = session['User']['title']
if 'Player' in session:
if 'state' in session['Player']:
user['State'] = session['Player']['state']
...
users.append(user)
streaming = False
for user in users:
print(user)
if user['State'] != 'paused':
streaming = True
return streaming
except Exception:
return True
The script runs once per minute and suspends the server after 15 consecutive minutes without any activity by calling /usr/sbin/pm-suspend
.
To avoid the need to run the script as root, consider adding my_user ALL=NOPASSWD:/usr/sbin/pm-suspend to your /etc/sudoers
file (then execute sudo pm-suspend
).
###### 2022-10-14 15:35:56
New file access check...having a look at '/mnt'
File access: True
New streaming check...having a look at Plex sessions
Currently there are 2 users online ...
{'User': 'my_user1', 'State': 'paused', 'Platform': 'Firefox', 'IP': 'a.b.c.d', 'Started': '2022-10-14 12:29:18'}
{'User': 'my_user2', 'State': 'playing', 'Platform': 'Chrome', 'IP': 'a.b.c.d', 'Started': '2022-10-14 15:34:57'}
Streaming: True
Currently there are 14 activities in the past 15 minutes...
Will check again in 60 seconds...
There are many tutorials out there.
Check your BIOS/UEFI first, then install ethtool
, and configure a wol.service
that re-enables WoL every time you reboot the machine.
We create a mobile-friendly website that queries and displays the current server status using the Home Assistant REST API.
It also offers a button that sends a magic packet using a wakeonlan Perl script (Python 3 version).
Currently, there is no authentication in place.
If you feel the need, it should be super easy to add HTTP Basic Authentication by tweaking the configuration of your Web server.
Note: The Home Assistant Wake on LAN integration also allows to wake the machine, but I explicitly opted for this Perl script-setup, because it enables me to wake the server even in the case Home Assistant is down or currently unavailable.
OMG, PHP! Yes, I know. 🤦 After installing and configuring the Home Assistant Wake on LAN integration, we can query the Home Assistant REST API.
For authentication, you need a Long-Lived Access Token (valid for 10 years) that you can generate in your user profile (https://example.org/profile
).
header('Content-Type: application/json; charset=utf-8');
$info = json_decode(getStateFromHASS(), true);
$state = $info['state'];
$last_changed = parseDate($info['last_changed']);
$output = json_encode(array('state' => $state, 'last_changed' => $last_changed));
echo $output; // {"state":"on","last_changed":"2022-10-14 10:17:07"}
function getStateFromHASS() {
// Init
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://example.org/api/states/switch.wake_on_lan'); // Home Assistant's REST API
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Authorization: Bearer abc-secret-123...'));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// Query
$result = curl_exec($ch);
curl_close($ch);
return $result; // {..., "state": "on", "last_changed": "2022-10-14T09:17:07.277838+00:00", ...}
}
To wake the server, we run the Perl script and hardcode the server's MAC address (shell_exec
is dangerous, do not allow user input here).
Hint: First test the wakeonlan
Perl script on your shell, before integrating and executing it via PHP.
header('Content-Type: application/json; charset=utf-8');
if (!empty($_GET['act'])) {
// This is evil, we do not allow any user input here!
// https://github.com/jpoliv/wakeonlan/blob/master/wakeonlan
shell_exec('./wakeonlan DE:AD:BE:EF:12:34');
$output = json_encode(array('info' => 'Wake up signal was sent.'));
} else {
$output = json_encode(array('info' => 'No wake up signal sent.'));
}
echo $output; //{"info":"Wake up signal was sent."}
Wake on LAN (WoL) only works when you use the hostname of the server, not its IP address. This way, macOS will automatically send a magic packet every time it starts a backup, which will wake the server. Big thanks to @jessikat for this hint.
You can find the source code of all those scripts here:
Download Scripts from GitHub
I hope this is helpful for some 😊.
There are properly many issues with it, so please feel free to fork and improve!