diff --git a/flake.lock b/flake.lock index 1fb8902..ea2838d 100644 --- a/flake.lock +++ b/flake.lock @@ -885,11 +885,11 @@ }, "nixpkgs_6": { "locked": { - "lastModified": 1755186698, - "narHash": "sha256-wNO3+Ks2jZJ4nTHMuks+cxAiVBGNuEBXsT29Bz6HASo=", + "lastModified": 1754725699, + "narHash": "sha256-iAcj9T/Y+3DBy2J0N+yF9XQQQ8IEb5swLFzs23CdP88=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "fbcf476f790d8a217c3eab4e12033dc4a0f6d23c", + "rev": "85dbfc7aaf52ecb755f87e577ddbe6dbbdbc1054", "type": "github" }, "original": { diff --git a/modules/core/virtualization.nix b/modules/core/virtualization.nix index 3ad3ae8..eeca5d6 100644 --- a/modules/core/virtualization.nix +++ b/modules/core/virtualization.nix @@ -1,17 +1,9 @@ -{ pkgs, host, ... }: +{ pkgs, ... }: { virtualisation = { # vmware.host.enable = true; # Causes issues for now :p - waydroid.enable = if (host == "sakura") then true else false; - libvirtd.enable = - if (host == "violet") then - true - else if (host == "sakura") then - true - else if (host == "yoshino") then - true - else - false; + waydroid.enable = true; + libvirtd.enable = true; spiceUSBRedirection.enable = true; }; diff --git a/modules/home/hyprland/config.nix b/modules/home/hyprland/config.nix index cd1d242..b538f0c 100644 --- a/modules/home/hyprland/config.nix +++ b/modules/home/hyprland/config.nix @@ -1,9 +1,4 @@ -{ - pkgs, - host, - username, - ... -}: +{ pkgs, host, ... }: { fonts.fontconfig.enable = true; home.packages = [ @@ -64,6 +59,7 @@ source = "~/nixos-config/modules/home/hyprland/displays.conf"; "debug:disable_scale_checks" = true; + # monitor = "eDP-1, 2256x1504@60, 0x0, 1.5"; monitor = if (host == "sakura") then "eDP-1, 2256x1504@60, 0x0, 1.5" @@ -219,7 +215,7 @@ "$mainMod, F, fullscreen, 0" # set 1 to 0 to set full screen without waybar "$mainMod, Space, togglefloating," "$mainMod, D, exec, bemenu-run -l 5 --ignorecase" - "SUPER SHIFT, L, exec, swaylock --image /home/${username}/.local/share/bg.png" + "SUPER SHIFT, L, exec, hyprlock" "$mainMod, E, exec, thunar" "$mainMod SHIFT, B, exec, pkill -SIGUSR1 .waybar-wrapped" "$mainMod, C,exec, hyprpicker -a" diff --git a/modules/home/swaync/default.nix b/modules/home/swaync/default.nix index 45d3fe4..f70bcbd 100644 --- a/modules/home/swaync/default.nix +++ b/modules/home/swaync/default.nix @@ -1,233 +1,381 @@ { pkgs, username, ... }: { home = { - packages = with pkgs; [ - swaynotificationcenter - wlogout - ]; + packages = with pkgs; [ swaynotificationcenter ]; file."/home/${username}/.config/swaync/config.json".text = '' - { - "$schema": "/etc/xdg/swaync/configSchema.json", - "positionX": "right", - "positionY": "top", - "layer": "overlay", - "layer-shell": true, - "cssPriority": "user", - - "control-center-width": 380, - "control-center-height": 860, - "control-center-margin-top": 8, - "control-center-margin-bottom": 8, - "control-center-margin-right": 8, - "control-center-margin-left": 8, - - "notification-window-width": 400, - "notification-icon-size": 48, - "notification-body-image-height": 160, - "notification-body-image-width": 200, - - "widgets": ["buttons-grid", "title", "dnd", "notifications", "mpris"], - "widget-config": { - "title": { - "text": "Notifications", - "clear-all-button": true, - "button-text": "Clear All" - }, - "dnd": { - "text": "Do Not Disturb" - }, - "label": { - "max-lines": 1, - "text": " " - }, - "mpris": { - "image-size": 60, - "image-radius": 12 - }, - "buttons-grid": { - "actions": [ - { - "label": " ", - "command": "kitty -e nmtui-connect" - }, - { - "label": "󰂯", - "command": "waybar-bluetooth toggle" - }, - { - "label": "󰏘", - "command": "kitty -e walp" - }, - { - "label": "⏻", - "command": "wlogout" - } - ] - } - } + { + "positionX": "right", + "positionY": "top", + "layer": "overlay", + "layer-shell": "true", + "cssPriority": "application", + "control-center-margin-top": 10, + "control-center-margin-bottom": 10, + "control-center-margin-right": 10, + "control-center-margin-left": 10, + "notification-icon-size": 64, + "notification-body-image-height": 128, + "notification-body-image-width": 200, + "timeout": 10, + "timeout-low": 5, + "timeout-critical": 0, + "fit-to-screen": true, + "control-center-width": 400, + "control-center-height": 650, + "notification-window-width": 350, + "keyboard-shortcuts": true, + "image-visibility": "when-available", + "transition-time": 200, + "hide-on-clear": false, + "hide-on-action": true, + "script-fail-notify": true, + "widgets": [ + "title", + "dnd", + "notifications" + ], + "widget-config": { + "title": { + "text": "Notifications", + "clear-all-button": true, + "button-text": " Clear all " + }, + "dnd": { + "text": " Do not disturb" + }, + } } ''; - file."/home/${username}/.config/swaync/style.css".text = '' - @import "../../.cache/wal/colors-waybar.css"; + file = { + "/home/${username}/.config/swaync/style.css".text = '' + * { + all: unset; + font-size: 14px; + font-family: "GohuFont 14 Nerd Font Mono"; + transition: 200ms; + } - @define-color text @foreground; - @define-color bg @color1; - @define-color selected @color6; - @define-color hover alpha(@selected, .4); + trough highlight { + background: #cdd6f4; + } - * { - outline: none; - transition: 200ms; - padding: 1px; - background: transparent; - } + scale trough { + margin: 0rem 1rem; + background-color: #313244; + min-height: 8px; + min-width: 70px; + } - .notification-row { - outline: none; - margin: 0; - padding: 0px; - } + slider { + background-color: #89b4fa; + } - .notification-row .notification-background .close-button { - /* The notification Close Button */ - background: transparent; - color: @text; - text-shadow: none; - box-shadow: none; - margin-top: 2px; - margin-right: 2px; - padding: 0; - border: none; - border-radius: 100%; - min-width: 24px; - min-height: 24px; - } + .floating-notifications.background .notification-row .notification-background { + box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.8), inset 0 0 0 1px #313244; + margin: 18px; + background-color: #000000; + color: #cdd6f4; + padding: 0; + } - .notification-row .notification-background .close-button:hover { - box-shadow: none; - background: transparent; - transition: background 0.15s ease-in-out; - border: 0px; - } + .floating-notifications.background .notification-row .notification-background .notification { + padding: 7px; + } - .notification-row .notification-background .notification { - /* The actual notification */ - background: transparent; - } + .floating-notifications.background .notification-row .notification-background .notification.critical { + box-shadow: inset 0 0 7px 0 #f38ba8; + } - .notification-group .notification-group-headers { - /* Notficiation Group Headers */ - margin-top: 10px; - margin-bottom: 10px; - } + .floating-notifications.background .notification-row .notification-background .notification .notification-content { + margin: 7px; + } - .notification-group .notification-group-headers .notification-group-header { - font-size: 20px; - margin-left: 3px; - } + .floating-notifications.background .notification-row .notification-background .notification .notification-content .summary { + color: #cdd6f4; + } - .notification-group.collapsed .notification-row .notification { - background: alpha(@background, 0.55); - } + .floating-notifications.background .notification-row .notification-background .notification .notification-content .time { + color: #a6adc8; + } - .control-center { - /* The Control Center which contains the old notifications + widgets */ - margin: 18px; - padding: 14px; - box-shadow: 0px 2px 5px black; - background: alpha(@background, 0.55); - border: 2px solid @selected; - } + .floating-notifications.background .notification-row .notification-background .notification .notification-content .body { + color: #cdd6f4; + } - .control-center-clear-all { - /* Clear All button */ - background: transparent; - padding: 5px; - } + .floating-notifications.background .notification-row .notification-background .notification > *:last-child > * { + min-height: 3.4em; + } - .control-center-clear-all:hover { - background: @hover; - } + .floating-notifications.background .notification-row .notification-background .notification > *:last-child > * .notification-action { + color: #cdd6f4; + background-color: #000000; + box-shadow: inset 0 0 0 1px #45475a; + margin: 7px; + } - .control-center-clear-all:active { - background: @selected; - } + .floating-notifications.background .notification-row .notification-background .notification > *:last-child > * .notification-action:hover { + box-shadow: inset 0 0 0 1px #45475a; + background-color: #000000; + color: #cdd6f4; + } - /*** Widgets ***/ - /* Title widget */ - .widget-title { - background: transparent; - margin-top: 15px; - margin-left: 15px; - margin-right: 15px; - } + .floating-notifications.background .notification-row .notification-background .notification > *:last-child > * .notification-action:active { + box-shadow: inset 0 0 0 1px #45475a; + background-color: #000000; + color: #cdd6f4; + } - /* Do Not Disturb widget */ - .widget-dnd { - background: transparent; - margin-left: 15px; - margin-right: 15px; - } + .floating-notifications.background .notification-row .notification-background .close-button { + margin: 7px; + padding: 2px; + color: #1e1e2e; + background-color: #000000; + } - .widget-dnd > switch { - background: @bg; - font-size: initial; - border-radius: 12px; - box-shadow: none; - padding: 2px; - } + .floating-notifications.background .notification-row .notification-background .close-button:hover { + background-color: #000000; + color: #1e1e2e; + } - /* Media Player widget */ - @define-color mpris-album-art-overlay rgba(0, 0, 0, 0.55); - @define-color mpris-button-hover rgba(0, 0, 0, 0.50); + .floating-notifications.background .notification-row .notification-background .close-button:active { + background-color: #000000; + color: #1e1e2e; + } - .widget-mpris { - } + .control-center { + box-shadow: 0 0 8px 0 rgba(0, 0, 0, 0.8), inset 0 0 0 1px #313244; + margin: 18px; + background-color: #000000; + color: #cdd6f4; + padding: 14px; + } - .widget-mpris .widget-mpris-player { - padding: 10px; - margin: 8px 15px; - /* background-color: @mpris-album-art-overlay; */ - box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.75); - border: 2px; - } + .control-center .widget-title > label { + color: #cdd6f4; + font-size: 1.3em; + } - .widget-mpris .widget-mpris-player .widget-mpris-title { - font-size: 16px; - } + .control-center .widget-title button { + color: #cdd6f4; + background-color: #313244; + box-shadow: inset 0 0 0 1px #45475a; + padding: 8px; + } - .widget-mpris .widget-mpris-player .widget-mpris-subtitle { - font-size: 14px; - } + .control-center .widget-title button:hover { + box-shadow: inset 0 0 0 1px #45475a; + background-color: #585b70; + color: #cdd6f4; + } - /* Buttons widget */ - .widget-buttons-grid { - /* background-color: alpha(@color2, 0.5); */ - } + .control-center .widget-title button:active { + box-shadow: inset 0 0 0 1px #45475a; + background-color: #74c7ec; + color: #1e1e2e; + } - .widget-buttons-grid > flowbox > flowboxchild > button { - /* background: alpha(@color2, 0.5); */ - /* border-radius: 12px; */ - min-width: 45px; - } + .control-center .notification-row .notification-background { + color: #cdd6f4; + background-color: #313244; + box-shadow: inset 0 0 0 1px #45475a; + margin-top: 14px; + } - .control-center .notification-row .notification-background .notification { - padding: 10px; - } + .control-center .notification-row .notification-background .notification { + padding: 7px; + } - .floating-notifications.background .notification-row .notification-background .close-button { - margin: 10px; - padding: 2px; - } + .control-center .notification-row .notification-background .notification.critical { + box-shadow: inset 0 0 7px 0 #f38ba8; + } - .floating-notifications.background .notification-row .notification-background { - margin: 18px; - padding: 0; - } + .control-center .notification-row .notification-background .notification .notification-content { + margin: 7px; + } - .floating-notifications.background .notification-row .notification-background .notification { - padding: 7px; - } - ''; + .control-center .notification-row .notification-background .notification .notification-content .summary { + color: #cdd6f4; + } + + .control-center .notification-row .notification-background .notification .notification-content .time { + color: #a6adc8; + } + + .control-center .notification-row .notification-background .notification .notification-content .body { + color: #cdd6f4; + } + + .control-center .notification-row .notification-background .notification > *:last-child > * { + min-height: 3.4em; + } + + .control-center .notification-row .notification-background .notification > *:last-child > * .notification-action { + color: #cdd6f4; + background-color: #11111b; + box-shadow: inset 0 0 0 1px #45475a; + margin: 7px; + } + + .control-center .notification-row .notification-background .notification > *:last-child > * .notification-action:hover { + box-shadow: inset 0 0 0 1px #45475a; + background-color: #313244; + color: #cdd6f4; + } + + .control-center .notification-row .notification-background .notification > *:last-child > * .notification-action:active { + box-shadow: inset 0 0 0 1px #45475a; + background-color: #74c7ec; + color: #cdd6f4; + } + + .control-center .notification-row .notification-background .close-button { + margin: 7px; + padding: 2px; + color: #1e1e2e; + background-color: #eba0ac; + } + + .close-button { + } + + .control-center .notification-row .notification-background .close-button:hover { + background-color: #f38ba8; + color: #1e1e2e; + } + + .control-center .notification-row .notification-background .close-button:active { + background-color: #f38ba8; + color: #1e1e2e; + } + + .control-center .notification-row .notification-background:hover { + box-shadow: inset 0 0 0 1px #45475a; + background-color: #7f849c; + color: #cdd6f4; + } + + .control-center .notification-row .notification-background:active { + box-shadow: inset 0 0 0 1px #45475a; + background-color: #74c7ec; + color: #cdd6f4; + } + + .notification.critical progress { + background-color: #f38ba8; + } + + .notification.low progress, + .notification.normal progress { + background-color: #89b4fa; + } + + .control-center-dnd { + margin-top: 5px; + background: #313244; + border: 1px solid #45475a; + box-shadow: none; + } + + .control-center-dnd:checked { + background: #313244; + } + + .control-center-dnd slider { + background: #45475a; + } + + .widget-dnd { + margin: 0px; + font-size: 1.1rem; + } + + .widget-dnd > switch { + font-size: initial; + background: #313244; + border: 1px solid #45475a; + box-shadow: none; + } + + .widget-dnd > switch:checked { + background: #313244; + } + + .widget-dnd > switch slider { + background: #45475a; + border: 1px solid #6c7086; + } + + .widget-mpris .widget-mpris-player { + background: #313244; + padding: 7px; + } + + .widget-mpris .widget-mpris-title { + font-size: 1.2rem; + } + + .widget-mpris .widget-mpris-subtitle { + font-size: 0.8rem; + } + + .widget-menubar > box > .menu-button-bar > button > label { + font-size: 3rem; + padding: 0.5rem 2rem; + } + + .widget-menubar > box > .menu-button-bar > :last-child { + color: #f38ba8; + } + + .power-buttons button:hover, + .powermode-buttons button:hover, + .screenshot-buttons button:hover { + background: #313244; + } + + .control-center .widget-label > label { + color: #cdd6f4; + font-size: 2rem; + } + + .widget-buttons-grid { + padding-top: 1rem; + } + + .widget-buttons-grid > flowbox > flowboxchild > button label { + font-size: 2.5rem; + } + + .widget-volume { + padding-top: 1rem; + } + + .widget-volume label { + font-size: 1.5rem; + color: #74c7ec; + } + + .widget-volume trough highlight { + background: #74c7ec; + } + + .widget-backlight trough highlight { + background: #f9e2af; + } + + .widget-backlight scale { + margin-right: 1rem; + } + + .widget-backlight label { + font-size: 1.5rem; + color: #f9e2af; + } + + .widget-backlight .KB { + padding-bottom: 1rem; + } + ''; + }; }; } diff --git a/modules/services/borg.nix b/modules/services/borg.nix index 668e70c..fad706a 100644 --- a/modules/services/borg.nix +++ b/modules/services/borg.nix @@ -37,12 +37,6 @@ in paths = [ "/var/lib" ]; - exclude = [ - "/var/lib/matrix-synapse" - "/var/lib/mautrix-signal" - "/var/lib/mautrix-whatsapp" - "/var/lib/bitwarden_rs" - ]; repo = "${baseRepo}/var-lib"; encryption.mode = "none"; compression = "auto,zstd"; diff --git a/modules/services/paperless-ngx.nix b/modules/services/paperless-ngx.nix deleted file mode 100644 index 4ab78b1..0000000 --- a/modules/services/paperless-ngx.nix +++ /dev/null @@ -1,29 +0,0 @@ -{ - services = { - paperless = { - enable = true; - consumptionDirIsPublic = true; - settings = { - PAPERLESS_CONSUMER_IGNORE_PATTERN = [ - ".DS_STORE/*" - "desktop.ini" - ]; - PAPERLESS_OCR_LANGUAGE = "deu+eng+nld"; - PAPERLESS_OCR_USER_ARGS = { - optimize = 1; - pdfa_image_compression = "lossless"; - }; - PAPERLESS_URL = "https://documents.liv.town"; - }; - }; - nginx.virtualHosts."documents.liv.town" = { - forceSSL = true; - sslCertificate = "/var/lib/acme/liv.town/cert.pem"; - sslCertificateKey = "/var/lib/acme/liv.town/key.pem"; - locations."/" = { - proxyPass = "http://127.0.0.1:28981"; - proxyWebsockets = true; - }; - }; - }; -} diff --git a/modules/services/vaultwarden.nix b/modules/services/vaultwarden.nix index 93ce630..38a2192 100644 --- a/modules/services/vaultwarden.nix +++ b/modules/services/vaultwarden.nix @@ -1,70 +1,34 @@ +{ config, ... }: { - config, - host, - pkgs, - username, - ... -}: -let - baseRepo = "ssh://liv@dandelion:9123/spinners/rootvol/backups/${host}"; -in -{ - services = { - vaultwarden = { - enable = true; - dbBackend = "sqlite"; - config = { - SIGNUPS_ALLOWED = false; - ENABLE_WEBSOCKET = true; - SENDS_ALLOWED = true; - INVITATIONS_ENABLED = true; - EMERGENCY_ACCESS_ALLOWED = true; - EMAIL_ACCESS_ALLOWED = true; - DOMAIN = "https://passwords.liv.town"; - ROCKET_ADDRESS = "0.0.0.0"; - ROCKET_PORT = 8003; - }; + services.vaultwarden = { + enable = true; + dbBackend = "sqlite"; + config = { + SIGNUPS_ALLOWED = false; + ENABLE_WEBSOCKET = true; + SENDS_ALLOWED = true; + INVITATIONS_ENABLED = true; + EMERGENCY_ACCESS_ALLOWED = true; + EMAIL_ACCESS_ALLOWED = true; + DOMAIN = "https://passwords.liv.town"; + ROCKET_ADDRESS = "0.0.0.0"; + ROCKET_PORT = 8003; }; - nginx = { - enable = true; - recommendedProxySettings = true; - recommendedTlsSettings = true; - virtualHosts = { - "passwords.liv.town" = { - forceSSL = true; - sslCertificate = "/var/lib/acme/liv.town/cert.pem"; - sslCertificateKey = "/var/lib/acme/liv.town/key.pem"; - locations."/" = { - proxyPass = "http://127.0.0.1:${toString config.services.vaultwarden.config.ROCKET_PORT}/"; - proxyWebsockets = true; - }; + }; + services.nginx = { + enable = true; + recommendedProxySettings = true; + recommendedTlsSettings = true; + virtualHosts = { + "passwords.liv.town" = { + forceSSL = true; + sslCertificate = "/var/lib/acme/liv.town/cert.pem"; + sslCertificateKey = "/var/lib/acme/liv.town/key.pem"; + locations."/" = { + proxyPass = "http://127.0.0.1:${toString config.services.vaultwarden.config.ROCKET_PORT}/"; + proxyWebsockets = true; }; }; }; - borgbackup.jobs."violet-vaultwarden" = { - paths = [ "/var/lib/bitwarden_rs" ]; - repo = "${baseRepo}/var-vaultwarden"; - encryption.mode = "none"; - compression = "auto,zstd"; - startAt = "daily"; - preHook = '' - systemctl stop vaultwarden - ''; - postHook = '' - systemctl start vaultwarden - if [ $exitStatus -eq 2 ]; then - ${pkgs.ntfy-sh}/bin/ntfy send https://notify.liv.town/${host} "borgbackup: ${host} backup (vaultwarden) failed with errors" - else - ${pkgs.ntfy-sh}/bin/ntfy send https://notify.liv.town/${host} "borgbackup: ${host} backup (vaultwarden) completed succesfully with exit status $exitStatus" - fi - ''; - user = "root"; - extraCreateArgs = [ - "--stats" - ]; - environment = { - BORG_RSH = "ssh -p 9123 -i /home/${username}/.ssh/id_ed25519"; - }; - }; }; } diff --git a/modules/services/violet.nix b/modules/services/violet.nix index 15639ef..122aa03 100644 --- a/modules/services/violet.nix +++ b/modules/services/violet.nix @@ -7,7 +7,6 @@ ++ [ (import ./binternet-proxy.nix) ] ++ [ (import ./bluemap-proxy.nix) ] ++ [ (import ./docker.nix) ] - ++ [ (import ./email.nix) ] ++ [ (import ./forgejo.nix) ] ++ [ (import ./grafana.nix) ] ++ [ (import ./guacamole.nix) ] @@ -18,12 +17,9 @@ ++ [ (import ./matrix/default.nix) ] ++ [ (import ./mumble.nix) ] ++ [ (import ./monitoring.nix) ] - # ++ [ (import ./minio.nix) ] - # ++ [ (import ./nextcloud.nix) ] ++ [ (import ./ntfy.nix) ] ++ [ (import ./nginx.nix) ] ++ [ (import ./nix-serve.nix) ] - ++ [ (import ./paperless-ngx.nix) ] ++ [ (import ./radicale.nix) ] ++ [ (import ./remote-build.nix) ] ++ [ (import ./readarr.nix) ] diff --git a/roles/gui.nix b/roles/gui.nix index ca6cc4b..de10ee3 100644 --- a/roles/gui.nix +++ b/roles/gui.nix @@ -26,7 +26,6 @@ in element-desktop gajim signal-desktop - mumble anki-bin obs-studio wdisplays diff --git a/secrets/violet/secrets.yaml b/secrets/violet/secrets.yaml index f497f8d..64f39ae 100644 --- a/secrets/violet/secrets.yaml +++ b/secrets/violet/secrets.yaml @@ -22,7 +22,7 @@ sops: S3pjSjlhZjZiSDBNakhLVzNKMjd3bWsKC2geLVXFp190lkjxtmZKq8aLN0XMNeAI VqbwIY3a30iuWAaxqf8h1ZuCGJvbAZZBevFZraj9yktRHc54JV3Aww== -----END AGE ENCRYPTED FILE----- - lastmodified: "2025-08-17T12:59:12Z" - mac: ENC[AES256_GCM,data:d0fls75JYsDd4QyF1lUi6CGinPJBRNOwgHg4fbnp40P/5rhGZsH8dL9+n2qQjCqEWPtBDbAGz0LYQ8T/VxaNHQwuvCX1Q1LCjlwbrZHEMLXNhd3VOa4b7e9k8+mgccLKDj52PlXSNxW+ATplZSTCMFnrBuDkfiW09gz8He8s8jo=,iv:v8XNLDFu1VhQ7A+CpimWJb95YvzRCWpgqRj6Uhx5wLU=,tag:kAg/NDen7VkMrOfV92SEkA==,type:str] + lastmodified: "2025-08-06T12:33:06Z" + mac: ENC[AES256_GCM,data:9U5QLQrk+61pIgfWSC468m7url0GGztamVlu0D74hSOf8iyLHiL1RHIKuB9t8Dk5bIGruVQE2zN8TZIHzktMofc1arSP69Zxl41EGNCyrm2uAyEguQelxZzRb/7bNe/Kkm6oLN1fQ8TJ1t7nyiTQCdbhl/wFZwSZxGemv7t8hKI=,iv:AEvP5cdOl0dX3o/0Zoy8112rqhE0cKNX8B5lyFEe6f4=,tag:LRqM5L3DB4Tyhe++4Hcqcw==,type:str] unencrypted_suffix: _unencrypted version: 3.10.2