Parental Control for Linux Mint 22

I want my kids to learn how to use a computer but at the same time I do not want them to be exposed to everything the internet has to offer.

There are two mayor aspects I want to address:

  1. Usage duration
  2. Internet access

Usage duration (Timekpr-nExT)

The usage duration in my case is very simple. I just want them to have a specified time each day with an exception für weekends. To archive that, I installed Timekpr-nExT which can be found in the Linux Mint Saftware Store.

  • Open the (SU) Timekpr-nExT Control Panel (superuser mode) menu entry
  • Enter your password to get root access
  • Select the Username of your child's account
  • Under Linit configuration set the Week day limits for each day and Hour intervals for each day while selected(!) and then hit verify for each entry(!)
  • I did not use the PlayTime feature
  • Click Apply daily limits when done

This set the usage time allowance and makes sure the child user is logged out when the time expired.

Simple remote page

Install a web server like nginx or lighttpd with php-fpm configured. Then create these files in the wwwroot directory configured for the server. Alternatively, hijack CTParental in /var/www/CTParental and just create these files in a sub-directory.

data.php:

<?php 
    $adminPass = ''; // <- set hash here
    $account = 'child'; // <- set child's user account here

    $do = $_GET['do'];
    switch ($do) {
        case 'info':
            $result = shell_exec('sudo /usr/bin/timekpra --userinfo ' . $account);
            $options = explode("\n", $result);
            $data = array();
            foreach ($options as $line) {
                if ($line == '') continue;
                if (str_starts_with($line, '#')) continue;
                $parts = explode(': ', $line, 2);
                if (count($parts) == 2) {
                    $data[$parts[0]] = $parts[1];
                }
            }
            header('Content-Type: application/json; charset=utf-8');
            print(json_encode($data));
            break;
        case 'pass':
            print(password_hash($_GET['pass'], PASSWORD_DEFAULT));
            break;
        case 'settimeleft':
            $pass = $_GET['pass'] or die('No pass!');
            if (!password_verify($pass, $adminPass))
                die('Wrong pass!');
            $time = $_GET['time'] or die('No time!');
            if (preg_match('/^([0-9]{1,8})$/', $time, $matches) == 0)
               die('Invalid time!');
            $result = shell_exec('sudo /usr/bin/timekpra --settimeleft ' . $account . ' = ' . $matches[0]);
            $data = array();
            $data['result'] = $result;
            header('Content-Type: application/json; charset=utf-8');
            print(json_encode($data));
            break;
        default:
            phpinfo();
            break;
    }
?>

index.htm:

<!DOCTYPE html>
<html>
<head>
    <title>TimeKpr</title>
    <meta charset="utf-8" />
    <script type="text/javascript">
        function toHumanReadable(secs) {
            if (typeof(secs) != 'number') secs = parseInt(secs);
            var result = '';
            if (secs > 3600) {
                result += Math.floor(secs / 3600).toString().padStart(2, '0') + ':';
                secs = secs % 3600;
            }
            result += Math.floor(secs / 60).toString().padStart(2, '0') + ':';
            secs = secs % 60;
            result += Math.floor(secs).toString().padStart(2, '0');
            return result;
        }

        function fromHumanReadable(time) {
            var result = 0;
            var parts = time.split(':');
            for (var i = 0; i < parts.length; i++) {
                var nr = parseInt(parts[i]);
                result = result * 60 + nr;
            }
            return result;
        }

        function onUpdate(json) {
            var timeLeft = document.getElementById('timeLeft');
            if (json['TIME_LEFT_DAY']) {
                document.getElementById('timeLeft').value = toHumanReadable(json['TIME_LEFT_DAY']);
            }
            if (json['TIME_SPENT_DAY']) {
                document.getElementById('timeSpentDay').innerText = toHumanReadable(json['TIME_SPENT_DAY']);
            }
            if (json['TIME_SPENT_WEEK']) {
                document.getElementById('timeSpentWeek').innerText = toHumanReadable(json['TIME_SPENT_WEEK']);
            }
        }

        function setTimeLeft() {
            var time = fromHumanReadable(document.getElementById('timeLeft').value);
            var pass = encodeURIComponent(document.getElementById('pass').value);
            fetch('data.php?do=settimeleft&pass='+pass+'&time='+time)
                .then(response => response.json())
                .then(data => {
                    console.log(data);
                    //window.location.reload();
                })
                .catch(error => console.error('Error:', error));
        }

        function init() {
            fetch('data.php?do=info')
                .then(response => response.json())
                .then(data => { console.log(data); onUpdate(data); })
                .catch(error => console.error('Error:', error));
        }
    </script>
</head>
<body onload="init();">
    <h1>TimeKpr</h1>
    <table>
        <tr>
            <td><b>Password:</b></td>
            <td><input type="password" id="pass" /></td>
        </tr>
        <tr>
            <td><b>Time spent week:</b></td>
            <td><span id="timeSpentWeek"></span></td>
        </tr>
        <tr>
            <td><b>Time spent today:</b></td>
            <td><span id="timeSpentDay"></span></td>
        </tr>
        <tr>
            <td><b>Time left:</b></td>
            <td>
                <input id="timeLeft" />
                <button onclick="setTimeLeft();">Set</button>
            </td>
        </tr>
    </table>
</body>
</html>

You'll have to allow the user www-data to exeute timekpra without a password - this is a bit over-reaching but I couldn't find a better way yet:

www-data   ALL=NOPASSWD: ALL

Afterwards, you can create a password hash by opening a browser in private mode and opening the address:

http://localhost/data.php?do=pass&pass=YOUR_PASSWORD

Adjust your password and if you created this in a sub-directory, the path.

Internet access (CTParental)

Download the latest DEB version (Debian 13 in my case) from the GitLab CTParentalGroup releases: CTParental Releases

Then install it:

sudo apt install /path/to/downloaded.deb

After installation and setting up you can open the administration page admin.ct.local.

This is my whitelist for these porposes:

  • MineCraft Microsoft login
  • uBlock Filter lists
  • FragFINN and some linked pages

Keep in mind that this is not a good list! GitHub for example is much too broad but I don't expect my child to access them.

.microsoft.com
.minecraft.com
.minecraft.net
.minecraftservices.com
.minecraft-services.net
.outlook.com
.outlook.de
.live.com
.xboxlive.com
.msftauth.net
.azureedge.net
.azure.com
.assets.adobedtm.com
.office365.com

.github.com
.githubassets.com
.githubusercontent.com
.github.io
.gitlab.com
.easylist.to
.pgl.yoyo.org
.fanboy.co.nz
.adguard.com
.adguard-dns.io
.adblockplus.org

.fragfinn.de
.westermann.de
.local
.zum.de
.telegram.org
.t.me
.telesco.pe
.telegram.me
.tg.dev
.kika.de
.logo.de
.internet-abc.de
.radiofuechse.de
.digitallearninglab.de
.kleinezeitung.at
.homeschooling4kids.at
.tierchenwelt.de
.kindernetz.de
.pbskids.org
.bfn.de
.schubu.org
.matheretter.de
.wdrmaus.de
.kindersache.de
.rofu.de
.sivakids.de
.kinderzeitmaschine.de
.fessie.de
.labbe.de
.singkinderlieder.de
.baumhausbande.com
.liederprojekt.org
.kruschel.de
.milchland.de
.buttinette.com
.naturpark-detektive.de
.natur-vision.de
.ideenlabor-natur.de
.natur-ranger.de
.natur-beobachtungen.de
.natur-entdecken-online.de
.naturinfo.ch
.das-macht-schule.net
.knax.de
.learnattack.de
.meteoros.de
.lehrer-online.de
.mail4kidz.de
.malvorlagen-bilder.de
.kinderweltreise.de
.kinderuni.online
.kinderfotopreis.de
.kaenguru-online.de
.berufe-lexikon.de
.kakadu.de
.wissensschule.de
.wikimedia.org
.code-it-studio.de
.handysektor.de
.readspeaker.com
.ebhelm.de

This page was last edited on 2026-01-01 18:37

Powered by Wiki|Docs

This page was last edited on 2026-01-01 18:37

Bjørn Singer
Private wiki!

Powered by Wiki|Docs