# PortGuard Agent for macOS — 0.1.16 Testing

Terminal installer for macOS 14 or later, Apple Silicon (arm64) and Intel (x86_64).
Python is bundled: after downloading the package, installation needs no dependency
download. The Agent connects outbound to the Console and waits for administrator
approval; no enrollment JSON or inbound Agent port is required.

## Install from Terminal

Replace `http://IP-CONSOLE` with your Console address. Run these commands as your
normal user; only the final installation step needs sudo. A graphical browser is
not required.

```sh
arch="$(uname -m)"
case "$arch" in arm64|x86_64) ;; *) echo 'Unsupported architecture'; exit 1 ;; esac
base='https://portguard.kamindo.co/downloads/agent/macos/0.1.16'
file="PortGuardAgent-0.1.16-macos-${arch}.tar.gz"
curl --fail --location --output "$file" "$base/$file"
curl --fail --location --output "$file.sha256" "$base/$file.sha256"
shasum -a 256 -c "$file.sha256"
# Continue only if checksum verification reports OK.
tar -xzf "$file"
cd "PortGuardAgent-0.1.16-macos-${arch}"
sudo ./macos/install.sh install --console-url http://IP-CONSOLE --allow-insecure-http
```

HTTP does not encrypt traffic. Use a trusted LAN or configure valid HTTPS on the
Console. For HTTPS, omit `--allow-insecure-http`. The Testing package is not signed
with Developer ID or notarized. Do not disable Gatekeeper/SIP to force installation;
report any security prompt to the testing team. No tray GUI is included.

The default fresh installation enables storage control with initial **Allow**.
It adds the Agent and mount-guard LaunchDaemons. Approve the installation UUID under
**Endpoints → Enroll agents** in Console; a pending connection does not use a seat.
After approval, heartbeat, USB status, and internal disk capacity are reported.
Use `--plan` to preview without installing or enrolling. Add `--telemetry-only` to
omit the mount helper, or `--enable-usb-control` to explicitly enable it on upgrade.
Without either flag, an upgrade preserves the existing installation mode.

## Storage control and scope

Use **Block USB Storage**, **Allow USB Storage**, and **Check Status** in Console.
USB storage control is implemented as a Disk Arbitration mount policy. Block denies
new mounts for classified external USB storage. It does not disable hardware or
prevent privileged raw disk access. Keyboard/mouse devices and internal disks are
not targeted. Guard startup/crash gaps are not a kernel-level security guarantee.

**Block now attempts a normal, safe unmount of already-mounted USB volumes.** New
USB mounts are denied while the Block policy is active. The Agent waits for the
native callback and verifies that no targeted volume is still mounted before
reporting Protected. It never uses force-unmount, kills applications, or erases data.
If macOS reports the drive busy, Block reports failure with instructions to close
files/applications and retry. A timeout or incomplete result is not verified
protection. Allow permits subsequent mounts; reconnect or use Disk Utility to mount.
Uncertain backing, including some external APFS/virtual layouts, produces an
unverified result instead of claiming protection. Physical USB/UASP/APFS acceptance
is still pending; use a spare Mac and a disposable test flash drive.

## Team acceptance checklist

1. Verify package checksum and install on a designated test Mac. Confirm both
   launchd services and approve its UUID in Console. Record macOS and CPU model.
2. Confirm Online heartbeat and disk capacity; shared APFS pools count once.
3. Mount the test USB and close its files. Request Block: confirm the normal unmount
   completes and the drive is no longer accessible. Check the native result in Console.
4. Repeat with an intentionally open test file: macOS may refuse unmount. Confirm a
   failed/unverified result rather than Protected; close the file and retry. Reconnect
   under Block and confirm mounting is denied.
5. Request Allow, reconnect and confirm read/write access. Check internal disks and
   USB keyboard/mouse remain usable. Repeat with each USB/UASP filesystem available.
6. Test network loss/recovery, reboot, helper interruption, identity-preserving
   upgrade and uninstall. Record any interval with a mounted drive before the guard
   is ready; do not treat simulated tests as hardware acceptance.
7. Return to Allow before ending the test. Uninstall when finished and deliberately
   retire the endpoint in Console if its seat should be released.

## Status, upgrade and uninstall

```sh
sudo launchctl print system/co.kamindo.portguard.agent
sudo launchctl print system/co.kamindo.portguard.mount-guard
sudo '/Library/Application Support/PortGuard Agent/versions/0.1.16/python/bin/python3' -I -B \
  '/Library/Application Support/PortGuard Agent/versions/0.1.16/macos/entry.py' agent \
  --config '/Library/Application Support/PortGuard Agent/data/config.json' --status
# From the extracted package directory:
sudo ./macos/install.sh uninstall
```

Upgrade by running the newer installer with the same Console address. Installation
UUID, enrolled identity, credentials, sequence and policy are retained. Never copy
`/Library/Application Support/PortGuard Agent/data/` to another computer. Changed
Console addresses are checked against the enrolled Console identity.

Uninstall removes the two services and stops mount enforcement, retaining program
files, private identity and logs for recovery/reinstall. It does not release a seat:
retire the endpoint in Console separately. Native install/upgrade/uninstall/reboot
acceptance is pending. Candidate services run as root; privilege separation and log
rotation require further review before production rollout.

## Build and validation

Build on macOS with Command Line Tools Swift and Python 3.12+: `python3 macos/build.py`.
Inputs are pinned in python-runtime.json; dependency licenses are retained in the
runtime. Automated tests and the packaged Apple Silicon loopback enrollment test
are separate from native service/hardware acceptance. Intel binaries are cross-built;
execution requires testing on an Intel Mac. See the release validation report.

## Reuse this Mac with a fresh Console database

A new Console database has a new identity. Normal upgrades and uninstall retain the
Agent binding, so reinstalling the package alone does not make it a new device.
An address-mismatch error after a successful checksum is an identity/protocol check,
not evidence of a corrupt download. A new IP for the same retained Console database
uses the normal address-change path.

For a deliberately new Console, follow the current guide to stop services, retain
the old data as a private backup, and enroll a new identity:
https://portguard.kamindo.co/manual/agent/#macos-new-console

Approve the new installation UUID from the new Console. The old Console's endpoint
and seat are not automatically retired. If already connected to the intended Console,
no reset or reinstall is needed. Existing backup credentials remain private.
