Systemd и контейнеры: знакомство с systemd-nspawn

Invalid configurations

  • upstart

    Refuses to start a job if it contains invalid syntax.

  • systemd

    Ignores invalid directives and starts the service. Note that you must test your configuration carefully since a typo will not be detected! Use systemd-analyze verify file to get warnings on typos and badly formatted options.

Override Files

  • upstart
    • Upstart allows part or all of a Job Configuration («/etc/init/$job.conf«) file to be overridden. To create an override file:

    • Create file «/etc/init/$job.override» containing one or more stanzas which take priority over their counterparts from the original «$job.conf» file.

  • systemd
    • Systemd allows a similar facility via «drop-ins»: a drop-in allows a directive to be modified without changing the original unit file. To create a drop-in:
    • Create a subdirectory below either «/etc/systemd/system/» or «/lib/systemd/system/» called «${unit}.d/«.

    • Create files called <something>.conf in the «${unit}.d/» directory containing the directives that you wish to override.

Behavioral differences (aka Gotchas)

  • systemd will refuse to run a binary (via ExecStart, ExecStartPre, etc) unless the full path to the binary is specified:

    BAD (systemd will not search for sleep in $PATH):
    ExecStart=sleep 20
    GOOD (absolute path specified):
    ExecStart=/bin/sleep 20

Commands

Note that these are commands for interactive human usage. Package maintainer scripts, ifupdown hooks and similar must always use the init system agnostic abstractions like invoke-rc.d.

Operation

Upstart Command

Systemd equivalent

Notes

Start service

start $job

systemctl start $unit

Stop service

stop $job

systemctl stop $unit

Restart service

restart $job

systemctl restart $unit

See status of services

initctl list

systemctl status

Check configuration is valid

init-checkconf /tmp/foo.conf

systemd-analyze verify <unit_file>

Show job environment

initctl list-env

systemctl show-environment

Set job environment variable

initctl set-env foo=bar

systemctl set-environment foo=bar

Remove job environment variable

initctl unset-env foo

systemctl unset-environment foo

View job log

cat /var/log/upstart/$job.log

sudo journalctl -u $unit

tail -f job log

tail -f /var/log/upstart/$job.log

sudo journalctl -u $unit -f

Show relationship between services

initctl2dot

systemctl list-dependencies --all

Shows pstree-style output.

Спор вокруг систем инициализации в Linux

System V init (или просто «SysV») — это система инициализации, которая существует со времен операционной системы System V, которая была выпущена в 1983 году. SysV оставалась системой инициализации в течение почти трех десятилетий (за некоторыми исключениями). Многие IT-специалисты и программисты в силу своей привычки не хотели отказываться от SysV, да и к тому же она была очень простой для понимания.

Проблема с SysV заключалась в том, что в её основе лежали концепции, существовавшие много лет назад. SysV не хватало возможности нативно обрабатывать многие вещи, такие как: обнаружение съемных носителей, корректное обнаружение аппаратного обеспечения и загрузка встроенного ПО, загрузка различных модулей и пр. Кроме того, при использовании данной системы, инициализация процессов происходит последовательно, т.е. одна задача запускается только после успешного завершения предыдущей. Часто это приводило к задержке и длительному времени загрузки. Задачи, подобные монтированию файловых систем, выполняются один раз во время загрузки, после чего «забываются». Но такого подхода недостаточно для автоматизированного управления сервисами, требующими к себе постоянного внимания.

В попытке привнести больше возможностей в процесс инициализации Linux-систем, компания Canonical в 2006 году вместе с релизом Ubuntu 6.10 (Edgy Eft) выпускает систему инициализации Upstart, которая с самого начала разрабатывалась с учетом обратной совместимости. Она может запускать демоны без каких-либо изменений в их скриптах запуска.

Другой системой инициализации, восходящей своими корнями к операционной системе 4.4BSD, является rc.init. Она применяется в таких дистрибутивах, как: FreeBSD, NetBSD и Slackware. В 2007 году разработчики Gentoo выпустили улучшенный вариант данной системы инициализации, сделав её модульной и назвав OpenRC. Большинство других дистрибутивов Linux исторически продолжало использовать SysV.

