Automatically shutdown servers behind UPS without NUT when there’s a power outage

Standard

Introduction

When dealing with power outages in home lab environments, proper shutdown procedures are crucial to avoid data corruption and hardware damage. However, not all UPS (Uninterruptible Power Supply) systems support Network UPS Tools (NUT), making it difficult to determine when a server should be shut down.

For example, I have a power station with UPS mode for camping. When I’m not camping, I use it as a UPS for my non-tier-1 devices/servers in my homelab.

To solve this problem, we can leverage IoT devices that lose power when the main supply goes down. By periodically pinging these devices, we can detect when a power outage has occurred and initiate a safe shutdown process after a specified threshold.

This blog post walks through a Bash script designed for this purpose, explaining how it works and how you can implement it.

How the Script Works

This Bash script continuously monitors IoT devices (or any networked devices) by pinging them. If all the defined devices fail to respond for a set duration (e.g., 20 minutes), it safely shuts down the server.

Key Features:

✔️ Works without NUT – Ideal for UPS systems that don’t support monitoring.
✔️ Uses standard ping checks – No additional dependencies beyond basic network tools.
✔️ Customizable threshold – Configure how long the server should wait before shutting down.
✔️ Runs as a cron job – Automatically executes at regular intervals.

Breaking Down the Script

Here’s the complete script:

#!/bin/bash

# Define the target host. If ANY host is up, the server will NOT be shut down.
TARGET_HOSTS=("example.com" "device1.iot.example.com")
THRESHOLD=20  # 20 * 1 min = 20 minutes (1 min is the crontab interval)
# different distro may have different shutdown command. 
# e.g. Proxmox needs absolute path '/usr/sbin/shutdown -h now'; Synology is fine with 'shutdown -h now'
SHUTDOWN_CMD="shutdown -h now" 

# Define a status file to track the downtime duration
STATUS_FILE="/tmp/host_down_count"

# Ensure the script is run as root
if [ "$(id -u)" -ne 0 ]; then
    echo "This script must be run as root!"
    exit 1
fi

# Ensure ping is installed
if ! command -v ping &> /dev/null; then
    echo "Ping not found. Installing..."
    apt update && apt install -y iputils-ping
fi

# Function to check if any host is up
is_any_host_up() {
    local hosts=("$@")
    for host in "${hosts[@]}"; do
        if ping -c 2 -W 2 "$host" > /dev/null 2>&1; then
            echo "$(date): Host $host is up."
            return 0  # At least one host is up
        else
            echo "$(date): Host $host is down."
        fi
    done
    echo "$(date): No host is up."
    return 1  # No host is up
}

# Check if any of the hosts is up
if is_any_host_up "${TARGET_HOSTS[@]}"; then
    echo "$(date): At least one host is up."
    rm -f "$STATUS_FILE"  # Reset status file if any host is up
    exit 0
else
    echo "$(date): All hosts are down."
    
    # Increase downtime count
    if [ -f "$STATUS_FILE" ]; then
        COUNT=$(cat "$STATUS_FILE")
        COUNT=$((COUNT + 1))
    else
        COUNT=1
    fi
    echo "$COUNT" > "$STATUS_FILE"
    echo "$(date): Count: $COUNT"

    # If the server has been down for $THRESHOLD * 1 minutes (1-minute intervals → $THRESHOLD checks)
    if [ "$COUNT" -ge "$THRESHOLD" ]; then
        echo "$(date): All target hosts have been down for $THRESHOLD minutes. Shutting down server..."
        rm -f "$STATUS_FILE"  # Reset status file before shutdown
        $SHUTDOWN_CMD
    fi
    exit 1
fi

2115. Find All Possible Recipes from Given Supplies

Standard

Problem

Given an array of integers nums and an integer k, return the total number of subarrays whose sum equals to k.

A subarray is a contiguous non-empty sequence of elements within an array.

Example 1:

Input: nums = [1,1,1], k = 2
Output: 2

Example 2:

Input: nums = [1,2,3], k = 3
Output: 2

Constraints:

  • 1 <= nums.length <= 2 * 104
  • -1000 <= nums[i] <= 1000
  • -107 <= k <= 107

Solution

class Solution {
    private HashMap<String, Integer> recipes;
    private HashMap<String, Boolean> visitedAndFound; // false to detect cycle
    private List<List<String>> ingredients;
    private HashSet<String> supplies;

