Add CampusNAS menu bar agent

Native macOS agent that keeps the office VPN and the NAS shares working
without the shell wrapper it replaces.

- VPN: full port of the `ovpn` script — host-route pinning, gateway
  discovery via networksetup, tunnel liveness by address plus pushed
  route, startup grace, restart of a wedged daemon. Root steps are
  batched into one system authorization prompt.
- SMB: mounts via NetFS, discovers shares from the server, detects
  mounts event-driven through NSWorkspace, matches the host by resolved
  alias so externally created mounts are recognised.
- Credentials never pass through the process: the VPN auth file is
  written by `security` straight to disk, and the password is stored via
  SecItemAdd rather than a command line visible in ps.
- UI: status window, settings editor, ⌘+/⌘− zoom that re-lays out rather
  than scaling a bitmap.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-31 13:19:31 +06:00
co-authored by Claude Opus 5
commit cd53b6a1a1
20 changed files with 2897 additions and 0 deletions
Executable
+103
View File
@@ -0,0 +1,103 @@
#!/bin/bash
# Builds a transferable bundle: the universal .app, the sources to rebuild it,
# and install instructions.
set -euo pipefail
cd "$(dirname "$0")"
NAME="CampusNAS"
STAMP="$(date +%Y%m%d)"
OUT="dist/${NAME}-${STAMP}"
echo "==> universal build"
UNIVERSAL=1 ./build.sh >/dev/null
ARCHS="$(lipo -archs "build/${NAME}.app/Contents/MacOS/${NAME}")"
echo " architectures: ${ARCHS}"
rm -rf "$OUT"
mkdir -p "${OUT}/source"
echo "==> collecting"
# ditto keeps the bundle structure and the ad-hoc signature intact; cp -R does
# not always preserve extended attributes on a signed bundle.
ditto "build/${NAME}.app" "${OUT}/${NAME}.app"
cp -R Sources Package.swift build.sh package.sh README.md "${OUT}/source/"
cat > "${OUT}/INSTALL.md" <<'DOC'
# CampusNAS — установка на другой Mac
## Что внутри
- `CampusNAS.app` — готовое приложение, universal (Apple Silicon + Intel)
- `source/` — исходники, чтобы пересобрать при необходимости
- `README.md` в `source/` — как всё устроено и почему
## Установка
```bash
cp -R CampusNAS.app /Applications/
xattr -dr com.apple.quarantine /Applications/CampusNAS.app
open /Applications/CampusNAS.app
```
Вторая строка обязательна. Приложение подписано ad-hoc, без Developer ID,
поэтому файл, приехавший с другой машины, помечается карантином и Gatekeeper
его не пустит. Команда снимает эту метку.
Иконка появится в меню-баре. Окно — повторный запуск приложения либо
«Показать окно» в меню.
## Настройка
Меню-бар → **Настройки…** (⌘,):
- путь к профилю `.ovpn` и к бинарю `openvpn`
- IP VPN-сервера, сеть туннеля, офисная сеть
- запись Keychain, логин и пароль VPN
- логин SMB
Адрес NAS и список шар правятся в `~/.config/campusnas-monitor/config.json`,
он создаётся при первом запуске.
## Что нужно на целевой машине
- **openvpn** — `brew install openvpn`. Приложение само проверит наличие и
покажет кнопку установки, если не найдёт.
- **профиль `.ovpn`** — скопировать отдельно, в комплект он не входит: там
сертификаты.
- **пароль VPN** — ввести в окне настроек, он ляжет в Keychain.
- **пароль SMB** — спросит сам macOS при первом монтировании. Поставьте галку
«Запомнить пароль в связке ключей», иначе автомонтирование работать не будет.
Если NAS доступен только по имени, а DNS его не отдаёт — добавьте запись:
```bash
printf '192.168.77.43\tCAMPUSNAS\n' | sudo tee -a /etc/hosts
```
## Пересборка
```bash
cd source
UNIVERSAL=1 ./build.sh
```
Нужен Xcode с командными утилитами. Собирается за несколько секунд.
## Чего приложение не делает
Не хранит паролей. Пароль VPN пишется прямо в Keychain и читается оттуда
системной утилитой при подключении. Пароль SMB приложение не видит вообще —
им занимается macOS.
DOC
echo "==> archiving"
cd dist
ditto -c -k --sequesterRsrc --keepParent "${NAME}-${STAMP}" "${NAME}-${STAMP}.zip"
cd ..
SIZE="$(du -h "dist/${NAME}-${STAMP}.zip" | cut -f1)"
echo
echo "Готово: $(pwd)/dist/${NAME}-${STAMP}.zip (${SIZE})"