Compare commits

..
25 Commits
Author SHA1 Message Date
me 177bcafe95 Remove sleeping for 10 seconds at the end of the loop since we're much more precise now. Only sleep for 5 minutes after notification dismissal if we're planning another one. 2026-11-03 19:28:59 -07:00
me 5fd554e7d5 Make the recurring notifications optional. Sleep exactly as many seconds as needed until the next work needs to be done. 2026-11-03 19:22:19 -07:00
me 28c0f61696 Fix alert not popping up, SUNSET_TIME is not used in API route. 2026-11-02 20:56:35 -07:00
me 949e1baacd Fix false positives when time was not getting successfully retrieved. 2026-11-02 07:36:49 -07:00
me 7d9bc589c5 Explicitly ask for current day. 2026-11-01 06:08:26 -07:00
me 9cc194fcb4 Enhance the usage, output, latlong handling, and error reporting. Feels much closer to done. 2026-10-29 21:56:16 -07:00
me e1995dff80 Add error message if someone tries to use comma or other character instead of colon for LATLONG. 2026-10-29 20:06:14 -07:00
me 4aed123213 Sleep 5 minutes between each alert since it must be dismissed manually. 2026-10-29 19:57:21 -07:00
me b0acb7793a Improve the notification message and frequency. 2026-10-29 19:54:12 -07:00
me 1fc42b4e4e Fix the sed which was getting the sunset time format and failing rather than the actual time (since sunset is mentioned twice in the response). 2026-10-29 19:36:15 -07:00
me 35db6c2c5f Get the time from epoch of when the sunset is. 2026-10-29 19:28:26 -07:00
me f671e3d94d Use more private options for checking Internet connectivity. 2026-10-29 19:28:07 -07:00
me 68b703ff7e Make the notification red (critical). 2026-10-29 19:18:42 -07:00
me 21f02945bb Add icon and persistence to notification. 2026-10-29 19:16:03 -07:00
me fae03c94d9 ASked agent to fix the program using sunset time rather than midnight. Seems to be working properly now. 2026-10-29 19:05:01 -07:00
me ffc29eabb2 Generated a new version on the updated README with qwen3.6:35b. It says it tested fine, my testing and potential installation is still TBD. 2026-10-28 17:44:59 -07:00
me b14e6a3660 Remove the old and unfinished files, will regenerate from scratch. 2026-10-26 09:38:23 -07:00
me 9e294d070f main, remove the unfinished code. 2026-10-17 17:01:35 -07:00
me 68ae3e1b9a Add each iteration so far, for funzies. 2026-10-17 14:34:43 -07:00
me 3a36ec9651 README, small edit for usage specificity. 2026-10-17 14:31:37 -07:00
me eb0555fd77 README, updates to make it a better prompt. 2026-10-17 14:28:48 -07:00
me f145f7122e v3, unfinished. 2026-10-17 14:28:24 -07:00
me 0713442b78 v2, bash. 2026-10-17 14:27:58 -07:00
me 6ce4a9bbf0 v1 2026-10-17 13:32:50 -07:00
me 15c909d954 Add design. 2026-10-16 09:07:56 -07:00
2 changed files with 356 additions and 2 deletions

No files matched your search