В 2010 году инженеры компании Red Hat Леннарт Пёттеринг и Кей Сиверс приступили к разработке новой системы инициализации — systemd, которая разрабатывалась с учетом недостатков, имеющихся в SysV. В состав systemd, помимо прочего, также входят и различные пакеты, утилиты и библиотеки, позволяющие производить параллельный запуск процессов, сокращая тем самым время загрузки системы и количество необходимых вычислений. Весной того же года Fedora 15 стала первым дистрибутивом, в котором по умолчанию использовалась система инициализации systemd. После чего, на протяжении следующих трех лет, большинство дистрибутивов массово перешли на systemd.

Но, если все остальные дистрибутивы отдают предпочтение systemd и считают её лучшей системой инициализации, как для предприятий, так и для любителей, почему так много споров вокруг нее?

systemd, по сравнению с SysV и Upstart, содержит большое количество различных улучшений, а также предлагает и другие компоненты, имеющие более тесную интеграцию с системой, с помощью которых разработчики могут уменьшить объем выполняемой работы. Что в этом плохого? Ну, поскольку разработчики создают программное обеспечение, которое зависит от systemd и/или от любой из её многочисленных служб (journald, udevd, consoled, logind или networkd), то такое ПО становится менее совместимым с системами, в которых systemd не применяется. По мере того, как количество служб, предоставляемых проектом systemd, продолжает расти, systemd сама становится все более зависимой от них.

В результате systemd становится самостоятельной платформой, и её повсеместное распространение непреднамеренно препятствует разработке программного обеспечения, которое является переносимым и совместимым с операционными системами, не поддерживающими systemd. Но действительно ли всё так печально? Конечно, нет. Прежде всего, это проект с открытым исходным кодом, и у людей есть выбор: использовать его или нет. Пользователи и разработчики могут извлечь выгоду из наличия нескольких конкурирующих систем инициализации, и нет вины systemd в том, что основные дистрибутивы переключились на нее из-за её плюсов.

/etc/default files which enable/disable jobs

enable=1|0 type settings in /etc/default files should generally be avoided. The canonical way to enable/disable a service in an init system agnostic way is update-rc.d <service> enable|disable, which will translate to init system specific actions such as adding/removing symlinks (SysV and systemd) or creating/removing job override files (upstart). For systemd in particular, admins also often call systemctl enable|disable <service> directly. Thus these settings are redundant in /etc/default.

There is no clean way to evaluate these in a systemd unit. You can check them in ExecStartPre=, but that would mean that the unit will be in «failed» state if the service gets disabled that way, and so, is not desirable.

For these reasons (confusing/duplication/cannot be modelled in systemd), these settings should be removed. This was done in whoopsie 0.2.42, you can check its diff for a transition which respects the old default setting and removes it on upgrade.

Example Systemd service

/lib/systemd/system/foo.service:


Description=Job that runs the foo daemon
Documentation=man:foo(1)


Type=forking
Environment=statedir=/var/cache/foo
ExecStartPre=/usr/bin/mkdir -p ${statedir}
ExecStart=/usr/bin/foo-daemon --arg1 "hello world" --statedir ${statedir}


WantedBy=multi-user.target

Outstanding Work

If you’d like to help out with the migration, take a look at …

  • Outstanding packages to convert:
    • http://people.canonical.com/~jhunt/systemd/packages-to-convert/

      Note that the priority are the packages in «main».

  • blueprint: https://blueprints.launchpad.net/ubuntu/+spec/core-1411-systemd-migration

… and then come and chat to us on #ubuntu-devel.

Unit

Unit – это описание сервиса (в широком смысле этого слова). Unit-файл описывает все настройки сервиса, как его запускать, когда (очередность, зависимости) и что делать, если запуск не удался. Unit-ы, которые пишет пользователь руками – должны находится в и иметь окончание в названии. Юниты, которые устанавливают пакеты – находятся в ином месте. Если в нескольких папках лежит юнит с одним и тем же именем – применяется тот, что лежит в . Пример юнита:

Я специально взял юнит посложнее, чтобы пример был наглядным

