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

Рассмотрим ещё один интересный пример – эмуляция «cron». Crontab – системный сервис позволяющий запускать другие сервисы, службы или приложения по заданному расписанию (о нём я расскажу как-то в следующий раз). Сам по себе crontab напрямую не связан с launchd, однако при помощи ключа «StartCalendarInterval», заданного в конфиге, можно «заставить» launchd обрабатывать службы как crontab. Создадим файл с таким содержимым:

<?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.getnewposts</string>
	<key>ProgramArguments</key>
		<array>
			<string>open</string>
			<string>http://macdaily.me</string>
		</array>
	<key>StartCalendarInterval</key>
		<dict>
			<key>Hour</key>
			<integer>11</integer>
			<key>Minute</key>
			<integer>0</integer>
		</dict>
</dict>
</plist>

Данный сценарий будет открывать блог MacDaily.me, при помощи браузера указанного в системе по-умолчанию, каждый день в 11:00. Здесь стоит обратить внимание на вот эту часть кода:

<key>ProgramArguments</key>
	<array>
		<string>open</string>
		<string>http://macdaily.me</string>
	</array>

Как видно, в массиве заданы два параметра, которые аналогичны «терминальной» команде:

open http://macdaily.me

Каждый новый параметр указан в новом сегменте <string>параметр</string>. Для примера, чтоб системный голос Vicki произнёс текущее время нужно выполнить следующую команду:

say -v Vicki echo $(date "+%H:%M")

В преобразованном виде эта команда будет иметь следующий вид:

<key>ProgramArguments</key>
	<array>
		<string>say</string>
		<string>-v</string>
		<string>Vicki</string>
		<string>echo $(date "+%H:%M")</string>
	</array>

Можно задать и более жёсткие временные условия. Например, поставим напоминание поздравить блог MacDaily.me с днём рождения:

<?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.happybirthday</string>
	<key>ProgramArguments</key>
		<array>
		<string>open</string>
		<string>http://macdaily.me/about/?yoursubject=HappyBirthday#mail-to-admin</string>
		</array>
	<key>StartCalendarInterval</key>
		<dict>
			<key>Month</key>
			<integer>4</integer>
			<key>Day</key>
			<integer>7</integer>
			<key>Hour</key>
			<integer>12</integer>
			<key>Minute</key>
			<integer>0</integer>
		</dict>
</dict>
</plist>

Данный сервис будет запускать браузер и открывать в нём страницу блога с контактной формой (для поздравления) 7 апреля каждого года в 12:00 по полудню. Разберём основные доступные подключи «StartCalendarInterval», и их параметры:

<key>Month</key>
<integer>4</integer>

Month – указывает месяц для запуска сценария. Может принимать значение от 1 до 12.

<key>Weekday</key>
<integer>0</integer>

Weekday – указывает день недели для запуска сценария. Может принимать значение 0, 1, 2, 3, 4, 5, 6, 7 (0 и 7 зарезервированы за воскресеньем). В связке с ключом Day служба будет запускаться только тогда, когда указанный в ключе день месяца будет совпадать с указанным в ключе Weekday днём недели.

<key>Day</key>
<integer>7</integer>

Day – указывает день месяца для запуска сценария. Может принимать значения от 1 до 31.

<key>Hour</key>
<integer>12</integer>

Hour – указывает время суток для запуска сценария. Может принимать значения от 0 до 23 (0 – полночь).

<key>Minute</key>
<integer>0</integer>

Minute – указывает время в минутах для запуска сценария. Без указания ключей MonthWeekdayDay и Hour сценарий будет запускаться в указанные минуты каждого часа.

Но и это ещё не всё. Можно так же запускать какое-либо приложение с определённым интервалом времени, например:

<?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.openeveryhour</string>
	<key>ProgramArguments</key>
		<array>
			<string>open</string>
			<string>http://macdaily.me</string>
		</array>
	<key>StartInterval</key>
	<integer>3600</integer>
</dict>
</plist>

Результатом работы данной службы будет открытие в браузере главной страницы данного блога через каждый час. За периодический запуск чего-либо отвечает ключ «StartInterval», который в секундах задаёт периодичность выполнения:

<key>StartInterval</key>
<integer>3600</integer>

Пойдём далее…

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