+104 -2
View File
@@ -1,3 +1,105 @@
# template-mit
# Sunset Alarm (CLI)
Template with MIT license, protected `main` branch, and available `dev` branch.
Simple command line shell script which can be run as a user daemon for alerts.
## Description
Sunset Alarm allows a user to receive an alert at a specified amount of minutes
before sunset. This may be helpful for starting any practices such as preparing
for sungazing, or as a reminder to go and find a campsite before it gets dark.
## Usage
`()` sections are required.
`[]` sections are optional.
`|` separates multiple options in a group.
```console
sunset-timer (-l LAT:LONG | -t HH24:MI) (-m MINUTES) [-h]
```
## Example
Set an alarm 90 minutes before for your lat/long experiences sunset:
```shell
sunset-alarm -l 32.123:-111.111 -m 90
```
Set an alarm 5 minutes after a manually determined sunset time:
```shell
sunset-alarm -t 18:00 -m -5
```
## Install
Recommended installation location is under `/usr/local/*`.
```console
# git clone http://git.hyperling.com/me/sunset-alarm-cli /usr/local/src/sunset_alarm_cli
# ln -s /usr/local/src/sunset_alarm_cli/sunset-alarm /usr/local/bin/
```
### Daemonize
**runit**
Create the run file:
```console
# vi /etc/sv/sunset-alarm/run:
```
```shell
#!/bin/sh -e
exec 2>&1
chpst -u user /usr/local/bin/sunset-alarm -l 33:-112 -m 15
```
Enable the service:
```console
# ln -s /etc/sv/sunset-alarm /var/services/
```
### i3
Simply add an exec to the end of your config file.
```shell
echo "exec --no-startup-id /usr/local/bin/sunset-alarm -l 33:-112 -m 15" >> ~/.config/i3/config
```
## Parameters
### Required
-m : Accepts a minute value which is used to check if we have approached the
alert period before sunset. Can be positive, negative, or 0.
### Optional
-l : Accepts a lat:long value which is used to check Open Meteo for the
location's sunset time. This is required if -t is not passed.
-t : Accepts a time value for sunset time. If this is passed, it bypasses the
Open Meteo check and acts as a hardcoded time. The minutes parameter is still
required to know when to alert prior to this time.
## Design
App displays a persistent alert via `fyi` with fallbacks of `notify-send`,
`wall`, and others. Appears once when current time is between (sunset - minutes)
and midnight. Even if the system is powered on after the alert time has passed,
the program continues displaying the alert repeatedly until the day has passed.
App requires an internet connection. If no connection is found, it sleeps for 5
minutes and then tries again to retrieve the sunset time. Once the sunset time
has been retrieved for the day, it stops checking Open Meteo until the next day.
App is short, sweet, concise, and succinct without using any odd syntax or line
combining to achieve this. Does not pull in any extraneous libraries, only uses
utilities which are already installed on the system. Program is coded to use a
range of utilities in case the primary choices are not available. If no choices
are installed, displays an ERROR message with a list of software to download.
Executable
+252
View File
@@ -0,0 +1,252 @@
#!/bin/sh
# sunset-alarm -- Alert before or after sunset.
# Displays an alert when current time is within (sunset - specified minutes).
###############################################################################
# Defaults
###############################################################################
declare LATLONG="" SUNSET_TIME="" MINUTES="" LAST_DATE="" PERSIST="" NOTIFIED=""
typeset -i SUNSET_EPOCH=0 CURRENT_TIME=0
###############################################################################
# Usage
###############################################################################
usage() {
cat <<EOF
Usage: sunset-alarm (-l LAT:LONG | -t HH24:MI) (-m MINUTES) [-p] [-h]
Display a notification when a certain time related to sunset has been reached.
Parameters:
-l) Takes a LAT:LONG parameter to check Open Meteo for the sunset time if
there is an Internet connection. Otherwise may fail to notify.
-t) Takes a hard-coded 24-hour formatted time for guessing when sunset is.
-m) Takes the number of minutes that the alert should appear before sunset.
Using a negative time is allowed if you prefer having the alert after.
-p) Persist the notification, showing back up every 5 minutes to midnight.
-h) Display this usage.
Example: Show the alert 90 minutes before sunset in Colorado City AZ.
sunset-alarm -l 37:-113 -m 90
Example: Show the alert at exactly 8:30 PM.
sunset-alarm -t 20:30 -m 0
Example: Show the alert 30 minutes after sunset in Flagstaff AZ.
sunset-alarm -l 35.1987671:-111.651813 -m -30
EOF
exit 1
}
###############################################################################
# Parse arguments (handles negative values for -m by using "$@")
###############################################################################
while [ $# -gt 0 ]; do
case "$1" in
-l) LATLONG="$2"; shift 2 ;;
-t) SUNSET_TIME="$2"; shift 2 ;;
-m) MINUTES="$2"; shift 2 ;;
-p) PERSIST="true"; shift 1 ;;
-h) usage ;;
--) shift; break ;;
*) echo "ERROR: unknown argument '$1'" >&2; usage ;;
esac
done
# Validate inputs.
if [ -z "$MINUTES" ]; then
echo "ERROR: -m MINUTES is required." >&2
usage
fi
if [ -z "$LATLONG" ] && [ -z "$SUNSET_TIME" ]; then
echo "ERROR: must specify either -l LAT:LONG or -t HH:MI." >&2
usage
fi
LATLONG=${LATLONG//\,/:}
LATLONG=${LATLONG//\ /:}
LATLONG=${LATLONG//\_/:}
LATLONG=${LATLONG//\&/:}
LATLONG=${LATLONG//\?/:}
if [[ -n "$LATLONG" && "$LATLONG" != *":"* ]]; then
echo "ERROR: LAT:LONG is missing the colon." >&2
usage
fi
###############################################################################
# Internet connectivity check
###############################################################################
check_internet() {
if curl -s --max-time 5 -o /dev/null https://hyperling.com; then
echo "Confirmed connectivity with Hyperling."
return 0
fi
if curl -s --max-time 5 -o /dev/null https://mullvad.net; then
echo "Confirmed connectivity with Mullvad."
return 0
fi
echo "ERROR: Connectivity checks failed." >&2
return 1
}
###############################################################################
# Get sunset from Open Meteo API
###############################################################################
fetch_sunset_time() {
echo "Checking for sunset time of lat:long ${LATLONG}."
LAT=$(echo "$LATLONG" | cut -d: -f1)
LON=$(echo "$LATLONG" | cut -d: -f2)
TODAY="`date +'%Y-%m-%d'`"
#REQUEST="https://api.open-meteo.com/v1/forecast?latitude=${LAT}&longitude=${LON}&timezone=auto&daily=sunset&date=today"
#REQUEST="https://api.open-meteo.com/v1/forecast?latitude=${LAT}&longitude=${LON}&timezone=auto&daily=sunset&forecast_days=1"
REQUEST="https://api.open-meteo.com/v1/forecast?latitude=${LAT}&longitude=${LON}&timezone=auto&daily=sunset&start_date=${TODAY}&end_date=${TODAY}"
RESPONSE=$(curl -s "$REQUEST")
status="$?"
echo "Curl command completed with a status of $status."
if [[ -z "$RESPONSE" ]]; then
echo "ERROR: Did not receive response. $REQUEST" >&2
return 1
fi
# Extract sunset timestamp like "2025-06-15T19:32:00".
SUNSET_RAW=$(echo "$RESPONSE" | jq -r '.daily.sunset[0]')
if [[ -z "$SUNSET_RAW" ]]; then
echo "ERROR: Could not parse time. $RESPONSE" >&2
return 1
fi
# Convert to epoch.
SUNSET_EPOCH=$(date -d "$(echo "$SUNSET_RAW" | cut -dT -f1) $(echo "$SUNSET_RAW" | cut -dT -f2)" +%s 2>/dev/null)
if [[ -z "$SUNSET_EPOCH" ]]; then
echo "ERROR: Could not calculate epoch. $SUNSET_RAW" >&2
return 1
fi
echo "Got ${SUNSET_RAW} and converted it to epoch ${SUNSET_EPOCH}."
return 0
}
###############################################################################
# Show alert via fallback chain: fyi -> notify-send -> wall
###############################################################################
show_alert() {
ALERT=$(date -d @${ALERT_WINDOW_START} +"%H:%M")
SUNSET=$(date -d @${SUNSET_EPOCH} +"%H:%M")
PLURAL=$(if (( $MINUTES != 1 )); then echo "s"; fi)
TITLE="Sunset Alarm ($ALERT)"
MESSAGE="It's ${MINUTES} minute${PLURAL} to $SUNSET."
keep_alive="-t 0 --wait"
icon="-i weather-clear -u critical"
if command -v fyi >/dev/null 2>&1; then
fyi $icon $keep_alive "$TITLE" "$MESSAGE"
elif command -v notify-send >/dev/null 2>&1; then
notify-send $icon $keep_alive "$TITLE" "$MESSAGE"
elif command -v wall >/dev/null 2>&1; then
echo "${TITLE}: ${MESSAGE}" | wall
else
echo "ERROR: No notification tool found." >&2
echo "Install one of: fyi, notify-send (libnotify-bin), wall (bsd-write)" >&2
return 1
fi
}
###############################################################################
# Sleep until a certain amount of time
###############################################################################
rest() {
if [[ "$CURRENT_TIME" -ge 0 ]]; then
TARGET="$1"
sleep_seconds=$(( TARGET - CURRENT_TIME ))
echo "Sleeping ${sleep_seconds}s until $(date -d @$TARGET +%H:%M:%S)."
sleep "$sleep_seconds"
fi
}
###############################################################################
# Main loop
###############################################################################
while true; do
TODAY=$(date +%Y-%m-%d)
CURRENT_TIME=$(date +%s)
# Check if we need to fetch new sunset time for a new day.
if [[ "$LAST_DATE" != "$TODAY" || "$SUNSET_EPOCH" == 0 ]]
then
LAST_DATE="$TODAY"
if [[ -n "$LATLONG" ]]; then
# Need internet to get sunset time.
if ! check_internet; then
echo "ERROR: No internet connection. Retrying in 5 minutes." >&2
sleep 300
continue
fi
fetch_sunset_time || {
echo "ERROR: Failed fetching sunset time. Retrying in 5 minutes." >&2
sleep 300
continue
}
if [[ "$SUNSET_EPOCH" == 0 ]]; then
echo "ERROR: Sunset time is still null. Retrying in 5 minutes." >&2
sleep 300
continue
fi
else
# User provided sunset time directly, convert to epoch.
SUNSET_EPOCH=$(date -d "${TODAY} ${SUNSET_TIME}" +%s 2>/dev/null)
if [[ -z "$SUNSET_EPOCH" ]]; then
echo "ERROR: Could not parse time '${SUNSET_TIME}'." >&2
sleep 60
continue
fi
fi
fi
# Calculate alert window start (sunset minus specified minutes).
ALERT_WINDOW_START=$(( SUNSET_EPOCH - (MINUTES * 60) ))
# Calculate midnight of the current day
MIDNIGHT=$(date -d "${TODAY} +1 day" +%s)
# Alert while current time is within the window (from window start until midnight).
if [[ "$SUNSET_EPOCH" -ge 0 \
&& ( -z "$NOTIFIED" || -n "$PERSIST" ) \
&& "$CURRENT_TIME" -ge "$ALERT_WINDOW_START" \
&& "$CURRENT_TIME" -lt "$MIDNIGHT" ]]
then
echo "Alert time has been reached, show message."
show_alert
NOTIFIED="true"
echo "Alert has been dismissed."
if [[ -n "$PERSIST" ]]; then
echo "Sleeping for 5 minutes before next notification."
sleep 300
fi
elif [[ "$SUNSET_EPOCH" == 0 ]]; then
echo "ERROR: Sunset time has not been calculated yet, cannot alert." >&2
elif [[ -n "$NOTIFIED" && -z "$PERSIST" ]]; then
echo "Already notified for today ($TODAY)."
rest $MIDNIGHT
else
echo "Not time to notify yet."
rest $ALERT_WINDOW_START
fi
# Reset when we've passed midnight so we fetch the new time for the day.
if [[ "$CURRENT_TIME" -ge "$MIDNIGHT" ]]; then
LAST_DATE=""
NOTIFIED=""
fi
done