    public List<String> findAllRecipes(String[] recipes, List<List<String>> ingredients, String[] supplies) {
        this.recipes = new HashMap<>();
        for (int i = 0; i < recipes.length; i++) {
            this.recipes.put(recipes[i], i);
        }
        this.ingredients = ingredients;
        this.visitedAndFound = new HashMap<>();
        this.supplies = new HashSet<>();
        for (String supply : supplies) {
            this.supplies.add(supply);
        }

        List<String> res = new LinkedList<>();
        for (String recipe : recipes) {
            if (findRecipe(recipe)) {
                res.add(recipe);
            }
        }
        return res;
    }

    public boolean findRecipe(String recipe) {
        if (visitedAndFound.containsKey(recipe)) {
            return visitedAndFound.get(recipe);
        }
        visitedAndFound.put(recipe, false);

        if (supplies.contains(recipe)) {
            visitedAndFound.put(recipe, true);
            return true;
        }
        if (!recipes.containsKey(recipe)) {
            return false;
        }

        for (String ingredient : ingredients.get(recipes.get(recipe))) {
            if (findRecipe(ingredient) == false) {
                return false;
            }
        }
        visitedAndFound.put(recipe, true);
        return true;
    }
}

253. Meeting Rooms II

Standard

Problem

Given an array of meeting time intervals intervals where intervals[i] = [starti, endi], return the minimum number of conference rooms required.

Example 1:

Input: intervals = [[0,30],[5,10],[15,20]]
Output: 2

Example 2:

Input: intervals = [[7,10],[2,4]]
Output: 1

Constraints:

  • 1 <= intervals.length <= 104
  • 0 <= starti < endi <= 106

Java Solution

class Solution {
    public int minMeetingRooms(int[][] intervals) {
        int[] starts = new int[intervals.length];
        int[] ends = new int[intervals.length];
        for (int i = 0; i < intervals.length; i++) {
            starts[i] = intervals[i][0];
            ends[i] = intervals[i][1];
        }
        Arrays.sort(starts);
        Arrays.sort(ends);
        int room = 0, endIdx = 0;
        for (int i = 0; i < starts.length; i++) {
            if (starts[i] < ends[endIdx]) {
                room++;
            } else {
                endIdx++;
            }
        }
        return room;
    }
}

539. Minimum Time Difference

Standard

Problem

Given a list of 24-hour clock time points in “HH:MM” format, return the minimum minutes difference between any two time-points in the list.

Example 1:

Input: timePoints = ["23:59","00:00"]
Output: 1

Example 2:

Input: timePoints = ["00:00","23:59","00:00"]
Output: 0

Constraints:

  • 2 <= timePoints.length <= 2 * 104
  • timePoints[i] is in the format “HH:MM”.

Java Solution

class Solution {
    public int findMinDifference(List<String> timePoints) {
        int[] exist = new int[24 * 60];
        for (String time : timePoints) {
            int minutes = Integer.parseInt(time.substring(0, 2)) * 60 + Integer.parseInt(time.substring(3, 5));
            if (exist[minutes] != 0) {
                return 0;
            } else {
                exist[minutes] = 1;
            }
        }

        int prev = -1;
        int res = Integer.MAX_VALUE, start = Integer.MAX_VALUE, end = Integer.MIN_VALUE;
        for (int i = 0; i < 24 * 60; i++) {
            if (exist[i] == 1) {
                if (prev == -1) {
                    prev = i;
                } else {
                    res = Math.min(res, i - prev);
                }
                prev = i;
                start = Math.min(start, i);
                end = Math.max(end, i);
            }
        }
        return Math.min(res, 24 * 60 - (end - start));
    }
}

1048. Longest String Chain

Standard

Problem

You are given an array of words where each word consists of lowercase English letters.

wordA is a predecessor of wordB if and only if we can insert exactly one letter anywhere in wordA without changing the order of the other characters to make it equal to wordB.

  • For example, "abc" is a predecessor of "abac", while "cba" is not a predecessor of "bcad".

word chain is a sequence of words [word1, word2, ..., wordk] with k >= 1, where word1 is a predecessor of word2word2 is a predecessor of word3, and so on. A single word is trivially a word chain with k == 1.

Return the length of the longest possible word chain with words chosen from the given list of words.

Example 1:

Input: words = ["a","b","ba","bca","bda","bdca"]
Output: 4
Explanation: One of the longest word chains is ["a","ba","bda","bdca"].

Example 2:

Input: words = ["xbc","pcxbcf","xb","cxbc","pcxbc"]
Output: 5
Explanation: All the words can be put in a word chain ["xb", "xbc", "cxbc", "pcxbc", "pcxbcf"].

Example 3:

Input: words = ["abcd","dbqca"]
Output: 1
Explanation: The trivial word chain ["abcd"] is one of the longest word chains.
["abcd","dbqca"] is not a valid word chain because the ordering of the letters is changed.

Constraints:

  • 1 <= words.length <= 1000
  • 1 <= words[i].length <= 16
  • words[i] only consists of lowercase English letters.

Java Solution

class Solution {
    public int longestStrChain(String[] words) {
        HashMap<String, Integer> longestPre = new HashMap<>(); // Including the word itself
        TreeMap<Integer, List<String>> wordsByCharLength = new TreeMap<>();
        int res = 1;
        for (String word : words) {
            wordsByCharLength.putIfAbsent(word.length(), new LinkedList<>());
            wordsByCharLength.get(word.length()).add(word);
        }
        int longest = wordsByCharLength.lastKey();
        for (int i = 1; i <= longest; i++) {
            if (!wordsByCharLength.containsKey(i)) {
                continue;
            }
            for (String curWord : wordsByCharLength.get(i)) {
                if (!wordsByCharLength.containsKey(i - 1)) {
                    longestPre.put(curWord, 1);
                    continue;
                }
                int prevLongest = 0;
                for (String prevWord : wordsByCharLength.get(i - 1)) {
                    if (isPredecessorOf(prevWord, curWord)) {
                        prevLongest = Math.max(prevLongest, longestPre.get(prevWord));
                    }
                }
                longestPre.put(curWord, prevLongest + 1);
                res = Math.max(res, prevLongest + 1);
            }
        }
        return res;
    }

    private boolean isPredecessorOf(String prevWord, String curWord) {
        int skipped = 0;
        int i = 0;
        while (i < prevWord.length() && i + skipped < curWord.length()) {
            if (prevWord.charAt(i) != curWord.charAt(i + skipped)) {
                if (skipped == 0) {
                    skipped = 1;
                    continue;
                } else {
                    return false;
                }
            }
            i++;
        }
        return true;
    }
}

110. Balanced Binary Tree

Standard

Given a binary tree, determine if it is height-balanced.

For this problem, a height-balanced binary tree is defined as:

a binary tree in which the left and right subtrees of every node differ in height by no more than 1.

Java Solution:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Result {
    boolean val;
    Result() {
        this.val = true;
    }
    public void setValue(boolean val) {
        this.val = val;
    }
    public boolean getValue() {
        return val;
    }
}
class Solution {
    public boolean isBalanced(TreeNode root) {
        Result isBalanced = new Result();
        getHeight(root, isBalanced);
        return isBalanced.getValue();
    }
    
    private int getHeight(TreeNode node, Result isBalanced) {
        if (node == null) return 0;
        int leftHeight = getHeight(node.left, isBalanced);
        int rightHeight = getHeight(node.right, isBalanced);
        if (Math.abs(leftHeight - rightHeight) > 1) {
            isBalanced.setValue(false);
        }
        return Math.max(leftHeight, rightHeight) + 1;
    }
}

Essentially, trying to “hack” the pass-by-reference C++ has into the Java solution. But it solves the problem in one pass.

Deploy Jupyter behind Apache reverse proxy

Standard

Jupyter was deployed on one LXD container and Apache was deployed on another. At first Jupyter kept complaining “Connecting to kernel… Failed.”

Ubuntu: 16.04LTS

Apache: Server version: Apache/2.4.18 (Ubuntu)

Solution:

Add those two lines BEFORE “/” one:

ProxyPass /api/kernels/ ws://xxx.lxd:8888/api/kernels/
ProxyPassReverse /api/kernels/ http://xxx.lxd:8888/api/kernels/

AND: make sure proxy_wstunnel is enabled for Apache:

a2enmod proxy proxy_wstunnel

Convertion of grey code and binary

Standard

Source: http://www.cnblogs.com/grandyang/p/4315607.html

以下转换代码摘自维基百科 Wikipedia:

/*
The purpose of this function is to convert an unsigned
binary number to reflected binary Gray code.

The operator >> is shift right. The operator ^ is exclusive or.
*/
unsigned int binaryToGray(unsigned int num)
{
return (num >> 1) ^ num;
}

/*
The purpose of this function is to convert a reflected binary
Gray code number to a binary number.
*/
unsigned int grayToBinary(unsigned int num)
{
unsigned int mask;
for (mask = num >> 1; mask != 0; mask = mask >> 1)
{
num = num ^ mask;
}
return num;
}