intuneme initsuccessfully pullsghcr.io/frostyard/ubuntu-intune:latest, extracts rootfs via podman, configures user/PAM/keyring/systemd, installs polkit rulesintuneme startboots nspawn container, discovers host session (DISPLAY, XAUTHORITY, DBUS), launchesintune-portalGUI on host display- Intune Portal launched and is functional
-
sudo needs TTY —
r.Run()captures output so sudo can't prompt for password. Fixed by usingr.RunAttached()for all sudo operations. -
OCI image has existing UID 1000 user — The frostyard image ships with
ubuntu:1000.useradd --uid 1000fails with "UID is not unique". Fixed by detecting existing users by UID and renaming withusermod --login. -
--pipeconsole warning —systemd-nspawn --pipewarns when stdin is a TTY. Changed to--console=pipe. Warning still appears (cosmetic, harmless). -
teedoesn't take content as arg —InstallPolkitRulepassed rule content as a positional argument tosudo tee. Fixed by writing to a temp file andsudo cp. -
Leftover podman containers — If init fails partway through,
podman create --name intuneme-extractfails on retry because the container already exists. Fixed by pre-cleaning withpodman rm intuneme-extractbefore create. -
Identity broker error 501271 — Broker not running when intune-portal launches. Fixed by creating
start-intune.shthat starts broker before portal. -
Java errors + STATE_DIRECTORY null — Broker is a Java 11 app needing JAVA_HOME and systemd env vars. Fixed by setting JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 and STATE_DIRECTORY/RUNTIME_DIRECTORY/LOGS_DIRECTORY.
-
Boot steals terminal —
RunAttachedfor nspawn boot shows login prompt, blocks shell. Fixed by addingRunBackgroundto Runner interface,--console=pipeto boot args, andsudo -vto pre-cache credentials. -
Gnome keyring write failure (BIGGEST ISSUE) — Multiple root causes:
- $HOME bind mount hides rootfs keyring files → create at runtime
- Host's D-Bus forwarded but keyring needs container's own bus → override DBUS_SESSION_BUS_ADDRESS
--unlockdoesn't CREATE keyrings, only opens existing → use--loginfirst--loginincompatible with--replace→ two-step: --login then --replace --unlock -d- Empty password (0 bytes) doesn't work → use real password ("intuneme") stored in ~/.keyring-password
- Stale keyring from previous sessions with different password → always delete login.keyring before --login
secret-toolnot installed → use busctl for verification instead
-
Password compliance — Intune requires password policies. Fixed by writing
/etc/security/pwquality.confin WriteFixups (minlen=12, dcredit/ucredit/lcredit/ocredit=-1).
- Intune enrollment completes when keyring initializes correctly
- Still testing: pwquality compliance checks, stop/start lifecycle
- Bubblewrap approach (yesterday) — got tangled quickly, abandoned
v1 approach of sharing $HOME and $XDG_RUNTIME_DIR created cascading complexity: host-vs-container D-Bus confusion, keyring two-step dance, machinectl shell broken, stale state from bind-mounted home. Scaled back to run everything (including Edge) inside the container.
~/Intuneon host → bind-mounted as container user's home (was: full $HOME)- Individual socket binds for Wayland/PipeWire/Xauthority (was: entire XDG_RUNTIME_DIR)
- Container's own systemd/D-Bus/keyring (was: forwarding host session)
/etc/profile.d/intuneme.shsets env on login (was: start-intune.sh manual launcher)machinectl shellworks for interactive access (was: broken in v1)- No more host session discovery (
internal/sessionpackage deleted)
machinectl shell— gives real logind session with XDG_RUNTIME_DIR, D-Bus, keyring- Profile.d script — DISPLAY, XAUTHORITY, keyring init all handled automatically
- gnome-keyring — starts and unlocks via profile.d, no more two-step password dance
- X11 auth missing — needed XAUTHORITY bind mount (
/run/host-xauthority) and profile.d to set it. Without it, GTK init fails with "no authorization protocol specified". - Keyring default collection not created —
ReadAlias "default"returns/(no collection).secret-toolnot installed. Need to either installlibsecret-toolsor find another way to initialize the collection. Testing whether enrollment works regardless.
- X11 auth missing — needed XAUTHORITY bind mount (
/run/host-xauthority) and profile.d to set it. Without it, GTK init fails with "no authorization protocol specified". - Keyring default collection not created —
ReadAlias "default"returns/(no collection). Fixed by installinglibsecret-toolsand usingsecret-tool storein profile.d to force collection creation. - Edge not installed — frostyard image doesn't include Edge. Added
InstallPackagesprovisioning step with apt-get install. - Edge apt repo not configured — separate repo URL from intune packages. Added sources.list entry during provisioning.
- Edge GPG key missing — different signing key than intune repo. Downloaded on host side and wrote into rootfs (container lacks curl/gpg).
- sudo not installed — frostyard image doesn't include sudo. Added to package install list.
- System device broker not running — needed
systemctl enable microsoft-identity-device-brokerduring provisioning.
- Enrollment succeeds
- Compliance check passes
- Edge launches inside container with display on host
- intune-portal launches and authenticates
- machinectl shell provides real user session with D-Bus, keyring, XDG_RUNTIME_DIR
- ~/Intune persists state across container restarts
Added PulseAudio socket forwarding so Edge has full-duplex audio (playback + mic for Teams calls):
- Detect host PulseAudio socket (
/run/user/{uid}/pulse/native) inDetectHostSockets() - Bind-mount to
/run/host-pulsein container - Set
PULSE_SERVER=unix:/run/host-pulsein profile.d script - Install
libpulse0(PulseAudio client library) during provisioning - PipeWire socket forwarding was already in place; PulseAudio socket is the one Edge actually uses
Changed Go module from github.com/bjk/intuneme to github.com/frostyard/intune for public push.
-
Device broker not starting on boot —
microsoft-identity-device-broker.serviceis a "static" unit (no[Install]section), sosystemctl enablewas a silent no-op. Fixed by creating themulti-user.target.wantssymlink directly during provisioning. -
Keyring collection never created —
printf ''sends 0 bytes tognome-keyring-daemon --unlock, but it needs a newline to create the login collection. Changed toecho "". Also added broker restart after keyring init — the broker starts before first login and fails withstorage_keyring_write_failure, then never retries.
Replaced cmd.Execute() with fang.Execute() to get batteries-included CLI features:
- Styled help pages and error messages
--versionflag with build metadata (version/commit/date/builtBy via ldflags)- Hidden
mancommand (mango-powered single man page) - Hidden
completioncommand (bash/zsh/fish/powershell) - Signal handling via
signal.NotifyContext+fang.WithNotifySignal
Refactored cmd/root.go to export RootCmd() — subcommand files unchanged.
Created scripts/completions.sh and scripts/manpages.sh for goreleaser before.hooks. Use go run . to invoke fang's hidden commands before the binary is built.
Adapted .goreleaser.yaml from igloo reference:
- Removed
pkg/dracut/95etc-overlay/references from nfpms (not applicable) - Fixed release footer URL (
frostyard/notbketelsen/)
- Replaced broken
docstarget (referenced nonexistentgendocscommand) - Added
completions,manpages,docstargets using fang commands
Rewrote .github/workflows/test.yml — removed igloo-specific jobs (docker build, integration tests with loop devices, nbc binary references, ./pkg/... test paths). Simplified to 4 jobs: lint, test, verify, build.
Fixed all 18 errcheck violations across 6 files. Added CLAUDE.md with make fmt / make lint reminder.
Renamed Go module from github.com/frostyard/intune to github.com/frostyard/intuneme to match the actual repo name. The mismatch caused goreleaser's gomod.proxy to fail — it tried to fetch the module via the Go proxy and couldn't resolve frostyard/intune since that repo doesn't exist.
First tagged release. GoReleaser pipeline passes: builds binary, generates completions + man page, packages deb/rpm/apk, publishes to frostyard repo.
After merging the ubuntu-intune/ container build into the repo, consolidated responsibilities: the container image handles everything static (packages, PAM, pwquality, systemd overrides, Edge wrapper), the Go CLI handles only host-specific setup (user, hostname, polkit) and lifecycle.
/etc/environment→system_files/etc/environment- PAM pwquality profile →
system_files/usr/share/pam-configs/pwquality - intune-agent.timer override →
system_files/etc/systemd/user/intune-agent.timer.d/override.conf - intune-agent.timer enable symlink →
system_files/etc/systemd/user/default.target.wants/ - device-broker override →
system_files/etc/systemd/system/microsoft-identity-device-broker.service.d/override.conf - broker display override →
system_files/etc/systemd/user/microsoft-identity-broker.service.d/override.conf - Edge wrapper →
system_files/usr/local/bin/microsoft-edge - Package installation (Edge, libsecret-tools, sudo, libpulse0) →
build_files/build pam-auth-update→build_files/buildsystemctl enable microsoft-identity-device-broker→build_files/buildpwquality.conf→ generated inline inbuild_files/build
~210 lines removed from internal/provision/provision.go, ~15 from cmd/init.go.
Symptom: Password enforcement non-compliant in Intune. chpasswd during init showed cracklib_dict.pwd: No such file or directory.
Root cause: Two problems:
cracklib-runtimepackage was never installed — onlylibcrack2(the library) was present- Containerfile used
--mount=type=cache,dst=/var/cachewhich swallowed any files written to/var/cache/during build (including cracklib dictionaries) Fix: Addedcracklib-runtimeto package list in build script. Narrowed cache mount from/var/cacheto/var/cache/aptso cracklib dictionary files persist in the final image.
Symptom: /etc/pam.d/common-password had pam_pwquality.so retry=3 instead of the full parameter line with dcredit=-1 ocredit=-1 ucredit=-1 lcredit=-1 minlen=12.
Root cause: COPY system_files / runs before RUN /ctx/build. The libpam-pwquality package install overwrites the custom /usr/share/pam-configs/pwquality with its default.
Fix: Added a tee command in the build script to re-write the custom PAM profile after package installation, before pam-auth-update.
Symptom: Decryption errors (Failed to decrypt with key:LinuxBrokerRegularUserSecretKey, WorkplaceJoinFailure: [-100]) after destroy + re-init.
Root cause: cmd/destroy.go cleaned ~/Intune/.local/share/microsoft-identity-broker but the broker actually stores state in ~/Intune/.local/state/microsoft-identity-broker (via StateDirectory=).
Fix: Changed destroy to clean .local/state/microsoft-identity-broker instead of .local/share/microsoft-identity-broker.
- Enrollment succeeds
- All three fixes verified on fresh destroy → init → start → shell → enroll cycle
Symptom: intune-portal failed with Authorization required, but no authorization protocol specified / Unable to initialize GTK+ after host reboot.
Root cause: BuildShellArgs ran machinectl shell user@machine which starts a non-login shell. /etc/profile.d/intuneme.sh never executed, so XAUTHORITY was never set — X11 auth failure.
Fix: Pass /bin/bash --login to machinectl shell so profile.d scripts run.
Symptom: After host reboot, brokers failed with storage_keyring_write_failure and intune-portal showed "Get the app" (credential invalid, default account not found).
Root cause: The .init_done marker was stored in ~/.local/share/keyrings/ on the persistent bind-mounted home dir. After reboot, profile.d saw the stale marker and skipped keyring initialization entirely — gnome-keyring never unlocked.
Fix: Moved marker to /tmp/.intuneme-keyring-init-done (tmpfs, resets every boot). Also added restart of the system-level microsoft-identity-device-broker service after keyring init — it was only restarting the user-level broker before.