diff --git a/Readme.md b/Readme.md index 260af6cf..00189de4 100644 --- a/Readme.md +++ b/Readme.md @@ -1,6 +1,23 @@ # PlayOS -A custom Linux system (built on [NixOS](https://nixos.org/)) to run web applications in a fullscreen kiosk. Built for Dividat Play. +PlayOS is a Linux system to act as an application kiosk. PlayOS is a layer on top of [NixOS](https://nixos.org/). + +The base layer functionality of PlayOS is: + +- Automatic A/B update mechanism +- Read-only system partitions, with selected persisted configuration data +- Web-based configuration interface for + - network (LAN, WLAN, HTTP proxy, static IP), + - localization, + - system status, + - remote maintenance. +- Installer +- Live system +- Remote maintenance via ZeroTier + +The application layer may be defined in a single Nix file. + +This repository contains the application layer for running the Dividat Play web app and supporting system services in a fullscreen kiosk. See the [documentation](docs/arch) for more information. @@ -34,10 +51,10 @@ A helper is available to quickly start a virtual machine: ./build vm ``` -In order to get the vm system journal, look at the output of `run-playos-in-vm` +In order to get the vm system journal, look at the output of `run-in-vm` for a command starting with `socat`. -See the output of `run-playos-in-vm --help` for more information. +See the output of `run-in-vm --help` for more information. #### Guest networking @@ -59,7 +76,7 @@ Tests added to `test/integration` are executed via a GitHub Action when pushing ## Deployment -Update bundles are hosted on Amazon S3. The script `bin/deploy-playos-update` will handle signing and uploading of bundle. +Update bundles are hosted on Amazon S3. The script `bin/deploy-update` will handle signing and uploading of bundle. The arguments `updateUrl` (from where updates will be fetched by PlayOS systems), `deployURL` (where bundles should be deployed to) must be specified. For example: `nix build --arg updateUrl https://dist.dividat.com/releases/playos/master/ --arg deployUrl s3://dist.dividat.ch/releases/playos/master/`. @@ -69,7 +86,7 @@ To release an update to the `develop` channel: ``` ./build develop -./result/bin/deploy-playos-update --key PATH_TO_KEY.pem +./result/bin/deploy-update --key PATH_TO_KEY.pem ``` ### Key switch diff --git a/application.nix b/application.nix new file mode 100644 index 00000000..d564e400 --- /dev/null +++ b/application.nix @@ -0,0 +1,182 @@ +rec { + fullProductName = "Dividat PlayOS"; + safeProductName = "playos"; + version = "2023.9.0"; + + greeting = label: '' + _ + , -"" "". + ,' ____ `. + ,' ,' `. `._ + (`. _..--.._ ,' ,' \\ \\ + (`-.\\ .-"" ""' / ( d _b + (`._ `-"" ,._ ( `-( \\ + <_ ` ( <`< \\ `-._\\ + <`- (__< < : ${label} + (__ (_<_< ; + -----`------------------------------------------------------ ----------- ------- ----- --- -- - + ''; + + overlays = [ + (import ./application/overlays version) + # Limit virtual terminals that can be switched to + # Virtual terminal 7 is the kiosk, 8 is the status screen + (import ./application/overlays/xorg { activeVirtualTerminals = [ 7 8 ]; }) + ]; + + module = { config, lib, pkgs, ... }: { + + imports = [ ./application/playos-status.nix ]; + + # Kiosk runs as a non-privileged user + users.users.play = { + isNormalUser = true; + home = "/home/play"; + extraGroups = [ + "dialout" # Access to serial ports for the Senso flex + ]; + }; + + # Note that setting up "/home" as persistent fails due to https://github.com/NixOS/nixpkgs/issues/6481 + playos.storage.persistentFolders."/home/play" = { + mode = "0700"; + user = "play"; + group = "users"; + }; + + # System-wide packages + environment.systemPackages = with pkgs; [ breeze-contrast-cursor-theme ]; + + # Kiosk session + services.xserver = let sessionName = "kiosk-browser"; + in { + enable = true; + + desktopManager = { + xterm.enable = false; + session = [{ + name = sessionName; + start = '' + # Disable screen-saver control (screen blanking) + xset s off + xset s noblank + xset -dpms + + # Localization for xsession + if [ -f /var/lib/gui-localization/lang ]; then + export LANG=$(cat /var/lib/gui-localization/lang) + fi + if [ -f /var/lib/gui-localization/keymap ]; then + setxkbmap $(cat /var/lib/gui-localization/keymap) || true + fi + + # Set preferred screen resolution + scaling_pref=$(cat /var/lib/gui-localization/screen-scaling 2>/dev/null || echo "default") + case "$scaling_pref" in + "default" | "full-hd") + xrandr --size 1920x1080;; + "native") + # Nothing to do, let system decide. + ;; + *) + echo "Unknown scaling preference '$scaling_pref'. Ignoring." + ;; + esac + + # Enable Qt WebEngine Developer Tools (https://doc.qt.io/qt-5/qtwebengine-debugging.html) + export QTWEBENGINE_REMOTE_DEBUGGING="127.0.0.1:3355" + + ${pkgs.playos-kiosk-browser}/bin/kiosk-browser \ + ${config.playos.kioskUrl} \ + http://localhost:3333/ + + waitPID=$! + ''; + }]; + }; + + displayManager = { + # Always automatically log in play user + lightdm = { + enable = true; + greeter.enable = false; + autoLogin.timeout = 0; + }; + + autoLogin = { + enable = true; + user = "play"; + }; + + defaultSession = sessionName; + + sessionCommands = '' + ${pkgs.xorg.xrdb}/bin/xrdb -merge < ${ttyPath} diff --git a/system/modules/playos.nix b/base/default.nix similarity index 66% rename from system/modules/playos.nix rename to base/default.nix index b43f8dda..1a27b9e7 100644 --- a/system/modules/playos.nix +++ b/base/default.nix @@ -1,16 +1,19 @@ # This is the toplevel module for all PlayOS related functionalities. # Things that are injected into the system -{pkgs, version, updateCert, kioskUrl, greeting, playos-controller}: +{pkgs, version, kioskUrl, safeProductName, fullProductName, greeting, playos-controller}: {config, lib, ...}: with lib; { imports = [ + (import ./networking.nix { hostName = safeProductName; inherit lib pkgs config; }) + ./localization.nix + ./remote-maintenance.nix + ./self-update ./system-partition.nix ./volatile-root.nix - ./playos-status.nix ]; options = { @@ -22,31 +25,26 @@ with lib; playos.kioskUrl = mkOption { type = types.str; }; - - playos.updateCert = mkOption { - type = types.package; - }; - }; config = { - # Use overlayed pkgs. - nixpkgs.pkgs = pkgs; + nixpkgs.pkgs = lib.mkDefault pkgs; # Custom label when identifying OS - system.nixos.label = "PlayOS-${version}"; + system.nixos.label = "${safeProductName}-${version}"; # disable installation of bootloader boot.loader.grub.enable = false; - playos = { - inherit version updateCert kioskUrl; - }; + # disable installation of inaccessible documentation + documentation.enable = false; + + playos = { inherit version kioskUrl; }; # 'Welcome Screen' services.getty = { - greetingLine = greeting "Dividat PlayOS (${version})"; + greetingLine = greeting "${fullProductName} (${version})"; helpLine = ""; }; diff --git a/system/localization.nix b/base/localization.nix similarity index 93% rename from system/localization.nix rename to base/localization.nix index a1546800..c791bbeb 100644 --- a/system/localization.nix +++ b/base/localization.nix @@ -7,7 +7,7 @@ let in { # Localization configuration - volatileRoot.persistentFolders."/var/lib/gui-localization" = { + playos.storage.persistentFolders."/var/lib/gui-localization" = { mode = "0755"; user = "root"; group = "root"; diff --git a/system/networking/default.nix b/base/networking.nix similarity index 95% rename from system/networking/default.nix rename to base/networking.nix index 3151fecf..47dd3c65 100644 --- a/system/networking/default.nix +++ b/base/networking.nix @@ -1,4 +1,4 @@ -{config, pkgs, lib, ... }: +{config, pkgs, lib, hostName, ... }: { # Enable non-free firmware @@ -37,7 +37,7 @@ }; networking = { - hostName = "playos"; + hostName = hostName; wireless = { enable = true; @@ -74,7 +74,7 @@ ''; # Make connman folder persistent - volatileRoot.persistentFolders."/var/lib/connman" = { + playos.storage.persistentFolders."/var/lib/connman" = { mode = "0700"; user = "root"; group = "root"; diff --git a/base/remote-maintenance.nix b/base/remote-maintenance.nix new file mode 100644 index 00000000..c96cec21 --- /dev/null +++ b/base/remote-maintenance.nix @@ -0,0 +1,56 @@ +{config, pkgs, lib, ... }: +let + cfg = config.playos.remoteMaintenance; +in +{ + options = { + playos.remoteMaintenance = with lib; { + enable = mkEnableOption "Remote maintenance"; + + networks = mkOption { + default = []; + example = []; + type = types.listOf types.str; + description = "ZeroTier networks to join"; + }; + + authorizedKeys = mkOption { + default = []; + example = []; + type = types.listOf types.str; + description = "Public SSH keys authorized to log in"; + }; + + requireOptIn = mkOption { + default = true; + example = false; + description = "With required opt-in ZeroTier needs to be started on the machine before remote access is possible"; + type = lib.types.bool; + }; + + }; + }; + + config = lib.mkIf cfg.enable { + services.zerotierone = { + enable = true; + joinNetworks = cfg.networks; + }; + + # If opt-in is enabled, prevent ZeroTier from running on startup + systemd.services.zerotierone.wantedBy = lib.mkIf cfg.requireOptIn (lib.mkForce []); + + # Allow remote access via OpenSSH + services.openssh = { + enable = true; + + # Restrict authentication to authorized keys + settings.PasswordAuthentication = false; + settings.KbdInteractiveAuthentication = false; + }; + + # only with these special keys: + users.users.root.openssh.authorizedKeys.keys = cfg.authorizedKeys; + + }; +} diff --git a/base/self-update/default.nix b/base/self-update/default.nix new file mode 100644 index 00000000..2775bc3a --- /dev/null +++ b/base/self-update/default.nix @@ -0,0 +1,79 @@ +{ config, pkgs, lib, ... }: +let + cfg = config.playos.selfUpdate; +in +{ + options = { + playos.selfUpdate = with lib; { + enable = mkEnableOption "Online self update"; + + updateCert = mkOption { + default = null; + example = "path to public key"; + type = types.path; + description = "The public key for the key bundles are signed with"; + }; + }; + }; + + config = lib.mkIf cfg.enable { + environment.systemPackages = with pkgs; [ rauc ]; + + services.dbus.packages = with pkgs; [ rauc ]; + + systemd.services.rauc = { + description = "RAUC Update Service"; + serviceConfig.ExecStart = "${pkgs.rauc}/bin/rauc service"; + serviceConfig.User = "root"; + wantedBy = [ "multi-user.target" ]; + }; + + environment.etc."rauc/system.conf" = { + text = '' + [system] + compatible=dividat-play-computer + bootloader=grub + grubenv=/boot/grub/grubenv + statusfile=/boot/status.ini + + [keyring] + path=cert.pem + + [slot.system.a] + device=/dev/disk/by-label/system.a + type=ext4 + bootname=a + + [slot.system.b] + device=/dev/disk/by-label/system.b + type=ext4 + bootname=b + ''; + }; + + environment.etc."rauc/cert.pem" = { + source = cfg.updateCert; + }; + + # This service adjusts for a known weakness of the update mechanism that is due to the + # use of the `/boot` partition for storing RAUC's statusfile. The `/boot` partition + # was chosen to use FAT32 in order to use it as EFI system partition. FAT32 has no + # journaling and so the atomicity guarantees RAUC tries to give for statusfile updates + # are diminished. This service looks for leftovers from interrupted statusfile updates + # and tries to recover. + # Note that as previous installations will keep their boot partition unchanged even + # after system updates, this or a similar recovery mechanism would be required even if + # we change partition layout for new systems going forward. + systemd.services.statusfile-recovery = { + description = "status.ini recovery"; + serviceConfig.ExecStart = "${pkgs.bash}/bin/bash ${./recover-from-tmpfile} /boot/status.ini"; + serviceConfig.Type = "oneshot"; + serviceConfig.User = "root"; + serviceConfig.StandardOutput = "syslog"; + serviceConfig.SyslogIdentifier = "statusfile-recovery"; + serviceConfig.RemainAfterExit = true; + wantedBy = [ "multi-user.target" ]; + }; + + }; +} diff --git a/system/rauc/recover-from-tmpfile b/base/self-update/recover-from-tmpfile similarity index 100% rename from system/rauc/recover-from-tmpfile rename to base/self-update/recover-from-tmpfile diff --git a/system/modules/system-partition.nix b/base/system-partition.nix similarity index 92% rename from system/modules/system-partition.nix rename to base/system-partition.nix index 0cb1404d..5ae6e359 100644 --- a/system/modules/system-partition.nix +++ b/base/system-partition.nix @@ -1,11 +1,11 @@ { config, pkgs, lib, options, ... }: with lib; let - cfg = config.systemPartition; + cfg = config.playos.storage.systemPartition; in { options = { - systemPartition = { + playos.storage.systemPartition = { enable = mkEnableOption "System partition"; device = mkOption { @@ -32,7 +32,7 @@ in }; }; - config = mkIf config.systemPartition.enable { + config = mkIf cfg.enable { fileSystems = { "/mnt/system" = { device = cfg.device; diff --git a/system/modules/volatile-root.nix b/base/volatile-root.nix similarity index 97% rename from system/modules/volatile-root.nix rename to base/volatile-root.nix index baa27c67..b4f2d767 100644 --- a/system/modules/volatile-root.nix +++ b/base/volatile-root.nix @@ -1,11 +1,11 @@ { config, pkgs, lib, ... }: let - cfg = config.volatileRoot; + cfg = config.playos.storage; in with lib; { options = { - volatileRoot = { + playos.storage = { persistentDataPartition = { device = mkOption { default = null; diff --git a/bootloader/rescue/default.nix b/bootloader/rescue/default.nix index b4cbd367..10bd514f 100644 --- a/bootloader/rescue/default.nix +++ b/bootloader/rescue/default.nix @@ -1,4 +1,5 @@ { stdenv, lib +, application , squashfsTools, closureInfo, makeInitrd, linkFarm , importFromNixos , writeScript, dialog @@ -16,7 +17,7 @@ let do ${dialog}/bin/dialog --clear --title "" \ - --backtitle "PlayOS - Rescue System" \ + --backtitle "${application.fullProductName} - Rescue System" \ --nocancel \ --menu "Please Select an action" 0 0 0 \ "wipe-user-data" "Delete all user data." \ @@ -87,7 +88,7 @@ in ''; }; networking = { - hostName = "playos-rescue"; + hostName = "${application.safeProductName}-rescue"; # enable wpa_supplicant wireless = { enable = true; diff --git a/build b/build index d9145048..acedea07 100755 --- a/build +++ b/build @@ -26,7 +26,7 @@ if [ "$TARGET" == "vm" ]; then --arg buildDisk false) echo - echo "Run ./result/bin/run-playos-in-vm to start a VM." + echo "Run ./result/bin/run-in-vm to start a VM." echo echo "- unfocus: ctrl-alt-g" echo "- quit: ctrl-alt-q" @@ -44,7 +44,7 @@ elif [ "$TARGET" == "develop" ]; then --arg buildDisk false) echo - echo "Run ./result/bin/deploy-playos-update to deploy." + echo "Run ./result/bin/deploy-update to deploy." elif [ "$TARGET" == "validation" ]; then @@ -58,7 +58,7 @@ elif [ "$TARGET" == "validation" ]; then --arg buildDisk false) echo - echo "Run ./result/bin/deploy-playos-update to deploy." + echo "Run ./result/bin/deploy-update to deploy." elif [ "$TARGET" == "master" ]; then @@ -72,7 +72,7 @@ elif [ "$TARGET" == "master" ]; then --arg buildDisk false) echo - echo "Run ./result/bin/deploy-playos-update to deploy." + echo "Run ./result/bin/deploy-update to deploy." # Create a stuck system that will not self-update # usage: ./build stuck https://url-for-kiosk diff --git a/controller/Changelog.md b/controller/Changelog.md index dcbbb7b9..3ba37e70 100644 --- a/controller/Changelog.md +++ b/controller/Changelog.md @@ -1,3 +1,19 @@ +# [2023.9.0] - 2023-09-12 + +# [2023.9.0-VALIDATION] - 2023-09-11 + +## Changed + +- os: Make Full HD the default screen resolution +- os: Widen rules for captive portal detection +- os: Split system definition into base and application layers +- os: Make build outputs and displayed system name configurable through application layer +- os: Change installer script to exclude installer medium from installation targets +- os: Include live system ISO in deployed outputs +- os: Update nixpkgs channel to 23.05 +- status screen: Display persistent storage usage statistics +- controller: Allow opening captive portal when settings are open + # [2023.2.0] - 2023-03-06 # [2023.2.0-VALIDATION] - 2023-02-27 diff --git a/controller/default.nix b/controller/default.nix index faa0f6f6..368ef141 100644 --- a/controller/default.nix +++ b/controller/default.nix @@ -1,4 +1,4 @@ -{ pkgs, version, updateUrl, kioskUrl }: +{ pkgs, version, bundleName, updateUrl, kioskUrl }: with pkgs; @@ -18,12 +18,20 @@ ocamlPackages.buildDunePackage rec { -e "s,@PLAYOS_UPDATE_URL@,${updateUrl},g" \ -e "s,@PLAYOS_KIOSK_URL@,${kioskUrl},g" \ ./server/info.ml + + sed -i \ + -e "s,@PLAYOS_BUNDLE_NAME@,${bundleName},g" \ + ./server/update.ml ''; useDune2 = true; - buildInputs = with ocamlPackages; [ + nativeBuildInputs = [ discount # Transform Markdown to HTML + ocamlPackages.obus + ]; + + buildInputs = with ocamlPackages; [ nodejs utop ]; diff --git a/controller/gui/style.css b/controller/gui/style.css index 4051ab32..335574ee 100644 --- a/controller/gui/style.css +++ b/controller/gui/style.css @@ -155,11 +155,11 @@ html { /* Info */ -.d-Info__RemoteManagementForm { +.d-Info__RemoteMaintenanceForm { display: inline; } -.d-Info__RemoteManagementAddress { +.d-Info__RemoteMaintenanceAddress { margin-right: var(--spacing-dog); } diff --git a/controller/server/gui.ml b/controller/server/gui.ml index 932f957e..b4eef3b6 100644 --- a/controller/server/gui.ml +++ b/controller/server/gui.ml @@ -21,8 +21,9 @@ let static () = Opium.Middleware.static ~local_path:static_dir ~uri_prefix:"/static" () let page html = + let headers = Cohttp.Header.init_with "content-type" "text/html" in Format.asprintf "%a" (Tyxml.Html.pp ()) html - |> Response.of_string_body + |> Response.of_string_body ~headers let success content = page (Page.html (Tyxml.Html.txt content)) @@ -492,7 +493,7 @@ module ChangelogGui = struct Lwt.return (page (Changelog_page.html changelog))) end -module RemoteManagementGui = struct +module RemoteMaintenanceGui = struct let rec wait_until_zerotier_is_on () = match%lwt Zerotier.get_status () with @@ -504,18 +505,18 @@ module RemoteManagementGui = struct let build ~systemd app = app - |> post "/remote-management/enable" (fun _ -> + |> post "/remote-maintenance/enable" (fun _ -> let%lwt () = Systemd.Manager.start_unit systemd "zerotierone.service" in with_timeout { duration = 2.0 ; on_timeout = fun () -> - let msg = "Timeout starting remote management service." in + let msg = "Timeout starting remote maintenance service." in let%lwt () = Logs_lwt.err (fun m -> m "%s" msg) in fail_with msg } wait_until_zerotier_is_on) - |> post "/remote-management/disable" (fun _ -> + |> post "/remote-maintenance/disable" (fun _ -> let%lwt () = Systemd.Manager.stop_unit systemd "zerotierone.service" in redirect' (Uri.of_string "/info")) end @@ -540,7 +541,7 @@ let routes ~systemd ~shutdown ~health_s ~update_s ~rauc ~connman app = |> LabelGui.build |> StatusGui.build ~health_s ~update_s ~rauc |> ChangelogGui.build - |> RemoteManagementGui.build ~systemd + |> RemoteMaintenanceGui.build ~systemd (* NOTE: probably easier to create a record with all the inputs instead of passing in x arguments. *) let start ~port ~systemd ~shutdown ~health_s ~update_s ~rauc ~connman = diff --git a/controller/server/update.ml b/controller/server/update.ml index ab96f20a..c8150f9c 100644 --- a/controller/server/update.ml +++ b/controller/server/update.ml @@ -4,6 +4,8 @@ open Sexplib.Conv let log_src = Logs.Src.create "update" +let bundle_name = + "@PLAYOS_BUNDLE_NAME@" (* Version handling *) @@ -65,14 +67,15 @@ let get_version_info ~proxy url rauc = |> return ) |> Lwt_result.catch +let bundle_file_name version = + Format.sprintf "%s-%s.raucb" bundle_name version + let latest_download_url ~update_url version_string = - let bundle = Format.sprintf "playos-%s.raucb" version_string in - Format.sprintf "%s%s/%s" update_url version_string bundle + Format.sprintf "%s%s/%s" update_url version_string (bundle_file_name version_string) (** download RAUC bundle *) let download ?proxy url version = - let bundle = Format.sprintf "playos-%s.raucb" version in - let bundle_path = Format.sprintf "/tmp/%s" bundle in + let bundle_path = Format.sprintf "/tmp/%s" (bundle_file_name version) in let options = [ "--continue-at"; "-" (* resume download *) ; "--limit-rate"; "10M" diff --git a/controller/server/view/common/page.ml b/controller/server/view/common/page.ml index 25afde2b..bfa3a107 100644 --- a/controller/server/view/common/page.ml +++ b/controller/server/view/common/page.ml @@ -69,7 +69,7 @@ let html ?current_page ?header content = ~a:[ a_class [ "d-Layout__Aside" ] ] [ nav ([ Info; Network; Localization; SystemStatus; Changelog ] - |> List.map (menu_item current_page)) + |> List.concat_map (fun page -> [ menu_item current_page page; txt " " ])) ; div ~a: [ a_class [ "d-Layout__Shutdown" ] ] [ menu_item current_page Shutdown ] diff --git a/controller/server/view/info_page.ml b/controller/server/view/info_page.ml index 4a486277..dfe837a5 100644 --- a/controller/server/view/info_page.ml +++ b/controller/server/view/info_page.ml @@ -1,11 +1,11 @@ open Info open Tyxml.Html -let remote_management_form action button_label = +let remote_maintenance_form action button_label = form - ~a:[ a_action ("/remote-management/" ^ action) + ~a:[ a_action ("/remote-maintenance/" ^ action) ; a_method `Post - ; a_class [ "d-Info__RemoteManagementForm" ] + ; a_class [ "d-Info__RemoteMaintenanceForm" ] ; Unsafe.string_attrib "is" "disable-after-submit" ] [ input @@ -16,20 +16,20 @@ let remote_management_form action button_label = () ] -let remote_management address = +let remote_maintenance address = match address with | Some address -> [ span - ~a:[ a_class [ "d-Info__RemoteManagementAddress" ] ] + ~a:[ a_class [ "d-Info__RemoteMaintenanceAddress" ] ] [ txt address ] - ; remote_management_form "disable" "Disable" + ; remote_maintenance_form "disable" "Disable" ] | None -> [ div ~a:[ a_class [ "d-Note" ] ] - [ txt "Enabling remote management allows Dividat to access this computer at a distance. For this purpose the computer's public IP address is shared with ZeroTier, a US-based company providing an overlay network." + [ txt "Enabling remote maintenance allows Dividat to access this computer at a distance. For this purpose the computer's public IP address is shared with ZeroTier, a US-based company providing an overlay network." ] - ; remote_management_form "enable" "Enable" + ; remote_maintenance_form "enable" "Enable" ] let html server_info = @@ -53,7 +53,7 @@ let html server_info = ; Definition.term [ txt "Local time" ] ; Definition.description [ txt server_info.local_time ] - ; Definition.term [ txt "Remote management" ] - ; Definition.description (remote_management server_info.zerotier_address) + ; Definition.term [ txt "Remote maintenance" ] + ; Definition.description (remote_maintenance server_info.zerotier_address) ] ]) diff --git a/controller/shell.nix b/controller/shell.nix index 4fe0b547..2ed2ad09 100644 --- a/controller/shell.nix +++ b/controller/shell.nix @@ -1,11 +1,14 @@ let - pkgs = import ../pkgs { - version = "1.0.0"; + pkgs = import ../pkgs {}; + playos-controller = import ./default.nix { + pkgs = pkgs; + version = "1.0.0-dev"; + bundleName = "playos"; updateUrl = "http://localhost:9999/"; kioskUrl = "https://dev-play.dividat.com/"; }; in - pkgs.playos-controller.overrideAttrs(oldAttrs: { + playos-controller.overrideAttrs(oldAttrs: { buildInputs = oldAttrs.buildInputs ++ (with pkgs; [ python37Packages.pywatchman ]); diff --git a/default.nix b/default.nix index 05f2ad44..4e059f9c 100644 --- a/default.nix +++ b/default.nix @@ -1,8 +1,11 @@ +let + bootstrap = (import {}); +in { ###### Configuration that is passed into the build system ####### # Certificate used for verification of update bundles - updateCert ? (import {}).lib.warn "Using dummy update certificate. Build artifacts can only be used for local development." ./pki/dummy/cert.pem + updateCert ? (bootstrap.lib.warn "Using dummy update certificate. Build artifacts can only be used for local development." ./pki/dummy/cert.pem) # url from where updates should be fetched , updateUrl ? "http://localhost:9000/" @@ -11,6 +14,8 @@ # url where kiosk points , kioskUrl ? "https://play.dividat.com" +, applicationPath ? ./application.nix + ##### Allow disabling the build of unused artifacts when developing/testing ##### , buildInstaller ? true , buildBundle ? true @@ -19,31 +24,29 @@ }: let - version = "2023.2.0"; - # List the virtual terminals that can be switched to from the Xserver - activeVirtualTerminals = [ 7 8 ]; + application = import applicationPath; - pkgs = import ./pkgs { inherit version updateUrl kioskUrl activeVirtualTerminals; }; + pkgs = import ./pkgs (with application; { + applicationOverlays = application.overlays; + }); # lib.makeScope returns consistent set of packages that depend on each other (and is my new favorite nixpkgs trick) components = with pkgs; lib.makeScope newScope (self: with self; { - greeting = label: '' - _ - , -"" "". - ,' ____ `. - ,' ,' `. `._ - (`. _..--.._ ,' ,' \\ \\ - (`-.\\ .-"" ""' / ( d _b - (`._ `-"" ,._ ( `-( \\ - <_ ` ( <`< \\ `-._\\ - <`- (__< < : ${label} - (__ (_<_< ; - -----`------------------------------------------------------ ----------- ------- ----- --- -- - - ''; - - inherit updateUrl deployUrl kioskUrl version; + inherit updateUrl deployUrl kioskUrl; + inherit (application) version safeProductName fullProductName; + + greeting = lib.attrsets.attrByPath [ "greeting" ] (label: label) application; + + # Controller + playos-controller = import ./controller { + pkgs = pkgs; + version = version; + bundleName = safeProductName; + updateUrl = updateUrl; + kioskUrl = kioskUrl; + }; # Documentations docs = callPackage ./docs {}; @@ -51,11 +54,11 @@ let # Certificate used for verification of update bundles updateCert = copyPathToStore updateCert; - # NixOS system toplevel - systemToplevel = callPackage ./system {}; + # System image as used in full installation + systemImage = callPackage ./system-image { application = application; }; # USB live system - live = callPackage ./live {}; + live = callPackage ./live { application = application; }; # Installation script install-playos = callPackage ./installer/install-playos { @@ -63,32 +66,32 @@ let }; # Rescue system - rescueSystem = callPackage ./bootloader/rescue {}; + rescueSystem = callPackage ./bootloader/rescue { application = application; }; # Installer ISO image installer = callPackage ./installer {}; # Script to deploy updates - deploy-playos-update = callPackage ./deployment/deploy-playos-update {}; + deploy-update = callPackage ./deployment/deploy-update { application = application; }; # RAUC bundle unsignedRaucBundle = callPackage ./rauc-bundle {}; # NixOS system toplevel with test machinery - testingToplevel = callPackage ./testing/system {}; + testingToplevel = callPackage ./testing/system { application = application; }; # Disk image containing pre-installed system disk = if buildDisk then callPackage ./testing/disk {} else null; # Script for spinning up VMs - run-playos-in-vm = callPackage ./testing/run-playos-in-vm {}; + run-in-vm = callPackage ./testing/run-in-vm {}; }); in with pkgs; stdenv.mkDerivation { - name = "playos-${version}"; + name = "${components.safeProductName}-${components.version}"; buildInputs = [ rauc @@ -102,25 +105,25 @@ with pkgs; stdenv.mkDerivation { ln -s ${components.docs} $out/docs mkdir -p $out/bin - cp ${components.run-playos-in-vm} $out/bin/run-playos-in-vm - chmod +x $out/bin/run-playos-in-vm + cp ${components.run-in-vm} $out/bin/run-in-vm + chmod +x $out/bin/run-in-vm # Certificate used to verify update bundles ln -s ${updateCert} $out/cert.pem '' + lib.optionalString buildLive '' - ln -s ${components.live}/iso/playos-live-${version}.iso $out/playos-live-${version}.iso + ln -s ${components.live}/iso/${components.safeProductName}-live-${components.version}.iso $out/${components.safeProductName}-live-${components.version}.iso '' # Installer ISO image + lib.optionalString buildInstaller '' - ln -s ${components.installer}/iso/playos-installer-${version}.iso $out/playos-installer-${version}.iso + ln -s ${components.installer}/iso/${components.safeProductName}-installer-${components.version}.iso $out/${components.safeProductName}-installer-${components.version}.iso '' # RAUC bundle + lib.optionalString buildBundle '' - ln -s ${components.unsignedRaucBundle} $out/playos-${version}-UNSIGNED.raucb - cp ${components.deploy-playos-update} $out/bin/deploy-playos-update - chmod +x $out/bin/deploy-playos-update + ln -s ${components.unsignedRaucBundle} $out/${components.safeProductName}-${components.version}-UNSIGNED.raucb + cp ${components.deploy-update} $out/bin/deploy-update + chmod +x $out/bin/deploy-update ''; } diff --git a/deployment/deploy-playos-update/default.nix b/deployment/deploy-playos-update/default.nix deleted file mode 100644 index 88d7e392..00000000 --- a/deployment/deploy-playos-update/default.nix +++ /dev/null @@ -1,11 +0,0 @@ -{ substituteAll -, version, updateCert, unsignedRaucBundle, docs, installer -, deployUrl, updateUrl, kioskUrl -, rauc, awscli, python39 -}: -substituteAll { - src = ./deploy-playos-update.py; - dummyBuildCert = ../../pki/dummy/cert.pem; - inherit version updateCert unsignedRaucBundle docs installer deployUrl updateUrl kioskUrl; - inherit rauc awscli python39; -} diff --git a/deployment/deploy-update/default.nix b/deployment/deploy-update/default.nix new file mode 100644 index 00000000..818a9e71 --- /dev/null +++ b/deployment/deploy-update/default.nix @@ -0,0 +1,12 @@ +{ substituteAll +, application, updateCert, unsignedRaucBundle, docs, installer, live +, deployUrl, updateUrl, kioskUrl +, rauc, awscli, python39 +}: +substituteAll { + src = ./deploy-update.py; + dummyBuildCert = ../../pki/dummy/cert.pem; + inherit updateCert unsignedRaucBundle docs installer live deployUrl updateUrl kioskUrl; + inherit rauc awscli python39; + inherit (application) fullProductName safeProductName version; +} diff --git a/deployment/deploy-playos-update/deploy-playos-update.py b/deployment/deploy-update/deploy-update.py similarity index 88% rename from deployment/deploy-playos-update/deploy-playos-update.py rename to deployment/deploy-update/deploy-update.py index 9defcc7b..df3c877b 100755 --- a/deployment/deploy-playos-update/deploy-playos-update.py +++ b/deployment/deploy-update/deploy-update.py @@ -10,8 +10,11 @@ UNSIGNED_RAUC_BUNDLE = "@unsignedRaucBundle@" INSTALLER_ISO = "@installer@" +LIVE_ISO = "@live@" DOCS = "@docs@" VERSION = "@version@" +FULL_PRODUCT_NAME = "@fullProductName@" +SAFE_PRODUCT_NAME = "@safeProductName@" # Certificate installed on system UPDATE_CERT = "@updateCert@" @@ -94,7 +97,7 @@ def sign_rauc_bundle(key, cert, out): def _main(opts): - print("Deploying PlayOS update:\n") + print(f"Deploying {FULL_PRODUCT_NAME} update:\n") output_cert = UPDATE_CERT if opts.override_cert == None else opts.override_cert @@ -106,8 +109,7 @@ def _main(opts): os.makedirs(version_dir, exist_ok=True) # Sign RAUC bundle (and verify signature) - signed_bundle = os.path.join(version_dir, - "playos-" + VERSION + ".raucb") + signed_bundle = os.path.join(version_dir, f"{SAFE_PRODUCT_NAME}-{VERSION}.raucb") sign_rauc_bundle(key=opts.key, cert=output_cert, out=signed_bundle) # Write latest file @@ -116,14 +118,21 @@ def _main(opts): latest.write(VERSION + "\n") # Write installer ISO - installer_iso_filename = "playos-installer-" + VERSION + ".iso" + installer_iso_filename = f"{SAFE_PRODUCT_NAME}-installer-{VERSION}.iso" installer_iso_src = os.path.join(INSTALLER_ISO, "iso", installer_iso_filename) installer_iso_dst = os.path.join(version_dir, installer_iso_filename) subprocess.run(["cp", installer_iso_src, installer_iso_dst], check=True) + # Write live system ISO + live_iso_filename = f"{SAFE_PRODUCT_NAME}-live-{VERSION}.iso" + live_iso_src = os.path.join(LIVE_ISO, "iso", live_iso_filename) + live_iso_dst = os.path.join(version_dir, live_iso_filename) + subprocess.run(["cp", live_iso_src, live_iso_dst], + check=True) + # Write PDF manual - manual_filename = "playos-manual-" + VERSION + ".pdf" + manual_filename = f"{SAFE_PRODUCT_NAME}-manual-{VERSION}.pdf" manual_src = os.path.join(DOCS, "user-manual.pdf") manual_dst = os.path.join(version_dir, manual_filename) subprocess.run(["cp", manual_src, manual_dst], check=True) @@ -198,7 +207,7 @@ def _main(opts): if __name__ == '__main__': - parser = argparse.ArgumentParser(description="Deploy PlayOS update") + parser = argparse.ArgumentParser(description=f"Deploy {FULL_PRODUCT_NAME} update") parser.add_argument('-v', '--version', action='version', version=VERSION) parser.add_argument('--key', help="key file or PKCS#11 URL", required=True) parser.add_argument('--override-cert', help="use a previous cert when switching PKI pairs") diff --git a/dev-tools/captive-portal.py b/dev-tools/captive-portal.py index 9d8f13db..f84188a4 100755 --- a/dev-tools/captive-portal.py +++ b/dev-tools/captive-portal.py @@ -30,7 +30,7 @@ def do_GET(self): redirectTo(self, f'http://{host}/portal') elif isAuthorized: - textHtml(self, 'Success') + textHtml(self, 'Open Sesame') else: redirectTo(self, f'http://{host}/portal') diff --git a/docs/arch/Readme.org b/docs/arch/Readme.org index 043fbe0a..a5bfb7f1 100644 --- a/docs/arch/Readme.org +++ b/docs/arch/Readme.org @@ -2,27 +2,48 @@ * Overview -PlayOS is a custom Linux system for running Dividat Play. This document describes technical architecture of PlayOS. +PlayOS is a custom Linux system for running Dividat Play. This document describes the fundamental architecture of PlayOS. ** Background -[[https://dividat.com/en/products/dividat][Dividat Play]] is a web-based application used in conjunction with the [[https://dividat.com/en/products/dividat][Dividat Senso]] hardware as a game-based training system. +Dividat Play is a web-based application used in conjunction with the Dividat Senso hardware as a game-based training system. Installations of Dividat Play and Dividat Senso usually receive a dedicated computer to run the software. PlayOS is a custom Linux system for such computers. PlayOS is a custom [[https://nixos.org/][NixOS]] system that runs Dividat Play in a restricted kiosk environment. Installations can be upgraded atomically over-the-air. Deployed machines have two system partitions (A/B), each containing a complete system. PlayOS is compatible with NixOS modules and packages, everything that is available from upstream NixOS can be used. -** Build System +** Layers -[[https://nixos.org/nix/][Nix]] is used as build system. Running ~nix build~ in the repository root will build all artifacts required to deploy system (via fresh installation or upgrade). +PlayOS is a layer on top of NixOS and is itself organized into a base and application layer. The base layer provides: -** Testing +- Automatic A/B update mechanism +- Read-only system partitions, with selected persisted configuration data +- Web-based configuration interface for + - network (LAN, WLAN, HTTP proxy, static IP), + - localization, + - system status, + - remote maintenance. +- Installer +- Live system +- Remote maintenance via ZeroTier + +The application layer configures the base system's parameters for remote update and maintenance and defines the "payload" application that PlayOS should run. + +** Development and Testing + +*** Build System + +[[https://nixos.org/nix/][Nix]] is used as build system. Running ~nix build~ in the repository root will build all artifacts required to deploy the system (via fresh installation or upgrade). *** Virtual Machines -System can be run in a virtual machine. For this the tool ~run-playos-in-vm~ is provided. +The system can be run in a virtual machine. + +**** QEMU VM -With minimal test instrumentation a virtual machine can be started without creating a (virtual) disk. For this a system partition is created on a folder on the host and shared to the virtual machine via [[https://wiki.qemu.org/Documentation/9psetup][9P]]. This allows for rapid development cycles as no images containing the entire system have to be built. However low-level system components (such as bootloader) are bypassed with test instrumentation. +For rapid testing the QEMU instrumentation ~run-in-vm~ is provided as part of the Nix build. + +With this tool a virtual machine can be started without creating a (virtual) disk. For this a system partition is created on a folder on the host and shared to the virtual machine via [[https://wiki.qemu.org/Documentation/9psetup][9P]]. This allows for rapid development cycles as no images containing the entire system have to be built. Note however that low-level system components (such as the bootloader) are bypassed in this build. For a more complete test a virtual disk containing all necessary partitions and pre-installed systems can be used. @@ -43,19 +64,8 @@ For a more complete test a virtual disk containing all necessary partitions and machine. Don’t forget to remove the optical drive in ~Settings > Storage~ once the installation has been completed. -*** TODO Automated Testing - -NixOS has a very cool [[https://nixos.org/nixos/manual/index.html#sec-obtaining][testing framework]] (see also [[https://nixos.org/~eelco/talks/issre-nov-2010.pdf][this presentation]]). It relies on the module and to set up a efficient virtual machine. - -However ~qemu-vm.nix~ assumes a certain filesystem layout which is incompatible with our ~system-partition~ module. Possible solutions: - -- Create our own test machinery (extending ~run-playos-in-vm~). -- Do testing without ~system-partition~ (and other low-level) modules. -- Figure out how to override filesystem assumptions in ~qemu-vm.nix~ and use the NixOS tooling machinery. - -Doing testing without low-level modules is not desirable. Those are exactly the things that need to be tested extensively and creating own machinery is unnecessary. We need to figure out how to override assumptions in ~qemu-vm.nix~ and possibly adapt our modules to be testable with existing machinery. +* Base Layer -* System ** Disk layout A PlayOS installation has 4 partitions: @@ -65,17 +75,23 @@ A PlayOS installation has 4 partitions: - System partition A - System partition B +** Installer + +A bootable image is built that can be used to install systems. The installation is performed by a Python script (~install-playos.py~). It will automatically detect a suitable device to install the system to and ask for confirmation before partitioning, formatting and installing the system. Optionally the script can be used non-interactively. + +Reasons for using Python include the [[https://github.com/dcantrell/pyparted][pyparted]] bindings to the [[https://www.gnu.org/software/parted/][GNU parted]] library for partitioning. + ** Booting PlayOS can only boot in UEFI mode. [[https://www.gnu.org/software/grub/][GNU Grub]] is used as bootloader. -The bootloader automatically [[*Boot selection logic][chooses system to boot]] (A or B) based on persistent variables (GRUB environment variables on the EFI system partition). Automatic selection can be interrupted by user pressing the ~~ key. +The bootloader automatically [[*Boot selection logic][chooses the system to boot]] (A or B) based on persistent variables. Automatic selection can be interrupted by user pressing the ~~ key. The bootloader boots the selected system by loading the kernel and initial ram disk from the [[*System partition][system partition]]. The bootloader passes the device the system partition is located on as well as the [[*Update Mechanism][RAUC]] slot as kernel arguments (e.g. ~root=/dev/by-label/system.a rauc.slot=a~). *** Boot selection logic -The [[*Booting][GRUB]] environment variables ~ORDER~, ~a_TRY~, ~b_TRY~, ~a_OK~ and ~b_OK~ are used to select system to boot: +The [[*Booting][GRUB]] environment variables ~ORDER~, ~a_TRY~, ~b_TRY~, ~a_OK~ and ~b_OK~ are used to select which system to boot: - ~ORDER~ describes the order in which boots should be attempted (e.g. ~"a b"~). - ~a_TRY~ and ~b_TRY~ describes the number of attempts to boot the respective systems. @@ -83,15 +99,15 @@ The [[*Booting][GRUB]] environment variables ~ORDER~, ~a_TRY~, ~b_TRY~, ~a_OK~ a GRUB attempts to boot the first system in ~ORDER~ which has a value less than 3 in the respecitve ~TRY~ variable and where ~a_OK~ is not equal ~0~. If there are no boot options available GRUB will display a boot selection menu. -See also sections on the [[*Update Mechanism][Update Mechanism]] and [[*Mark system as good][Mark system as good]]. +See also the sections on the [[*Update Mechanism][Update Mechanism]] and [[*Mark system as good][Mark system as good]]. ** Init system -After low-level system is initialized from the initial ram disk (Stage 1) the ~/init~ script on the system partition (Stage 2) is run, which will start all necessary services to make system usable. +After low-level system is initialized from the initial ram disk (Stage 1) the ~/init~ script on the system partition (Stage 2) is run, which will start all necessary services. ** System partition -A system partition contains following files: +A system partition contains the following files: - ~/kernel~: Linux kernel - ~/initrd~: Initial ram disk @@ -102,7 +118,7 @@ The system partition is mounted on ~/mnt/system~ (read-only). ** Volatile root -A temporary filesystem in volatile memory ([[https://www.kernel.org/doc/Documentation/filesystems/tmpfs.txt][tmpfs]]) is used as root. Folders containing persistent user data need to be specified explicitly and are bind mounted to correct locations on root. +A temporary filesystem in volatile memory ([[https://www.kernel.org/doc/Documentation/filesystems/tmpfs.txt][tmpfs]]) is used as root. Folders containing persistent user data need to be specified explicitly at build-time and are bind mounted to correct locations on root. ** Machine ID @@ -110,64 +126,46 @@ Every machine is assigned a [[https://tools.ietf.org/html/rfc4122][Universal Uni The machine-id is set on boot via the ~system.machine_id~ kernel argument and then [[https://www.freedesktop.org/software/systemd/man/machine-id.html][handled by the init system]]. -** Dividat Driver - -The [[https://github.com/dividat/driver][Dividat Driver]], which handles connectivity between Dividat Play and Dividat Senso hardware, is installed and runs as a system service. - -** Kiosk - -System automatically logs in the user ~play~, starts an X session and launches a custom Kiosk Application based on [[http://doc.qt.io/qt-5/qtwebengine-index.html][QtWebEngine]]. The Kiosk Application loads Dividat Play in a restricted environment. - -A [[*User interface][user interface for system configuration]] can be accessed with the key-combination ~Ctrl-Shift-F12~. - -If a captive portal is detected, which requires user interaction before granting Internet access, a prompt appears to open it. - -For debugging the [[https://doc.qt.io/qt-5/qtwebengine-debugging.html][Qt WebEngine Developer Tools]] are enabled and accessible at http://localhost:3355 and chrome://inspect/#devices. The Dev Tools can be used to inspect and interact with the running page (e.g. load a new page with ~location.replace("https://nixos.org")~). - -** TODO PlayOS Controller +** PlayOS Controller The PlayOS Controller is an application that manages system updates, checks system health and offers a unified graphical user interface for system configuration. The PlayOS Controller runs as a system service. -PlayOS controller is implemented in [[https://ocaml.org/][OCaml]]. OCaml allows [[https://ocaml.github.io/ocamlunix/ocamlunix.html][lower-level system programming]] offers an excellent [[https://github.com/diml/obus][D-Bus interface]] and various libraries/tools for creating web-based user interfaces (e.g. [[https://github.com/rgrinberg/opium][opium]] and [[https://github.com/ocsigen/tyxml][tyxml]]). +The PlayOS controller is implemented in [[https://ocaml.org/][OCaml]]. OCaml allows [[https://ocaml.github.io/ocamlunix/ocamlunix.html][lower-level system programming]], offers an excellent [[https://github.com/diml/obus][D-Bus interface]] and various libraries/tools for creating web-based user interfaces (e.g. [[https://github.com/rgrinberg/opium][opium]] and [[https://github.com/ocsigen/tyxml][tyxml]]). *** Update Mechanism -[[https://www.rauc.io/][RAUC]] is used as update client. Updates are distributed as RAUC [[https://rauc.readthedocs.io/en/latest/basic.html#update-artifacts-bundles][bundles]], that are installed on the inactive system partition. [[*Bundle verification][Bundle verification]], target system partition selection, atomic update and boot loader integration are handled by RAUC. Checking for available updates and downloading them is handled by the controller, which then invokes RAUC to complete installation. +[[https://www.rauc.io/][RAUC]] is used as the self-update client. Updates are distributed as [[https://rauc.readthedocs.io/en/latest/basic.html#update-artifacts-bundles][RAUC bundles]] and are always installed to the inactive system partition. [[*Bundle verification][Bundle verification]], target system partition selection, atomic update and boot loader integration are handled by RAUC. Checking for available updates and downloading them is handled by the controller, which then invokes RAUC to install the update. **** Checking for new available versions -The controller retrieves the version of the latest available release from a predefined URL, the update URL. An update is downloaded and installed if the booted system is outdated. Note that an update will not be downloaded if the booted system is up to date but the inactive partition is outdated. That means that in normal operation the active partition will be run the latest available version, whereas the inactive partition has the (latest-1) version installed. +The controller retrieves the version of the latest available release from a predefined URL, the update URL. An update is downloaded and installed if the booted system is outdated. Note that an update will not be downloaded if the booted system is up to date but the inactive partition is outdated. That means that in normal operation the active partition will be running the latest available version, whereas the inactive partition has the (latest-1) version installed. **** Bundle verification -RAUC bundles are signed. Before installing an update RAUC will verify signature against certificate installed on system (see [[https://rauc.readthedocs.io/en/latest/advanced.html#security][here]]). +RAUC bundles are signed. Before installing an update RAUC will verify the bundle signature against the certificate installed on the system (see [[https://rauc.readthedocs.io/en/latest/advanced.html#security][here]]). The certificate to be installed on the system must be passed to the build system with the ~updateCert~ argument. -The RAUC bundle produced by the build system is signed by a dummy development/testing key. The bundle needs to be [[https://rauc.readthedocs.io/en/latest/advanced.html#resigning-bundles][resigned]] before it can be deployed. The script ~deploy-playos-update~ automates this process. +The RAUC bundle produced by the build system is signed by a dummy development/testing key. The bundle needs to be [[https://rauc.readthedocs.io/en/latest/advanced.html#resigning-bundles][resigned]] before it can be deployed. The script ~deploy-update~ automates this process. **** Installation -During installation of a new system on slot ~x~ the ~x_OK~ variable is set to ~0~, marking the system non-bootable. After successful installation, [[https://rauc.readthedocs.io/en/latest/reference.html#grub][RAUC sets]] the ~ORDER~ to contain ~x~ as first element and sets the number of tries to 0 (~x_TRY=0~) and marks the system bootable (~x_OK=1~). On next boot GRUB attempts to boot system ~x~ for 3 times before falling back to the next system in ~ORDER~ (see [[*Boot selection logic][boot selection logic]]). +During installation of a new system on slot ~x~ the ~x_OK~ variable is set to ~0~, marking the system non-bootable. After successful installation, [[https://rauc.readthedocs.io/en/latest/reference.html#grub][RAUC sets]] the ~ORDER~ to contain ~x~ as first element and sets the number of tries to 0 (~x_TRY=0~) and marks the system bootable (~x_OK=1~). On next boot, GRUB attempts to boot system ~x~ for 3 times before falling back to the next system in ~ORDER~ (see [[*Boot selection logic][boot selection logic]]). **** Deployment of updates -Updates are deployed to Amazon S3. +Bundles are deployed to Amazon S3. *** Mark system as good -The controller marks the currently running system good after: +The controller marks the currently running system as good after: - Controller is running for at least 30 seconds - System state as reported by systemd is "Running" If system ~x~ is considered to be running satisfactory the system is marked good via RAUC, which resets the number of boot attempts (~x_TRY=0~) and marks the system bootable (~x_OK=1~) (see [[*Boot selection logic][boot selection logic]]). -Note that if system is rebooted before controller can mark the system as good, the boot attempt counter (~x_TRY~) will be incremented. [[*Boot selection logic][The boot selection logic]] will not boot a system with more than 3 boot attempts. - -*** TODO Logging Mechanism - -Important system events should be logged to ~log.dividat.com~. +Note that if the system is rebooted before the controller can mark the system as good, the boot attempt counter (~x_TRY~) will be incremented. [[*Boot selection logic][The boot selection logic]] will not boot a system with more than 3 boot attempts. *** User interface @@ -176,89 +174,60 @@ A web-based graphical user interface is provided for system configuration and ob **** System information -Basic information, such as version and id are displayed. +Basic information, such as system version and machine-id are displayed. **** Network configuration -The controller periodically checks Internet connectivity (with a HTTP request to ~http://api.dividat.com~). If Internet is connected this is indicated with a check mark. +The controller periodically checks Internet connectivity (with an HTTP request to ~http://captive.dividat.com~). If the probe can be reached, this is indicated with a check mark. -If Internet connectivity is not available a list of available wireless networks will be displayed. User can connect to the network and optionally provide a passphrase for WEP/WPA. +The user can connect to the network and optionally provide a passphrase for WEP/WPA. -[[https://01.org/connman][ConnMan]] is used as network manager. The controller interfaces with ConnMan via its D-Bus API. ConnMan is used in favor of NetworkManager as it is more lightweight, has more predictable behavior when connecting with link-local networks (see [[https://mail.gnome.org/archives/networkmanager-list/2009-April/msg00102.html][here]]) and has a easy to use D-Bus API (see [[https://git.kernel.org/pub/scm/network/connman/connman.git/tree/doc][documentation in the project repository]]). +[[https://01.org/connman][ConnMan]] is used as network manager. The controller interfaces with ConnMan via its D-Bus API. ConnMan is used in favor of NetworkManager as it is more lightweight, has more predictable behavior when connecting with link-local networks (see [[https://mail.gnome.org/archives/networkmanager-list/2009-April/msg00102.html][here]]) and has an easy to use D-Bus API (see [[https://git.kernel.org/pub/scm/network/connman/connman.git/tree/doc][documentation in the project repository]]). -***** Limitations +Ethernet interfaces are configured automatically and use DHCP if available or default to a link-local address scheme (which is important for connecting to Dividat Senso via Ethernet). If required for Internet access, it is also possible to configure a static IP for a specific Ethernet interace. -- Manual configuration of Ethernet interfaces is currently not possible. Ethernet interfaces are configured to use DHCP and fall back to link-local address configuration. This allows plug-and-play connectivity to Dividat Senso (link-local) and networks using DHCP. -- Only WEP or WPA with passphrase is supported by the GUI. Further methods may be implemented by extending the ~Connman.Agent~ module and support in GUI. Manual configuration of ConnMan can be done via ~connmanctl~ (e.g. [[https://wiki.archlinux.org/index.php/ConnMan#Connecting_to_eduroam_(802.1X)][connecting to WPA Enterprise]]). +Connecting to WiFi networks with a passphrase is supported by the GUI. There is no support for connecting to WPA Enterprise. -**** TODO Audio +** Remote maintenance - Audio is handled with [[https://www.freedesktop.org/wiki/Software/PulseAudio/][PulseAudio]] running as a [[https://www.freedesktop.org/wiki/Software/PulseAudio/Documentation/User/SystemWide/][system-wide]] daemon. +In order to allow remote troubleshooting, the system can connect to a private [[http://zerotier.com/][ZeroTier]] network which allows root access via SSH to special keys held by technical support staff. This connection is inactive by default and only established on an opt-in basis. - Controller should set default audo output to HDMI and set volume to maximum. User configuration of volume should be done through the HDMI display device (e.g. the TV). +** Rescue system - https://github.com/savonet/ocaml-pulseaudio +A minimal Linux rescue system is installed on the ESP partition. The rescue system can be started by manually selecting the entry from the boot loader menu. -***** TODO Bluetooth +After booting a menu is shown where user can choose to wipe user data (reformat data partition), reboot or access a Linux shell. - Functionality to connect to bluetooth audio devices (e.g. bluetooth headphone). This would require an additional D-Bus binding to [[http://www.bluez.org/][Bluez]] and an user interface for setting up device. +The rescue system consists of a Linux kernel and an initial ramdisk with an embedded squashfs containing the system software. +The main purpose of the rescue system is to perform a factory reset by wiping user data. In general, reinstalling the system completely is a safe alternative for restoring system functionality. Nevertheless RAUC and Grub utilities are installed. -** Remote management +* Application Layer -In order to allow manual remote management, the system is connected to a private [[http://zerotier.com/][ZeroTier]] network and allows root access via SSH to special keys held by technical support staff. +** Dividat Driver -This feature is intended to weed out issues in early phases of deployment. Once system is considered stable this feature will be disabled. +The [[https://github.com/dividat/driver][Dividat Driver]], which handles connectivity between Dividat Play and Dividat Senso hardware, is installed and runs as a system service. -** Rescue system +** Kiosk -A minimal Linux rescue system is installed on the ESP partition. It's main purpose is to wipe any user data by reformatting the data partition. +The system automatically logs in the user ~play~, starts an X session and launches a custom Kiosk Application based on [[http://doc.qt.io/qt-5/qtwebengine-index.html][QtWebEngine]]. The Kiosk Application loads Dividat Play in a restricted environment. -The rescue system can be started by manually selecting the entry from the boot loader menu. +The [[*User interface][user interface for system configuration]] can be accessed with the key-combination ~Ctrl-Shift-F12~. -#+CAPTION: Rescue System -#+NAME: fig:rescue-system -#+attr_html: :width 800px -[[../screenshots/rescue-system.png]] +If a captive portal is detected, which requires user interaction before granting Internet access, a prompt appears to open it. -After booting a menu is shown where user can choose to wipe user data (reformat data partition), reboot, access a Linux shell or play a game. +For debugging the [[https://doc.qt.io/qt-5/qtwebengine-debugging.html][Qt WebEngine Developer Tools]] are enabled and accessible at http://localhost:3355 and chrome://inspect/#devices. The Dev Tools can be used to inspect and interact with the running page (e.g. load a new page with ~location.replace("https://nixos.org")~). -The rescue system consists of a Linux kernel and a initial ramdisk with an embedded squashfs containing the system software. +** Audio -Use cases for rescue system beside wiping user data are not clear. In general reinstalling the system completely is a safer way of restoring system functionality. Nevertheless RAUC and Grub utilities are installed. +Audio is handled with [[https://www.freedesktop.org/wiki/Software/PulseAudio/][PulseAudio]], trying to play sound on all available output devices. User configuration of volume should be done through the HDMI display device (e.g. the TV). -* TODO Installer +* Limitations -A bootable image is built that can be used to install systems. The installation is performed by a Python script (~install-playos.py~). It will automatically detect a suitable device to install the system to and ask for confirmation before partitioning, formatting and installing the system. Optionally the script can be used non-interactively. +** Layers -Reasons for using Python include the [[https://github.com/dcantrell/pyparted][pyparted]] bindings to the [[https://www.gnu.org/software/parted/][GNU parted]] library for partitioning. +At the moment the split into base and application layers still has some impurities which could be avoided to further clarify which is which: -** TODO Use RAUC bundle during installation - -Currently installation script copies the target system directly from the Nix store. This allows for more efficient creation of disk images for testing as the system to be installed only is copied to a disk or bundle once. The disadvantage is that initial installation is different than updating a system with RAUC. - -One way in which this difference manifests itself is that RAUC writes certain meta-data to the ~/boot/status.ini~ file which is required by the [[*Update Mechanism][update mechanism implemented by the controller]]. On initial installation this meta-data is written by the installation script, impersonating RAUC. - -Some work has been done towards using RAUC bundles during installation: - -- [2018-12-07 Fri] Initial experimentation - - Not compressing system tarball with xz increases size of rauc bundle from 180MB to 280MB (no X system). - - Rauc has [[https://rauc.readthedocs.io/en/latest/examples.html#write-slots-without-update-mechanics][write-slot]] option that can write an image to a slot. This still requires rauc to be properly configured on installation system (it needs to know about slots). - - Using Rauc nicely makes the installer script more complicated. Currently not worth the effort. - - Another idea: use rauc bundle but bypass rauc (bundle is just a squashfs image). However crypthographic verification of bundle is also bypassed. -- [2019-01-18 Fri] More thoughts - - Maybe using RAUC nicely is not such a bad idea, as then version information is correctly set. And having a working RAUC is very useful. Also from rescue system. -- [2019-01-21 Mon] Another try - - RAUC bundle creation is more efficient now and installing with RAUC is fast (i.e. ~rauc install~). - - Attempted to use ~rauc install~ with ~--conf~ option: - #+BEGIN_SRC shell - Error creating proxy: Could not connect: No such file or directory - D-Bus error while installing `/nix/store/75zbfm75ymvxq9cn5bqvp4hfxiwrx9kc-bundle-2019.1.0-dev.raucb` - Error creating proxy: Could not connect: No such file or directory - D-Bus error while installing `/nix/store/75zbfm75ymvxq9cn5bqvp4hfxiwrx9kc-bundle-2019.1.0-dev.raucb` - #+END_SRC - Fails as RAUC needs to have D-Bus access which requires system configuration not present in the installer or the environment used to create the testing disk image. - - Mounting the RAUC bundle and simply untaring also failed: ~mount: /mnt/rauc-bundle: mount failed: Operation not permitted.~. Me thinks the minimal Linux kernel used does not have squashfs suport. - - Using the bundle for installation would be very nice as certain RAUC meta data is set properly (installed version, etc.) and also makes difference between fresh install and updated system smaller. Running RAUC (with proper D-Bus setup) on installer system is feasible. Currently the test disk image is created with the same installation script as is on the installer. If the installation script would require a fully running RAUC, then the disk creation would have to be adapted to either use a more complete Linux system (possibly the installer - making creation of the test disk very slow) or not use the installation script (not the idea of the testing disk). Further pondering required. - -** TODO Check for latest version of bundle over network +- The kiosk URL is conceptually part of the application but passed as a parameter to base components for purely informational purposes. The application could instead specify arbitrary metadata for the base system to display in suitable places (controller, installer). +- The PlayOS name is hardcoded and could be made parametric. +- Some localization options (system language, keyboard, screen resolution) from the base system have no effect if the application layer does not apply them. diff --git a/docs/default.nix b/docs/default.nix index e3485576..24a293d6 100644 --- a/docs/default.nix +++ b/docs/default.nix @@ -9,7 +9,16 @@ stdenv.mkDerivation { src = ./.; - buildInputs = [ pandoc python39Packages.weasyprint ]; + buildInputs = + let + # Workaround to allow setting custom fontconfig file. + # Should be fixed in next version of Nixpkgs, so the regular package + # can again be used (https://github.com/NixOS/nixpkgs/pull/254239). + weasyprint = python39Packages.weasyprint.overrideAttrs (o: { + makeWrapperArgs = [ "--set-default FONTCONFIG_FILE ${o.FONTCONFIG_FILE}" ]; + }); + in + [ pandoc weasyprint ]; installPhase = '' DATE=$(date -I) diff --git a/docs/screenshots/bios-uefi.png b/docs/screenshots/bios-uefi.png index df0cb7dc..a67bbc13 100644 Binary files a/docs/screenshots/bios-uefi.png and b/docs/screenshots/bios-uefi.png differ diff --git a/docs/screenshots/boot-selection-dh110.png b/docs/screenshots/boot-selection-dh110.png index 33d336a2..59131efe 100644 Binary files a/docs/screenshots/boot-selection-dh110.png and b/docs/screenshots/boot-selection-dh110.png differ diff --git a/docs/screenshots/controller-information.png b/docs/screenshots/controller-information.png index d6ac3aa7..245db47b 100644 Binary files a/docs/screenshots/controller-information.png and b/docs/screenshots/controller-information.png differ diff --git a/docs/screenshots/secure-boot-dh110.png b/docs/screenshots/secure-boot-dh110.png index 815c57a4..0eb4ee1c 100644 Binary files a/docs/screenshots/secure-boot-dh110.png and b/docs/screenshots/secure-boot-dh110.png differ diff --git a/docs/user-manual/Readme.org b/docs/user-manual/Readme.org index f3186262..beeea1b1 100644 --- a/docs/user-manual/Readme.org +++ b/docs/user-manual/Readme.org @@ -49,7 +49,7 @@ Items: - Kiosk URL: Location of web application that is loaded automatically - Machine ID: A unique identifier for the machine - Local time: Current time with timezone -- Remote maintenance: Button to enable remote maintenance mode +- Remote maintenance: Button to temporarily allow remote maintenance ** Network diff --git a/installer/configuration.nix b/installer/configuration.nix index 1ff8242c..c87906a2 100644 --- a/installer/configuration.nix +++ b/installer/configuration.nix @@ -1,15 +1,15 @@ -{ config, pkgs, lib, install-playos, version, greeting, ... }: +{ config, pkgs, lib, install-playos, version, safeProductName, fullProductName, greeting, ... }: with lib; { - # Force use of already overlayed nixpkgs in modules - nixpkgs.pkgs = pkgs; - imports = [ (pkgs.importFromNixos "modules/installer/cd-dvd/iso-image.nix") ]; + # Custom label when identifying OS + system.nixos.label = "${safeProductName}-${version}"; + environment.systemPackages = [ install-playos ]; @@ -24,7 +24,7 @@ with lib; # Allow the user to log in as root without a password. users.users.root.initialHashedPassword = ""; - services.getty.greetingLine = greeting "Dividat PlayOS installer (${version})"; + services.getty.greetingLine = greeting "${fullProductName} installer (${version})"; environment.loginShellInit = '' install-playos --reboot @@ -49,7 +49,7 @@ with lib; }; networking = { - hostName = "playos-installer"; + hostName = "${safeProductName}-installer"; # enable wpa_supplicant wireless = { @@ -58,7 +58,7 @@ with lib; }; # ISO naming. - isoImage.isoName = "playos-installer-${version}.iso"; + isoImage.isoName = "${safeProductName}-installer-${version}.iso"; isoImage.volumeID = substring 0 11 "PLAYOS_ISO"; diff --git a/installer/default.nix b/installer/default.nix index 108d40a8..1377986a 100644 --- a/installer/default.nix +++ b/installer/default.nix @@ -1,12 +1,12 @@ # Build NixOS system { config, lib, pkgs -, version, greeting, install-playos +, version, safeProductName, fullProductName, greeting, install-playos }: let nixos = pkgs.importFromNixos ""; configuration = (import ./configuration.nix) { - inherit config pkgs lib install-playos version greeting; + inherit config pkgs lib install-playos version safeProductName fullProductName greeting; }; in diff --git a/installer/install-playos/default.nix b/installer/install-playos/default.nix index 18b63045..e1887e56 100644 --- a/installer/install-playos/default.nix +++ b/installer/install-playos/default.nix @@ -9,7 +9,7 @@ , pv , closureInfo -, systemToplevel +, systemImage , rescueSystem , grubCfg , version @@ -17,7 +17,7 @@ , kioskUrl }: let - systemClosureInfo = closureInfo { rootPaths = [ systemToplevel ]; }; + systemClosureInfo = closureInfo { rootPaths = [ systemImage ]; }; python = python39.withPackages(ps: with ps; [pyparted]); in @@ -26,7 +26,7 @@ stdenv.mkDerivation { src = substituteAll { src = ./install-playos.py; - inherit grubCfg systemToplevel rescueSystem systemClosureInfo version updateUrl kioskUrl; + inherit grubCfg systemImage rescueSystem systemClosureInfo version updateUrl kioskUrl; inherit python; }; diff --git a/installer/install-playos/install-playos.py b/installer/install-playos/install-playos.py index 39c66448..a8db2d9c 100755 --- a/installer/install-playos/install-playos.py +++ b/installer/install-playos/install-playos.py @@ -5,6 +5,7 @@ import sys import shutil import argparse +import json import parted import uuid import configparser @@ -16,7 +17,7 @@ GRUB_CFG = "@grubCfg@" GRUB_ENV = '/mnt/boot/grub/grubenv' -SYSTEM_TOP_LEVEL = "@systemToplevel@" +SYSTEM_IMAGE = "@systemImage@" RESCUE_SYSTEM = "@rescueSystem@" SYSTEM_CLOSURE_INFO = "@systemClosureInfo@" VERSION = "@version@" @@ -27,13 +28,32 @@ def find_device(device_path): """Return suitable device to install PlayOS on""" - devices = parted.getAllDevices() + all_devices = parted.getAllDevices() + + print(f"Found {len(all_devices)} disk devices:") + for device in all_devices: + print(f'\t{device_info(device)}') + + # We want to avoid installing to the installer medium, so we filter out + # devices from the boot disk. We use the fact that we mount from the + # installer medium at `/iso`. + boot_device = get_blockdevice("/iso") + if boot_device is None: + print("Could not identify installer medium. Considering all disks as installation targets.") + available_devices = all_devices + else: + available_devices = [device for device in all_devices if not device.path.startswith(boot_device)] + + print(f"Found {len(available_devices)} possible installation targets:") + for device in available_devices: + print(f'\t{device_info(device)}') + device = None - if device_path == None: + if device_path is None: # Use the largest available disk try: device = sorted( - parted.getAllDevices(), + available_devices, key=lambda d: d.length * d.sectorSize, reverse=True)[0] except IndexError: @@ -41,13 +61,30 @@ def find_device(device_path): else: try: device = next( - device for device in devices if device.path == device_path) + device for device in all_devices if device.path == device_path) except StopIteration: pass - if device == None: - raise ValueError('No suitable device to install on found.') - else: - return device + + return device + + +def get_blockdevice(mount_path): + """Find the block device path for a given mountpoint.""" + result = subprocess.run(['lsblk', '-J'], capture_output=True, text=True) + lsblk_data = json.loads(result.stdout) + + # Find the parent device of the partition with mountpoint + for device in lsblk_data['blockdevices']: + if 'children' in device: + for child in device['children']: + if 'mountpoints' in child and mount_path in child['mountpoints']: + return '/dev/' + device['name'] + return None + + +def device_info(device): + gb_size = int((device.sectorSize * device.length) / (10**9)) + return f'{device.path} ({device.model} - {gb_size} GB)' def commit(disk): @@ -205,13 +242,13 @@ def install_system(partitionPath, label): # Copy kernel, initrd and init subprocess.run( - ['cp', '-av', SYSTEM_TOP_LEVEL + '/kernel', '/mnt/system/kernel'], + ['cp', '-av', SYSTEM_IMAGE + '/kernel', '/mnt/system/kernel'], check=True) subprocess.run( - ['cp', '-av', SYSTEM_TOP_LEVEL + '/initrd', '/mnt/system/initrd'], + ['cp', '-av', SYSTEM_IMAGE + '/initrd', '/mnt/system/initrd'], check=True) subprocess.run( - ['cp', '-av', SYSTEM_TOP_LEVEL + '/init', '/mnt/system/init'], + ['cp', '-av', SYSTEM_IMAGE + '/init', '/mnt/system/init'], check=True) # Unmount system partition @@ -249,11 +286,11 @@ def _suppress_unless_fails(completed_process): # from http://code.activestate.com/recipes/577058/ def _query_continue(question, default=False): valid = {"yes": True, "y": True, "ye": True, "no": False, "n": False} - if default == None: + if default is None: prompt = " [y/n] " - elif default == True: + elif default is True: prompt = " [Y/n] " - elif default == False: + elif default is False: prompt = " [y/N] " else: raise ValueError("invalid default answer: '%s'" % default) @@ -269,9 +306,9 @@ def _query_continue(question, default=False): def _ensure_machine_id(passed_machine_id, device): - if passed_machine_id == None: + if passed_machine_id is None: previous_machine_id = _get_grubenv_entry('machine_id', device) - if previous_machine_id == None: + if previous_machine_id is None: return uuid.uuid4() else: return uuid.UUID(previous_machine_id) @@ -298,7 +335,7 @@ def _get_grubenv_entry(entry_name, device): universal_newlines=True) entry_match = re.search("^" + entry_name + "=(.+)$", grub_list.stdout, re.MULTILINE) - if entry_match == None: + if entry_match is None: return None else: return entry_match.group(1) @@ -310,13 +347,8 @@ def _get_grubenv_entry(entry_name, device): stderr=subprocess.DEVNULL) -def _device_size_in_gb(device): - return (device.sectorSize * device.length) / (10**9) - - def confirm(device, machine_id, no_confirm): - print('\n\nInstalling PlayOS ({}) to {} ({} - {:n}GB)'.format( - VERSION, device.path, device.model, _device_size_in_gb(device))) + print('\n\nInstalling PlayOS ({}) to {}'.format(VERSION, device_info(device))) print(' Machine ID: {}'.format(machine_id.hex)) print(' Update URL: {}'.format(PLAYOS_UPDATE_URL)) print(' Kiosk URL: {}\n'.format(PLAYOS_KIOSK_URL)) @@ -326,6 +358,9 @@ def confirm(device, machine_id, no_confirm): def _main(opts): # Detect device to install to device = find_device(opts.device) + if device is None: + print('\nNo suitable device to install on found.') + exit(1) # Ensure machine-id exists and is valid machine_id = _ensure_machine_id(opts.machine_id, device) @@ -349,7 +384,7 @@ def _main(opts): if opts.reboot: subprocess.run(['reboot']) else: - print("Done. Please remove install medium and reboot.") + print('\nDone. Please remove install medium and reboot.') exit(0) diff --git a/kiosk/kiosk_browser/captive_portal.py b/kiosk/kiosk_browser/captive_portal.py index 2c699fb7..bdc07599 100644 --- a/kiosk/kiosk_browser/captive_portal.py +++ b/kiosk/kiosk_browser/captive_portal.py @@ -9,6 +9,7 @@ import time import logging from enum import Enum, auto +from http import HTTPStatus from PyQt5 import QtWidgets check_connection_url = 'http://captive.dividat.com/' @@ -30,6 +31,25 @@ def sleep(status): else: time.sleep(60) +def is_redirect(status_code): + """Check whether a status code is a redirect that is mandatorily paired with a location header.""" + return status_code in [ + HTTPStatus.MOVED_PERMANENTLY, + HTTPStatus.FOUND, + HTTPStatus.SEE_OTHER, + HTTPStatus.TEMPORARY_REDIRECT, + HTTPStatus.PERMANENT_REDIRECT + ] + +def is_likely_replaced_page(status_code): + """Check whether a status code is known or surmised to be used by captive portals that replace page contents.""" + return status_code in [ + HTTPStatus.OK, + HTTPStatus.UNAUTHORIZED, + HTTPStatus.PROXY_AUTHENTICATION_REQUIRED, + HTTPStatus.NETWORK_AUTHENTICATION_REQUIRED + ] + class CaptivePortal(): def __init__(self, get_current_proxy, show_captive_portal_message): @@ -52,13 +72,17 @@ def _check(self): try: r = requests.get(check_connection_url, allow_redirects = False) - if r.status_code == 200: + if r.status_code == HTTPStatus.OK and 'Open Sesame' in r.text: self._status = Status.DIRECT_CONNECTED - elif r.status_code in [301, 302, 303, 307]: + elif is_redirect(r.status_code): self._status = Status.DIRECT_CAPTIVE self.show_captive_portal_message(r.headers['Location']) + elif is_likely_replaced_page(r.status_code): + self._status = Status.DIRECT_CAPTIVE + self.show_captive_portal_message(check_connection_url) + else: self._status = Status.DIRECT_DISCONNECTED diff --git a/kiosk/kiosk_browser/main_widget.py b/kiosk/kiosk_browser/main_widget.py index 25c4d799..99780193 100644 --- a/kiosk/kiosk_browser/main_widget.py +++ b/kiosk/kiosk_browser/main_widget.py @@ -1,9 +1,24 @@ from PyQt5 import QtWidgets, QtCore +from dataclasses import dataclass from kiosk_browser import browser_widget, captive_portal from kiosk_browser import proxy as proxy_module from kiosk_browser import webview_dialog +@dataclass +class Closed: + pass + +@dataclass +class Settings: + dialog: QtWidgets.QDialog + +@dataclass +class CaptivePortal: + dialog: QtWidgets.QDialog + +Dialog = Closed | Settings | CaptivePortal + class MainWidget(QtWidgets.QWidget): def __init__(self, kiosk_url, settings_url, toggle_settings_key): @@ -15,6 +30,7 @@ def __init__(self, kiosk_url, settings_url, toggle_settings_key): proxy = proxy_module.Proxy() proxy.start_monitoring_daemon() + self._dialog = Closed() self._settings_url = settings_url self._toggle_settings_key = toggle_settings_key self._browser_widget = browser_widget.BrowserWidget( @@ -28,13 +44,12 @@ def __init__(self, kiosk_url, settings_url, toggle_settings_key): self._layout.addWidget(self._browser_widget) # Captive portal - self._is_captive_portal_dialog_open = False self._captive_portal_url = '' self._captive_portal_message = captive_portal.open_message(self._show_captive_portal) self._captive_portal = captive_portal.CaptivePortal(proxy.get_current, self._show_captive_portal_message) self._captive_portal.start_monitoring_daemon() - QtWidgets.QShortcut(toggle_settings_key, self).activated.connect(self._show_settings) + QtWidgets.QShortcut(toggle_settings_key, self).activated.connect(self._toggle_settings) self.setLayout(self._layout) self.show() @@ -43,31 +58,58 @@ def __init__(self, kiosk_url, settings_url, toggle_settings_key): def _show_captive_portal_message(self, url): self._captive_portal_url = QtCore.QUrl(url) - if self._captive_portal_message.parentWidget() == None and not self._is_captive_portal_dialog_open: - self._layout.addWidget(self._captive_portal_message) + if self._captive_portal_message.parentWidget() == None: + match self._dialog: + case CaptivePortal(_): + pass + case _: + self._layout.addWidget(self._captive_portal_message) + + def _toggle_settings(self): + match self._dialog: + case Closed(): + self._show_settings() + case _: + self._close_dialog() def _show_settings(self): self._browser_widget.show_overlay() - webview_dialog.widget( + dialog = webview_dialog.widget( parent = self, title = "System Settings", url = self._settings_url, additional_close_keys = [self._toggle_settings_key], - on_close = self._browser_widget.reload - ).exec_() + on_close = lambda: self._close_dialog() + ) + self._dialog = Settings(dialog) + # Open modeless to allow accessing captive portal message banner + # https://doc.qt.io/qtforpython-5/PySide2/QtWidgets/QDialog.html#modeless-dialogs + # Focus directly to allow tabbing + dialog.show() + dialog.raise_() + dialog.activateWindow() def _show_captive_portal(self): + self._close_dialog(reload_browser_widget = False) self._browser_widget.show_overlay() - self._is_captive_portal_dialog_open = True self._captive_portal_message.setParent(None) - webview_dialog.widget( + dialog = webview_dialog.widget( parent = self, title = "Network Login", url = self._captive_portal_url, - additional_close_keys = [], - on_close = self._on_captive_portal_dialog_close - ).exec_() + additional_close_keys = [self._toggle_settings_key], + on_close = lambda: self._close_dialog() + ) + self._dialog = CaptivePortal(dialog) + dialog.exec_() - def _on_captive_portal_dialog_close(self): - self._is_captive_portal_dialog_open = False - self._browser_widget.reload() + def _close_dialog(self, reload_browser_widget = True): + match self._dialog: + case Settings(dialog): + dialog.close() + self._dialog = Closed() + case CaptivePortal(dialog): + dialog.close() + self._dialog = Closed() + if reload_browser_widget: + self._browser_widget.reload() diff --git a/kiosk/kiosk_browser/webview_dialog.py b/kiosk/kiosk_browser/webview_dialog.py index b22fd752..172caca6 100644 --- a/kiosk/kiosk_browser/webview_dialog.py +++ b/kiosk/kiosk_browser/webview_dialog.py @@ -21,17 +21,14 @@ def widget(parent, title, url, additional_close_keys, on_close): int(w * dialog_ratio), int(h * dialog_ratio)) - show_webview_window(dialog, title, url) + show_webview_window(dialog, title, url, lambda: focus_parent_then_close(parent, on_close)) for key in set(['ESC', *additional_close_keys]): - QtWidgets.QShortcut(key, dialog).activated.connect(dialog.close) - - # Finish after close - dialog.finished.connect(lambda: finish(parent, dialog, on_close)) + QtWidgets.QShortcut(key, dialog).activated.connect(lambda: focus_parent_then_close(parent, on_close)) return dialog -def show_webview_window(dialog, title, url): +def show_webview_window(dialog, title, url, on_close): """ Show webview window with decorations. """ @@ -43,13 +40,13 @@ def show_webview_window(dialog, title, url): layout.setContentsMargins(window_border, 0, window_border, window_border) # left, top, right, bottom widget.setLayout(layout) - layout.addWidget(title_line(dialog, title)) + layout.addWidget(title_line(dialog, title, on_close)) webview = QtWebEngineWidgets.QWebEngineView(dialog) webview.page().setUrl(url) layout.addWidget(webview) -def title_line(dialog, title): +def title_line(dialog, title, on_close): """ Title and close button. """ @@ -79,7 +76,7 @@ def title_line(dialog, title): background-color: rgba(255, 255, 255, 0.3); } """) - button.clicked.connect(dialog.close) + button.clicked.connect(on_close) layout = QtWidgets.QHBoxLayout() layout.setContentsMargins(5, 5, 5, 0) # left, top, right, bottom @@ -90,8 +87,8 @@ def title_line(dialog, title): return line -def finish(parent, dialog, on_close): - """ Give back the focus to the parent. +def focus_parent_then_close(parent, on_close): + """ Give back the focus to the parent, then close. """ parent.activateWindow() diff --git a/kiosk/shell.nix b/kiosk/shell.nix index a137110e..a93c36c2 100644 --- a/kiosk/shell.nix +++ b/kiosk/shell.nix @@ -1,8 +1,9 @@ let - pkgs = import ../pkgs { - version = "1.0.0-dev"; - updateUrl = "http://localhost:9999"; - kioskUrl = "https://dev-play.dividat.com/"; - }; + pkgs = import ../pkgs {}; in - pkgs.playos-kiosk-browser + import ./default.nix { + pkgs = pkgs; + system_name = "PlayOS"; + system_version = "1.0.0-dev"; + } + diff --git a/live/default.nix b/live/default.nix index 84fa7cb2..308f6f34 100644 --- a/live/default.nix +++ b/live/default.nix @@ -1,39 +1,45 @@ # Build NixOS system -{pkgs, lib, version, updateCert, kioskUrl, greeting, playos-controller}: +{pkgs, lib, kioskUrl, playos-controller, application}: with lib; let nixos = pkgs.importFromNixos ""; in (nixos { configuration = {...}: { imports = [ - # general PlayOS modules - ((import ../system/modules/playos.nix) {inherit pkgs version updateCert kioskUrl greeting playos-controller;}) - # Play Kiosk and Driver - ../system/play-kiosk.nix - # Networking - ../system/networking - # Localization - ../system/localization.nix + # Base layer + ((import ../base) { + inherit pkgs kioskUrl playos-controller; + inherit (application) safeProductName fullProductName greeting version; + }) + # Application-specific + application.module (pkgs.importFromNixos "modules/installer/cd-dvd/iso-image.nix") ]; config = { - # Force use of already overlayed nixpkgs in modules - nixpkgs.pkgs = pkgs; - # ISO image customization isoImage.makeEfiBootable = true; isoImage.makeUsbBootable = true; - isoImage.isoName = "playos-live-${version}.iso"; + isoImage.isoName = "${application.safeProductName}-live-${application.version}.iso"; isoImage.appendToMenuLabel = " Live System"; # Set up as completely volatile system - systemPartition.enable = mkForce false; - volatileRoot.persistentDataPartition = { + fileSystems."/boot" = { device = "tmpfs"; fsType = "tmpfs"; options = [ "mode=0755" ]; }; + playos.storage = { + systemPartition.enable = false; + persistentDataPartition = { + device = "tmpfs"; + fsType = "tmpfs"; + options = [ "mode=0755" ]; + }; + }; + + # Live system does not self update + playos.selfUpdate.enable = false; # There is no persistent state for a live system system.stateVersion = lib.trivial.release; diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/LICENSE b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/LICENSE deleted file mode 100644 index 5185fd3f..00000000 --- a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/LICENSE +++ /dev/null @@ -1,346 +0,0 @@ -NOTE! The GPL below is copyrighted by the Free Software Foundation, but -the instance of code that it refers to (the kde programs) are copyrighted -by the authors who actually wrote it. - ---------------------------------------------------------------------------- - - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc. - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Library General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) 19yy - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) 19yy name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Library General -Public License instead of this License. diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/00000000000000020006000e7e9ffc3f b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/00000000000000020006000e7e9ffc3f deleted file mode 100644 index 6be9a423..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/00000000000000020006000e7e9ffc3f and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/00008160000006810000408080010102 b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/00008160000006810000408080010102 deleted file mode 100644 index bd4922df..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/00008160000006810000408080010102 and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/03b6e0fcb3499374a867c041f52298f0 b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/03b6e0fcb3499374a867c041f52298f0 deleted file mode 100644 index 92726342..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/03b6e0fcb3499374a867c041f52298f0 and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/08e8e1c95fe2fc01f976f1e063a24ccd b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/08e8e1c95fe2fc01f976f1e063a24ccd deleted file mode 100644 index 6be9a423..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/08e8e1c95fe2fc01f976f1e063a24ccd and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/1081e37283d90000800003c07f3ef6bf b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/1081e37283d90000800003c07f3ef6bf deleted file mode 100644 index 6afe61f0..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/1081e37283d90000800003c07f3ef6bf and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/3085a0e285430894940527032f8b26df b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/3085a0e285430894940527032f8b26df deleted file mode 100644 index dd8ecebb..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/3085a0e285430894940527032f8b26df and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/3ecb610c1bf2410f44200f48c40d3599 b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/3ecb610c1bf2410f44200f48c40d3599 deleted file mode 100644 index 6be9a423..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/3ecb610c1bf2410f44200f48c40d3599 and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/4498f0e0c1937ffe01fd06f973665830 b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/4498f0e0c1937ffe01fd06f973665830 deleted file mode 100644 index c001d7c2..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/4498f0e0c1937ffe01fd06f973665830 and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/5c6cd98b3f3ebcb1f9c7f1c204630408 b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/5c6cd98b3f3ebcb1f9c7f1c204630408 deleted file mode 100644 index 860be88c..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/5c6cd98b3f3ebcb1f9c7f1c204630408 and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/6407b0e94181790501fd1e167b474872 b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/6407b0e94181790501fd1e167b474872 deleted file mode 100644 index 6afe61f0..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/6407b0e94181790501fd1e167b474872 and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/640fb0e74195791501fd1ed57b41487f b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/640fb0e74195791501fd1ed57b41487f deleted file mode 100644 index dd8ecebb..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/640fb0e74195791501fd1ed57b41487f and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/9081237383d90e509aa00f00170e968f b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/9081237383d90e509aa00f00170e968f deleted file mode 100644 index c001d7c2..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/9081237383d90e509aa00f00170e968f and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/9d800788f1b08800ae810202380a0822 b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/9d800788f1b08800ae810202380a0822 deleted file mode 100644 index 06a1b7da..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/9d800788f1b08800ae810202380a0822 and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/a2a266d0498c3104214a47bd64ab0fc8 b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/a2a266d0498c3104214a47bd64ab0fc8 deleted file mode 100644 index dd8ecebb..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/a2a266d0498c3104214a47bd64ab0fc8 and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/alias b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/alias deleted file mode 100644 index dd8ecebb..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/alias and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/all-scroll b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/all-scroll deleted file mode 100644 index 6754ff2d..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/all-scroll and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/b66166c04f8c3109214a4fbd64a50fc8 b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/b66166c04f8c3109214a4fbd64a50fc8 deleted file mode 100644 index 6afe61f0..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/b66166c04f8c3109214a4fbd64a50fc8 and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/bottom_left_corner b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/bottom_left_corner deleted file mode 100644 index 1ca29261..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/bottom_left_corner and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/bottom_right_corner b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/bottom_right_corner deleted file mode 100644 index e58d54bb..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/bottom_right_corner and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/bottom_side b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/bottom_side deleted file mode 100644 index 07fbc453..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/bottom_side and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/cell b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/cell deleted file mode 100644 index efddc92f..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/cell and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/center_ptr b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/center_ptr deleted file mode 100644 index 1503d6f0..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/center_ptr and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/circle b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/circle deleted file mode 100644 index 92726342..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/circle and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/closedhand b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/closedhand deleted file mode 100644 index c001d7c2..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/closedhand and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/col-resize b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/col-resize deleted file mode 100644 index af252e9d..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/col-resize and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/color-picker b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/color-picker deleted file mode 100644 index 592ad65b..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/color-picker and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/context-menu b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/context-menu deleted file mode 100644 index dc7bf617..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/context-menu and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/copy b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/copy deleted file mode 100644 index 6afe61f0..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/copy and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/cross b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/cross deleted file mode 100644 index f7a1f1b8..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/cross and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/crossed_circle b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/crossed_circle deleted file mode 100644 index 92726342..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/crossed_circle and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/crosshair b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/crosshair deleted file mode 100644 index f7a1f1b8..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/crosshair and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/d9ce0ab605698f320427677b458ad60b b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/d9ce0ab605698f320427677b458ad60b deleted file mode 100644 index 860be88c..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/d9ce0ab605698f320427677b458ad60b and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/default b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/default deleted file mode 100644 index e3a0d5b5..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/default and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/dnd-copy b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/dnd-copy deleted file mode 100644 index 6afe61f0..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/dnd-copy and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/dnd-move b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/dnd-move deleted file mode 100644 index c001d7c2..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/dnd-move and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/dnd-no-drop b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/dnd-no-drop deleted file mode 100644 index 55379157..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/dnd-no-drop and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/dnd-none b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/dnd-none deleted file mode 100644 index c001d7c2..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/dnd-none and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/down-arrow b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/down-arrow deleted file mode 100644 index caa3be6b..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/down-arrow and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/draft b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/draft deleted file mode 100644 index 678258dc..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/draft and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/e-resize b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/e-resize deleted file mode 100644 index c9c87a72..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/e-resize and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/e29285e634086352946a0e7090d73106 b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/e29285e634086352946a0e7090d73106 deleted file mode 100644 index 06a1b7da..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/e29285e634086352946a0e7090d73106 and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/fcf21c00b30f7e3f83fe0dfd12e71cff b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/fcf21c00b30f7e3f83fe0dfd12e71cff deleted file mode 100644 index c001d7c2..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/fcf21c00b30f7e3f83fe0dfd12e71cff and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/fleur b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/fleur deleted file mode 100644 index a7c24402..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/fleur and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/forbidden b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/forbidden deleted file mode 100644 index ff13a03a..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/forbidden and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/h_double_arrow b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/h_double_arrow deleted file mode 100644 index c9c87a72..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/h_double_arrow and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/half-busy b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/half-busy deleted file mode 100644 index 6be9a423..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/half-busy and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/hand1 b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/hand1 deleted file mode 100644 index 06a1b7da..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/hand1 and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/hand2 b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/hand2 deleted file mode 100644 index 06a1b7da..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/hand2 and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/help b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/help deleted file mode 100644 index 860be88c..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/help and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/ibeam b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/ibeam deleted file mode 100644 index a885b6d7..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/ibeam and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/left-arrow b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/left-arrow deleted file mode 100644 index 23a4ea73..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/left-arrow and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/left_ptr b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/left_ptr deleted file mode 100644 index e3a0d5b5..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/left_ptr and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/left_ptr_help b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/left_ptr_help deleted file mode 100644 index 860be88c..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/left_ptr_help and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/left_ptr_watch b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/left_ptr_watch deleted file mode 100644 index 6be9a423..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/left_ptr_watch and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/left_side b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/left_side deleted file mode 100644 index 8ddd71d3..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/left_side and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/link b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/link deleted file mode 100644 index dd8ecebb..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/link and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/move b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/move deleted file mode 100644 index c001d7c2..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/move and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/no-drop b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/no-drop deleted file mode 100644 index ff13a03a..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/no-drop and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/not-allowed b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/not-allowed deleted file mode 100644 index 92726342..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/not-allowed and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/openhand b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/openhand deleted file mode 100644 index dc0d7900..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/openhand and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/pencil b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/pencil deleted file mode 100644 index 0385e108..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/pencil and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/pirate b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/pirate deleted file mode 100644 index 9874d836..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/pirate and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/plus b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/plus deleted file mode 100644 index efddc92f..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/plus and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/pointer b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/pointer deleted file mode 100644 index 06a1b7da..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/pointer and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/pointing_hand b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/pointing_hand deleted file mode 100644 index 06a1b7da..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/pointing_hand and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/progress b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/progress deleted file mode 100644 index 6be9a423..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/progress and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/question_arrow b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/question_arrow deleted file mode 100644 index 860be88c..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/question_arrow and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/right-arrow b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/right-arrow deleted file mode 100644 index ead9c257..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/right-arrow and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/right_ptr b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/right_ptr deleted file mode 100644 index 87e6075f..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/right_ptr and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/right_side b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/right_side deleted file mode 100644 index 3f24a26d..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/right_side and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/row-resize b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/row-resize deleted file mode 100644 index 876e0bdb..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/row-resize and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/s-resize b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/s-resize deleted file mode 100644 index bd4922df..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/s-resize and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/sb_h_double_arrow b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/sb_h_double_arrow deleted file mode 100644 index c9c87a72..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/sb_h_double_arrow and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/sb_v_double_arrow b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/sb_v_double_arrow deleted file mode 100644 index bd4922df..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/sb_v_double_arrow and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/size-bdiag b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/size-bdiag deleted file mode 100644 index e3a0d5b5..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/size-bdiag and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/size-fdiag b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/size-fdiag deleted file mode 100644 index e3a0d5b5..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/size-fdiag and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/size-hor b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/size-hor deleted file mode 100644 index e3a0d5b5..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/size-hor and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/size-ver b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/size-ver deleted file mode 100644 index e3a0d5b5..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/size-ver and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/size_all b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/size_all deleted file mode 100644 index a7c24402..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/size_all and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/size_bdiag b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/size_bdiag deleted file mode 100644 index 7d591434..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/size_bdiag and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/size_fdiag b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/size_fdiag deleted file mode 100644 index c0a4c32c..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/size_fdiag and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/size_hor b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/size_hor deleted file mode 100644 index c9c87a72..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/size_hor and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/size_ver b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/size_ver deleted file mode 100644 index bd4922df..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/size_ver and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/split_h b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/split_h deleted file mode 100644 index af252e9d..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/split_h and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/split_v b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/split_v deleted file mode 100644 index 876e0bdb..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/split_v and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/text b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/text deleted file mode 100644 index a885b6d7..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/text and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/top_left_corner b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/top_left_corner deleted file mode 100644 index e1b3a1ac..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/top_left_corner and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/top_right_corner b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/top_right_corner deleted file mode 100644 index abc17e3d..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/top_right_corner and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/top_side b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/top_side deleted file mode 100644 index aefc71c7..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/top_side and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/up-arrow b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/up-arrow deleted file mode 100644 index f246c88f..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/up-arrow and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/v_double_arrow b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/v_double_arrow deleted file mode 100644 index bd4922df..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/v_double_arrow and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/vertical-text b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/vertical-text deleted file mode 100644 index 957e9351..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/vertical-text and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/w-resize b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/w-resize deleted file mode 100644 index c9c87a72..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/w-resize and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/wait b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/wait deleted file mode 100644 index 502c17dd..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/wait and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/watch b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/watch deleted file mode 100644 index 502c17dd..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/watch and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/wayland-cursor b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/wayland-cursor deleted file mode 100644 index 6c61a830..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/wayland-cursor and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/whats_this b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/whats_this deleted file mode 100644 index 860be88c..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/whats_this and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/x-cursor b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/x-cursor deleted file mode 100644 index 71fe5d19..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/x-cursor and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/xterm b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/xterm deleted file mode 100644 index a885b6d7..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/xterm and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/zoom-in b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/zoom-in deleted file mode 100644 index db401ebd..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/zoom-in and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/zoom-out b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/zoom-out deleted file mode 100644 index f8b95143..00000000 Binary files a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/cursors/zoom-out and /dev/null differ diff --git a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/index.theme b/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/index.theme deleted file mode 100644 index e80c5579..00000000 --- a/pkgs/breeze-contrast-cursor-theme/theme/share/icons/Breeze_Contrast/index.theme +++ /dev/null @@ -1,70 +0,0 @@ -[Icon Theme] -Name=Breeze Contrast -Name[ca]=Brisa -Name[cs]=Breeze Contrast -Name[da]=Breeze Contrast -Name[de]=Breeze Contrast -Name[el]=Breeze Contrast -Name[en_GB]=Breeze Contrast -Name[es]=Brisa -Name[fi]=Breeze Contrast -Name[fr]=Brise -Name[gl]=Breeze Contrast -Name[hu]=Breeze Contrast -Name[ia]=Brisa -Name[it]=Brezza -Name[ko]=Breeze Contrast -Name[lt]=Breeze Contrast -Name[nb]=Breeze Contrast -Name[nds]=Breeze Contrast -Name[nl]=Breeze Contrast -Name[pl]=Bryza -Name[pt]=Brisa -Name[pt_BR]=Breeze Contrast -Name[ru]=Breeze Contrast -Name[sk]=Breeze Contrast -Name[sl]=Sapica -Name[sr]=Поветарац -Name[sr@ijekavian]=Поветарац -Name[sr@ijekavianlatin]=Povetarac -Name[sr@latin]=Povetarac -Name[sv]=Breeze Contrast -Name[tr]=Breeze Contrast -Name[uk]=Breeze Contrast -Name[x-test]=xxBreeze Contrastxx -Name[zh_CN]=微风 -Name[zh_TW]=微風 -Comment=KDE High-Contrast Plasma Cursor Theme -Comment[ca]=Tema de cursor del Plasma pel KDE -Comment[cs]=Motiv kurzorů Plasma KDE -Comment[da]=Markørtema til KDE Plasma -Comment[de]=Plasma-Zeigerdesign für KDE -Comment[el]=Θέμα δρομέα του KDE Plasma -Comment[en_GB]=KDE Plasma Cursor Theme -Comment[es]=Tema de cursores de KDE Plasma -Comment[fi]=KDE Plasman osoitinteema -Comment[fr]=Thème Plasma de curseur -Comment[gl]=Tema do cursor de Plasma de KDE -Comment[hu]=KDE plazma kurzortéma -Comment[it]=Tema di puntatori di KDE Plasma -Comment[ko]=KDE Plasma 커서 테마 -Comment[lt]=KDE Plasma žymeklių tema -Comment[nb]=KDE Plasma pekertema -Comment[nds]=KDE-Plasma-Wiesermuster -Comment[nl]=Cursorthema van KDE Plasma -Comment[pl]=Zestaw wskaźników Plazmy KDE -Comment[pt]=Tema de Cursores do Plasma para KDE -Comment[pt_BR]=Tema de cursor do Plasma -Comment[ru]=Тема курсоров KDE Plasma -Comment[sk]=Téma kurzora KDE Plasma -Comment[sl]=Tema kazalk za KDE Plasma -Comment[sr]=КДЕ плазма тема показивача -Comment[sr@ijekavian]=КДЕ плазма тема показивача -Comment[sr@ijekavianlatin]=KDE plasma tema pokazivača -Comment[sr@latin]=KDE plasma tema pokazivača -Comment[sv]=KDE Plasma muspekartema -Comment[tr]=KDE Plasma İmleç Teması -Comment[uk]=Тема курсорів Плазми KDE -Comment[x-test]=xxKDE Plasma Cursor Themexx -Comment[zh_CN]=KDE Plasma 光标主题 -Comment[zh_TW]=KDE Plasma 游標主題 diff --git a/pkgs/default.nix b/pkgs/default.nix index 0b2f87ae..d1e19eea 100644 --- a/pkgs/default.nix +++ b/pkgs/default.nix @@ -1,11 +1,11 @@ -{ version, updateUrl, kioskUrl, activeVirtualTerminals ? [] }: +{ applicationOverlays ? [] }: let nixpkgs = builtins.fetchTarball { - # nixos-22.11 2023-01-11 - url = "https://github.com/nixos/nixpkgs/archive/54644f409ab471e87014bb305eac8c50190bcf48.tar.gz"; - sha256 = "1pqgwbprmm84nsylp8jjhrwchzn3cv9iiaz1r89mazfil9qcadz0"; + # nixos-23.05 2023-08-14 + url = "https://github.com/nixos/nixpkgs/archive/720e61ed8de116eec48d6baea1d54469b536b985.tar.gz"; + sha256 = "0ii10wmm8hqdp7bii7iza58rjaqs4z3ivv71qyix3qawwxx48hw9"; }; overlay = @@ -15,39 +15,17 @@ let rauc = (import ./rauc) super; - dividat-driver = (import ./dividat-driver) { - inherit (super) stdenv fetchFromGitHub buildGoModule; - pkgs = self; - }; - - playos-kiosk-browser = import ../kiosk { - pkgs = self; - system_name = "PlayOS"; - system_version = version; - }; - - breeze-contrast-cursor-theme = super.callPackage ./breeze-contrast-cursor-theme {}; - ocamlPackages = super.ocamlPackages.overrideScope' (self: super: { semver = self.callPackage ./ocaml-modules/semver {}; obus = self.callPackage ./ocaml-modules/obus {}; + opium = self.callPackage ./ocaml-modules/opium {}; + opium_kernel = self.callPackage ./ocaml-modules/opium_kernel {}; }); - # Controller - playos-controller = import ../controller { - pkgs = self; - version = version; - updateUrl = updateUrl; - kioskUrl = kioskUrl; - }; - }; in - import nixpkgs { - overlays = [ - overlay - (import ./xorg { inherit activeVirtualTerminals; }) - ]; - } +import nixpkgs { + overlays = [ overlay ] ++ applicationOverlays; +} diff --git a/pkgs/ocaml-modules/obus/default.nix b/pkgs/ocaml-modules/obus/default.nix index 645173d6..56e6d5ed 100644 --- a/pkgs/ocaml-modules/obus/default.nix +++ b/pkgs/ocaml-modules/obus/default.nix @@ -18,12 +18,11 @@ buildDunePackage rec { useDune2 = true; - buildInputs = [ ]; + nativeBuildInputs = [ menhir ]; propagatedBuildInputs = [ lwt_log lwt_ppx lwt_react - menhir ocaml_lwt ppxlib react diff --git a/pkgs/ocaml-modules/opium/default.nix b/pkgs/ocaml-modules/opium/default.nix new file mode 100644 index 00000000..5db3d1b4 --- /dev/null +++ b/pkgs/ocaml-modules/opium/default.nix @@ -0,0 +1,32 @@ +{ buildDunePackage + +, ppx_sexp_conv +, ppx_fields_conv + +, cmdliner +, cohttp-lwt-unix +, logs +, magic-mime +, opium_kernel +, stringext + +, alcotest +}: + +buildDunePackage { + pname = "opium"; + inherit (opium_kernel) version src meta minimumOCamlVersion; + + useDune2 = true; + + doCheck = true; + + buildInputs = [ + ppx_sexp_conv ppx_fields_conv + alcotest + ]; + + propagatedBuildInputs = [ + opium_kernel cmdliner cohttp-lwt-unix magic-mime logs stringext + ]; +} diff --git a/pkgs/ocaml-modules/opium_kernel/default.nix b/pkgs/ocaml-modules/opium_kernel/default.nix new file mode 100644 index 00000000..6b51443d --- /dev/null +++ b/pkgs/ocaml-modules/opium_kernel/default.nix @@ -0,0 +1,44 @@ +{ lib +, buildDunePackage +, fetchurl + +, ppx_fields_conv +, ppx_sexp_conv + +, cohttp-lwt +, ezjsonm +, hmap +, sexplib +, fieldslib +}: + +buildDunePackage rec { + pname = "opium_kernel"; + version = "0.18.0"; + + useDune2 = true; + + minimumOCamlVersion = "4.04.1"; + + src = fetchurl { + url = "https://github.com/rgrinberg/opium/releases/download/${version}/opium-${version}.tbz"; + sha256 = "0a2y9gw55psqhqli3a5ps9mfdab8r46fnbj882r2sp366sfcy37q"; + }; + + doCheck = true; + + buildInputs = [ + ppx_sexp_conv ppx_fields_conv + ]; + + propagatedBuildInputs = [ + hmap cohttp-lwt ezjsonm sexplib fieldslib + ]; + + meta = { + description = "Sinatra like web toolkit for OCaml based on cohttp & lwt"; + homepage = "https://github.com/rgrinberg/opium"; + license = lib.licenses.mit; + maintainers = [ lib.maintainers.pmahoney ]; + }; +} diff --git a/rauc-bundle/default.nix b/rauc-bundle/default.nix index ea22c0e6..bfa90c57 100644 --- a/rauc-bundle/default.nix +++ b/rauc-bundle/default.nix @@ -3,7 +3,7 @@ , rauc , version -, systemToplevel +, systemImage , closureInfo }: @@ -12,7 +12,7 @@ let testingKey = ../pki/dummy/key.pem; testingCert = ../pki/dummy/cert.pem; - systemClosureInfo = closureInfo { rootPaths = [ systemToplevel ]; }; + systemClosureInfo = closureInfo { rootPaths = [ systemImage ]; }; # TODO: Create tar ball used for RAUC bundle in one derivation. This will reduce disk space usage as the tarball alone is not (unnecessarily) stored in the nix store. systemTarball = (importFromNixos "lib/make-system-tarball.nix") { @@ -22,21 +22,21 @@ let contents = [ { - source = systemToplevel + "/initrd"; + source = systemImage + "/initrd"; target = "/initrd"; } { - source = systemToplevel + "/kernel"; + source = systemImage + "/kernel"; target = "/kernel"; } { - source = systemToplevel + "/init" ; + source = systemImage + "/init" ; target = "/init"; } ]; storeContents = [{ - object = systemToplevel; + object = systemImage; symlink = "/run/current-system"; }]; } + "/tarball/system.tar.xz"; @@ -58,9 +58,9 @@ stdenv.mkDerivation { done # copy initrd, kernel and init - cp -a "${systemToplevel}/initrd" initrd - cp -a "${systemToplevel}/kernel" kernel - cp -a "${systemToplevel}/init" init + cp -a "${systemImage}/initrd" initrd + cp -a "${systemImage}/kernel" kernel + cp -a "${systemImage}/init" init mkdir -p ../rauc-bundle time tar --sort=name --mtime='@1' --owner=0 --group=0 --numeric-owner -c * | pixz > ../rauc-bundle/system.tar.xz diff --git a/system-image/default.nix b/system-image/default.nix new file mode 100644 index 00000000..65fc931b --- /dev/null +++ b/system-image/default.nix @@ -0,0 +1,43 @@ +# Build an installable system image assuming a disk layout of a full A/B installation +{pkgs, lib, updateCert, kioskUrl, playos-controller, application }: +with lib; +let nixos = pkgs.importFromNixos ""; in +(nixos { + configuration = {...}: { + imports = [ + # Base layer of PlayOS + ((import ../base) { + inherit pkgs kioskUrl playos-controller; + inherit (application) safeProductName fullProductName greeting version; + }) + + # Application-specific module + application.module + ]; + + # Storage + fileSystems = { + "/boot" = { + device = "/dev/disk/by-label/ESP"; + }; + }; + playos.storage = { + systemPartition = { + enable = true; + device = "/dev/root"; + options = [ "ro" ]; + }; + persistentDataPartition.device = "/dev/disk/by-label/data"; + }; + + playos.selfUpdate = { + enable = true; + updateCert = updateCert; + }; + + # As we control which state can be persisted past a reboot, we always set the stateVersion the system was built with. + system.stateVersion = lib.trivial.release; + + }; + system = "x86_64-linux"; +}).config.system.build.toplevel diff --git a/system/configuration.nix b/system/configuration.nix deleted file mode 100644 index 2f7290d4..00000000 --- a/system/configuration.nix +++ /dev/null @@ -1,58 +0,0 @@ -# NixOS configuration file - -{ config, pkgs, lib, ... }: - -with lib; - -{ - - imports = [ - # Play Kiosk and Driver - ./play-kiosk.nix - - # Remote management - ./remote-management.nix - - # Localization - ./localization.nix - - # Update Machinery - ./rauc - - # Networking - ./networking - ]; - - systemPartition = { - enable = true; - device = "/dev/root"; - options = [ "rw" ]; - }; - - volatileRoot.persistentDataPartition.device = "/dev/disk/by-label/data"; - - - fileSystems = { - "/boot" = { - device = "/dev/disk/by-label/ESP"; - }; - }; - - # Set a low default timeout when stopping services, to prevent the Windows 95 shutdown experience - systemd.extraConfig = "DefaultTimeoutStopSec=15s"; - - # disable installation of documentation - documentation.enable = false; - - # Enable persistent journaling with low maximum size - volatileRoot.persistentFolders."/var/log/journal" = { - mode = "0755"; - user = "root"; - group = "root"; - }; - services.journald.extraConfig = '' - Storage=persistent - SystemMaxUse=1G - ''; - -} diff --git a/system/default.nix b/system/default.nix deleted file mode 100644 index e685ffaf..00000000 --- a/system/default.nix +++ /dev/null @@ -1,20 +0,0 @@ -# Build NixOS system -{pkgs, lib, version, updateCert, kioskUrl, playos-controller, greeting }: -with lib; -let nixos = pkgs.importFromNixos ""; in -(nixos { - configuration = {...}: { - imports = [ - # general PlayOS modules - ((import ./modules/playos.nix) {inherit pkgs version updateCert kioskUrl greeting playos-controller;}) - - # system configuration - ./configuration.nix - ]; - - # As we control which state can be persisted past a reboot, we always set the stateVersion the system was built with. - system.stateVersion = lib.trivial.release; - - }; - system = "x86_64-linux"; -}).config.system.build.toplevel diff --git a/system/play-kiosk.nix b/system/play-kiosk.nix deleted file mode 100644 index bfbf08b2..00000000 --- a/system/play-kiosk.nix +++ /dev/null @@ -1,132 +0,0 @@ -{ config, pkgs, ... }: - -{ - - # Kiosk runs as a non-privileged user - users.users.play = { - isNormalUser = true; - home = "/home/play"; - extraGroups = [ - "dialout" # Access to serial ports for the Senso flex - ]; - }; - - # Note that setting up "/home" as persistent fails due to https://github.com/NixOS/nixpkgs/issues/6481 - volatileRoot.persistentFolders."/home/play" = { - mode = "0700"; - user = "play"; - group = "users"; - }; - - # System-wide packages - environment.systemPackages = with pkgs; [ - breeze-contrast-cursor-theme - ]; - - # Kiosk session - services.xserver = let sessionName = "kiosk-browser"; in { - enable = true; - - desktopManager = { - xterm.enable = false; - session = [ - { name = sessionName; - start = '' - # Disable screen-saver control (screen blanking) - xset s off - xset s noblank - xset -dpms - - # Localization for xsession - if [ -f /var/lib/gui-localization/lang ]; then - export LANG=$(cat /var/lib/gui-localization/lang) - fi - if [ -f /var/lib/gui-localization/keymap ]; then - setxkbmap $(cat /var/lib/gui-localization/keymap) || true - fi - - # force resolution - scaling_pref=/var/lib/gui-localization/screen-scaling - if [ -f "$scaling_pref" ] && [ $(cat "$scaling_pref") = "full-hd" ]; then - xrandr --size 1920x1080 - fi - - # We want to avoid making the user configure audio outputs, but - # instead route audio to both the standard output and any connected - # displays. This looks for any "HDMI" device on ALSA card 0 and - # tries to add a sink for it. Both HDMI and DisplayPort connectors - # will count as "HDMI". We ignore failure from disconnected ports. - for dev_num in $(aplay -l | grep "^card 0:" | grep "HDMI" | grep "device [0-9]\+" | sed "s/.*device \([0-9]\+\):.*/\1/"); do - printf "Creating ALSA sink for device $dev_num: " - pactl load-module module-alsa-sink device="hw:0,$dev_num" sink_name="hdmi$dev_num" sink_properties="device.description='HDMI-$dev_num'" || true - done - pactl load-module module-combine-sink sink_name=combined - pactl set-default-sink combined - - # Enable Qt WebEngine Developer Tools (https://doc.qt.io/qt-5/qtwebengine-debugging.html) - export QTWEBENGINE_REMOTE_DEBUGGING="127.0.0.1:3355" - - ${pkgs.playos-kiosk-browser}/bin/kiosk-browser \ - ${config.playos.kioskUrl} \ - http://localhost:3333/ - - waitPID=$! - ''; - } - ]; - }; - - displayManager = { - # Always automatically log in play user - lightdm = { - enable = true; - greeter.enable = false; - autoLogin.timeout = 0; - }; - - autoLogin = { - enable = true; - user = "play"; - }; - - defaultSession = sessionName; - - sessionCommands = '' - ${pkgs.xorg.xrdb}/bin/xrdb -merge <. # TODO: Set up a Unix Socket from Python and connect to shell - '-chardev', 'socket,id=shell,path={}/not-working-shell,server,nowait'.format(backdoor_dir), + '-chardev', 'socket,id=shell,path={}/not-working-shell,server=on,wait=off'.format(backdoor_dir), '-device', 'virtio-serial', '-device', 'virtconsole,chardev=shell', # This is hvc1 and will show a login prompt - '-chardev', 'socket,id=backdoor,path={}/backdoor,server,nowait'.format(backdoor_dir), + '-chardev', 'socket,id=backdoor,path={}/backdoor,server=on,wait=off'.format(backdoor_dir), '-device', 'virtio-serial', '-device', 'virtconsole,chardev=backdoor' ] + qemu_opts) diff --git a/testing/system/default.nix b/testing/system/default.nix index 4a969e3e..82662d57 100644 --- a/testing/system/default.nix +++ b/testing/system/default.nix @@ -1,15 +1,18 @@ -{pkgs, lib, version, updateCert, kioskUrl, playos-controller, greeting}: +{pkgs, lib, kioskUrl, playos-controller, application}: let nixos = pkgs.importFromNixos ""; in (nixos { configuration = {...}: { imports = [ - # general PlayOS modules - (import ../../system/modules/playos.nix { inherit pkgs version updateCert kioskUrl greeting playos-controller; }) + # Base layer + (import ../../base { + inherit pkgs kioskUrl playos-controller; + inherit (application) safeProductName fullProductName greeting version; + }) - # system configuration - ../../system/configuration.nix + # Application-specific + application.module # Testing machinery (import ./testing.nix { inherit lib pkgs; }) diff --git a/testing/system/testing.nix b/testing/system/testing.nix index 7d4560e7..39f65808 100644 --- a/testing/system/testing.nix +++ b/testing/system/testing.nix @@ -8,22 +8,24 @@ ]; config = { - systemPartition = lib.mkForce { - device = "system"; - fsType = "9p"; - options = [ "trans=virtio" "version=9p2000.L" "cache=loose" ]; - }; - - volatileRoot.persistentDataPartition = lib.mkForce { + fileSystems."/boot" = { device = "tmpfs"; fsType = "tmpfs"; options = [ "mode=0755" ]; }; + playos.storage = { + systemPartition = { + enable = true; + device = "system"; + fsType = "9p"; + options = [ "trans=virtio" "version=9p2000.L" "cache=loose" ]; + }; - fileSystems."/boot" = lib.mkForce { - device = "tmpfs"; - fsType = "tmpfs"; - options = [ "mode=0755" ]; + persistentDataPartition = { + device = "tmpfs"; + fsType = "tmpfs"; + options = [ "mode=0755" ]; + }; }; networking.hostName = lib.mkForce "playos-test";