На что обратить внимание:

  • Description – человеко-читаемое описание. Показывается по команде
  • After – начать загрузку после того, как начнется загрузка сервиса (или цели)
  • Wants – опциональная зависимость. Подробнее ниже, в разделе про зависимости
  • Environment – создать переменную окружения при запуске этого сервиса
  • WorkingDir – демон запускается из этой папки. Аналогично перед запуском
  • Type – тип сервиса. Подробнее ниже
  • User – имя пользователя, от которого будет запущен сервис
  • PermissionsStartOnly – используется, если перед стартом нужна какая-то специальная подготовка – создание папок, изменение прав и так далее. При эти действия будут выполнятся от root. Без – от имени User
  • ExecStart – что, собственно, запускать. Обязательно полный путь
  • RestartOn – при каких условиях перезапускать
  • WantedBy – в какой target должен быть установлен сервис. Подробнее – в разделе про target-ы

Виды Unit-ов

Systemd может обслуживать процессы с разным поведением. Тип описывает, как systemd будет с ним взаимодействовать. Есть следующие варианты:

  • – самый стандартный тип. Процесс остается в foreground, stdout перехватывается systemd. Это тип по умолчанию.
  • – прямая противоположность. Процесс должен форкнуться и отсоединится от foreground. Для этого типа юнитов должен быть указан pid через директиву .
  • – процесс, который успешно выполняется (не делая fork) и завершается. Пример – монтирование файловых систем. Рекомендуется добавить в юнит, чтобы результаты работы процесса остался в статусе юнита.
  • – аналог simple, но в этом случае сам процесс сообщит systemd о том, что он закончил загрузку и готов к работе.

Взаимодействие с unit-ами

После каждого изменения файла юнита (создание/изменение/удаление) – нужно перечитывать изменения, так как состояния юнитов systemd кеширует:

Запус, состояние, остановка:

Systemd имеет свою собственную реализацию логирования (хотя по умолчанию в syslog копию сообщения он тоже отправляет). Чтение сообщений от сервисов – командой journalctl. Команда очень мощная, умеет много. Ниже примеры

Управление зависимостями, очередность загрузки юнитов

Для управления зависимостями в unit есть ключевые слова , и :

  • – сервис начнет загрузку после того, как начнет загружаться сервис, указанный в .
  • – сервис начнет загрузку после того, как закончит загружаться сервис, указанный в . Статус загрузки этого сервиса не важен – даже если он упал и загрузится не смог – юнит попытается стартовать. То есть зависимость эта опциональная, и нужна она только для того, чтобы наш сервис начал загружаться не раньше, чем другой – закончит.
  • – сервис начнет загрузку после того, как сервис, указанный в закончит загрузку успешно. Если сервис-зависимость загрузится не смог – наш сервис так же упадет с ошибкой (точнее – он даже не будет стартовать).

Написание файлов юнитов

  • /usr/lib/systemd/system/: юниты, предоставляемые пакетами при их установке
  • /etc/systemd/system/: юниты, устанавливаемые системным администратором

Обработка зависимостей

В случае использования systemd зависимости могут быть указаны правильным построением файлов юнитов. Наиболее частый случай — юниту A требуется, чтобы юнит B был запущен перед тем, как запустится сам юнит A. В этом случае добавьте строки Requires=B и After=B в секцию файла службы A. Если подобная зависимость не является обязательной, взамен указанных выше добавьте, соответственно, строки Wants=B и After=B

Обратите внимание, что Wants= и Requires= не подразумевают After=, что означает, что если After= не определено, два юнита будут запущены параллельно друг другу.

Обычно зависимости указываются в файлах служб, а не в целевых юнитах. Например, network.target потребуется любой службе, которая связана с настройкой ваших сетевых интерфейсов, поэтому в любом случае определите загрузку вашего пользовательского юнита после запуска network.target.

Типы служб

Существует несколько различных типов запуска служб, которые надо иметь в виду при написании пользовательского файла службы. Тип определяется параметром Type= в секции :

  • Type=simple (по умолчанию): systemd предполагает, что служба будет запущена незамедлительно. Процесс при этом не должен разветвляться. Не используйте этот тип, если другие службы зависят от очередности при запуске данной службы. Исключение — активация сокета
  • Type=forking: systemd предполагает, что служба запускается однократно и процесс разветвляется с завершением родительского процесса. Используйте данный тип для запуска классических демонов за исключением тех случаев, когда, как вам известно, в таком поведении процесса нет необходимости. Вам следует также определить PIDFile=, чтобы systemd могла отслеживать основной процесс
  • Type=oneshot: полезен для скриптов, которые выполняют одно задание и завершаются. Вам может понадобиться также установить параметр RemainAfterExit=yes, чтобы systemd по-прежнему считала процесс активным, даже после его завершения
  • Type=notify: идентичен параметру Type=simple, но с той оговоркой, что демон пошлет systemd сигнал о своей готовности. Эталонная реализация данного уведомления представлена в libsystemd-daemon.so
  • Type=dbus: сервис считается находящимся в состоянии готовности, когда определенное BusName появляется в системной шине DBus
  • Type=idle: systemd задержит выполнение двоичного файла службы до тех пор, пока все задания отправляются. Кроме того, поведение очень похоже на Type=simple.

