# pawletprofiled PawletOS's Linux device management daemon — a native root-run systemd service. One binary, two subsystems. This repo is Linux-only. The Android implementation is a separate, independently-written app (platform-signed priv-app + device owner, not a native daemon — most of what these payloads need on Android is Java-SDK first): [`android_packages_apps_PawletProfiled`](https://git.oxmc.me/PawletOS/android_packages_apps_PawletProfiled). Same `.vconfig` profile format and payload types on both platforms; no shared code between the two implementations. --- ## Subsystems ### Profile Service D-Bus system service at `os.pawlet.ProfiledService`. Parses, verifies, persists, and applies `.vconfig` profiles. The allowed payload types are controlled by the separately-installed `pawletprofiled-config-schema` package — OEMs can restrict the configuration surface without touching the daemon binary. ### ZTE Client Zero-Touch Enrollment. Runs as a background thread. Watches NetworkManager for internet connectivity. On first connection, queries a ZTE lookup server with the device's hardware identity. Downloads and installs the enrollment profile automatically via the local D-Bus service. Handles the deferred case: if no network at first boot, enrolls on the first successful connection later. --- ## Package layout ``` pawletprofiled/ ├── CMakeLists.txt Linux CMake build ├── zte.conf.example ZTE client config │ ├── src/ │ ├── main.cpp Entry point │ ├── PawletProfileService.h/.cpp D-Bus service │ ├── ProfileParser.h/.cpp YAML parser (libyaml) │ ├── ProfileStore.h/.cpp Disk persistence + password hashing │ ├── SignatureVerifier.h/.cpp CMS/PKCS#7 verification (OpenSSL) │ ├── payloads/ │ │ ├── PayloadHandler.h Handler contract + PayloadUtil.h helpers │ │ └── PayloadHandlers.cpp Dispatch — forwards to platform/linux/*.cpp │ └── platform/ │ ├── .h Per-payload-type contract (apply/revert) │ └── linux/.cpp One real implementation per payload type │ ├── src/zte/ │ ├── DeviceIdentity.h/.cpp Hardware identity collection │ ├── ZTELookupClient.h/.cpp ZTE server HTTPS client (libcurl) │ └── ConnectivityWatcher.h/.cpp NM D-Bus connectivity watcher │ ├── systemd/pawletprofiled.service Systemd unit ├── dbus/os.pawlet.ProfiledService.conf D-Bus policy ├── apparmor/pawletprofiled AppArmor MAC profile └── debian/ Debian Trixie packaging ``` --- ## Dependencies ### Runtime | Package | Used for | |---|---| | `pawletprofiled-config-schema` | Schema defining allowed payload types | | `libssl3` | CMS signature verification, PBKDF2 | | `libyaml-0-2` | YAML parsing | | `libdbus-1-3` | D-Bus system service | | `libcurl4` | ZTE HTTPS lookups | | `network-manager` | WiFi/Ethernet/VPN keyfile reload | ### Recommended (enable specific payload handlers) | Package | Payload types | |---|---| | `cloud-init` | `mdm`, `first-boot` | | `nftables` | `firewall` | | `network-manager-strongswan` | `vpn` (IKEv2) | | `network-manager-l2tp` | `vpn` (L2TP) | | `libpam-pwquality` | `passcode` (complexity) | | `libpam-faillock` | `passcode` (lockout) | | `libnss3-tools` | `pkcs12` (NSSDB import) | | `unattended-upgrades` | `software-update` | | `dconf-cli` | `desktop`, `screensaver`, `first-boot` | | `tpm2-tools` | ZTE TPM EK detection | --- ## Building — Linux (Debian Trixie) ```bash # Build dependencies sudo apt install -y \ cmake libssl-dev libyaml-dev libdbus-1-dev \ libsystemd-dev libcurl4-openssl-dev pkg-config # Build .deb (includes pawletprofiled-config-schema as a dep) dpkg-buildpackage -us -uc -b sudo apt install \ ../pawletprofiled-config-schema_1.0.0-1_all.deb \ ../pawletprofiled_1.0.0-1_amd64.deb # Or build without packaging cmake -B build -DCMAKE_BUILD_TYPE=Release cmake --build build -j$(nproc) sudo cmake --install build ``` --- ## Android Not built from this repo — see [`android_packages_apps_PawletProfiled`](https://git.oxmc.me/PawletOS/android_packages_apps_PawletProfiled), a platform-signed priv-app + device owner with its own independent implementation of every payload type in this daemon's schema. --- ## Verifying the installation ```bash # Service status systemctl status pawletprofiled # D-Bus connectivity dbus-send --system --print-reply \ --dest=os.pawlet.ProfiledService \ /os/pawlet/ProfiledService \ os.pawlet.IProfiledService.IsDeviceManaged # List installed profiles dbus-send --system --print-reply \ --dest=os.pawlet.ProfiledService \ /os/pawlet/ProfiledService \ os.pawlet.IProfiledService.ListProfiles # ZTE enrollment state cat /var/lib/pawletprofiled/zte_state.json # Live logs journalctl -u pawletprofiled -f ``` --- ## Installing a profile from the command line ```python #!/usr/bin/env python3 import dbus, sys with open(sys.argv[1], 'rb') as f: data = f.read() bus = dbus.SystemBus() obj = bus.get_object('os.pawlet.ProfiledService', '/os/pawlet/ProfiledService') svc = dbus.Interface(obj, 'os.pawlet.IProfiledService') uuid = svc.InstallProfile(dbus.ByteArray(data)) print('Installed profile UUID:', uuid) ``` --- ## Signing profiles Profiles containing `mdm`, `kiosk`, `asam`, or `removal-password` payloads must be CMS/PKCS#7 signed. Unsigned profiles install with a trust warning. ```bash # Generate a development signing key and CA openssl req -x509 -newkey rsa:4096 -days 3650 -nodes \ -keyout /etc/pawletprofiled/signing_key.pem \ -out /etc/pawletprofiled/profile_ca.pem \ -subj "/CN=PawletOS Profile Signing/O=oxmc" # Sign a profile openssl cms -sign \ -in my.vconfig \ -out my.signed.vconfig \ -signer /etc/pawletprofiled/profile_ca.pem \ -inkey /etc/pawletprofiled/signing_key.pem \ -outform DER -nodetach ``` The CA at `/etc/pawletprofiled/profile_ca.pem` is trusted automatically. For a system-wide trusted badge, also install it as a system CA: ```bash cp /etc/pawletprofiled/profile_ca.pem \ /usr/local/share/ca-certificates/pawletos-profile-ca.crt update-ca-certificates ``` --- ## Preinstalled profiles Drop signed `.vconfig` files into `/etc/pawletprofiled/preinstalled/` before first boot. The daemon applies them at startup, **before** `cloud-init-local.service` starts (enforced via `Before=cloud-init-local.service` in the systemd unit). This lets image builders bake in WiFi credentials, first-boot GNOME setup suppression, and MDM enrollment — all applied before cloud-init tries to reach its datasource. --- ## Zero-Touch Enrollment ZTE configuration lives at `/etc/pawletprofiled/zte.conf` (copy from `zte.conf.example`). The key setting is `server_url`, which points to your `pawlet-zte-server` instance. ```ini server_url=https://zte.yourdomain.com ``` Hardware identity priority used for the ZTE lookup: | Priority | Source | Path | |---|---|---| | 1 | DMI system UUID | `/sys/class/dmi/id/product_uuid` | | 2 | Board serial | `/sys/class/dmi/id/board_serial` | | 3 | System serial | `/sys/class/dmi/id/product_serial` | | 4 | machine-id | `/etc/machine-id` | | 5 | Permanent MAC | `/sys/class/net//perm_address` | --- ## Bugs fixed vs. previous iterations | Bug | Fix | |---|---| | `ProfileParser.cpp` used `android-base/logging.h` and `LOG()` | Replaced with `syslog()` throughout | | `debian/rules` had postinst `install -d` lines leaked after `override_dh_installsystemd` | Removed stray lines | | `sepolicy/pawletprofiled.te` had typo `vesperprofrofiled` | Fixed | | `apparmor/pawletprofiled` used `abstractions/openssl` (doesn't exist on Debian) | Replaced with explicit `/etc/ssl/certs/` and network socket rules | | `ZTELookupClient` called `curl_global_init` but never `curl_global_cleanup` | Added to destructor | | `debian/control` missing `Depends: pawletprofiled-config-schema` | Added |