diff --git a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
index 02e3bc194120..89cd015ccfc8 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml
@@ -150,6 +150,14 @@
services.prosody-filer.
+
+
+ timetagger,
+ an open source time-tracker with an intuitive user experience
+ and powerful reporting.
+ services.timetagger.
+
+
diff --git a/nixos/doc/manual/release-notes/rl-2205.section.md b/nixos/doc/manual/release-notes/rl-2205.section.md
index d42ba989f9de..2554292b6f12 100644
--- a/nixos/doc/manual/release-notes/rl-2205.section.md
+++ b/nixos/doc/manual/release-notes/rl-2205.section.md
@@ -46,6 +46,8 @@ In addition to numerous new and upgraded packages, this release has the followin
- [prosody-filer](https://github.com/ThomasLeister/prosody-filer), a server for handling XMPP HTTP Upload requests. Available at [services.prosody-filer](#opt-services.prosody-filer.enable).
+- [timetagger](https://timetagger.app), an open source time-tracker with an intuitive user experience and powerful reporting. [services.timetagger](options.html#opt-services.timetagger.enable).
+
## Backward Incompatibilities {#sec-release-22.05-incompatibilities}
- `pkgs.ghc` now refers to `pkgs.targetPackages.haskellPackages.ghc`.
diff --git a/nixos/modules/services/web-apps/timetagger.nix b/nixos/modules/services/web-apps/timetagger.nix
new file mode 100644
index 000000000000..373f4fcd52f8
--- /dev/null
+++ b/nixos/modules/services/web-apps/timetagger.nix
@@ -0,0 +1,80 @@
+{ config, lib, pkgs, ... }:
+
+let
+ inherit (lib) mkEnableOption mkIf mkOption types literalExpression;
+
+ cfg = config.services.timetagger;
+in {
+
+ options = {
+ services.timetagger = {
+ enable = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Tag your time, get the insight
+
+
+ This app does not do authentication.
+ You must setup authentication yourself or run it in an environment where
+ only allowed users have access.
+
+ '';
+ };
+
+ bindAddr = mkOption {
+ description = "Address to bind to.";
+ type = types.str;
+ default = "127.0.0.1";
+ };
+
+ port = mkOption {
+ description = "Port to bind to.";
+ type = types.port;
+ default = 8080;
+ };
+
+ package = mkOption {
+ description = ''
+ Use own package for starting timetagger web application.
+
+ The ${literalExpression ''pkgs.timetagger''} package only provides a
+ "run.py" script for the actual package
+ ${literalExpression ''pkgs.python3Packages.timetagger''}.
+
+ If you want to provide a "run.py" script for starting timetagger
+ yourself, you can do so with this option.
+ If you do so, the 'bindAddr' and 'port' options are ignored.
+ '';
+
+ default = pkgs.timetagger.override { addr = cfg.bindAddr; port = cfg.port; };
+ defaultText = literalExpression ''
+ pkgs.timetagger.override {
+ addr = ${cfg.bindAddr};
+ port = ${cfg.port};
+ };
+ '';
+ type = types.package;
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ systemd.services.timetagger = {
+ description = "Timetagger service";
+ wantedBy = [ "multi-user.target" ];
+
+ serviceConfig = {
+ User = "timetagger";
+ Group = "timetagger";
+ StateDirectory = "timetagger";
+
+ ExecStart = "${cfg.package}/bin/timetagger";
+
+ Restart = "on-failure";
+ RestartSec = 1;
+ };
+ };
+ };
+}
+
diff --git a/pkgs/development/python-modules/asgineer/default.nix b/pkgs/development/python-modules/asgineer/default.nix
new file mode 100644
index 000000000000..3a1861bbd182
--- /dev/null
+++ b/pkgs/development/python-modules/asgineer/default.nix
@@ -0,0 +1,32 @@
+{ lib
+, buildPythonPackage
+, fetchFromGitHub
+, pytestCheckHook
+, requests
+}:
+
+buildPythonPackage rec {
+ pname = "asgineer";
+ version = "0.8.1";
+
+ # PyPI tarball doesn't include tests directory
+ src = fetchFromGitHub {
+ owner = "almarklein";
+ repo = pname;
+ rev = "v${version}";
+ sha256 = "0hd1i9pc8m7sc8bkn31q4ygkmnl5vklrcziq9zkdiqaqm8clyhcx";
+ };
+
+ checkInputs = [
+ pytestCheckHook
+ requests
+ ];
+
+ meta = with lib; {
+ description = "A really thin ASGI web framework";
+ license = licenses.bsd2;
+ homepage = "https://asgineer.readthedocs.io";
+ maintainers = [ maintainers.matthiasbeyer ];
+ };
+}
+
diff --git a/pkgs/development/python-modules/itemdb/default.nix b/pkgs/development/python-modules/itemdb/default.nix
new file mode 100644
index 000000000000..f9afc4c5cb36
--- /dev/null
+++ b/pkgs/development/python-modules/itemdb/default.nix
@@ -0,0 +1,26 @@
+{ lib
+, buildPythonPackage
+, fetchFromGitHub
+}:
+
+buildPythonPackage rec {
+ pname = "itemdb";
+ version = "1.1.1";
+
+ # PyPI tarball doesn't include tests directory
+ src = fetchFromGitHub {
+ owner = "almarklein";
+ repo = pname;
+ rev = "v${version}";
+ sha256 = "0ksad5j91nlbsn0a11clf994qz7r9ijand5hxnjhgd66i9hl3y78";
+ };
+
+ meta = with lib; {
+ description = "Easy transactional database for Python dicts, backed by SQLite";
+ license = licenses.bsd2;
+ homepage = "https://itemdb.readthedocs.io";
+ maintainers = [ maintainers.matthiasbeyer ];
+ };
+}
+
+
diff --git a/pkgs/development/python-modules/pscript/default.nix b/pkgs/development/python-modules/pscript/default.nix
new file mode 100644
index 000000000000..fae2c8a42818
--- /dev/null
+++ b/pkgs/development/python-modules/pscript/default.nix
@@ -0,0 +1,39 @@
+{ lib
+, buildPythonPackage
+, fetchFromGitHub
+, pytestCheckHook
+, nodejs
+}:
+
+buildPythonPackage rec {
+ pname = "pscript";
+ version = "0.7.6";
+
+ # PyPI tarball doesn't include tests directory
+ src = fetchFromGitHub {
+ owner = "flexxui";
+ repo = pname;
+ rev = "v${version}";
+ sha256 = "169px5n4jjnpdn9y86f28qwd95bwf1q1rz0a1h3lb5nn5c6ym8c4";
+ };
+
+ checkInputs = [
+ pytestCheckHook
+ nodejs
+ ];
+
+ preCheck = ''
+ # do not execute legacy tests
+ rm -rf pscript_legacy
+ '';
+
+ meta = with lib; {
+ description = "Python to JavaScript compiler";
+ license = licenses.bsd2;
+ homepage = "https://pscript.readthedocs.io";
+ maintainers = [ maintainers.matthiasbeyer ];
+ };
+}
+
+
+
diff --git a/pkgs/development/python-modules/timetagger/default.nix b/pkgs/development/python-modules/timetagger/default.nix
new file mode 100644
index 000000000000..58bf190495f4
--- /dev/null
+++ b/pkgs/development/python-modules/timetagger/default.nix
@@ -0,0 +1,47 @@
+{ lib
+, python3Packages
+, fetchFromGitHub
+, pytestCheckHook
+, requests
+}:
+
+python3Packages.buildPythonPackage rec {
+ pname = "timetagger";
+ version = "22.1.2";
+
+ src = fetchFromGitHub {
+ owner = "almarklein";
+ repo = pname;
+ rev = "v${version}";
+ sha256 = "0xrajx0iij7r70ch17m4y6ydyh368dn6nbjsv74pn1x8frd686rw";
+ };
+
+ meta = with lib; {
+ homepage = "https://timetagger.app";
+ license = licenses.gpl3;
+ description = "Tag your time, get the insight";
+ maintainers = with maintainers; [ matthiasbeyer ];
+ };
+
+ checkInputs = [
+ pytestCheckHook
+ requests
+ ];
+
+ preCheck = ''
+ # https://github.com/NixOS/nixpkgs/issues/12591
+ mkdir -p check-phase
+ export HOME=$(pwd)/check-phase
+ '';
+
+ propagatedBuildInputs = with python3Packages; [
+ asgineer
+ itemdb
+ jinja2
+ markdown
+ pscript
+ pyjwt
+ uvicorn
+ ];
+
+}
diff --git a/pkgs/servers/timetagger/default.nix b/pkgs/servers/timetagger/default.nix
new file mode 100644
index 000000000000..5e4629f455b2
--- /dev/null
+++ b/pkgs/servers/timetagger/default.nix
@@ -0,0 +1,39 @@
+{ lib
+, pkgs
+, python3Packages
+, fetchFromGitHub
+
+, addr ? "127.0.0.1"
+, port ? 8082
+}:
+
+#
+# Timetagger itself is a library that a user must write a "run.py" script for
+# We provide a basic "run.py" script with this package, which simply starts
+# timetagger.
+#
+
+let
+ tt = python3Packages.timetagger;
+in
+python3Packages.buildPythonPackage rec {
+ pname = tt.name;
+ version = tt.version;
+ src = tt.src;
+ meta = tt.meta;
+
+ propagatedBuildInputs = [ tt ]
+ ++ (with python3Packages; [
+ setuptools
+ ]);
+
+ format = "custom";
+ installPhase = ''
+ mkdir -p $out/bin
+ echo "#!${pkgs.python3}/bin/python3" >> $out/bin/timetagger
+ cat run.py >> $out/bin/timetagger
+ sed -Ei 's,0\.0\.0\.0:80,${addr}:${toString port},' $out/bin/timetagger
+ chmod +x $out/bin/timetagger
+ '';
+}
+
diff --git a/pkgs/top-level/all-packages.nix b/pkgs/top-level/all-packages.nix
index ccbbcfc78b30..a2f722cc9d08 100644
--- a/pkgs/top-level/all-packages.nix
+++ b/pkgs/top-level/all-packages.nix
@@ -10202,6 +10202,8 @@ with pkgs;
timetrap = callPackage ../applications/office/timetrap { };
+ timetagger = callPackage ../servers/timetagger { };
+
timekeeper = callPackage ../applications/office/timekeeper { };
timezonemap = callPackage ../development/libraries/timezonemap { };
diff --git a/pkgs/top-level/python-packages.nix b/pkgs/top-level/python-packages.nix
index 83f068845ae9..1689bfdcb399 100644
--- a/pkgs/top-level/python-packages.nix
+++ b/pkgs/top-level/python-packages.nix
@@ -634,6 +634,8 @@ in {
asgi-csrf = callPackage ../development/python-modules/asgi-csrf { };
+ asgineer = callPackage ../development/python-modules/asgineer { };
+
asgiref = callPackage ../development/python-modules/asgiref { };
asmog = callPackage ../development/python-modules/asmog { };
@@ -4055,6 +4057,8 @@ in {
itemadapter = callPackage ../development/python-modules/itemadapter { };
+ itemdb = callPackage ../development/python-modules/itemdb { };
+
itemloaders = callPackage ../development/python-modules/itemloaders { };
iterm2 = callPackage ../development/python-modules/iterm2 { };
@@ -6332,6 +6336,8 @@ in {
psautohint = callPackage ../development/python-modules/psautohint { };
+ pscript = callPackage ../development/python-modules/pscript { };
+
psd-tools = callPackage ../development/python-modules/psd-tools { };
psutil = callPackage ../development/python-modules/psutil { };
@@ -9713,6 +9719,8 @@ in {
timeout-decorator = callPackage ../development/python-modules/timeout-decorator { };
+ timetagger = callPackage ../development/python-modules/timetagger { };
+
timezonefinder = callPackage ../development/python-modules/timezonefinder { };
tinycss2 = callPackage ../development/python-modules/tinycss2 { };