惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Hacker News: Ask HN
Hacker News: Ask HN
WordPress大学
WordPress大学
H
Help Net Security
小众软件
小众软件
N
Netflix TechBlog - Medium
C
Check Point Blog
量子位
Last Week in AI
Last Week in AI
GbyAI
GbyAI
Martin Fowler
Martin Fowler
M
MIT News - Artificial intelligence
博客园 - 聂微东
Engineering at Meta
Engineering at Meta
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
J
Java Code Geeks
D
DataBreaches.Net
Project Zero
Project Zero
P
Proofpoint News Feed
T
Threat Research - Cisco Blogs
Security Latest
Security Latest
Cisco Talos Blog
Cisco Talos Blog
Recorded Future
Recorded Future
I
Intezer
L
Lohrmann on Cybersecurity
Cyberwarzone
Cyberwarzone
博客园_首页
C
Cyber Attacks, Cyber Crime and Cyber Security
L
LangChain Blog
P
Palo Alto Networks Blog
V
V2EX
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
The Exploit Database - CXSecurity.com
The Hacker News
The Hacker News
Blog — PlanetScale
Blog — PlanetScale
G
GRAHAM CLULEY
T
The Blog of Author Tim Ferriss
C
Cisco Blogs
The Register - Security
The Register - Security
L
LINUX DO - 热门话题
P
Privacy & Cybersecurity Law Blog
Scott Helme
Scott Helme
F
Full Disclosure
博客园 - 司徒正美
Recent Announcements
Recent Announcements
IT之家
IT之家
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Attack and Defense Labs
Attack and Defense Labs
Cloudbric
Cloudbric
Help Net Security
Help Net Security
The Last Watchdog
The Last Watchdog

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Хардкорная агентская разработка под iOS, часть 1: отдельный Mac Mini для агентов
Валентин · 2026-06-26 · via Все публикации подряд на Хабре

Средний

14 мин

14

Mac Mini рядом с макбуком на столе

Mac Mini рядом с макбуком на столе

Вступление

Агентская разработка развивается семимильными шагами. Но не всё идёт так радужно, как хотелось бы. Например, ИИ-агенты по умолчанию заваливают разработчика вопросами: а можно я сделаю вот так? а если скриптик написать? а можно я на гитхаб схожу? а можно я соседний проект гляну?

Это приводит к consent fatigue — я не раз ловил себя на мысли, что я уже давно только и делаю, что жму “allow”, “allow everytime”, редко вникая в суть того, что спрашивает агент, но полностью доверить агенту делать всё, что ему заблагорассудится, мне как-то боязно. Всё-таки на основном макбуке много личных и рабочих данных, которые агент может случайно (или через prompt injection) удалить или слить в сеть. Стрёмно. Поэтому я какое-то время играл в игру “я типа читаю, что ты у меня спрашиваешь”.

Хотя правильное решение — запуск агента в режиме YOLO (в случае claude — с флагом --dangerously-skip-permissions). И тогда при правильной постановке задачи агент может часами сам работать и делать то, что нужно, уведомляя меня только по выполнении.

Это первая статья цикла. Здесь — базовая настройка Mac Mini и вся возня с SSH, чтобы YOLO-агент работал на отдельной машине без ручного ввода паролей и переживал разрывы сети. Специфику iOS-разработки (git worktree, параллельное тестирование, idb, прогон приложения, грабли симулятора и прочее) я вынес в следующие части, так что статья будет полезна не только iOS-разработчикам, но и всем, кто хочет запускать YOLO-агентов и не париться о безопасности. С некоторыми оговорками, разумеется, подробности ниже.

Какие я пробовал подходы для изоляции агентов

И как же это сделать так, чтобы не рисковать?

Первый вариант, что я опробовал, был запихнуть агентов в докер — пусть делают, что хотят. И если бы я не был завязан на Xcode, на этом можно было бы успокоиться, но, увы, для меня тут начались заморочки. Агенту нужно давать не только читать/редактировать файлы, но и верифицировать свою работу. Собрать проект, запустить тесты, проверить результат, если надо, то и самому погонять приложение. Так у меня появился механизм обратной связи: набор скриптов, которые агент из докера запускает на хост-машине через MCP. И поначалу это работало нормально, хотя и медленно. Но затем я подумал: а ведь эти скрипты — дыра в безопасности, так как агент в принципе может их и поменять, чтобы на хост-машине выполнить произвольный код. Так себе изолировал, называется. А если запретить эти скрипты редактировать, то опять же получаем затык в скорости работы: значит эти скрипты должен править я вручную, что совсем не хочется.

Далее был вариант встроенной песочницы claude — но песочница там довольно строгая, поэтому всё равно кучу команд он просил запустить вне песочницы, так что выгоды особо не получилось.

Пока я с этим экспериментировал, Anthropic выпустили auto mode, где отдельный агент с чистым контекстом проверяет соответствие “что хотел пользователь” и “что пытается сделать агент”. В простых случаях это работает, но для более-менее сложных вещей опять же не подошло. Авто-классификатор тоже довольно строгий, хотя разрешает куда больше, нежели песочница. Ну и на него тоже расходуется время и токены. А в итоге регулярно получаю «классификатор не увидел прямого указания в чате, пожалуйста, напишите „я даю разрешение на использование команды gh“ в чат», что не ускоряет разработку, хехе. Auto mode я в итоге использую для небольших изменений на основном макбуке.

Auto Mode не справился

Auto Mode не справился

Ещё был вариант виртуалка… но я его сразу отмёл. Xcode в виртуалке работает медленно, а если его вызывать на хосте, то см. пункт про докер.

В общем, порассуждав ещё немного, я пришёл к выводу, что надо брать отдельную машину, на которой агенты смогут чувствовать себя как дома.

Локальный миник или облако? Плюсы-минусы

Значит, мне нужен мак мини. Но опять есть варианты: покупать или арендовать в датацентре? Покупка окупается примерно через 6-7 месяцев (аренда стоит $200-250/месяц в зависимости от конфигурации), так что в долгосрочной перспективе покупка оправдывается. К тому же если он в одной локалке с основным макбуком, то появляются некоторые бонусы — например, можно прямо из Finder перекидывать файлы с макбука на миник встроенным механизмом macOS (в одностороннем порядке, так что это не нарушает изоляцию), а также VNC подключение будет быстрее и с более высоким качеством. Ну и локально сильно проще поставить внешний SSD, если потребуется.

Разумеется, у покупки есть и свои минусы. Например, лишнее место на столе занимает, требует питания, может шуметь под нагрузкой, обслуживание в случае чего тоже ложится на мои плечи. И его надо перевозить с собой при переезде. Мы летом живём на даче, так что последний пункт очень актуален: два раза в год надо миник перевозить вместе с остальными вещами. Мелочь на фоне перевоза нескольких гитар, кучи детских игрушек, книг, кота и собаки, но всё же. И на дачу пришлось ИБП поставить, тк бывают кратковременные отключения по разным причинам. ИБП, как понимаете, тоже шума добавляет, а в моём случае пришлось ещё тянуть дополнительную линию проводки, чтобы от ИБП запитать одновременно роутеры и макмини с монитором и при этом держать ИБП в кладовке.

Так что купил Mac Mini 2024 M4 24Gb и поставил себе на стол, прямо под стойку для макбука. Ну и обозвал я свой миник macmini-work.local.

Базовая настройка: без сюрпризов

Итак, купив миник и поставив его на стол, я быстро настроил ssh-подключение с основного компа и попросил claude написать скрипт, который через ssh устанавливает всё, что нужно. Так что базовая настройка была быстрой, скрипт сам установил нужные пакеты/тулзы, в том числе несколько агентов. Далее настраиваю алиас alias claude="claude --dangerously-skip-permissions" — и claude делает всё без вопросов. Одно но: мне нужно всем этим управлять через ssh.

У меня к минику всегда подключён через HDMI-кабель во второй вход монитора (в первый подключён макбук). Если бы не это, пришлось бы втыкать HDMI-заглушку (так называемый dummy plug, имитатор монитора) — иначе headless-режим работает криво. Если нет внешнего монитора и клавиатуры, то остаётся лишь вариант отключить file vault, тогда можно будет по VNC подключаться до логина в систему. Но это снижает безопасность как ни как.

SSH: где начинаются заморочки

Примечание: я привык работать с zsh, если кому-то привычнее bash, то придётся малость модифицировать мои скрипты и функции.

Чтобы не вводить пароль при каждом подключении, первым делом включил вход по ключу — скопировал свой публичный ключ на миник одной командой ssh-copy-id macmini-work.local. А для удобства завёл alias work="ssh macmini-work.local". Дальше я решил разграничить локальный терминал и терминал, ведущий на mac mini. Я уже много лет использую iTerm, привык к нему, но для доступа к минику решил установить Ghostty, чтобы на уровне приложений разграничить локальный и удалённый шелл. Дополнительно, чтобы не путаться, даже добавил картинку с миником на фон терминала. Кроме того, в промпт zsh я добавил красный префикс [W] (в статуслайн claude на минике тоже). Причём если я внутри zmx-сессии (zmx — мой выбор вместо tmux, подробнее ниже), префикс показывает ещё и её имя — [W my-feature], так что всегда видно, в какой сессии работаешь:

if [[ -n "$ZMX_SESSION" ]]; then
  PROMPT="%F{red}[W $ZMX_SESSION]%f $PROMPT"
else
  PROMPT="%F{red}[W]%f $PROMPT"
fi

А при подключении миник ещё и здоровается строкой вида [WORK my-feature] <дата> — сразу понятно, куда попал.

Ghostty с открытым claude и приветствием ssh-сессии

Ghostty с открытым claude и приветствием ssh-сессии

Далее начинаются некоторые ограничения ssh-сессии в макоси в сравнении с обычной сессией. По дефолту в неинтерактивной ssh-сессии:

  • Не добавляется дефолтный ssh ключ — лечится eval "$(ssh-agent -s)" && ssh-add

  • Не разблокируется keychain, а без него claude не сохраняет авторизацию — лечится security unlock-keychain

  • Тип терминала не определяется, поэтому всего 16 цветов и нет кликабельных ссылок — лечится export COLORTERM=truecolor в .zshrc плюс TERM=xterm-256color прямо в алиасе клода: alias claude="TERM=xterm-256color claude --dangerously-skip-permissions"

В первой версии моего сетапа было не очень удобно: alias init-ssh="eval "$(ssh-agent -s)" && ssh-add && security unlock-keychain вызывался прямо из .zshrc на минике, а пароль для unlock-keychain приходилось вводить руками при каждом подключении — самый долгий этап, который меня реально бесил.

Теперь же init-ssh это более продвинутая функция на минике, которую запускает не менее продвинутая функция work с основного макбука при подключении, при этом пароль для разблокрировки кичейна приезжает с макбука. Вот как раз передача пароля для кичейна через ssh уже нетривиальная задача. Если передавать как аргумент напрямую в init-ssh, то пароль будет виден через ps, что явно никуда не годится. Поэтому был разработан более секьюрный вариант.

Фокус в том, чтобы достать пароль из keychain на макбуке и пробросить его в ssh-сессию через SendEnv/AcceptEnv и сразу же переменную окружения почистить. Финальная версия функции в ~/.zshrc на макбуке, принимающая опционально имя сессии для подключения:

work () {
  local host=macmini-work.local
  local kc
  kc=$(security find-generic-password -s macmini-keychain -w) || return
  local target='exec zsh -l'
  (( $# )) && target='exec zmx a '${(q)1}
  local remote='init-ssh || { echo "init-ssh failed — check stored keychain password"; exit 1; }
    '$target
  KCPASS=$kc autossh -M 0 -t -o SendEnv=KCPASS "$host" "$remote"
}

Что тут происходит:

  • security find-generic-password -s macmini-keychain -w достаёт пароль от миника из keychain макбука (один раз положил туда через security add-generic-password). Никаких плейнтекст-файлов вроде ~/.secrets. Важно: пароль от миника не должен совпадать с паролем от основного компа. Иначе получится, что локальный пароль хранится в локальном же кичейне, который уже разблокирован.

  • KCPASS=$kc ... -o SendEnv=KCPASS пробрасывает пароль в сессию через переменную окружения (на минике в sshd_config нужно разрешить AcceptEnv KCPASS). На той стороне init-ssh делает security unlock-keychain -p с этим паролем.

  • autossh -M 0 переподключается после разрыва сети (закрыл макбук на ночь — утром сессия снова жива), -t форсит pseudo-tty для интерактивного zsh/zmx.

  • Без аргумента work просто кидает в свежий шелл; work my-feature сразу аттачится к zmx-сессии.

Пару слов про сам zmx — это менеджер персистентных терминальных сессий. В отличие от tmux, он не лезет в окна/панели/сплиты, а делает ровно одно: держит сессию (и всё, что в ней запущено, в том числе claude) живой, даже когда отвалился клиент. Если кому-то привычней tmux, можно и его использовать.

Главных команд две (за остальными посылаю в ман): zmx a <name> — приаттачиться к сессии, создав её, если ещё нет (именно это и вызывает work my-feature), и zmx l — посмотреть список живых сессий. Благодаря zmx разрыв ssh, закрытый макбук или переподключение autossh агента не трогают: процесс крутится в сессии на минике, а я просто заново к ней цепляюсь. Так что связка autossh + zmx — основа всего моего сетапа.

На стороне миника работает парная функция init-ssh. Лежит она в .zshenv, а не в .zshrc/.zprofile, и это принципиально: неинтерактивный zsh -c (а именно так autossh запускает удалённую команду) читает только .zshenv. Поэтому и сама функция, и нужный brew-овский PATH прописаны именно там — иначе ни init-ssh, ни zmx не найдутся.

init-ssh() {
  if ! security show-keychain-info >/dev/null 2>&1; then
    if [ -n "$KCPASS" ]; then
      security unlock-keychain -p "$KCPASS" ~/Library/Keychains/login.keychain-db || return
    else
      security unlock-keychain || return        # интерактивный фолбэк
    fi
  fi
  unset KCPASS
  security set-keychain-settings ~/Library/Keychains/login.keychain-db   # без авто-лока

  ssh-add -l >/dev/null 2>&1
  [ $? -eq 2 ] && { rm -f "$SSH_AUTH_SOCK"; ssh-agent -a "$SSH_AUTH_SOCK" >/dev/null; }   # код 2 = агент недоступен, поднимаем заново
  ssh-add -l >/dev/null 2>&1 || ssh-add --apple-use-keychain || return
}

Несколько важных мелочей:

  • KCPASS используется один раз и тут же unset — пароль не висит в окружении сессии.

  • security set-keychain-settings снимает авто-лок keychain. Иначе через какое-то время он бы залочился сам и claude снова потерял бы авторизацию посреди ночной работы.

  • ssh-agent поднимается на фиксированном сокете (SSH_AUTH_SOCK=~/.ssh/agent.sock, тоже в .zshenv). Окружение не переживает разрыв ssh, и без фиксированного сокета каждое переподключение autossh плодило бы новые агенты. С фиксированным — все переиспользуют один.

  • Если KCPASS не приехал (зашёл руками без work), остаётся интерактивный фолбэк на ввод пароля.

Итог: чтобы продолжить с того места, где остановился вчера, достаточно набрать work my-feature — и сразу видно, что клод наделал за ночь. Ноль ручного ввода пароля. А можно даже не закрывать терминал и/или табы/панели в нём: autossh + zmx делают своё дело. В каком виде закрыл макбук вечером, в таком виде и открыл с утра.

Настройка для iOS-разработки: idb + ScriptExec

Я уже говорил, что важно давать агенту самому гонять приложение. Для этого я использую idb (iOS Development Bridge от Facebook) и кастомный скилл к нему, который постоянно дорабатываю. Ключевой принцип — агент «смотрит» на UI через accessibility-дерево (idb ui describe-all отдаёт структурированный JSON), а не через скриншоты: это экономит контекст и токены, и такие данные можно грепать и дифать между тапами.

Подробный сетап, весь инструментарий и накопленные грабли (включение accessibility, переустановка после пересборки, точечный поиск скрытых элементов и т.д.) разберу в следующей статье цикла.

Здесь же отмечу только главную заморочку именно ssh-сценария: повернуть симулятор. Ни у idb, ни у simctl команды поворота нет — это делается только через AppleScript, кликом по пункту меню Device у Simulator (да-да, всё так плохо). В скилле idb это сделано через osascript (важно сперва activate, иначе клик по меню молча игнорируется):

# повернуть влево
osascript -e 'tell application "Simulator" to activate' -e 'delay 0.5' \
  -e 'tell application "System Events" to tell process "Simulator" to click menu item "Rotate Left" of menu 1 of menu bar item "Device" of menu bar 1'

Результат проверяю по размерам корневого фрейма из describe-all: портретная — width < height (например, 428×926), альбомная — наоборот.

Локально это работает как есть. А вот по ssh osascript упирается в права: macOS блокирует доступ к System Events с ошибкой вида «not allowed assistive access» / «not allowed to send Apple events», потому что у ssh-сессии нет разрешений Accessibility/Automation. Зато опытным путём было выяснено, что open через ssh работает, а GUI-приложение, запущенное через open, наследует те самые разрешения из системных настроек. На этом и построен ScriptExec — крошечная программа, которая принимает на вход AppleScript, выполняет его и пишет результат в файл (тк open не отдаёт stdout). Эта программа включает в себя отдельный скилл, который ставится на миник глобально и срабатывает автоматически, когда osascript падает с ошибкой прав: тот же самый AppleScript просто прогоняется через ScriptExec. Поэтому скилл idb остаётся с osascript и одинаково работает и локально, и по ssh.

Опционально: удалённое управление с любого девайса

Я не стал заморачиваться с установкой OpenClaw или Hermes, но всё же сделал себе пару лазеек для выполнения простых задач с телефона.

  • Создал ~/Projects/meta, запустил в нём claude в режиме /remote-control — теперь эта сессия доступна и с телефона. Пара промптов заранее — и вики-система готова, обучается сама, знает, где какие проекты лежат, и может легко спавнить агентов для каких-либо задач.

  • Запустил Cowork в Claude Desktop, но пока что ни разу не воспользовался. Всё же основная работа через терминал.

Пока у меня всегда макбук и макминик в одной локальной сети, но если я захочу, например, поработать не из дома, а из кафе/коворкинга, то всегда можно поднять tailscale и опять оказаться в одной локальной сети, хоть и виртуальной.

Заброска файлов на миник: SSH Drop

SSH Drop в доке

SSH Drop в доке

При работе через два компа сразу возникают, разумеется, некоторые проблемы:

  • drag-n-drop файла в терминал не работает — подставляется локальный путь с макбука. Логи в claude приходится закидывать через file sharing, затем уже на минике брать полный путь к файлу. Не удобно, мягко говоря.

  • картинку из буфера не вставить — буфер локальный, до удалённой сессии он не доезжает. Привычка кидать скриншоты в claude через cmd+v сломалась.

Да, можно это делать через scp вместо file sharing вручную, но я же хочу оптимизировать рабочий процесс, а не усложнить! Поэтому пришлось написать SSH Drop — ещё одну небольшую утилиту под macOS. Кидаешь файл/папку/скриншот на иконку в доке или в окно программы — и всё перетащенное улетает на миник, а в буфер обмена возвращается полный путь к нему уже на минике. Дальше просто вставляю этот путь в claude — и агент счастлив пофиксить баг со скриншота или распарсить логи. Вставка тоже работает: ⌘V или кнопка Paste закидывает содержимое буфера на миник. Складывается всё в подпапку с текущей датой, на всякий случай к имени файла также добавляется таймстамп.

Под капотом — обычные ssh и scp с BatchMode=yes и мультиплексированием соединения (ControlMaster/ControlPersist), так что повторные заброски идут без нового хендшейка, мгновенно. Из настроек — только хост (алиас из ~/.ssh/config или user@host) и удалённый путь; passwordless ssh у меня и так уже настроен (см. выше).

По-хорошему было бы ещё удобнее форкнуть Ghostty и встроить это прямо в терминал, чтобы перетаскивать сразу в окно сессии. Возможно, до этого ещё доберусь.

Окно SSH Drop

Окно SSH Drop

Пока не решённые проблемы

Ещё есть один нерешённый нюанс — вход после перезагрузки. Каждый раз, когда миник перезагружается (или при переезде на дачу), к нему нужно подключить клавиатуру и монитор и залогиниться руками: до первого входа в систему ssh не работает в современных macOS. Вроде бы это тоже должно решиться отключением file vault, но я не пробовал, тк это базовая защита файлов, не хочется отрубать её. Пока мне редко приходится делать ребут, а раз в месяц можно и переподключить клавиатуру.

Модель угроз: от чего защищаемся, а от чего нет

Тут стоит притормозить и проговорить, что именно даёт этот сетап. Потому что соблазн сказать «вынес агента на отдельную машину — и я в безопасности» большой, но это будет неправдой. Изоляция файлов и изоляция вообще — разные вещи.

Полезно разделить угрозы на два класса:

  • Разрушение. Агент (сам или через prompt injection) сносит данные на минике. Вот тут всё ок: личных файлов на нём нет, а rm -rf / лечится переустановкой и бэкапом. Это восстановимо.

  • Утечка и боковое перемещение. Агент сливает наружу токены, ключи, исходники или пушит вредоносный код в реальные репозитории. А ещё миник стоит в той же локалке, что и макбук, роутер, NAS — и скомпрометированный миник всё это видит. Вот это бэкапами уже не откатишь: утёкший токен утёк (и обнаружить это непросто), вредный коммит уехал в origin (тут авторевью нейронкой частично спасает), Mythos обнуляет сетевую безопасность и делает что угодно в локалке.

Так что бэкапы закрывают только первый класс. И когда я в начале писал «не париться о безопасности» — это про consent fatigue и про то, что личные данные на макбуке агенту недоступны, а вовсе не про то, что YOLO-агент с доступом в сеть стал безопасным. Не стал. От второго класса угроз отдельная машина сама по себе не спасает, а кое-где я её и подставил:

  • На минике разлочен keychain и выключен авто-лок. Удобно — но это значит, что весь login-keychain постоянно доступен любому процессу, не только claude-токен.

  • gh авторизован моим аккаунтом — агент может пушить и читать всё, что видит токен.

  • Tailscale, который я предлагаю поднять для работы из кафе, растягивает этот периметр ещё дальше.

Что с этим реально делать (часть уже сделал, часть — на очереди):

  • Сегментировать сеть. Вынести миник в отдельный VLAN / гостевую сеть, чтобы он не видел макбук и остальной LAN. Finder-передачу при этом можно оставить точечно. Это самое сильное реальное улучшение, и упирается оно не в миник, а в роутер.

  • Сузить права GitHub. Не полный аккаунт, а выделенный machine-user или fine-grained PAT с доступом только к нужным репам. Тогда худший случай пуша/утечки ограничен парой репозиториев, а не всем, что у меня есть. Также на github стоит включить защиту основных веток от удаления, если вы этого ещё не сделали. Ведь не сделали, верно?

  • Отдельный keychain под агента. Разлочивать не весь login.keychain, а отдельный, куда положить только то, что агенту реально нужно. Личные и рабочие секреты тогда вне досягаемости YOLO-процесса. Ну и вообще не стоит в кичейн макмини вносить что-то, что не нужно для работы.

  • Защитить ~/.zshenv. Там лежит init-ssh, в который приходит пароль в виде KCPASS, — лакомая цель: получив запись в этот файл, агент подменит init-ssh и стащит пароль от keychain при следующем же work. Рута у агента нет, так что достаточно отдать файл root’у:

    sudo chown root:wheel ~/.zshenv
    sudo chmod 644 ~/.zshenv
    
.zshenv под защитой

.zshenv под защитой

Читать его zsh по-прежнему может, а вот изменить его могу лишь я через sudo.

  • Подумать про egress — что миник вообще может вытащить наружу и нужен ли ему доступ, например, к рабочему VPN.

Резюме без иллюзий: я не сделал YOLO-агента безопасным — это в принципе недостижимо для машины с доступом в сеть. Я сократил и сегментировал blast radius и осознанно разменял часть безопасности на скорость. Главное — понимать, на какой именно размен идёшь, а не прятаться за словом «изолировал».

Заключение

Данный сетап позволил мне значительно ускорить разработку, избавиться от навязчивых подтверждений и почувствовать себя в относительной безопасности. Теперь для работы над новой задачей я просто открываю в Ghostty новую вкладку, ввожу work new-feature, запускаю claude — и всё, агент может работать часами, гонять UI тесты или само приложение, устанавливать дополнительные тулзы, если нужно, ходить в сеть, искать код в соседних проектах и прочее. А мой макбук при этом изолирован: личные и рабочие данные на нём агенту недоступны. А если внезапно агент на минике выполнит rm -rf /, то восстановление будет быстрым. Вы же делаете бэкапы, верно? ;) Про остальные классы угроз — см. раздел про модель угроз выше: бэкапы тут не панацея.

Стоила ли игра свеч? Ведь и макмини денег стоит, и настройка не за полчаса была сделана…

Считаю, что да. Я устранил множество узких мест в своём рабочем процессе вроде подтверждения на каждый чих и прерывания работы при закрытии макбука. А потом и оптимизировал ввод пароля (макбук по отпечатку пальца, миник автоматом по ssh). Могу параллельно гонять несколько агентов в разных проектах (да и в одном тоже), не заботясь о том, что если агент не успел за рабочий день выполнить задачу, придётся его прерывать и назавтра восстанавливать контекст (а контекст в голове стоит дороже токенов).

Параллельно с приключением, итогом которого стала эта статья, шла настройка окружения для эффективной агентской разработки под iOS — об этом в следующих частях цикла.

PS: Если вы просто хотите настроить так же у себя, а статью читать лень, скиньте ссылку своему агенту и попросите “сделай мне так же”. ;)