diff --git a/config.template b/config.template index 05cf34f..55bdd8e 100644 --- a/config.template +++ b/config.template @@ -309,7 +309,9 @@ message = "MeshBot says Hello! DM for more info." # enable overides the above and uses the motd as the message schedulerMotd = False # value can be min,hour,day,mon,tue,wed,thu,fri,sat,sun. -# value can also be 'joke' (min/interval) or 'weather' (time/day) or 'link' (hour/interval) for special auto messages +# value can also be 'joke' (min/interval), 'weather' (time/day), 'link' (hour/interval) for special auto messages +# or 'news' (hour/interval), 'readrss' (hour/interval), 'mwx' (time/day), 'sysinfo' (hour/interval), +# 'tide' (time/day), 'solar' (time/day) for automated information broadcasts # 'custom' for module/scheduler.py custom schedule examples value = # interval to use when time is not set (e.g. every 2 days) diff --git a/etc/custom_scheduler.template b/etc/custom_scheduler.template index 4a32b66..daa068d 100644 --- a/etc/custom_scheduler.template +++ b/etc/custom_scheduler.template @@ -14,8 +14,24 @@ def setup_custom_schedules(send_message, tell_joke, welcome_message, handle_wxc, 5. Make sure to uncomment (delete the single #) the example schedules down at the end of the file to enable them Python is sensitive to indentation so be careful when editing this file. https://thonny.org is included on pi's image and is a simple IDE to use for editing python files. + + Available functions you can import and use: + - tell_joke() - Returns a random joke + - welcome_message - A welcome message string + - handle_wxc(message_from_id, deviceID, cmd, days=None) - Weather information + - handleNews(message_from_id, deviceID, message, isDM) - News reader + - get_rss_feed(msg) - RSS feed reader + - handle_mwx(message_from_id, deviceID, cmd) - Marine weather + - sysinfo(message, message_from_id, deviceID, isDM) - System information + - handle_tide(message_from_id, deviceID, channel_number) - Tide information + - handle_sun(message_from_id, deviceID, channel_number) - Sun information + - MOTD - Message of the day string """ try: + # Import additional functions for scheduling (optional, depending on your needs) + from mesh_bot import handleNews, sysinfo, handle_mwx, handle_tide, handle_sun + from modules.rss import get_rss_feed + # Example task functions, modify as needed the channel and interface parameters default to schedulerChannel and schedulerInterface def send_joke(channel, interface): ## uses system.send_message to send the result of tell_joke() @@ -49,6 +65,30 @@ def setup_custom_schedules(send_message, tell_joke, welcome_message, handle_wxc, ## uses system.send_message to send message of the day string which can be updated in runtime send_message(MOTD, channel, 0, interface) + def send_news(channel, interface): + ## uses system.send_message to send the result of handleNews() + send_message(handleNews(0, interface, 'readnews', False), channel, 0, interface) + + def send_rss(channel, interface): + ## uses system.send_message to send the result of get_rss_feed() + send_message(get_rss_feed(''), channel, 0, interface) + + def send_marine_weather(channel, interface): + ## uses system.send_message to send the result of handle_mwx() + send_message(handle_mwx(0, interface, 'mwx'), channel, 0, interface) + + def send_sysinfo(channel, interface): + ## uses system.send_message to send the result of sysinfo() + send_message(sysinfo('', 0, interface, False), channel, 0, interface) + + def send_tide(channel, interface): + ## uses system.send_message to send the result of handle_tide() + send_message(handle_tide(0, interface, channel), channel, 0, interface) + + def send_sun(channel, interface): + ## uses system.send_message to send the result of handle_sun() + send_message(handle_sun(0, interface, channel), channel, 0, interface) + ### Send a joke every 2 minutes #schedule.every(2).minutes.do(lambda: send_joke(schedulerChannel, schedulerInterface)) ### Send a good morning message every day at 9 AM @@ -67,6 +107,18 @@ def setup_custom_schedules(send_message, tell_joke, welcome_message, handle_wxc, #schedule.every().day.at("13:00").do(lambda: send_motd(schedulerChannel, schedulerInterface)) ### Send bbslink message every 2 days at 10 AM #schedule.every(2).days.at("10:00").do(lambda: send_message("bbslink MeshBot looking for peers", schedulerChannel, 0, schedulerInterface)) + ### Send news updates every 6 hours + #schedule.every(6).hours.do(lambda: send_news(schedulerChannel, schedulerInterface)) + ### Send RSS feed every day at 9 AM + #schedule.every().day.at("09:00").do(lambda: send_rss(schedulerChannel, schedulerInterface)) + ### Send marine weather every day at 6 AM + #schedule.every().day.at("06:00").do(lambda: send_marine_weather(schedulerChannel, schedulerInterface)) + ### Send system information every day at 12 PM + #schedule.every().day.at("12:00").do(lambda: send_sysinfo(schedulerChannel, schedulerInterface)) + ### Send tide information every day at 5 AM + #schedule.every().day.at("05:00").do(lambda: send_tide(schedulerChannel, schedulerInterface)) + ### Send sun information every day at 6 AM + #schedule.every().day.at("06:00").do(lambda: send_sun(schedulerChannel, schedulerInterface)) except Exception as e: logger.error(f"Error setting up custom schedules: {e}") \ No newline at end of file diff --git a/etc/set-permissions.sh b/etc/set-permissions.sh new file mode 100644 index 0000000..7d5e794 --- /dev/null +++ b/etc/set-permissions.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# Set ownership and permissions for Meshing Around application + +# Check if run as root +if [ "$EUID" -ne 0 ]; then + echo "Please run as root" + exit 1 +fi + +# Use first argument as user, or default to meshbot +TARGET_USER="${1:-meshbot}" + +# Check if user exists +if ! id "$TARGET_USER" &>/dev/null; then + echo "User '$TARGET_USER' does not exist." + read -p "Would you like to use the current user ($(logname)) instead? [y/N]: " yn + if [[ "$yn" =~ ^[Yy]$ ]]; then + TARGET_USER="$(logname)" + echo "Using current user: $TARGET_USER" + if ! id "$TARGET_USER" &>/dev/null; then + echo "Current user '$TARGET_USER' does not exist or cannot be determined." + exit 1 + fi + else + echo "Exiting." + exit 1 + fi +fi + +echo "Setting ownership to $TARGET_USER:$TARGET_USER" + +chown -R "$TARGET_USER:$TARGET_USER" "/opt/meshing-around/-around" +chown -R "$TARGET_USER:$TARGET_USER" "/opt/meshing-around/-around/logs" +chown -R "$TARGET_USER:$TARGET_USER" "/opt/meshing-around/-around/data" +chown "$TARGET_USER:$TARGET_USER" "/opt/meshing-around/-around/config.ini" +chmod 640 "/opt/meshing-around/-around/config.ini" +chmod 750 "/opt/meshing-around/-around/logs" +chmod 750 "/opt/meshing-around/-around/data" + +echo "Permissions and ownership have been set." \ No newline at end of file diff --git a/install.sh b/install.sh index b91ba5b..c26feb0 100755 --- a/install.sh +++ b/install.sh @@ -287,6 +287,11 @@ echo "Added user $whoami to dialout, tty, and bluetooth groups" sudo chown -R "$whoami:$whoami" "$program_path/logs" sudo chown -R "$whoami:$whoami" "$program_path/data" +sudo chown "$whoami:$whoami" "$program_path/config.ini" +sudo chmod 640 "$program_path/config.ini" +echo "Permissions set for meshbot on config.ini" +sudo chmod 750 "$program_path/logs" +sudo chmod 750 "$program_path/data" echo "Permissions set for meshbot on logs and data directories" # check and see if some sort of NTP is running diff --git a/modules/README.md b/modules/README.md index 389be7a..1b1ae22 100644 --- a/modules/README.md +++ b/modules/README.md @@ -541,17 +541,23 @@ Configure in `[scheduler]` section of `config.ini`. See modules/custom_scheduler.py for advanced scheduling using python **Purpose:** -`scheduler.py` provides automated scheduling for Mesh Bot, allowing you to send messages, jokes, weather updates, and custom actions at specific times or intervals. +`scheduler.py` provides automated scheduling for Mesh Bot, allowing you to send messages, jokes, weather updates, news, RSS feeds, marine weather, system info, tide info, sun info, and custom actions at specific times or intervals. **How to Use:** - The scheduler is configured via your bot’s settings or commands, specifying what to send, when, and on which channel/interface. -- Supports daily, weekly, hourly, and minutely schedules, as well as special jobs like jokes and weather. +- Supports daily, weekly, hourly, and minutely schedules, as well as special jobs like jokes, weather, news, RSS feeds, marine weather, system info, tide info, and sun info. - For advanced automation, you can define your own schedules in `etc/custom_scheduler.py` (copied to `modules/custom_scheduler.py` at install). **Features:** - **Basic Scheduling:** Send messages on a set schedule (e.g., every day at 09:00, every Monday at noon, every hour, etc.). - **Joke Scheduler:** Automatically send jokes every x min - **Weather Scheduler:** Send weather updates at time of day, daily. +- **News Scheduler:** Send news updates at specified intervals. +- **RSS Scheduler:** Send RSS feed updates at specified intervals. +- **Marine Weather Scheduler:** Send marine weather forecasts at time of day, daily. +- **System Info Scheduler:** Send system information at specified intervals. +- **Tide Scheduler:** Send tide information at time of day, daily. +- **Sun Scheduler:** Send sun information (sunrise/sunset) at time of day, daily. - **Custom Scheduler:** run your own scheduled jobs by editing `custom_scheduler.py`. - **Logging:** All scheduling actions are logged for debugging and monitoring. @@ -618,6 +624,48 @@ You can schedule messages or actions using the following options in your configu - Time: `08:00` - → Sends a weather update daily at 8:00a. +#### **news** +- Schedules the bot to send news updates at the specified interval (in hours). +- **Example:** + - Option: `news` + - Interval: `6` + - → Sends news updates every 6 hours. + +#### **readrss** +- Schedules the bot to send RSS feed updates at the specified interval (in hours). +- **Example:** + - Option: `readrss` + - Interval: `4` + - → Sends RSS feed updates every 4 hours. + +#### **mwx** +- Schedules the bot to send marine weather updates at the specified time of day, daily. +- **Example:** + - Option: `mwx` + - Time: `06:00` + - → Sends marine weather updates daily at 6:00a. + +#### **sysinfo** +- Schedules the bot to send system information at the specified interval (in hours). +- **Example:** + - Option: `sysinfo` + - Interval: `12` + - → Sends system information every 12 hours. + +#### **tide** +- Schedules the bot to send tide information at the specified time of day, daily. +- **Example:** + - Option: `tide` + - Time: `05:00` + - → Sends tide information daily at 5:00a. + +#### **solar** +- Schedules the bot to send sun information (sunrise/sunset) at the specified time of day, daily. +- **Example:** + - Option: `solar` + - Time: `06:00` + - → Sends sun information daily at 6:00a. + --- ### Days of the Week diff --git a/modules/scheduler.py b/modules/scheduler.py index 68b2627..544a38b 100644 --- a/modules/scheduler.py +++ b/modules/scheduler.py @@ -1,11 +1,9 @@ # modules/scheduler.py 2025 meshing-around -# Scheduler setup for Mesh Bot +# Scheduler module for mesh_bot import asyncio import schedule from datetime import datetime -from functools import partial from modules.log import logger -from modules.settings import MOTD from modules.system import send_message async def run_scheduler_loop(interval=1): @@ -80,7 +78,11 @@ def setup_scheduler( handle_riverFlow, handle_tide, handle_satpass, + handleNews, + handle_mwx, + sysinfo, ) + from modules.rss import get_rss_feed except ImportError as e: logger.warning(f"Some mesh_bot schedule features are unavailable by option disable in config.ini: {e} comment out the use of these methods in your custom_scheduler.py") @@ -103,8 +105,10 @@ def setup_scheduler( if any(option in schedulerValue for option in basicOptions): if schedulerValue == 'day': if schedulerTime: + # Specific time each day schedule.every().day.at(schedulerTime).do(send_sched_msg) else: + # Every N days schedule.every(schedulerIntervalInt).days.do(send_sched_msg) elif 'mon' in schedulerValue and schedulerTime: schedule.every().monday.at(schedulerTime).do(send_sched_msg) @@ -127,19 +131,49 @@ def setup_scheduler( logger.debug(f"System: Starting the basic scheduler to send '{scheduler_message}' on schedule '{schedulerValue}' every {schedulerIntervalInt} interval at time '{schedulerTime}' on Device:{schedulerInterface} Channel:{schedulerChannel}") elif 'joke' in schedulerValue: schedule.every(schedulerIntervalInt).minutes.do( - partial(send_message, tell_joke(), schedulerChannel, 0, schedulerInterface) + lambda: send_message(tell_joke(), schedulerChannel, 0, schedulerInterface) ) logger.debug(f"System: Starting the joke scheduler to send a joke every {schedulerIntervalInt} minutes on Device:{schedulerInterface} Channel:{schedulerChannel}") elif 'link' in schedulerValue: schedule.every(schedulerIntervalInt).hours.do( - send_message("bbslink MeshBot looking for peers", schedulerChannel, 0, schedulerInterface) + lambda: send_message("bbslink MeshBot looking for peers", schedulerChannel, 0, schedulerInterface) ) logger.debug(f"System: Starting the link scheduler to send link messages every {schedulerIntervalInt} hours on Device:{schedulerInterface} Channel:{schedulerChannel}") elif 'weather' in schedulerValue: schedule.every().day.at(schedulerTime).do( - partial(send_message, handle_wxc(0, schedulerInterface, 'wx', days=1), schedulerChannel, 0, schedulerInterface) + lambda: send_message(handle_wxc(0, schedulerInterface, 'wx', days=1), schedulerChannel, 0, schedulerInterface) ) logger.debug(f"System: Starting the weather scheduler to send weather updates every {schedulerIntervalInt} hours on Device:{schedulerInterface} Channel:{schedulerChannel}") + elif 'news' in schedulerValue: + schedule.every(schedulerIntervalInt).hours.do( + lambda: send_message(handleNews(0, schedulerInterface, 'readnews', False), schedulerChannel, 0, schedulerInterface) + ) + logger.debug(f"System: Starting the news scheduler to send news updates every {schedulerIntervalInt} hours on Device:{schedulerInterface} Channel:{schedulerChannel}") + elif 'readrss' in schedulerValue: + schedule.every(schedulerIntervalInt).hours.do( + lambda: send_message(get_rss_feed(''), schedulerChannel, 0, schedulerInterface) + ) + logger.debug(f"System: Starting the RSS scheduler to send RSS feeds every {schedulerIntervalInt} hours on Device:{schedulerInterface} Channel:{schedulerChannel}") + elif 'mwx' in schedulerValue: + schedule.every().day.at(schedulerTime).do( + lambda: send_message(handle_mwx(0, schedulerInterface, 'mwx'), schedulerChannel, 0, schedulerInterface) + ) + logger.debug(f"System: Starting the marine weather scheduler to send marine weather updates at {schedulerTime} on Device:{schedulerInterface} Channel:{schedulerChannel}") + elif 'sysinfo' in schedulerValue: + schedule.every(schedulerIntervalInt).hours.do( + lambda: send_message(sysinfo('', 0, schedulerInterface, False), schedulerChannel, 0, schedulerInterface) + ) + logger.debug(f"System: Starting the sysinfo scheduler to send system information every {schedulerIntervalInt} hours on Device:{schedulerInterface} Channel:{schedulerChannel}") + elif 'tide' in schedulerValue: + schedule.every().day.at(schedulerTime).do( + lambda: send_message(handle_tide(0, schedulerInterface, schedulerChannel), schedulerChannel, 0, schedulerInterface) + ) + logger.debug(f"System: Starting the tide scheduler to send tide information at {schedulerTime} on Device:{schedulerInterface} Channel:{schedulerChannel}") + elif 'solar' in schedulerValue: + schedule.every().day.at(schedulerTime).do( + lambda: send_message(handle_sun(0, schedulerInterface, schedulerChannel), schedulerChannel, 0, schedulerInterface) + ) + logger.debug(f"System: Starting the scheduler to send solar information at {schedulerTime} on Device:{schedulerInterface} Channel:{schedulerChannel}") elif 'custom' in schedulerValue: try: from modules.custom_scheduler import setup_custom_schedules # type: ignore @@ -151,7 +185,7 @@ def setup_scheduler( lambda: logger.info("System: Scheduled Broadcast Enabled Reminder") ) except Exception as e: - logger.warning("Custom scheduler file not found or failed to import. cp etc/custom_scheduler.py modules/custom_scheduler.py") + logger.warning("Custom scheduler file not found or failed to import. cp etc/custom_scheduler.template modules/custom_scheduler.py") except Exception as e: logger.error(f"System: Scheduler Error {e}") return True \ No newline at end of file diff --git a/update.sh b/update.sh index 9e3a92a..3cb78ff 100644 --- a/update.sh +++ b/update.sh @@ -63,6 +63,24 @@ if [[ -f "modules/custom_scheduler.py" ]]; then echo "Including custom_scheduler.py in backup..." cp modules/custom_scheduler.py data/ fi +# Check config.ini ownership and permissions +if [[ -f "config.ini" ]]; then + owner=$(stat -f "%Su" config.ini) + perms=$(stat -f "%A" config.ini) + echo "config.ini is owned by: $owner" + echo "config.ini permissions: $perms" + if [[ "$owner" == "root" ]]; then + echo "Warning: config.ini is owned by root check out the etc/set-permissions.sh script" + fi + if [[ $(stat -f "%Lp" config.ini) =~ .*[7,6,2]$ ]]; then + echo "Warning: config.ini is world-writable or world-readable! check out the etc/set-permissions.sh script" + fi + + echo "Including config.ini in backup..." + + cp config.ini data/config.backup +fi +#create the tar.gz backup tar -czf "$backup_file" "$path2backup" if [ $? -ne 0 ]; then echo "Error: Backup failed." @@ -70,7 +88,6 @@ else echo "Backup of ${path2backup} completed: ${backup_file}" fi - # Build a config_new.ini file merging user config with new defaults echo "Merging configuration files..." python3 script/configMerge.py > ini_merge_log.txt 2>&1