diff --git a/scripts/digital_wellbeing/README_THESIS_TRACKER.md b/scripts/digital_wellbeing/README_THESIS_TRACKER.md new file mode 100644 index 0000000..e950154 --- /dev/null +++ b/scripts/digital_wellbeing/README_THESIS_TRACKER.md @@ -0,0 +1,316 @@ +# Bachelor/Master's Thesis Work Tracker + +A comprehensive system to help you stay focused on your thesis by blocking distractions until you've put in your work hours. + +> **Note**: This tracker was originally requested for a bachelor thesis, but works equally well for master's thesis work. The default repository name `praca_magisterska` is Polish for "master's thesis" - you can customize this during installation. + +## Overview + +This system monitors your active windows and tracks time spent on thesis-related work. Steam and other distracting websites are blocked until you accumulate the required work time. It's designed to be as hard to circumvent as possible while remaining fair and transparent. + +## How It Works + +1. **Work Tracking**: The system monitors your active window every 5 seconds +2. **Time Accumulation**: When you're working on approved thesis applications, time accumulates +3. **Unlocking**: After reaching the work quota (default: 2 hours), distractions are unblocked +4. **Decay System**: Using Steam or distractions decays your work time (default: 30 minutes per hour) +5. **Re-blocking**: When work time falls below quota, distractions are blocked again + +## Tracked Applications + +The following applications count as "thesis work": + +### Game Engines +- **Unreal Engine** (all versions: UE4, UE5, UnrealEditor) +- **Unity Engine** (Unity Editor and Unity Hub) +- **Nvidia Omniverse** (Omniverse and Kit) + +### Development Tools +- **Visual Studio Code** - **ONLY** when working on the `praca_magisterska` repository + - The window title must contain the repository name + - Or the workspace must have the repository open + +## Blocked Sites + +When you haven't met your work quota, the following are blocked via `/etc/hosts`: + +### Gaming +- All Steam domains (steampowered.com, steamcommunity.com, etc.) + +### Social Media +- Reddit +- Twitter/X +- Facebook +- Instagram + +### Video/Entertainment +- YouTube +- Twitch +- 9gag +- Imgur + +## Installation + +### Quick Start + +```bash +# Clone or navigate to the repository +cd /path/to/scripts + +# Run the installer (will prompt for sudo) +sudo scripts/digital_wellbeing/setup_thesis_work_tracker.sh +``` + +### Custom Configuration + +```bash +# Set custom work quota (e.g., 3 hours) +sudo scripts/digital_wellbeing/setup_thesis_work_tracker.sh --work-quota 180 + +# Set custom decay rate (e.g., 20 minutes per hour) +sudo scripts/digital_wellbeing/setup_thesis_work_tracker.sh --decay-rate 20 + +# Set custom VS Code repository name +sudo scripts/digital_wellbeing/setup_thesis_work_tracker.sh --vscode-repo "my-thesis-repo" + +# Combine multiple options +sudo scripts/digital_wellbeing/setup_thesis_work_tracker.sh \ + --work-quota 150 \ + --decay-rate 25 \ + --vscode-repo "bachelor-thesis" +``` + +### Prerequisites + +The installer will check for required dependencies: +- `xdotool` - for window detection +- `systemd` - for service management + +On Arch Linux: +```bash +sudo pacman -S xdotool +``` + +On Ubuntu/Debian: +```bash +sudo apt install xdotool +``` + +## Usage + +### After Installation + +The system runs automatically as a systemd service. Just start working on your thesis! + +### Checking Your Progress + +```bash +# View current status +systemctl status thesis-work-tracker@$USER.service + +# View live logs +tail -f /var/log/thesis-work-tracker/tracker.log + +# Check your accumulated work time +sudo cat /var/lib/thesis-work-tracker/work-time.state +``` + +### Understanding the State File + +The state file shows: +- `TOTAL_WORK_SECONDS`: Your accumulated work time (in seconds) +- `STEAM_ACCESS_GRANTED`: Whether distractions are currently unblocked (1=yes, 0=no) +- `CURRENT_SESSION_SECONDS`: Time in your current work session +- `LAST_WORK_SESSION_START`: When your current session started + +### Managing the Service + +```bash +# Restart the service +sudo systemctl restart thesis-work-tracker@$USER.service + +# Stop the service temporarily +sudo systemctl stop thesis-work-tracker@$USER.service + +# Start the service +sudo systemctl start thesis-work-tracker@$USER.service + +# Disable auto-start +sudo systemctl disable thesis-work-tracker@$USER.service + +# Re-enable auto-start +sudo systemctl enable thesis-work-tracker@$USER.service +``` + +## Uninstallation + +```bash +sudo scripts/digital_wellbeing/setup_thesis_work_tracker.sh --uninstall +``` + +**Note**: This preserves your state file and logs. To completely remove everything: + +```bash +# Remove state directory +sudo chattr -i -R /var/lib/thesis-work-tracker +sudo rm -rf /var/lib/thesis-work-tracker + +# Remove logs +sudo rm -rf /var/log/thesis-work-tracker +``` + +## Security & Anti-Circumvention Features + +This system is designed to be difficult to bypass: + +### 1. **Immutable State Files** +- State files are protected with `chattr +i` (immutable flag) +- Cannot be edited even by root without removing the flag first +- Automatically re-applied after each update + +### 2. **Auto-Restart Service** +- Systemd service automatically restarts if killed +- Runs continuously in the background +- Starts automatically on boot + +### 3. **Hosts File Integration** +- Integrates with the repository's hosts guard system +- Uses immutable `/etc/hosts` file +- Cannot be easily bypassed by changing DNS + +### 4. **Process Integrity** +- Monitors actual active windows, not just running processes +- Detects if you switch away from work applications +- VS Code requires specific repository to be open + +### 5. **Decay Mechanism** +- Using Steam/distractions consumes your earned work time +- Forces sustained work habits, not just one-time work sessions +- Fair: 30 minutes of decay per hour of distraction usage + +### 6. **Locked Configuration** +- Configuration is embedded in the installed script +- Cannot be easily modified without reinstalling +- Protected script location in `/usr/local/bin` + +## Troubleshooting + +### Service Not Starting + +```bash +# Check service status +systemctl status thesis-work-tracker@$USER.service + +# Check for errors +journalctl -u thesis-work-tracker@$USER.service -n 50 + +# Verify dependencies +which xdotool +which systemctl +``` + +### Window Detection Not Working + +The tracker requires X11 and `xdotool`. Check: + +```bash +# Verify X11 is running +echo $DISPLAY + +# Test xdotool +xdotool getactivewindow getwindowname + +# Check XAUTHORITY +echo $XAUTHORITY +ls -la ~/.Xauthority +``` + +### VS Code Repository Not Detected + +Make sure: +1. The window title shows the repository name +2. You're working in the correct repository folder +3. The repository name matches what you specified during installation + +Test with: +```bash +xdotool getactivewindow getwindowname +# Should show something like: "praca_magisterska - Visual Studio Code" +``` + +### Hosts File Not Updating + +Check: +```bash +# View current hosts file +sudo cat /etc/hosts | grep steam + +# Check immutable flag +lsattr /etc/hosts + +# Service logs +tail -f /var/log/thesis-work-tracker/tracker.log +``` + +## Configuration Files + +- **Tracker Script**: `/usr/local/bin/thesis_work_tracker.sh` +- **Systemd Service**: `/etc/systemd/system/thesis-work-tracker@.service` +- **State File**: `/var/lib/thesis-work-tracker/work-time.state` +- **Log File**: `/var/log/thesis-work-tracker/tracker.log` + +## Tips for Success + +1. **Start Early**: Begin your work sessions in the morning when you're fresh +2. **Take Breaks**: The system only tracks active window time, so take regular breaks +3. **Focus Sessions**: Work in focused 2-hour blocks to unlock entertainment +4. **Monitor Progress**: Check your logs regularly to see your work patterns +5. **Be Honest**: The system trusts you're actually working when applications are open + +## FAQ + +### Can I bypass this system? + +Technically yes, but it's designed to make bypassing more effort than just doing the work: +- You'd need to disable the service (but it auto-restarts) +- You'd need to modify immutable files (requires chattr commands) +- You'd need to fake window activity (complex) +- You'd need to edit protected state files (also complex) + +The point isn't to make it impossible, but to add enough friction that doing your thesis work is easier. + +### What if I need to use VS Code for something else? + +VS Code only counts as work when you're in the `praca_magisterska` repository. Other projects won't count toward your thesis time. + +### Can I adjust the work quota after installation? + +Yes, but you need to: +1. Uninstall the current system +2. Reinstall with new parameters +3. Your accumulated time is preserved in the state file + +### Does this work on Wayland? + +Currently, this requires X11 for `xdotool` window detection. Wayland support would require adapting to use different tools like `wlrctl` or `swaymsg`. + +### What happens if I reboot? + +The service starts automatically on boot, and your accumulated work time is preserved in the state file. + +## License + +This is part of the kuhyx/scripts repository. Use at your own risk and discretion. + +## Contributing + +Found a bug or have a suggestion? Please open an issue in the main repository. + +## Acknowledgments + +This tool is built on top of the digital wellbeing framework in this repository, including: +- Hosts guard system +- Psychological friction mechanisms +- Systemd service patterns + +Good luck with your bachelor thesis! 🎓 diff --git a/scripts/digital_wellbeing/setup_thesis_work_tracker.sh b/scripts/digital_wellbeing/setup_thesis_work_tracker.sh new file mode 100755 index 0000000..8a72eb0 --- /dev/null +++ b/scripts/digital_wellbeing/setup_thesis_work_tracker.sh @@ -0,0 +1,365 @@ +#!/bin/bash +# Bachelor Thesis Work Tracker - One-Shot Installer +# +# This script installs a system that: +# 1. Monitors active windows for thesis-related work (Unreal Engine, Unity, Nvidia Omniverse, VS Code with specific repo) +# 2. Tracks accumulated work time with protection against tampering +# 3. Blocks Steam and other distractions via /etc/hosts until work quota is met +# 4. Provides psychological friction against circumvention +# +# The system is designed to be as hard to circumvent as possible: +# - State files are immutable (chattr +i) +# - Runs as a systemd service that auto-restarts +# - Integrated with hosts guard system +# - Protected against easy time manipulation +# +# Usage: +# sudo ./setup_thesis_work_tracker.sh [options] +# +# Options: +# --work-quota MINUTES Set required work time in minutes (default: 120 = 2 hours) +# --decay-rate MINUTES Set decay rate per hour of distraction usage (default: 30) +# --vscode-repo NAME Set required VS Code repository name (default: praca_magisterska) +# --dry-run Show what would be done without making changes +# --uninstall Remove the thesis work tracker system +# -h|--help Show this help +# +# Exit codes: +# 0 = success +# 1 = general failure +# 2 = argument error + +set -euo pipefail + +###################################################################### +# Configuration Defaults +###################################################################### +WORK_QUOTA_MINUTES=120 # 2 hours of work required +DECAY_RATE_MINUTES=30 # Lose 30 minutes per hour of Steam usage +VSCODE_REPO="praca_magisterska" +DRY_RUN=0 +UNINSTALL=0 + +###################################################################### +# Paths +###################################################################### +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +TRACKER_SCRIPT="$SCRIPT_DIR/thesis_work_tracker.sh" +STATUS_SCRIPT="$SCRIPT_DIR/thesis_work_status.sh" +SERVICE_FILE="$SCRIPT_DIR/systemd/thesis-work-tracker@.service" +INSTALL_BIN="/usr/local/bin/thesis_work_tracker.sh" +INSTALL_STATUS="/usr/local/bin/thesis_work_status" +INSTALL_SERVICE="/etc/systemd/system/thesis-work-tracker@.service" +STATE_DIR="/var/lib/thesis-work-tracker" +LOG_DIR="/var/log/thesis-work-tracker" + +###################################################################### +# Colors and Logging +###################################################################### +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +BOLD='\033[1m' +NC='\033[0m' # No Color + +msg() { printf "${GREEN}[+]${NC} %s\n" "$*"; } +note() { printf "${BLUE}[i]${NC} %s\n" "$*"; } +warn() { printf "${YELLOW}[!]${NC} %s\n" "$*"; } +err() { printf "${RED}[x]${NC} %s\n" "$*" >&2; } + +run() { + if [[ $DRY_RUN -eq 1 ]]; then + printf '%s[DRY-RUN]%s ' "${CYAN}" "${NC}" + printf '%q ' "$@" + printf '\n' + else + "$@" + fi +} + +###################################################################### +# Helpers +###################################################################### +require_root() { + if [[ $EUID -ne 0 ]]; then + exec sudo -E bash "$0" "$@" + fi +} + +usage() { + head -n 31 "$0" | tail -n +2 | sed 's/^# \{0,1\}//' +} + +check_dependencies() { + local missing=() + + for cmd in xdotool systemctl; do + if ! command -v "$cmd" &> /dev/null; then + missing+=("$cmd") + fi + done + + if [[ ${#missing[@]} -gt 0 ]]; then + err "Missing required dependencies: ${missing[*]}" + note "Install them with: sudo pacman -S ${missing[*]}" + return 1 + fi +} + +get_current_user() { + # Get the user who invoked sudo, or current user if not using sudo + if [[ -n ${SUDO_USER:-} ]]; then + echo "$SUDO_USER" + else + whoami + fi +} + +###################################################################### +# Parse Arguments +###################################################################### +while [[ $# -gt 0 ]]; do + case "$1" in + --work-quota) + WORK_QUOTA_MINUTES="${2:-}" + [[ -z $WORK_QUOTA_MINUTES ]] && { + err "--work-quota requires a value" + exit 2 + } + if ! [[ $WORK_QUOTA_MINUTES =~ ^[0-9]+$ ]] || [[ $WORK_QUOTA_MINUTES -le 0 ]]; then + err "--work-quota must be a positive integer (got: $WORK_QUOTA_MINUTES)" + exit 2 + fi + shift 2 + ;; + --decay-rate) + DECAY_RATE_MINUTES="${2:-}" + [[ -z $DECAY_RATE_MINUTES ]] && { + err "--decay-rate requires a value" + exit 2 + } + if ! [[ $DECAY_RATE_MINUTES =~ ^[0-9]+$ ]] || [[ $DECAY_RATE_MINUTES -lt 0 ]]; then + err "--decay-rate must be a non-negative integer (got: $DECAY_RATE_MINUTES)" + exit 2 + fi + shift 2 + ;; + --vscode-repo) + VSCODE_REPO="${2:-}" + [[ -z $VSCODE_REPO ]] && { + err "--vscode-repo requires a value" + exit 2 + } + shift 2 + ;; + --dry-run) + DRY_RUN=1 + shift + ;; + --uninstall) + UNINSTALL=1 + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + err "Unknown option: $1" + usage + exit 2 + ;; + esac +done + +###################################################################### +# Main Functions +###################################################################### + +uninstall_tracker() { + msg "Uninstalling thesis work tracker..." + + # Get current user for service name + local user + user=$(get_current_user) + + # Stop and disable service + if systemctl is-active --quiet "thesis-work-tracker@$user.service" 2>/dev/null; then + run systemctl stop "thesis-work-tracker@$user.service" + fi + + if systemctl is-enabled --quiet "thesis-work-tracker@$user.service" 2>/dev/null; then + run systemctl disable "thesis-work-tracker@$user.service" + fi + + # Remove service file + if [[ -f $INSTALL_SERVICE ]]; then + run rm -f "$INSTALL_SERVICE" + run systemctl daemon-reload + fi + + # Remove tracker script + if [[ -f $INSTALL_BIN ]]; then + run rm -f "$INSTALL_BIN" + fi + + # Remove status script + if [[ -f $INSTALL_STATUS ]]; then + run rm -f "$INSTALL_STATUS" + fi + + # Remove state directory (with immutable flags removed) + if [[ -d $STATE_DIR ]]; then + run chattr -i -R "$STATE_DIR" 2>/dev/null || true + note "State directory preserved at: $STATE_DIR" + note "To completely remove state: sudo rm -rf $STATE_DIR" + fi + + msg "Thesis work tracker uninstalled successfully" + note "Log files preserved at: $LOG_DIR" +} + +install_tracker() { + msg "Installing thesis work tracker..." + + # Check dependencies + check_dependencies || exit 1 + + # Verify source files exist + if [[ ! -f $TRACKER_SCRIPT ]]; then + err "Tracker script not found: $TRACKER_SCRIPT" + exit 1 + fi + + if [[ ! -f $STATUS_SCRIPT ]]; then + err "Status script not found: $STATUS_SCRIPT" + exit 1 + fi + + if [[ ! -f $SERVICE_FILE ]]; then + err "Service file not found: $SERVICE_FILE" + exit 1 + fi + + # Create directories + msg "Creating directories..." + run mkdir -p "$LOG_DIR" + run chmod 755 "$LOG_DIR" + + # Install tracker script with configuration + msg "Installing tracker script to $INSTALL_BIN..." + + # Copy script and update configuration values + run cp "$TRACKER_SCRIPT" "$INSTALL_BIN" + + # Update configuration in the installed script + local work_quota_seconds=$((WORK_QUOTA_MINUTES * 60)) + local decay_rate_seconds=$((DECAY_RATE_MINUTES * 60)) + + run sed -i "s/^WORK_QUOTA_REQUIRED=.*/WORK_QUOTA_REQUIRED=$work_quota_seconds # $WORK_QUOTA_MINUTES minutes/" "$INSTALL_BIN" + run sed -i "s/^WORK_DECAY_PER_HOUR=.*/WORK_DECAY_PER_HOUR=$decay_rate_seconds # $DECAY_RATE_MINUTES minutes/" "$INSTALL_BIN" + run sed -i "s/^VSCODE_REQUIRED_REPO=.*/VSCODE_REQUIRED_REPO=\"$VSCODE_REPO\"/" "$INSTALL_BIN" + + run chmod 755 "$INSTALL_BIN" + + # Install status script + msg "Installing status script to $INSTALL_STATUS..." + run cp "$STATUS_SCRIPT" "$INSTALL_STATUS" + + # Update quota in status script to match + run sed -i "s/^WORK_QUOTA_REQUIRED=.*/WORK_QUOTA_REQUIRED=$work_quota_seconds # $WORK_QUOTA_MINUTES minutes/" "$INSTALL_STATUS" + + run chmod 755 "$INSTALL_STATUS" + + # Install systemd service + msg "Installing systemd service..." + run cp "$SERVICE_FILE" "$INSTALL_SERVICE" + run chmod 644 "$INSTALL_SERVICE" + run systemctl daemon-reload + + # Get current user for service enablement + local user + user=$(get_current_user) + + # Enable and start service + msg "Enabling and starting service for user: $user..." + run systemctl enable "thesis-work-tracker@$user.service" + run systemctl restart "thesis-work-tracker@$user.service" + + # Wait a moment for service to start + sleep 2 + + # Check service status + if systemctl is-active --quiet "thesis-work-tracker@$user.service"; then + msg "Service started successfully!" + else + warn "Service may not have started properly. Check status with:" + warn " systemctl status thesis-work-tracker@$user.service" + fi + + # Display configuration summary + echo "" + echo "╔════════════════════════════════════════════════════════════════╗" + echo "║ Bachelor Thesis Work Tracker - Installation ║" + echo "╚════════════════════════════════════════════════════════════════╝" + echo "" + echo "Configuration:" + echo " • Work quota required: ${BOLD}${WORK_QUOTA_MINUTES} minutes${NC}" + echo " • Decay rate (per hour of Steam): ${BOLD}${DECAY_RATE_MINUTES} minutes${NC}" + echo " • VS Code repository: ${BOLD}${VSCODE_REPO}${NC}" + echo "" + echo "Tracked Applications:" + echo " ✓ Unreal Engine (all versions)" + echo " ✓ Unity Editor" + echo " ✓ Nvidia Omniverse" + echo " ✓ Visual Studio Code (only when working on '$VSCODE_REPO')" + echo "" + echo "Blocked Sites (until quota met):" + echo " ⛔ Steam (all domains)" + echo " ⛔ Social media (Reddit, Twitter, Facebook, Instagram)" + echo " ⛔ Video sites (YouTube, Twitch)" + echo " ⛔ Other distractions (9gag, Imgur)" + echo "" + echo "System Protection Features:" + echo " 🔒 State files protected with immutable flags" + echo " 🔒 Auto-restart on failure" + echo " 🔒 Integrated with hosts guard system" + echo " 🔒 Continuous monitoring every 5 seconds" + echo "" + echo "How it works:" + echo " 1. Work on your thesis using the approved applications" + echo " 2. Time accumulates in the background" + echo " 3. After ${WORK_QUOTA_MINUTES} minutes of work, Steam is unblocked" + echo " 4. Steam usage decays your work time at ${DECAY_RATE_MINUTES} min/hour" + echo " 5. When work time drops below quota, Steam is blocked again" + echo "" + echo "Useful Commands:" + echo " • Check progress: thesis_work_status" + echo " • Check status: systemctl status thesis-work-tracker@$user.service" + echo " • View logs: tail -f $LOG_DIR/tracker.log" + echo " • View state: sudo cat $STATE_DIR/work-time.state" + echo " • Restart: sudo systemctl restart thesis-work-tracker@$user.service" + echo " • Uninstall: sudo $0 --uninstall" + echo "" + echo "⚠️ IMPORTANT: This system is designed to be hard to circumvent!" + echo " State files are immutable and the service auto-restarts." + echo " To legitimately modify settings, uninstall and reinstall." + echo "" + echo "Good luck with your bachelor thesis! 🎓" + echo "" +} + +###################################################################### +# Main +###################################################################### +require_root "$@" + +if [[ $UNINSTALL -eq 1 ]]; then + uninstall_tracker +else + install_tracker +fi + +exit 0 diff --git a/scripts/digital_wellbeing/systemd/thesis-work-tracker@.service b/scripts/digital_wellbeing/systemd/thesis-work-tracker@.service new file mode 100644 index 0000000..3237f58 --- /dev/null +++ b/scripts/digital_wellbeing/systemd/thesis-work-tracker@.service @@ -0,0 +1,29 @@ +[Unit] +Description=Bachelor Thesis Work Tracker +Documentation=man:systemd.service(5) +After=graphical.target +Wants=graphical.target + +[Service] +Type=simple +ExecStart=/usr/local/bin/thesis_work_tracker.sh +Restart=always +RestartSec=10 + +# Run as the user who is logged in (for X11/window detection) +User=%i +Environment="DISPLAY=:0" +Environment="XAUTHORITY=/home/%i/.Xauthority" + +# Logging +StandardOutput=append:/var/log/thesis-work-tracker/tracker.log +StandardError=append:/var/log/thesis-work-tracker/tracker.log + +# Security hardening +NoNewPrivileges=false +PrivateTmp=true +ProtectSystem=false +ProtectHome=false + +[Install] +WantedBy=default.target diff --git a/scripts/digital_wellbeing/thesis_work_status.sh b/scripts/digital_wellbeing/thesis_work_status.sh new file mode 100755 index 0000000..92c7859 --- /dev/null +++ b/scripts/digital_wellbeing/thesis_work_status.sh @@ -0,0 +1,144 @@ +#!/bin/bash +# Quick status checker for thesis work tracker +# Shows current work progress and access status + +set -euo pipefail + +STATE_FILE="/var/lib/thesis-work-tracker/work-time.state" + +# Colors +GREEN='\033[0;32m' +RED='\033[0;31m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +BOLD='\033[1m' +NC='\033[0m' + +# Check if state file exists +if [[ ! -f $STATE_FILE ]]; then + echo -e "${RED}Error:${NC} Thesis work tracker is not installed or has not been initialized." + echo "Install with: sudo scripts/digital_wellbeing/setup_thesis_work_tracker.sh" + exit 1 +fi + +# Load state (need sudo to read immutable file) +if [[ $EUID -ne 0 ]]; then + exec sudo -E bash "$0" "$@" +fi + +# Temporarily remove immutable to read +sudo chattr -i "$STATE_FILE" 2>/dev/null || true + +# Parse state file safely without using source +# Only extract the numeric values we need +TOTAL_WORK_SECONDS=$(grep "^TOTAL_WORK_SECONDS=" "$STATE_FILE" 2>/dev/null | cut -d= -f2 || echo "0") +STEAM_ACCESS_GRANTED=$(grep "^STEAM_ACCESS_GRANTED=" "$STATE_FILE" 2>/dev/null | cut -d= -f2 || echo "0") +CURRENT_SESSION_SECONDS=$(grep "^CURRENT_SESSION_SECONDS=" "$STATE_FILE" 2>/dev/null | cut -d= -f2 || echo "0") +LAST_WORK_SESSION_START=$(grep "^LAST_WORK_SESSION_START=" "$STATE_FILE" 2>/dev/null | cut -d= -f2 || echo "0") + +# Validate that values are numeric +if ! [[ $TOTAL_WORK_SECONDS =~ ^[0-9]+$ ]]; then TOTAL_WORK_SECONDS=0; fi +if ! [[ $STEAM_ACCESS_GRANTED =~ ^[01]$ ]]; then STEAM_ACCESS_GRANTED=0; fi +if ! [[ $CURRENT_SESSION_SECONDS =~ ^[0-9]+$ ]]; then CURRENT_SESSION_SECONDS=0; fi +if ! [[ $LAST_WORK_SESSION_START =~ ^[0-9]+$ ]]; then LAST_WORK_SESSION_START=0; fi + +# Re-apply immutable +sudo chattr +i "$STATE_FILE" 2>/dev/null || true + +# Default values if not set +TOTAL_WORK_SECONDS=${TOTAL_WORK_SECONDS:-0} +STEAM_ACCESS_GRANTED=${STEAM_ACCESS_GRANTED:-0} +CURRENT_SESSION_SECONDS=${CURRENT_SESSION_SECONDS:-0} +LAST_WORK_SESSION_START=${LAST_WORK_SESSION_START:-0} + +# Constants (should match tracker script) +WORK_QUOTA_REQUIRED=7200 # 2 hours default + +# Calculate values +work_minutes=$((TOTAL_WORK_SECONDS / 60)) +work_hours=$((work_minutes / 60)) +work_remaining_minutes=$((work_minutes % 60)) + +quota_minutes=$((WORK_QUOTA_REQUIRED / 60)) +quota_hours=$((quota_minutes / 60)) +quota_remaining_minutes=$((quota_minutes % 60)) + +remaining_seconds=$((WORK_QUOTA_REQUIRED - TOTAL_WORK_SECONDS)) +if [[ $remaining_seconds -lt 0 ]]; then + remaining_seconds=0 +fi +remaining_minutes=$((remaining_seconds / 60)) + +session_minutes=$((CURRENT_SESSION_SECONDS / 60)) + +percentage=$((TOTAL_WORK_SECONDS * 100 / WORK_QUOTA_REQUIRED)) +if [[ $percentage -gt 100 ]]; then + percentage=100 +fi + +# Display status +echo "" +echo "╔════════════════════════════════════════════════════════════════╗" +echo "║ Bachelor Thesis Work Tracker - Status ║" +echo "╚════════════════════════════════════════════════════════════════╝" +echo "" + +# Work progress +echo -e "${BOLD}Work Progress:${NC}" +echo -e " Total work time: ${GREEN}${work_hours}h ${work_remaining_minutes}m${NC}" +echo -e " Required quota: ${BLUE}${quota_hours}h ${quota_remaining_minutes}m${NC}" + +# Progress bar +echo -n " Progress: [" +bar_length=40 +filled=$((percentage * bar_length / 100)) +for ((i=0; i/dev/null; then + echo -e " Tracker daemon: ${GREEN}RUNNING${NC} ✓" +else + echo -e " Tracker daemon: ${RED}NOT RUNNING${NC} ⚠" + echo -e " ${YELLOW}Start with: sudo systemctl start thesis-work-tracker@\$(whoami).service${NC}" +fi +echo "" + +# Useful commands +echo -e "${BOLD}Useful Commands:${NC}" +echo " • View live logs: tail -f /var/log/thesis-work-tracker/tracker.log" +echo " • Service status: systemctl status thesis-work-tracker@\$(whoami).service" +echo " • Restart tracker: sudo systemctl restart thesis-work-tracker@\$(whoami).service" +echo "" diff --git a/scripts/digital_wellbeing/thesis_work_tracker.sh b/scripts/digital_wellbeing/thesis_work_tracker.sh new file mode 100755 index 0000000..e7366d4 --- /dev/null +++ b/scripts/digital_wellbeing/thesis_work_tracker.sh @@ -0,0 +1,465 @@ +#!/bin/bash +# Bachelor Thesis Work Tracker +# Monitors active windows for thesis-related work (Unreal Engine, Unity, Nvidia Omniverse, VS Code with specific repo) +# Unlocks Steam and other distractions only after sufficient work time is accumulated +# +# This daemon runs continuously and: +# 1. Tracks active window time for approved thesis work applications +# 2. Maintains a protected state file with accumulated work time +# 3. Manages hosts file blocking/unblocking based on work quota +# 4. Provides psychological friction against circumvention + +set -euo pipefail + +# Configuration +SCRIPT_DIR="$(dirname "$(readlink -f "$0")")" +STATE_DIR="/var/lib/thesis-work-tracker" +STATE_FILE="$STATE_DIR/work-time.state" +LOCK_FILE="$STATE_DIR/tracker.lock" +LOG_DIR="/var/log/thesis-work-tracker" +LOG_FILE="$LOG_DIR/tracker.log" +CHECK_INTERVAL=5 # Check every 5 seconds + +# Work requirements (in seconds) +# 2 hours of work = 7200 seconds required before Steam access +WORK_QUOTA_REQUIRED=7200 # 2 hours +WORK_DECAY_PER_HOUR=1800 # Lose 30 minutes per hour of Steam usage + +# Thesis work applications - process names and window patterns +# These are the applications that count as "thesis work" +declare -A THESIS_APPS=( + ["UnrealEditor"]="Unreal Engine" + ["UE4Editor"]="Unreal Engine 4" + ["UE5Editor"]="Unreal Engine 5" + ["Unity"]="Unity Editor" + ["UnityHub"]="Unity Hub" + ["Code"]="Visual Studio Code" # Special handling for repo check + ["code"]="Visual Studio Code" # lowercase variant + ["omniverse"]="Nvidia Omniverse" + ["kit"]="Nvidia Omniverse Kit" +) + +# VS Code specific repo to track +VSCODE_REQUIRED_REPO="praca_magisterska" + +# Steam and distraction patterns for hosts blocking +STEAM_DOMAINS=( + "steampowered.com" + "steamcommunity.com" + "steamgames.com" + "store.steampowered.com" + "steamcdn-a.akamaihd.net" + "steamstatic.com" + "steamusercontent.com" +) + +# Additional distraction sites that should be blocked +DISTRACTION_DOMAINS=( + "reddit.com" + "twitter.com" + "x.com" + "facebook.com" + "instagram.com" + "youtube.com" + "twitch.tv" + "9gag.com" + "imgur.com" +) + +# Colors for logging +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[0;33m' +BLUE='\033[0;34m' +CYAN='\033[0;36m' +BOLD='\033[1m' +NC='\033[0m' # No Color + +# Logging function +log_message() { + local level="$1" + shift + local message="$*" + local timestamp + timestamp=$(date '+%Y-%m-%d %H:%M:%S') + echo "[${timestamp}] [${level}] ${message}" | tee -a "$LOG_FILE" +} + +log_info() { log_message "INFO" "$@"; } +log_warn() { log_message "WARN" "$@"; } +log_error() { log_message "ERROR" "$@"; } +log_debug() { + if [[ ${DEBUG:-0} -eq 1 ]]; then + log_message "DEBUG" "$@" + fi +} + +# Initialize directories and state file +init_state() { + # Create directories with proper permissions + if [[ ! -d $STATE_DIR ]]; then + sudo mkdir -p "$STATE_DIR" + sudo chmod 700 "$STATE_DIR" + fi + + if [[ ! -d $LOG_DIR ]]; then + sudo mkdir -p "$LOG_DIR" + sudo chmod 755 "$LOG_DIR" + fi + + # Initialize state file if it doesn't exist + if [[ ! -f $STATE_FILE ]]; then + cat < /dev/null +# Thesis Work Tracker State File +# DO NOT EDIT MANUALLY - Managed by thesis_work_tracker daemon +# Last updated: $(date) + +TOTAL_WORK_SECONDS=0 +LAST_UPDATE_TIMESTAMP=$(date +%s) +STEAM_ACCESS_GRANTED=0 +LAST_WORK_SESSION_START=0 +CURRENT_SESSION_SECONDS=0 +EOF + sudo chmod 600 "$STATE_FILE" + if ! sudo chattr +i "$STATE_FILE" 2>/dev/null; then + log_warn "Failed to set immutable flag on state file - protections may be weaker" + fi + fi +} + +# Load current state from file +load_state() { + if [[ ! -f $STATE_FILE ]]; then + log_error "State file not found: $STATE_FILE" + return 1 + fi + + # Temporarily remove immutable flag to read + sudo chattr -i "$STATE_FILE" 2>/dev/null || true + + # Parse state file safely without using source + # Only extract the numeric values we need + TOTAL_WORK_SECONDS=$(grep "^TOTAL_WORK_SECONDS=" "$STATE_FILE" 2>/dev/null | cut -d= -f2 || echo "0") + STEAM_ACCESS_GRANTED=$(grep "^STEAM_ACCESS_GRANTED=" "$STATE_FILE" 2>/dev/null | cut -d= -f2 || echo "0") + CURRENT_SESSION_SECONDS=$(grep "^CURRENT_SESSION_SECONDS=" "$STATE_FILE" 2>/dev/null | cut -d= -f2 || echo "0") + LAST_WORK_SESSION_START=$(grep "^LAST_WORK_SESSION_START=" "$STATE_FILE" 2>/dev/null | cut -d= -f2 || echo "0") + LAST_UPDATE_TIMESTAMP=$(grep "^LAST_UPDATE_TIMESTAMP=" "$STATE_FILE" 2>/dev/null | cut -d= -f2 || echo "0") + + # Validate that values are numeric + if ! [[ $TOTAL_WORK_SECONDS =~ ^[0-9]+$ ]]; then TOTAL_WORK_SECONDS=0; fi + if ! [[ $STEAM_ACCESS_GRANTED =~ ^[01]$ ]]; then STEAM_ACCESS_GRANTED=0; fi + if ! [[ $CURRENT_SESSION_SECONDS =~ ^[0-9]+$ ]]; then CURRENT_SESSION_SECONDS=0; fi + if ! [[ $LAST_WORK_SESSION_START =~ ^[0-9]+$ ]]; then LAST_WORK_SESSION_START=0; fi + + # Re-apply immutable flag + sudo chattr +i "$STATE_FILE" 2>/dev/null || true +} + +# Save current state to file +save_state() { + local total_work="$1" + local steam_access="$2" + local current_session="$3" + local session_start="$4" + + # Remove immutable flag + sudo chattr -i "$STATE_FILE" 2>/dev/null || true + + # Write new state + cat < /dev/null +# Thesis Work Tracker State File +# DO NOT EDIT MANUALLY - Managed by thesis_work_tracker daemon +# Last updated: $(date) + +TOTAL_WORK_SECONDS=$total_work +LAST_UPDATE_TIMESTAMP=$(date +%s) +STEAM_ACCESS_GRANTED=$steam_access +LAST_WORK_SESSION_START=$session_start +CURRENT_SESSION_SECONDS=$current_session +EOF + + sudo chmod 600 "$STATE_FILE" + # Re-apply immutable flag + if ! sudo chattr +i "$STATE_FILE" 2>/dev/null; then + log_warn "Failed to set immutable flag on state file after save" + fi +} + +# Check if a process is running +is_process_running() { + local process_name="$1" + pgrep -x "$process_name" > /dev/null 2>&1 +} + +# Get active window title and process name +get_active_window_info() { + if ! command -v xdotool &> /dev/null; then + log_error "xdotool not installed, cannot detect active window" + return 1 + fi + + local active_window_id + active_window_id=$(xdotool getactivewindow 2>/dev/null || echo "") + + if [[ -z $active_window_id ]]; then + return 1 + fi + + local window_name + window_name=$(xdotool getwindowname "$active_window_id" 2>/dev/null || echo "") + + local window_pid + window_pid=$(xdotool getwindowpid "$active_window_id" 2>/dev/null || echo "") + + local process_name="" + if [[ -n $window_pid ]]; then + process_name=$(ps -p "$window_pid" -o comm= 2>/dev/null || echo "") + fi + + echo "${process_name}|${window_name}" +} + +# Check if VS Code is working on the required repository +is_vscode_on_thesis_repo() { + local window_title="$1" + + # VS Code window titles typically contain the folder/workspace name + # Look for the repo name in the window title + # Window title format is usually: "filename - reponame - Visual Studio Code" + if [[ $window_title == *"$VSCODE_REQUIRED_REPO"* ]]; then + return 0 + fi + + return 1 +} + +# Check if current active window is thesis work +is_thesis_work_active() { + local window_info + window_info=$(get_active_window_info) + + if [[ -z $window_info ]]; then + return 1 + fi + + local process_name + local window_title + IFS='|' read -r process_name window_title <<< "$window_info" + + log_debug "Active window: process='$process_name' title='$window_title'" + + # Check each thesis application + for proc_pattern in "${!THESIS_APPS[@]}"; do + local app_name="${THESIS_APPS[$proc_pattern]}" + + # Check window title for application name (more reliable than process name) + if [[ $window_title == *"$app_name"* ]]; then + # Special handling for VS Code - must be on thesis repo + if [[ $proc_pattern == "Code" ]] || [[ $proc_pattern == "code" ]]; then + if is_vscode_on_thesis_repo "$window_title"; then + log_debug "Thesis work detected: VS Code on $VSCODE_REQUIRED_REPO" + return 0 + else + log_debug "VS Code detected but not on thesis repo" + continue + fi + fi + + log_debug "Thesis work detected: $app_name" + return 0 + fi + + # Also check process name with exact match + if [[ $process_name == "$proc_pattern" ]]; then + # Special handling for VS Code - must be on thesis repo + if [[ $proc_pattern == "Code" ]] || [[ $proc_pattern == "code" ]]; then + if is_vscode_on_thesis_repo "$window_title"; then + log_debug "Thesis work detected: VS Code on $VSCODE_REQUIRED_REPO" + return 0 + else + log_debug "VS Code detected but not on thesis repo" + continue + fi + fi + + log_debug "Thesis work detected: $app_name" + return 0 + fi + done + + return 1 +} + +# Block Steam and distractions in /etc/hosts +block_distractions() { + log_info "Blocking Steam and distractions in /etc/hosts" + + # Remove immutable flag temporarily + sudo chattr -i /etc/hosts 2>/dev/null || true + + # Add blocking entries if not already present + local hosts_modified=0 + + for domain in "${STEAM_DOMAINS[@]}" "${DISTRACTION_DOMAINS[@]}"; do + if ! grep -q "^0.0.0.0[[:space:]]*$domain" /etc/hosts 2>/dev/null; then + echo "0.0.0.0 $domain" | sudo tee -a /etc/hosts > /dev/null + hosts_modified=1 + fi + done + + # Re-apply immutable flag + sudo chattr +i /etc/hosts 2>/dev/null || true + + if [[ $hosts_modified -eq 1 ]]; then + log_info "Added distraction blocks to /etc/hosts" + fi +} + +# Unblock Steam and distractions from /etc/hosts +unblock_distractions() { + log_info "Unblocking Steam and distractions in /etc/hosts" + + # Remove immutable flag temporarily + sudo chattr -i /etc/hosts 2>/dev/null || true + + # Remove blocking entries using mktemp for security + local temp_hosts + temp_hosts=$(mktemp) || { + log_error "Failed to create temporary file" + return 1 + } + + sudo cp /etc/hosts "$temp_hosts" + + for domain in "${STEAM_DOMAINS[@]}" "${DISTRACTION_DOMAINS[@]}"; do + sudo sed -i "/^0.0.0.0[[:space:]]*$domain/d" "$temp_hosts" + done + + sudo mv "$temp_hosts" /etc/hosts + sudo chmod 644 /etc/hosts + + # Re-apply immutable flag + sudo chattr +i /etc/hosts 2>/dev/null || true + + log_info "Removed distraction blocks from /etc/hosts" +} + +# Check if Steam is currently running (to track decay) +is_steam_running() { + pgrep -x "steam" > /dev/null 2>&1 +} + +# Main tracking loop +main_loop() { + log_info "Starting thesis work tracker daemon" + + # Initialize state + init_state + + # Load initial state + load_state + + local total_work_seconds=${TOTAL_WORK_SECONDS:-0} + local steam_access=${STEAM_ACCESS_GRANTED:-0} + local session_start=${LAST_WORK_SESSION_START:-0} + local session_seconds=${CURRENT_SESSION_SECONDS:-0} + + # Apply initial blocking state + if [[ $steam_access -eq 0 ]]; then + block_distractions + fi + + local last_status_log=$(date +%s) + local last_decay_check=$(date +%s) + + while true; do + local current_time=$(date +%s) + + # Check if thesis work is active + if is_thesis_work_active; then + # Track work time + if [[ $session_start -eq 0 ]]; then + session_start=$current_time + log_info "Thesis work session started" + fi + + # Increment session time + session_seconds=$((session_seconds + CHECK_INTERVAL)) + total_work_seconds=$((total_work_seconds + CHECK_INTERVAL)) + + # Check if we've reached the quota + if [[ $total_work_seconds -ge $WORK_QUOTA_REQUIRED ]] && [[ $steam_access -eq 0 ]]; then + log_info "Work quota reached! Granting Steam access." + steam_access=1 + unblock_distractions + fi + + else + # No thesis work active + if [[ $session_start -ne 0 ]]; then + log_info "Thesis work session ended. Session duration: $((session_seconds / 60)) minutes" + session_start=0 + session_seconds=0 + fi + + # Check for Steam usage and apply decay + if [[ $steam_access -eq 1 ]] && is_steam_running; then + local time_since_decay=$((current_time - last_decay_check)) + if [[ $time_since_decay -ge 3600 ]]; then # Every hour + total_work_seconds=$((total_work_seconds - WORK_DECAY_PER_HOUR)) + if [[ $total_work_seconds -lt 0 ]]; then + total_work_seconds=0 + fi + last_decay_check=$current_time + log_info "Steam usage detected. Applied decay. Remaining work time: $((total_work_seconds / 60)) minutes" + + # Revoke access if below quota + if [[ $total_work_seconds -lt $WORK_QUOTA_REQUIRED ]]; then + log_info "Work quota depleted. Revoking Steam access." + steam_access=0 + block_distractions + fi + fi + fi + fi + + # Save state periodically + save_state "$total_work_seconds" "$steam_access" "$session_seconds" "$session_start" + + # Log status every 5 minutes + if [[ $((current_time - last_status_log)) -ge 300 ]]; then + local work_minutes=$((total_work_seconds / 60)) + local quota_minutes=$((WORK_QUOTA_REQUIRED / 60)) + local remaining_minutes=$((quota_minutes - work_minutes)) + if [[ $remaining_minutes -lt 0 ]]; then + remaining_minutes=0 + fi + + log_info "Status: Total work time: ${work_minutes}m / ${quota_minutes}m | Steam access: $steam_access | Need: ${remaining_minutes}m more" + last_status_log=$current_time + fi + + sleep "$CHECK_INTERVAL" + done +} + +# Handle signals for graceful shutdown +cleanup() { + log_info "Received shutdown signal, saving state and exiting" + rm -f "$LOCK_FILE" + exit 0 +} + +trap cleanup SIGTERM SIGINT + +# Check for lock file to prevent multiple instances +if [[ -f $LOCK_FILE ]]; then + log_error "Another instance is already running (lock file exists: $LOCK_FILE)" + exit 1 +fi + +# Create lock file +touch "$LOCK_FILE" + +# Run main loop +main_loop