Редактирование предоставленных пакетами файлов юнитов

Есть два способа редактирования файлов юнита, предоставленного пакетом: заменить весь блок файла на новый или создать фрагмент кода, который применяется в верхней части существующего блока файла. В обоих методах, чтобы применить изменения, нужно перезагрузить юнит. Это может быть сделано либо путем редактирования блока с помощью Шаблон:Ic (которая автоматически загружает модуль) либо при перезагрузке всех юнитов:

# systemctl daemon-reload

Замена файлов юнита

Чтобы заменить файл юнита /usr/lib/systemd/system/юнит, создайте файл /etc/systemd/system/юнит и перезапустите юнит для обновления символьных ссылок:

# systemctl reenable юнит

В качестве альтернативы, можно выполнить:

# systemctl edit --full юнит

Эта команда откроет /etc/systemd/system/юнит в вашем текстовом редакторе (копирует установленную версию, если она еще не существует) и автоматически загружает её, когда вы закончите редактирование.

Drop-in snippets

Чтобы создать drop-in snippets для файла юнита /usr/lib/systemd/system/юнит, создайте каталог /etc/systemd/system/юнит.d/ и поместите файлы .conf там, чтобы отменять или добавлять новые опции. systemd будет анализировать эти файлы .conf и применять их поверх оригинального юнита.

Самый простой способ чтобы выполнить это, сделайте:

# systemctl edit юнит

Эта команда откроет /etc/systemd/system/юнит.d/override.conf (создаст его если это потребуется) в вашем текстовом редакторе и автоматически перезапустит юнит, когда вы закончите редактирование.

Обнаружение системы 5 init

Как это ни парадоксально, как описано в https://unix.stackexchange.com/a/196197/5132 , один из способов в Debian Linux по крайней мере для обнаружения отсутствия System 5 — это отсутствие . Однако:

  • Это побочный эффектСпособ Debian для упаковки таких вещей, как .
  • Одна часть общей проблемы заключается в том, что используется, если в качестве параметра использовалась System 5 в любой момент прошлого , поскольку удаление пакета не удаляет его конфигурационный файл. (Это была значительная проблема для работы Debian 8, так как в Debian 7 есть несколько пакетов, которые устанавливают себя, добавляя записи в .)
  • Это инвертированный тест.

Полезные команды

Следующая команда загружает конфигурационный файл демона, а не файл его сервиса systemd. Используйте ее, когда вы делаете изменения конфигурации и хотите активировать ее, например, так, как в следующем примере для Apache:

# systemctl reload httpd.service

Перезагрузите файл сервиса — полностью остановите сервис, а затем перезапустите сервис. Если он не заработает, то начните со следующего:

# systemctl restart httpd.service

Вы можете перезапустить все сервисы с помощью одной команды. Она перезагружает все юнит файлы и пересоздает все дерево зависимостей systemd:

# systemctl daemon-reload

Вы можете в роли обычного непривилегированного пользователя выполнять операции reboot (перезагрузка), suspend (приостановка) и poweroff (отключение):

$ systemctl reboot
$ systemctl suspend
$ systemctl poweroff

Как всегда, есть много другого, что можно узнать о systemd. Хорошими введениями в systemd,
причем со ссылками на более подробные ресурсы являются статьи
Проходим еще раз, еще один Linux
Init: Введение в systemd и Изучаем и
используем Systemd.

Targets

Target – целевое состояние системы. Именно Target определяет, какие сервисы будут загружены и в каком порядке. Аналог из мира sysV init – runlevel. Основные виды таргетов:

  • – отключение системы
  • – режим восстановления, однопользовательский (init 1)
  • – сетевой режим без графической оболочки, (init 3)
  • – сетевой режим с графической оболочкой (init 5)
  • – перезагрузка
  • – аварийная командная строка, минимальный функционал

