Skip to content

Multiple issues to fix - patch provided #20

@StoicTheVast

Description

@StoicTheVast

Hi,

Thanks for the project. Unfortunately I had a number of issues getting it to work on my kids' computer (zorin-os 6, based on ubuntu 12.04 LTS).

Summary:

  • AT wasn't working out of the box - it was silently failing. I needed to create /var/spool/cron/atjobs/.SEQ owned by daemon:daemon for at to start working properly
  • Added the file /usr/lib/pm-utils/sleep.d/99zzkidtimer as suggested in another issue. Note this file must be executable! (chmod +x)
  • The use of /usr/bin/users to determine who is logged in is a kludge. It ignores locked screens (screensaver active) and switched user states. I don't have a fix for that right now - instead I just told the kids to log off when they are done rather than let the screensaver kick in, and log off rather than switch users between them. Not ideal. Any ideas about how to elegantly detect these subtle differences in current computer usage state????

In /usr/local/bin/kidtimer, I had to make the following changes:

  • Tweaked the timer countdown to not go negative, even if other things fail...
  • The script files created to do the warnings/logouts in /tmp weren't executable. They need to be chmod +x
  • /usr/local/bin/kidtimer shutdown is not a supported option - instead I have changed it to use go_logout if /tmp/kidtimer.shutdown.$I doesn't exist, as is done elsewhere in the code.
  • The user notification wasn't working because the x display detection using who doesn't work unless the user has an active shell running (my kids don't do that!). So instead I simply iterate through the first 10 displays and send the notification with the desired user to all of them - it fails gracefully on the screens that don't exist or have the wrong username, passes on the ones it needs to.
  • I couldn't decide if, when killing a user's processes they should be sent a TERM first before the KILL. Firefox will nicely reload its tabs if KILL'ed, but maybe other progs like Open Office would prefer to be TERM'ed first so they can clean up properly and only be KILL'ed if they fail to exit themselves.

Here is my patch to /usr/local/bin/kidtimer ...

--- kidtimer.orig       2014-09-23 23:53:41.970147275 +0930
+++ kidtimer.fixed      2014-09-24 00:08:35.104446943 +0930
@@ -65,11 +65,16 @@

 go_check () {
 for I in `/bin/cat $configdir/kid.list | sort -u`; do
+        # This method ( /usr/bin/users ) to determine who is logged in is a kludge.  It ignores locked screens (screensaver active) and switched user states.
         /usr/bin/users | /bin/grep -q $I
         if [ $? -eq 0 ]; then
                 if [ -e $basedir/time/$I.ttl ]; then
                         C=`/bin/cat $basedir/time/$I.ttl`
-                        C=$((C - 1))
+                        if [ $C -le 0 ]; then
+                                C=0
+                        else
+                                C=$((C - 1))
+                       fi
                         echo $C > $basedir/time/$I.ttl
                 else
                        get_ttl_time $I > $basedir/time/$I.ttl
@@ -77,8 +82,8 @@
                 fi
                # check time
                if [ $C -le 5 ]; then
+                       /usr/bin/passwd $I -l
                        if [ ! -e /tmp/kidtimer.shutdown.$I ]; then
-                               /usr/bin/passwd $I -l
                                go_logout $I
                        fi
                fi
@@ -128,7 +133,13 @@
                                 /usr/bin/passwd $I -u
                         else
                                 /usr/bin/passwd $I -l
-                                /usr/bin/users | /bin/grep -q $I && /usr/local/bin/kidtimer shutdown $I
+                                # This method ( /usr/bin/users ) to determine who is logged in is a kludge.  It ignores locked screens (screensaver active) and switched user states.
+                                /usr/bin/users | /bin/grep -q $I
+                                if [ $? -eq 0 ]; then
+                                       if [ ! -e /tmp/kidtimer.shutdown.$I ]; then
+                                               go_logout $I
+                                       fi
+                               fi
                         fi
                 fi
         done
@@ -155,24 +166,45 @@
 TEXT=$((6-$T))
 TMPFILE="/tmp/kidtimer.send$T.bash.$K"
 /bin/rm -f $TMPFILE
+touch $TMPFILE
+chmod +x $TMPFILE
+
 echo "#!/bin/bash" > $TMPFILE
-D=`/usr/bin/who | grep $K | grep -m1 "(:" | cut -d\( -f2 | sed s/\)// | sed s/://`
+
+# This method to determine the correct display doesn't work, at least on zorin.
+# It only seems to detect a user who has an active shell running
+#D=`/usr/bin/who | grep $K | grep -m1 "(:" | cut -d\( -f2 | sed s/\)// | sed s/://`
+
+# So instead I'll just send the message to all the first 10 displays, and the ones owned by the correct user will show the message, the rest won't
 if [ -e $basedir/locale/$LANG ]; then
-       MSG=`/bin/sed -n "$LINE"p /usr/local/kidtimer/locale/$LANG`
+       MSG="$K: `/bin/sed -n "$LINE"p /usr/local/kidtimer/locale/$LANG`"
 else
-       MSG="Your computer time will end in $TEXT minutes."
+       MSG="$K's computer time will end in $TEXT minutes."
 fi
-echo "/bin/su $K -c 'DISPLAY=:$D /usr/bin/notify-send -i /usr/local/kidtimer/icons/kidtimer-$TEXT.png \
+
+for (( D = 0 ; D < 10 ; D = D + 1 )); do
+        echo "/bin/su $K -c 'DISPLAY=:$D /usr/bin/notify-send -i /usr/local/kidtimer/icons/kidtimer-$TEXT.png \
        \"ALERT\" \"$MSG\"'" >> $TMPFILE
+done
+
 echo "/bin/rm -f $TMPFILE" >> $TMPFILE
 echo "/bin/bash $TMPFILE" | /usr/bin/at now + $T minutes
+# Note you need the file /var/spool/cron/atjobs/.SEQ owned by daemon:daemon for at to work properly!!!
 }


 go_killall () {
 K=$1
 /bin/rm -f /tmp/killall.bash.$K
+touch /tmp/killall.bash.$K
+chmod +x /tmp/killall.bash.$K
+
 echo "#!/bin/bash" > /tmp/killall.bash.$K
+# Not sure if it is better or worse to do a normal kill first...
+# Programs like Firefox will reopen all their previous tabs if it is killed with -KILL
+# But other programs would probably do better if given a chance to save state/data and clean up after themselves eg open office???
+#echo "/usr/bin/pkill -TERM -u $K" >> /tmp/killall.bash.$K
+#echo "/bin/sleep 5" >> /tmp/killall.bash.$K
 echo "/usr/bin/pkill -KILL -u $K" >> /tmp/killall.bash.$K
 echo "/bin/rm -rf /tmp/kidtimer.shutdown.$K" >> /tmp/killall.bash.$K
 echo "/bin/rm -f /tmp/killall.bash.$K" >> /tmp/killall.bash.$K
@@ -450,4 +482,3 @@
         ;;
 esac
 exit 0
-

And here's the full text of the fixed version:

#!/bin/bash
# Restrict kids computer access to specific hours and total time.
# By: Michael Groves - grover66_at_gmail_dot_com

#variables
#LANG="en_US.UTF-8" #unremark to change preferred language.
basedir="/usr/local/kidtimer"
configdir="/etc/kidtimer"
Cdate=`/bin/date +%Y-%m-%d`
TUI=0
HOUR=`/bin/date +%H`
DOW=`/bin/date +%u`
WEEKEND="no"
[ "$DOW" == "6" ] && WEEKEND="yes"
[ "$DOW" == "7" ] && WEEKEND="yes"
[ ! -e $configdir/kid.list ] && touch $configdir/kid.list
[ ! -e $basedir/locale/$LANG ] && LANG="en_US.UTF-8"

#arguments
[ $# -eq 0 ] && TUI=1
[ $# -eq 1 ] && COMMAND=$1
[ $# -eq 2 ] && COMMAND=$1 && KID=$2
[ $# -eq 3 ] && COMMAND=$1 && KID=$2 && Time=$3

################# Subroutines ##################
################################################

get_info () {
echo "kidtimer info"
echo "---------------------------------------------"
/bin/date
echo "---"
echo "find /usr/local/kidtimer/ -print | sort"
/usr/bin/find /usr/local/kidtimer/ -print | /usr/bin/sort
echo "---"
echo "cat $configdir/kid.list"
/bin/cat $configdir/kid.list
echo "---"
echo "passwd -S -a"
/usr/bin/passwd -S -a
echo "---"
echo "cat /usr/local/kidtimer/schedule/*"
/bin/cat /usr/local/kidtimer/schedule/*
echo "---"
echo "cat /usr/local/kidtimer/time/*"
/bin/cat /usr/local/kidtimer/time/*
echo "---"
echo "cat /etc/cron.d/kidtimer"
/bin/cat /etc/cron.d/kidtimer
echo "---"
echo "apt-cache showpkg kidtimer"
/usr/bin/apt-cache showpkg kidtimer
echo "---"
echo "cat /etc/lsb-release"
/bin/cat /etc/lsb-release
echo "---"
echo "uname -a"
/bin/uname -a
echo "---"
echo "env"
/usr/bin/env
echo
}


go_check () {
for I in `/bin/cat $configdir/kid.list | sort -u`; do
        # This method ( /usr/bin/users ) to determine who is logged in is a kludge.  It ignores locked screens (screensaver active) and switched user states.
        /usr/bin/users | /bin/grep -q $I
        if [ $? -eq 0 ]; then
                if [ -e $basedir/time/$I.ttl ]; then
                        C=`/bin/cat $basedir/time/$I.ttl`
                        if [ $C -le 0 ]; then
                                C=0
                        else
                                C=$((C - 1))
            fi
                        echo $C > $basedir/time/$I.ttl
                else
            get_ttl_time $I > $basedir/time/$I.ttl
                        C=`/bin/cat $basedir/time/$I.ttl`
                fi
        # check time
        if [ $C -le 5 ]; then
                    /usr/bin/passwd $I -l
                    if [ ! -e /tmp/kidtimer.shutdown.$I ]; then
                            go_logout $I
                    fi
            fi
        else
                go_clean_jobs $I
        fi
done
}


get_ttl_time () {
        [ "$WEEKEND" == "no" ] && /bin/cat $basedir/schedule/$1 | /bin/grep ^MAX | /usr/bin/awk '{ print $2 }'
        [ "$WEEKEND" == "yes" ] && /bin/cat $basedir/schedule/$1 | /bin/grep ^MAX | /usr/bin/awk '{ print $3 }'
}


go_clean_jobs () {
K=$1
for I in `/usr/bin/atq | /usr/bin/awk '{ print $1 }' | /usr/bin/sort`; do
        /usr/bin/at -c $I | /bin/grep -q "bash.$K"
        [ $? -eq 0 ] && /usr/bin/at -d $I
done
[ -e /tmp/kidtimer.shutdown.$K ] && /bin/rm -rf /tmp/kidtimer.shutdown.$K
}


go_daily () {
for I in `/bin/cat $configdir/kid.list | sort -u`; do
        /usr/bin/stat -c %y $basedir/time/$I.ttl | /bin/grep -q $Cdate
        if [ ! $? -eq 0 ]; then
        get_ttl_time $I > $basedir/time/$I.ttl
        fi
done
go_hourly
}


go_hourly () {
if [ -s $configdir/kid.list ]; then
        for I in `/bin/cat $configdir/kid.list | sort -u`; do
                if [ -e $basedir/schedule/$I ]; then
                        [ -e $basedir/time/$I.ttl ] && C=`/bin/cat $basedir/time/$I.ttl`
                        [ $C -le 0 ] && /usr/bin/passwd $I -l && exit 0
                        [ "$WEEKEND" == "no" ] && R=`/bin/grep ^$HOUR $basedir/schedule/$I | /usr/bin/awk '{ print $2 }'`
                        [ "$WEEKEND" == "yes" ] && R=`/bin/grep ^$HOUR $basedir/schedule/$I | /usr/bin/awk '{ print $3 }'`
                        if [ "$R" == "y" ]; then
                                /usr/bin/passwd $I -u
                        else
                                /usr/bin/passwd $I -l
                                # This method ( /usr/bin/users ) to determine who is logged in is a kludge.  It ignores locked screens (screensaver active) and switched user states.
                                /usr/bin/users | /bin/grep -q $I
                                if [ $? -eq 0 ]; then
                            if [ ! -e /tmp/kidtimer.shutdown.$I ]; then
                                    go_logout $I
                    fi
                        fi
                        fi
                fi
        done
fi
}


go_logout () {
K=$1
go_send $K 5
go_send $K 4
go_send $K 3
go_send $K 2
go_send $K 1
go_killall $K
/usr/bin/touch /tmp/kidtimer.shutdown.$K
}


go_send () {
K=$1
T=$2
LINE=$((7-$T))
TEXT=$((6-$T))
TMPFILE="/tmp/kidtimer.send$T.bash.$K"
/bin/rm -f $TMPFILE
touch $TMPFILE
chmod +x $TMPFILE

echo "#!/bin/bash" > $TMPFILE

# This method to determine the correct display doesn't work, at least on zorin.
# It only seems to detect a user who has an active shell running
#D=`/usr/bin/who | grep $K | grep -m1 "(:" | cut -d\( -f2 | sed s/\)// | sed s/://`

# So instead I'll just send the message to all the first 10 displays, and the ones owned by the correct user will show the message, the rest won't
if [ -e $basedir/locale/$LANG ]; then
    MSG="$K: `/bin/sed -n "$LINE"p /usr/local/kidtimer/locale/$LANG`"
else
    MSG="$K's computer time will end in $TEXT minutes."
fi

for (( D = 0 ; D < 10 ; D = D + 1 )); do
        echo "/bin/su $K -c 'DISPLAY=:$D /usr/bin/notify-send -i /usr/local/kidtimer/icons/kidtimer-$TEXT.png \
    \"ALERT\" \"$MSG\"'" >> $TMPFILE
done

echo "/bin/rm -f $TMPFILE" >> $TMPFILE
echo "/bin/bash $TMPFILE" | /usr/bin/at now + $T minutes
# Note you need the file /var/spool/cron/atjobs/.SEQ owned by daemon:daemon for at to work properly!!!
}


go_killall () {
K=$1
/bin/rm -f /tmp/killall.bash.$K
touch /tmp/killall.bash.$K
chmod +x /tmp/killall.bash.$K

echo "#!/bin/bash" > /tmp/killall.bash.$K
# Not sure if it is better or worse to do a normal kill first...
# Programs like Firefox will reopen all their previous tabs if it is killed with -KILL
# But other programs would probably do better if given a chance to save state/data and clean up after themselves eg open office???
#echo "/usr/bin/pkill -TERM -u $K" >> /tmp/killall.bash.$K
#echo "/bin/sleep 5" >> /tmp/killall.bash.$K
echo "/usr/bin/pkill -KILL -u $K" >> /tmp/killall.bash.$K
echo "/bin/rm -rf /tmp/kidtimer.shutdown.$K" >> /tmp/killall.bash.$K
echo "/bin/rm -f /tmp/killall.bash.$K" >> /tmp/killall.bash.$K
echo "/bin/bash /tmp/killall.bash.$K" | /usr/bin/at now + 6 minutes
}


go_echo_error () {
echo "`/bin/sed -n '12p' /usr/local/kidtimer/locale/$LANG`"
}


go_echo_done () {
echo "`/bin/sed -n '13p' /usr/local/kidtimer/locale/$LANG`"
}


go_reset_time () {
get_ttl_time $KID > $basedir/time/$KID.ttl
/usr/bin/passwd $KID -u
go_clean_jobs $KID
go_echo_done
}


go_addtime () {
U=$KID
A=$Time
grep -q $U $configdir/kid.list
if [ $? -eq 0 ]; then
    if [ "$A" == "reset" ]; then
        get_ttl_time $U > $basedir/time/$U.ttl
            go_echo_done
            exit 0
    elif [ "$A" == "" ]; then
            go_echo_error
        #english: Syntax: addtime <user> <minutes|reset>
            echo "`/bin/sed -n '22p' /usr/local/kidtimer/locale/$LANG`"
            exit 1
    else
            C=`/bin/cat $basedir/time/$KID.ttl`
            C=$((C + Time))
            echo $C > $basedir/time/$KID.ttl
        get_time
    fi
    /usr/bin/passwd $KID -u
    go_clean_jobs $KID
else
    go_echo_error
    #english: User not setup.
    echo "`/bin/sed -n '19p' /usr/local/kidtimer/locale/$LANG`"
    exit 1
fi
}


get_time () {
if [ -e $basedir/time/$KID.ttl ]; then
    cat $basedir/time/$KID.ttl
else
    echo
    #english: User not setup.
    echo "`/bin/sed -n '19p' /usr/local/kidtimer/locale/$LANG`"
    echo
fi
}


go_tui () {
go_command_list
echo -n "Choose: "; read X
case "$X" in
1) go_setup_user
        ;;
2) go_modify_user
        ;;
3) go_remove_user
        ;;
4) go_list_users
        ;;
5) exit 0
        ;;
esac
go_tui
}


go_command_list () {
echo
echo "1) "`/bin/sed -n '7p' /usr/local/kidtimer/locale/$LANG`
echo "2) "`/bin/sed -n '8p' /usr/local/kidtimer/locale/$LANG`
echo "3) "`/bin/sed -n '9p' /usr/local/kidtimer/locale/$LANG`
echo "4) "`/bin/sed -n '10p' /usr/local/kidtimer/locale/$LANG`
echo "5) "`/bin/sed -n '11p' /usr/local/kidtimer/locale/$LANG`
echo
}


go_list_users () {
echo
#english: Users configured for kidtimer:
echo "`/bin/sed -n '14p' /usr/local/kidtimer/locale/$LANG`"
if [ -s $configdir/kid.list ]; then
        /bin/cat $configdir/kid.list
else
    #english: No configured users.
        echo "`/bin/sed -n '15p' /usr/local/kidtimer/locale/$LANG`"
    echo
fi
}

go_setup_user () {
echo
#english: Username:
echo -n "`/bin/sed -n '16p' /usr/local/kidtimer/locale/$LANG` "; read U
/usr/bin/id $U > /dev/null 2>&1
if [ $? -eq 0 ]; then
        /bin/cp $basedir/schedule/blank $basedir/schedule/$U
    get_ttl_time $U > $basedir/time/$U.ttl
        echo $U >> $configdir/kid.list
        go_echo_done
        echo
    #english: Modify limits now ?(y/n):
        echo -n "`/bin/sed -n '17p' /usr/local/kidtimer/locale/$LANG`"; read M
        if [ "$M" == "y" ]; then
                if [ -e /usr/bin/nano ]; then
                    /usr/bin/nano $basedir/schedule/$U
            get_ttl_time $U > $basedir/time/$U.ttl
                    go_echo_done
            else
                    /usr/bin/vi $basedir/schedule/$U
            get_ttl_time $U > $basedir/time/$U.ttl
                    go_echo_done
                fi
        fi
else
    go_echo_error
    #english: User does not exist. Please create user using the useradd command first.
        echo "`/bin/sed -n '18p' /usr/local/kidtimer/locale/$LANG`"
fi
}


go_modify_user () {
echo
#english: Username:
echo -n "`/bin/sed -n '16p' /usr/local/kidtimer/locale/$LANG` "; read U
grep -q ^$U $configdir/kid.list
if [ $? -eq 0 ]; then
        if [ -e /usr/bin/nano ]; then
                /usr/bin/nano $basedir/schedule/$U
                go_echo_done
        else
                /usr/bin/vi $basedir/schedule/$U
                go_echo_done
        fi
else
    go_echo_error
    #english: User not setup.
        echo "`/bin/sed -n '19p' /usr/local/kidtimer/locale/$LANG`"
    echo
fi
}


go_remove_user () {
echo
#english: Username:
echo -n "`/bin/sed -n '16p' /usr/local/kidtimer/locale/$LANG` "; read U
grep -q ^$U $configdir/kid.list
if [ $? -eq 0 ]; then
        /bin/grep -v ^$U $configdir/kid.list > /tmp/kidtimer.tmp
        /bin/cat /tmp/kidtimer.tmp > $configdir/kid.list
        go_echo_done
else
    go_echo_error
    #english: User not setup.
        echo "`/bin/sed -n '19p' /usr/local/kidtimer/locale/$LANG`"
    echo
fi
}


go_help () {
echo
echo "Commands:"
echo "--------------------------------------------------------------------------------"
echo "addtime <user> <minutes> ... Increases allowed time for the day."
echo "gettime <user> ... Prints remaining time for the day."
echo "reset <user> ... Reset time for the day."
echo "logout <user> ... Starts logout sequence for user."
echo "hourly ... Enables/disables user access based on the schedule."
echo "daily ... Resets time for the new day."
echo "update ... Updates kidtimer to the latest version."
echo "info ... Gather local configurations to troubleshoot issues."
echo "help ... This list."
echo "--------------------------------------------------------------------------------"
}

check_dependencies () {
#at
P1="";P2="";P3="";pstatus="0"
/usr/bin/dpkg -s at >/dev/null 2>/dev/null
if [ ! $? -eq 0 ]; then
    P1="at"
    pstatus=1
fi
#libnotify-bin
/usr/bin/dpkg -s libnotify-bin >/dev/null 2>/dev/null
if [ ! $? -eq 0 ]; then
        P2="libnotify-bin"
        pstatus=1
fi
#bsdutils
/usr/bin/dpkg -s bsdutils >/dev/null 2>/dev/null
if [ ! $? -eq 0 ]; then
        P3="bsdutils"
        pstatus=1
fi
if [ "$pstatus" == "1" ]; then
    echo
    #english: Error. Missing package dependencies.
    echo "`/bin/sed -n '20p' /usr/local/kidtimer/locale/$LANG`"
    #english: Please install using the following line;
    echo "`/bin/sed -n '21p' /usr/local/kidtimer/locale/$LANG`"
    echo "sudo apt-get install "$P1" "$P2" "$P3
    exit 1
fi
}

get_update () {
URL='https://github.com/grover66/kidtimer/raw/master/DEBS/kidtimer_latest.deb'
if [ -e /usr/bin/wget ]; then
    /usr/bin/wget "$URL" -qO /tmp/kidtimer_latest.deb
    /usr/bin/dpkg -i /tmp/kidtimer_latest.deb
    /bin/rm -f /tmp/kidtimer_latest.deb
else
    echo
    echo "Error. Requires wget"
    echo "To install wget; sudo apt-get install wget"
fi
}


###################### Code ####################
################################################

if [ $TUI -eq 1 ]; then
    check_dependencies
    go_tui
fi

case "$COMMAND" in
addtime) go_addtime
        ;;
reset) go_reset_time
        ;;
gettime) get_time
        ;;
logout) go_logout $KID
        ;;
hourly) go_hourly
        ;;
daily) go_daily
        ;;
update) get_update
        ;;
check) go_check
        ;;
info) get_info
        ;;
-h) go_help
        ;;
help) go_help
        ;;
esac
exit 0

Thanks,
StoicTheVast

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions