nixos/timekpr: init at 0.5.8

Co-authored-by: Matt Sturgeon <matt@sturgeon.me.uk>
This commit is contained in:
Yang, Bo
2025-06-24 07:35:19 +00:00
parent c2ae88e026
commit 33dfc47d5e
7 changed files with 251 additions and 0 deletions

View File

@@ -152,3 +152,16 @@ original files are by default stored in `/var/lib/nixos`.
Userborn implements immutable users by re-mounting the password files
read-only. This means that unlike when using the Perl script, trying to add a
new user (e.g. via `useradd`) will fail right away.
## Restrict usage time {#sec-restrict-usage-time}
[Timekpr-nExT](https://mjasnik.gitlab.io/timekpr-next/) is a screen time managing application that helps optimizing time spent at computer for your subordinates, children or even for yourself.
You can enable it via:
```nix
{ services.timekpr.enable = true; }
```
This will install the `timekpr` package and start the `timekpr` service.
You can then use the `timekpra` application to configure time limits for users.

View File

@@ -335,6 +335,9 @@
"sec-userborn": [
"index.html#sec-userborn"
],
"sec-restrict-usage-time": [
"index.html#sec-restrict-usage-time"
],
"ch-file-systems": [
"index.html#ch-file-systems"
],

View File

@@ -80,6 +80,8 @@
- [mautrix-discord](https://github.com/mautrix/discord), a Matrix-Discord puppeting/relay bridge. Available as [services.mautrix-discord](#opt-services.mautrix-discord.enable).
- [Timekpr-nExT](https://mjasnik.gitlab.io/timekpr-next/), a time managing application that helps optimizing time spent at computer for your subordinates, children or even for yourself. Available as [](#opt-services.timekpr.enable).
- [SuiteNumérique Meet](https://github.com/suitenumerique/meet) is an open source alternative to Google Meet and Zoom powered by LiveKit: HD video calls, screen sharing, and chat features. Built with Django and React. Available as [services.lasuite-meet](#opt-services.lasuite-meet.enable).
- [lemurs](https://github.com/coastalwhite/lemurs), a customizable TUI display/login manager. Available at [services.displayManager.lemurs](#opt-services.displayManager.lemurs.enable).

View File

@@ -1468,6 +1468,7 @@
./services/security/sslmate-agent.nix
./services/security/step-ca.nix
./services/security/tang.nix
./services/security/timekpr.nix
./services/security/tor.nix
./services/security/torify.nix
./services/security/torsocks.nix

View File

@@ -0,0 +1,65 @@
{
pkgs,
lib,
config,
...
}:
let
cfg = config.services.timekpr;
targetBaseDir = "/var/lib/timekpr";
daemonUser = "root";
daemonGroup = "root";
in
{
options = {
services.timekpr = {
package = lib.mkPackageOption pkgs "timekpr" { };
enable = lib.mkEnableOption "Timekpr-nExT, a screen time managing application that helps optimizing time spent at computer for your subordinates, children or even for yourself";
adminUsers = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
example = [
"alice"
"bob"
];
description = ''
All listed users will become part of the `timekpr` group so they can manage timekpr settings without requiring sudo.
'';
};
};
};
config = lib.mkIf cfg.enable {
users.groups.timekpr = {
gid = 2000;
members = cfg.adminUsers;
};
environment.systemPackages = [
# Add timekpr to system packages so that polkit can find it
cfg.package
];
services.dbus.enable = true;
services.dbus.packages = [
cfg.package
];
environment.etc."timekpr" = {
source = "${cfg.package}/etc/timekpr";
};
systemd.packages = [
cfg.package
];
systemd.services.timekpr = {
enable = true;
wantedBy = [ "multi-user.target" ];
};
security.polkit.enable = true;
systemd.tmpfiles.rules = [
"d ${targetBaseDir} 0755 ${daemonUser} ${daemonGroup} -"
"d ${targetBaseDir}/config 0755 ${daemonUser} ${daemonGroup} -"
"d ${targetBaseDir}/work 0755 ${daemonUser} ${daemonGroup} -"
];
};
meta.maintainers = [ lib.maintainers.atry ];
}

17
nixos/tests/timekpr.nix Normal file
View File

@@ -0,0 +1,17 @@
{ pkgs, lib, ... }:
{
name = "timekpr";
meta.maintainers = [ lib.maintainers.atry ];
nodes.machine =
{ pkgs, lib, ... }:
{
services.timekpr.enable = true;
};
testScript = ''
start_all()
machine.wait_for_file("/etc/timekpr/timekpr.conf")
machine.wait_for_unit("timekpr.service")
'';
}

View File

@@ -0,0 +1,150 @@
{
fetchgit,
gitUpdater,
glib,
gobject-introspection,
gtk3,
lib,
python3Packages,
sound-theme-freedesktop,
stdenv,
wrapGAppsHook4,
}:
python3Packages.buildPythonApplication rec {
pname = "timekpr";
version = "0.5.8";
src = fetchgit {
url = "https://git.launchpad.net/timekpr-next";
tag = "v${version}";
hash = "sha256-Y0jAKl553HjoP59wJnKBKq4Ogko1cs8uazW2dy7AlBo=";
};
buildInputs = [
glib
gtk3
];
nativeBuildInputs = [
gobject-introspection
wrapGAppsHook4
];
pyproject = true;
build-system = with python3Packages; [
setuptools
];
dependencies = with python3Packages; [
dbus-python
pygobject3
psutil
];
# Generate setup.py because the upstream repository does not include it
SETUP_PY = ''
from setuptools import setup, find_namespace_packages
package_dir={"timekpr": "."}
setup(
name="timekpr-next",
version="${version}",
package_dir=package_dir,
packages=[
f"{package_prefix}.{package_suffix}"
for package_prefix, where in package_dir.items()
for package_suffix in find_namespace_packages(where=where)
],
install_requires=[
${lib.concatMapStringsSep ", " (dependency: "'${dependency.pname}'") dependencies}
],
)
'';
postPatch = ''
shopt -s globstar extglob nullglob
substituteInPlace bin/* **/*.py resource/server/systemd/timekpr.service \
--replace-quiet /usr/lib/python3/dist-packages "$out"/${lib.escapeShellArg python3Packages.python.sitePackages}
substituteInPlace **/*.desktop **/*.policy **/*.service \
--replace-fail /usr/bin/timekpr "$out"/bin/timekpr
substituteInPlace common/constants/constants.py \
--replace-fail /usr/share/sounds/freedesktop ${lib.escapeShellArg sound-theme-freedesktop}/share/sounds/freedesktop \
--replace-fail /usr/share/timekpr "$out"/share/timekpr \
--replace-fail /usr/share/locale "$out"/share/locale
substituteInPlace resource/server/timekpr.conf \
--replace-fail /usr/share/timekpr "$out"/share/timekpr \
# The original file name `timekpra` is renamed to `..timekpra-wrapped-wrapped` because `makeCWrapper` was used multiple times.
substituteInPlace client/admin/adminprocessor.py \
--replace-fail '"/timekpra" in ' '"/..timekpra-wrapped-wrapped" in '
printf %s "$SETUP_PY" > setup.py
'';
# We need to manually inject $PYTHONPATH here, because `buildPythonApplication` does not recognize timekpr's executables as Python scripts, and therefore it does not automatically inject $PYTHONPATH into them.
postFixup = ''
for executable in $out/bin/*
do
wrapProgram "$executable" --prefix PYTHONPATH : "$PYTHONPATH"
done
'';
preInstall = ''
while IFS= read -r line
do
# Trim leading/trailing whitespace
line=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
# Skip empty lines and comments
if [[ -z "$line" || "$line" =~ ^# ]]; then
continue
fi
# Separate source and destination
# This assumes the destination is the last field and source path doesn't contain problematic spaces
# More robust parsing might be needed if source paths have spaces.
source_path=$(echo "$line" | awk '{ $NF=""; print $0 }' | sed 's/[[:space:]]*$//')
dest_path=$(echo "$line" | awk '{ print $NF }')
# Check destination path prefix and map to $out/*
case "$dest_path" in
usr/share/*)
# Remove "usr/" prefix and prepend "$out/"
install -D --mode=444 "$source_path" --target-directory="$out/''${dest_path#usr/}"
;;
usr/bin/*)
# Remove "usr/" prefix and prepend "$out/"
install -D --mode=555 "$source_path" --target-directory="$out/''${dest_path#usr/}"
;;
etc/*|lib/*|var/*)
# Prepend "$out/"
install -D --mode=444 "$source_path" --target-directory="$out/$dest_path"
;;
usr/lib/python3/dist-packages/*)
# Skip this line if the destination is a Python module
# because it will be handled by the Python build process
continue
;;
*)
echo "Error: Unknown destination prefix: '$dest_path'" >&2
exit 1
;;
esac
done < debian/install
'';
passthru.updateScript = gitUpdater { rev-prefix = "v"; };
meta = {
description = "Manages and restricts user screen time by enforcing time limits";
homepage = "https://mjasnik.gitlab.io/timekpr-next/";
license = lib.licenses.gpl3;
maintainers = [ lib.maintainers.atry ];
platforms = lib.platforms.linux;
};
}