Цели могут наследоваться друг от друга. Например, graphical включает в себя загрузку всего, что есть multiuser + после этого – подгрузку графической оболочки.

Взаимодействие с целями:

10.3.6. Переход в аварийный режим

Аварийный режим предоставляет минимально возможную среду и позволяет восстановить систему даже тогда, когда она не может загрузиться в режим восстановления. Система монтирует root file system только для чтения и запускает только несколько основных сервисов, не пытается подключить другие файловые системы и не активирует сеть. В RHEL 7 аварийный режим требует ввода пароля root’а.
Чтобы сменить текущий таргет на аварийный режим, введите от root’а:

systemctl emergency

Эта команда похожа на , но она дополнительно отправляет сообщение всем пользователям, которые в настоящее время вошли в систему. Чтобы система не отправляла сообщение, запустите команду с опцией :

systemctl --no-wall emergency

Пример 10.15. Переход в аварийный режим
Чтобы перейти в аварийный режим с отправкой сообщения всем пользователям, запустите от root’а:

~# systemctl --no-wall emergency

Permanent switch back to upstart

Install the upstart-sysv package, which will remove ubuntu-standard and systemd-sysv (but should not remove anything else — if it does, yell!), and run sudo update-initramfs -u. After that, grub’s «Advanced options» menu will have a corresponding «Ubuntu, with Linux … (systemd)» entry where you can do an one-time boot with systemd.

If you want to switch back to systemd, install the systemd-sysv and ubuntu-standard packages.

High-level startup concept

Upstart’s model for starting processes (jobs) is «greedy event-based», i. e. all available jobs whose startup events happen are started as early as possible. During boot, upstart synthesizes some initial events like startup or rcS as the «tree root», the early services start on those, and later services start when the former are running. A new job merely needs to install its configuration file into /etc/init/ to become active.

systemd’s model for starting processes (units) is «lazy dependency-based», i. e. a unit will only start if and when some other starting unit depends on it. During boot, systemd starts a «root unit» (default.target, can be overridden in grub), which then transitively expands and starts its dependencies. A new unit needs to add itself as a dependency of a unit of the boot sequence (commonly multi-user.target) in order to become active.

Job vs. unit keywords

This maps the keywords that can occur in an upstart job to the corresponding ones in a systemd unit. Keywords which don’t have a direct equivalent are marked with «-«.

Upstart stanza

systemd unit file directive

systemd unit file section

Notes

apparmor load

AppArmorProfile

Available in systemd version 210 and later

apparmor switch

author

chdir

WorkingDirectory

Service

chroot

RootDirectory

console output

StandardOutput=tty, StandardError=tty

console owner

StandardOutput=tty, StandardError=tty

No real equivalent?

console none

StandardOutput=null, StandardError=null

description

Description

Unit

env

Environment, EnvironmentFile

Service

exec

ExecStart

Service

expect fork

Type=forking

Unit

expect daemon

Type=forking

Unit

expect stop

Type=notify

Unit

Similar, not equivalent. Requires daemon to link to libsystemd-daemon and call sd_notify().

instance

Use «%I» and «%i» in ExecStart, etc to specify an instance

See /lib/systemd/system/[email protected] for an example

kill signal

KillSignal

kill timeout

TimeoutStopSec

limit as

LimitAS

limit core

LimitCORE

limit cpu

LimitCPU

limit data

LimitDATA

limit fsize

LimitFSIZE

limit memlock

LimitMEMLOCK

limit msgqueue

LimitMSGQUEUE

limit nice

LimitNICE

limit nofile

LimitNOFILE

limit nproc

LimitNPROC

limit rss

LimitRSS

limit rtprio

LimitRTPRIO

limit sigpending

LimitSIGPENDING

limit stack

LimitSTACK

manual

No directive(?) — use systemctl disable foo.service

nice

Nice

Unit

normal exit

SuccessExitStatus

oom score

OOMScoreAdjust

post-start exec/script

ExecStartPost

Service

post-stop exec/script

ExecStopPost

Service

pre-start exec/script

ExecStartPre

Service

pre-stop

reload signal

ExecReload=/bin/kill -SIGFOO $MAINPID

Service

respawn

Restart=on-failure

Service

respawn limit

RestartSec

script/end script

See shell scripts below

setgid

Group

Service

