Методы автозагрузки приложений в Mac OS X. LaunchAgents и LaunchDaemons.

Рассмотрим пример запуска клиента Twitter вместе с любым пользователем системы. Да не просто запуск, а «назойливый» запуск, при котором Twitter будет самоперезапускаться всегда, даже после его завершения 🙂 И так, для начала нам нужно создать файл «сервиса» или «демона». Так как Twitter – это приложение с графической оболочкой не требующее привилегий администратора системы, то для него нужно создать именно сервис, а не демон, и покласть его в папку /Library/LaunchAgents (можно, конечно, его засунуть и в /System/Library/LaunchAgents, но так как там и так уйма системных служб, лучше этого не делать). Создадим файл следующего содержания:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
	"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
	<dict>
		<key>Label</key>
		<string>me.macdaily.twitter</string>
		<key>ProgramArguments</key>
			<array>
				<string>/Applications/Twitter.app/Contents/MacOS/Twitter</string>
			</array>
		<key>RunAtLoad</key>
		<true/>
		<key>KeepAlive</key>
		<true/>
	</dict>
</plist>

И сохраним его (пока) на Рабочем столе под именем me.macdaily.twitter.plist. Вообще имя может быть любым, главное – расширение .plist. Но обычно используют следующий формат:

com.companyname.appname.plist

где:

  • companyname – это название компании, сайт или имя разработчика (и тому подобное);
  • appname – соответственно, название приложения или службы.

Теперь разберём содержимое файла, а точнее его основные ключи и параметры:

<key>Label</key>
<string>me.macdaily.twitter</string>

Ключ «Label» задаёт название сервиса, которое будет отображаться в Мониторинге системы или логах утилиты Консоль. В качестве параметра указывают (обычно) имя конфигурационного .plist файла, но без самого «.plist». Можно, конечно же, указать и другое название.

<key>ProgramArguments</key>
	<array>
		<string>/Applications/Twitter.app/Contents/MacOS/Twitter</string>
	</array>

Ключ «ProgramArguments» содержит массив, в котором указаны путь к исполняемому бинарному файлу (или bash/shell скрипту), и параметры его запуска. У клиента Twitter, впрочем как и у любого другого приложения для Mac OS X, исполняемый бинарный файл находится в каталоге Название программы.app/Contents/MacOS/. В этой папке может быть один или несколько бинарных исполняемых файлов (без расширений). Если в каталоге таких файлов оказалось несколько, то узнать какой именно из них «главный» можно подсмотрев параметр CFBundleExecutable в файле Info.plist (Название программы.app/Contents/Info.plist):

	<key>CFBundleExecutable</key>
	<string>Twitter</string>

Из куска XML-кода видно, что у приложения Twitter.app исполняемым бинарным файлом является Twitter (он там, правда, один – трудно ошибиться). Кстати, если нужно запустить обычное приложение, например, браузер, плеер или какой-либо социальный клиент, то с прописыванием пути к исполняемому бинарному файлу можно не заморачиваться. Достаточно создать массив ссылаясь исключительно на само приложение, только при этом нужно указать параметр open, иначе launchd не поймёт, что мы от него хотим. На примере Twitter второй вариант запуска будет таким:

<key>ProgramArguments</key>
	<array>
		<string>open</string>
		<string>/Applications/Twitter.app</string>
	</array>

Для обычных приложений такой вариант более прост и понятен. Впрочем, мы пока разминаемся на Twitter, для которого не нужно никаких параметров загрузки, однако далее будут более интересные примеры.

<key>RunAtLoad</key>
<true/>

Ключ «RunAtLoad» указывает системе, что правило нужно исполнить при загрузке ОС. То есть в нашем случае Twitter загрузится сразу же, как только любой пользователь войдёт в свою учётную запись. Параметр может принимать значения: true – запускать, false – не запускать. Если ключ «RunAtLoad» отсутствует – это будет приравниваться к значению false.

<key>KeepAlive</key>
<true/>

Ключ «KeepAlive» со значением true указывает системе, что указанное в ключе «ProgramArguments» приложение должно быть всегда запущенно и с указанными в массиве параметрами. Если вручную завершить приложение, то система его автоматически запустит. Так будет до тех пор, пока служба принудительно не будет выгружена из памяти (ну или не будет удалён конфигурационный .plist и система не будет перезагружена).

Ну что, файл создан. Осталось переместить его в папку-назначение, поправить права доступа и атрибуты, и перезагрузиться. Запускаем Терминал и говорим системе, что мы работаем от root’а:

sudo -s

Перемещаем файл me.macdaily.twitter.plist в папку-назначение:

mv ~/Desktop/me.macdaily.twitter.plist /Library/LaunchAgents/

Правим права доступа:

chown root:wheel /Library/LaunchAgents/me.macdaily.twitter.plist

Правим атрибуты:

chmod 755 /Library/LaunchAgents/me.macdaily.twitter.plist

Перезагружаемся…

reboot

После очередной загрузки системы Twitter должен автоматом запуститься. А теперь попробуйте завершить его через Cmd ⌘ + Q. Ну как? Получилось? Интересно? – Ну тогда пойдём далее 😉

Share this article
0
Share
Prev Post

[AppStore] Rays. Лучи света на фотографии.

Next Post

[Киоск] MANSORY LIFESTYLE. Журнал для автомобилистов.

Comments 41
  1. Большое спасибо за отличную статью!
    С удовольствием слежу за вашими постами.

    1. Привет, Эд! Спасибо за отзыв. Раз уж iOS-разработчик так считает, значит не всё так плохо, как мне казалось 🙂

      1. Ну это вы зря, бетенька 🙂
        Честно, очень полезные статейки и хорошо все написано и объяснено.
        Не смотря на то, что я занимаюсь разработкой под iOS, но разница все-таки есть между настольной системой, хоть и не большая. Поэтому с удовольствием заполняю пробелы в знаниях, что бы позже еще писать софт и под Mac OS.
        Желаю творческих успехов! 😉

          1. Клятвенно обещаю, как что-то сделаю под взрослую систему (MacOS), сразу кину на MacDaily!
            Кстати, когда начну разработку ПО под мак, может вместе программку сварганим?

  2. Мне как новичку не понятно о чем речь, но как написано понравилось :). Буду следить за продолжением.

  3. Здравствуйте Александр!

    У меня следующая проблема:
    Мне нужно что бы следующий shell script выполнялся при старте системы:

    echo "Initializing kernel random number generator..."
            # Инициализировать генератор случайных чисел ядра значениями
            # последнего выключения (или запуска системы).  Загрузить и
            # затем сохранить 512 байт, которые составляют пул энтропии.
            if [ -f /var/random-seed ]; then
                    cat /var/random-seed >/dev/urandom
            fi
            dd if=/dev/urandom of=/var/random-seed count=1
    

    Я написал следующий XML:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    	<dict>
    		<key>Label</key>
    			<string>random-seed</string>
    		<key>ProgramArguments</key>
    			<array>
    				<string>echo "Initializing kernel random number generator..."</string>
    				<string>if</string>
    				<string>[ -f /var/random-seed ];</string>
    				<string>then</string>
    				<string>cat</string>
    				<string>/var/random-seed</string>
    				<string>></string>
    				<string>/dev/urandom</string>
    				<string>fi</string>
    				<string>dd</string>
    				<string>if=/dev/urandom</string>
    				<string>of=/var/random-seed</string>
    				<string>count=1</string>
    			</array>
    		<key>RunAtLoad</key>
    		<true/>
    	</dict>
    </plist>
    

    В логах вижу следующее:

    Jun 15 23:49:38 MacBook com.apple.launchd[1] (random-seed[57]): Job failed to exec(3). Setting up event to tell us when to try again: 2: No such file or directory
    Jun 15 23:49:38 MacBook com.apple.launchd[1] (random-seed[57]): Job failed to exec(3) for weird reason: 2
    Jun 15 23:49:38 MacBook com.apple.launchd[1] (random-seed): Job should be able to exec(3) now.
    

    Что я делаю неправильно? Заранее благодарен за помощь!

  4. <?xml version=”1.0″ encoding=”UTF-8″?>
    <!DOCTYPE plist PUBLIC “-//Apple//DTD PLIST 1.0//EN” “http://www.apple.com/DTDs/PropertyList-1.0.dtd”>
    <plist version=”1.0″>
    <dict>
    <key>Label</key>
    <string>random-seed</string>
    <key>ProgramArguments</key>
    <array>
    <string>echo “Initializing kernel random number generator…”</string>
    <string>if</string>
    <string>[ -f /var/random-seed ];</string>
    <string>then</string>
    <string>cat</string>
    <string>/var/random-seed</string>
    <string>></string>
    <string>/dev/urandom</string>
    <string>fi</string>
    <string>dd</string>
    <string>if=/dev/urandom</string>
    <string>of=/var/random-seed</string>
    <string>count=1</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    </dict>
    </plist>

    1. Ну да, при конвертации shell-скриптов в XML могут возникнуть некоторые непонятки. Можно попробовать вставлять или целостный скрипт, типа:

      <?xml version=»1.0″ encoding=»UTF-8″?>
      <!DOCTYPE plist PUBLIC «-//Apple//DTD PLIST 1.0//EN» «http://www.apple.com/DTDs/PropertyList-1.0.dtd»>
      <plist version=»1.0″>
      <dict>
      <key>Label</key>
      <string>random-seed</string>
      <key>ProgramArguments</key>
      <string>echo "Initializing kernel random number generator..."; if [ -f /var/random-seed ]; then cat /var/random-seed > /dev/urandom; fi; dd of=/var/random-seed count=1;</string>
      <key>RunAtLoad</key>
      <true/>
      </dict>
      </plist>
      

      Или вот так ещё можно попробовать:

      <?xml version=»1.0″ encoding=»UTF-8″?>
      <!DOCTYPE plist PUBLIC «-//Apple//DTD PLIST 1.0//EN» «http://www.apple.com/DTDs/PropertyList-1.0.dtd»>
      <plist version=»1.0″>
      <dict>
      <key>Label</key>
      <string>random-seed</string>
      <key>ProgramArguments</key>
      <array>
      <string>echo "Initializing kernel random number generator...";</string>
      <string>if [ -f /var/random-seed ]; then cat /var/random-seed > /dev/urandom; fi;</string>
      <string>dd of=/var/random-seed of=/var/random-seed count=1</string>
      </array>
      <key>RunAtLoad</key>
      <true/>
      </dict>
      </plist>
      

      Но самое проще (чтоб не манаться с «угадываниями») – создать bash-скрипт, например random-seed:

      #!/bin/bash
      
      echo "Initializing kernel random number generator..."
      
      if [ -f /var/random-seed ]; then
      cat /var/random-seed > /dev/urandom;
      fi
      
      dd if=/dev/urandom of=/var/random-seed count=1
      

      И в XML (.plist) указать к созданному скрипту полный путь:

      <?xml version=»1.0″ encoding=»UTF-8″?>
      <!DOCTYPE plist PUBLIC «-//Apple//DTD PLIST 1.0//EN» «http://www.apple.com/DTDs/PropertyList-1.0.dtd»>
      <plist version=»1.0″>
      <dict>
      <key>Label</key>
      <string>random-seed</string>
      <key>ProgramArguments</key>
      <array>
      <string>/bin/customs_cripts/random-seed</string>
      </array>
      <key>RunAtLoad</key>
      <true/>
      </dict>
      </plist>
      

      Естественно, скрипту random-seed нужно будет присвоит соответствующие права доступа и атрибуты.

      1. Громадное Спасибо, попробовал последний вариант – вроде всё получилось ( в логах поиск строки random-seed ничего не находится – это правильно?)

        И второй вопрос – Вы не знаете как сделать что бы скрипт выполнялся при ВЫХОДЕ из системы?

        Ответе пожалуйста!

        1. Да не за что. Проверить, отрабатывает ли скрипт, можно следующим способом. У вас в скрипте есть строка:

          echo "Initializing kernel random number generator..."
          

          Но так как это echo будет запускаться через launchd то в Консоль ничего выводиться не будет (кроме ошибок). Модифицируйте скрипт таким образом:

          #!/bin/bash
          
          LOGS='RandomSeed'
          (
          echo "Initializing kernel random number generator..."
          
          if [ -f /var/random-seed ]; then
          cat /var/random-seed > /dev/urandom;
          fi
          
          dd if=/dev/urandom of=/var/random-seed count=1
          
          ) | logger -s -t $LOGS
          

          И теперь все echo будут выводиться в Консоль. После перезагрузки запустите консоль и поищите слово «RandomSeed» или строку «Initializing kernel random number generator» в логах. Если они присутствуют – всё ОК, скрипт отрабатывает. Этот финт я использую как своего рода инструмент для отладки 😉

          По поводу выполнения скриптов перед выходом/выключением/перезагрузкой… До версии 10.4 Tiger, и включая её, была очень простая возможность исполнять любое множество криптов, просто поместив их в соответствующую папку. С выходом следующих версий Mac OS X этот принцип изминили, и в текущих 10.7 Lion и 10.8 Mountain Lion для запуска logout-скриптов необходимо использовать хук. Работает это так:
          1. Создадим shell-скрипт с нужными командами, например вот такой:

          #!/bin/bash
          
          LOGS='LogOutHook'
          (
          echo "I'm OS X and I logout now..."
          say "I'm OS X and I logout now..."
          ) | logger -s -t $LOGS
          

          2. Сохраним его, к примеру, в системной папке /sbin под именем logout_hook и назначим ему правильные права доступа и атрибуты:

          sudo chown root:wheel /sbin/logout_hook; chmod 555 /sbin/logout_hook;
          

          3. Теперь добавим хук:

          sudo defaults write com.apple.loginwindow LogoutHook /sbin/logout_hook
          

          Ну и попробуем… Я сам-то не пробовал, но, по идее, всё должно заработать.

              1. Оказывается я рано радовался:
                —————————————–
                Jun 16 22:08:01 MacBook.local loginwindow[343]: *** NSTask: Task create for path ‘/bin/customs_cripts/random-seed_Stop’ failed: 22, “Invalid argument”. Terminating temporary process.
                —————————————–
                random-seed_Stop:
                =======================
                LOGS=’RandomSeedStop’
                (
                echo “RandomSeedStop and I logout now…”
                say -v Milena “Я Random Seed Stop, Я завершаю работу…”
                # Сохранить источник случайности для генератора случайных чисел
                # при завершении работы. Сохранить 512 байт, которые составляют
                # пул энтропии для генератора случайных чисел.
                echo “Saving random seed…”
                dd if=/dev/urandom of=/var/random-seed count=1
                ) | logger -s -t $LOGS
                =======================
                Кто виноват и Что делать???

                  1. Так я же выше его привёл:

                    LOGS=’RandomSeedStop’
                    (
                    echo "RandomSeedStop and I logout now…"
                    say -v Milena "Я Random Seed Stop, Я завершаю работу…"
                    # Сохранить источник случайности для генератора случайных чисел
                    # при завершении работы. Сохранить 512 байт, которые составляют
                    # пул энтропии для генератора случайных чисел.
                    echo "Saving random seed…"
                    dd if=/dev/urandom of=/var/random-seed count=1
                    ) | logger -s -t $LOGS
                    
                    1. Во-первых, где #!/bin/bash вначале?
                      Во-вторых, если голосовые компоненты Milena будут отсутствовать в система – будет ошибка.
                      Лучше сделать так:

                      #!/bin/bash
                      
                      LOGS=’RandomSeedStop’
                      (
                      echo "RandomSeedStop and I logout now…"
                      say "I'm Random Seed Stop, and I end work now..."
                      # Сохранить источник случайности для генератора случайных чисел
                      # при завершении работы. Сохранить 512 байт, которые составляют
                      # пул энтропии для генератора случайных чисел.
                      echo "Saving random seed..."
                      dd if=/dev/urandom of=/var/random-seed count=1
                      ) | logger -s -t $LOGS
                      
  5. Огромное Вам спасибо за помощь и долготерпение!

    Все получилось – виноват был отсутствующий #!/bin/bash

      1. Здравствуйте, это опять я.
        После обновления до 10.9.2 в консоли для RandomSeedStart (см.выше) вот такое сообщение:
        20.03.14 1:33:57,305 locationd[73]: NBB-Could not get UDID for stable refill timing, falling back on random

        Как это исправить?

        1. Приветствую! Я догадался, что это снова вы 😉
          Сложный вопрос… Проверил у себя – у меня всё работает (тоже 10.9.2). Я не совсем понимаю суть фразы «NBB-Could not get UDID for stable refill timing, falling back on random», но, по-моему, там идётся речь о том, что не получается получить UDID за определённый (выделенный) промежуток времени.
          Попробуйте для начала при помощи Дисковой утилиты сделать проверку/восстановления диска (не прав доступа, а именно диска): Дисковая утилита → выбираете весь накопитель целиком → Первая помощьИсправить диск. Потом сделайте то же самое для системного раздела (Macintosh HD). Для проверки/восстановления системного раздела, скорее всего, придётся загрузиться с Recovery HD. Отпишитесь, пожалуйста, о результатах 😉

            1. Так это всего-лишь файл для автозапуска скрипта. У вас, судя по всему, скрипт отрабатывает правильно, проблема не в автозапуске, и не в скрипте. Проблема в том, что утилита dd не может вытянуть UDID из /dev/urandom, который, скорее всего, используется для генерации.

  6. Подскажите пожалуйста, если сталкивались. Есть сервер слушающий порт принимающий запросы и отправляющий ответы. При простом запуске под пользователем работает всегда без проблем. Прекрасно запускается в виде демона из любого каталога при старте системы. Диагностика показывает что все работает как надо, порт открыт. По безопасности порт открыт, но принимать соединения отказывается. Клиент не видит его. В нормальный рабочий режим переходит только если его снять и его снова поднимет launchd. Дальше проблем больше не возникает. В plist’e прописывал sockets по мануалу, но тогда клиенты зависают намертво, а сервер соединения все равно не видит и не принимает. Перезапуск в данном поведении ничего не меняет.

    Буду признателен за любую наводку так как в сети ничего найти пока не получилось. Да и применение Sockets разделов практически не рассматривается. Но точно знаю что есть ограничения по портам. Но опять же, какие…

  7. Доброго дня, подскажите что это за процесс, как его убить, RunOuc , в поиске не могу прогуглить, когда он запущен “кушает 90%” процессора, mac book air ^ при этом греется сильно, и батарея быстро садиться, после перезагрузки на какое то время пропадает, а потом может опять появиться…
    Спасибо заранее…

    1. Приветствую! Нужно смотреть. Это процесс автозагрузки (по моему). То есть что-то после старта системы должно было запуститься, но оно зависло и «кушает» проц.

  8. Добрый день. У меня выскочила вот такая ошибка. Делал действия на странице 2.
    2014-09-25 22:31:19.259 Net Monitor[629:507] *** WARNING: -[NSImage compositeToPoint:operation:] is deprecated in MacOSX 10.8 and later. Please use -[NSImage drawAtPoint:fromRect:operation:fraction:] instead.

    Программа не запускатся, и не перезапускается. Скрипт, полная копия вашего со второй страницы, только изменен путь к другому файлу.

  9. Простите что без уточнения. У меня не запускается все в автоматическом режиме. И на данный момент у меня Maveriks 10.9.5

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Read next