setuid

User

Service

start on

Wants, Requires, Before, After

Unit

stop on

Conflicts, BindsTo (but not commonly used)

Unit

task

Type=oneshot

Unit

umask

UMask

Unit

usage

Documentation

Unit

no direct equivalent

version

Shell scripts

systemd does not provide special support for shell scripts (by design). For short shell commands you can use something like

   ExecStart=/bin/sh -ec 'echo hello'

Longer scripts are usually program logic and should not be directly in a conffile and duplicated between upstart and systemd; factor it out in a proper script in e. g. /usr/share/myapp/ and call it from both the upstart job and the systemd unit.

Automatic starting

As described above, services which want to start during boot (i. e. are not activated through sockets, D-BUS, or similar) need to become a dependency of an existing boot target. Those need an section with a WantedBy= that specifies the unit which that new service wants to become a dependency of (see man systemd.unit). Very commonly this is multi-user.target, which is roughly equivalent to start on runlevel  in upstart; see man systemd.special for other common targets.

Xorg и systemd

Есть несколько способов запустить xorg в системных модулях. Ниже представлены два варианта: либо запустить новый пользовательский сеанс с процессом xorg, либо запустить xorg из пользовательской службы systemd.

Автоматический логин в Xorg без экранного менеджера

The factual accuracy of this article or section is disputed.

Эта опция запускает системный блок, который запускает сеанс пользователя с сервером xorg, а затем запускает обычный для запуска оконного менеджера и т.д.

Вам необходим установленный AUR. Настройте свой xinitrc, как указано в разделе .

Сеанс будет использовать собственный dbus демон, но различные утилиты systemd будут автоматически подключаться к экземпляру . Наконец, enable службу для автоматического входа при загрузке.Сеанс пользователя полностью находится в области видимости systemd, и все в сеансе пользователя должно работать нормально.

Xorg как пользовательская служба systemd

Кроме того, Xorg можно запустить из службы пользователя systemd. Это хорошо, поскольку другие связанные с X юниты могут зависеть от xorg и т. д. Но с другой стороны, у этого есть некоторые недостатки, объясненные ниже.

обеспечивает интеграцию с systemd двумя способами:

К сожалению, чтобы иметь возможность запускать xorg в непривилегированном режиме, он должен запускаться внутри сеанса. Итак, в данный момент недостаток запуска xorg в качестве пользовательской службы заключается в том, что он должен запускаться с привилегиями суперпользователя (как до 1.16) и не может использовать преимущества непривилегированного режима, представленного в 1.16.

Вот как запустить xorg из пользовательского сервиса:

1. Заставить xorg работать с правами суперпользователя и для любого пользователя путем редактирования

/etc/X11/Xwrapper.config
allowed_users=anybody
needs_root_rights=yes

2. Добавить следующие юниты в

~/.config/systemd/user/[email protected]
Description=Socket for xorg at display %i


ListenStream=/tmp/.X11-unix/X%i
~/.config/systemd/user/[email protected]
Description=Xorg server at display %i

Requires=xorg@%i.socket
After=xorg@%i.socket


Type=simple
SuccessExitStatus=0 1

ExecStart=/usr/bin/Xorg :%i -nolisten tcp -noreset -verbose 2 "vt${XDG_VTNR}"

где } — виртуальный терминал, на котором будет запущен xorg, либо прописанный в сервисном модуле, либо установленный в среде systemd с помощью

$ systemctl --user set-environment XDG_VTNR=1

Примечание: xorg должен быть запущен на том же виртуальном терминале, где пользователь вошел в систему. В противном случае logind будет считать сеанс неактивным.

3. Обязательно настройте переменную среды , как описано .

4. Затем, чтобы активировать сокет для xorg на дисплее 0 и tty 2, следует выполнить:

$ systemctl --user set-environment XDG_VTNR=2     # Так что [email protected] знает, какой vt использовать
$ systemctl --user start [email protected]            # начинает слушать на сокете для дисплея 0

Теперь запуск любого X приложения автоматически запустит xorg на виртуальном терминале 2.

Переменная среды может быть установлена в среде systemd из , а затем можно запустить любое приложение X, включая диспетчер окон, как системный модуль, зависящий от .

Рейтинг
( Пока оценок нет )
Понравилась статья? Поделиться с друзьями:
Мой редактор ОС
Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: