- Вопрос или проблема
- Получение имени хоста машины с помощью systemd.
- Идея получения IP-адреса с помощью systemd.
- Еще одна идея для получения IP-адреса с помощью systemd.
- Получение IP-адреса машины с помощью init.d.
- Использование ExecStartPre и set-environment
- Использование EnvironmentFile
- Использование сторонних инструментов
- Заключение
Вопрос или проблема
Мне нужно получить IP машины и использовать его в моем сервисе:
[Unit]
Description=Сервер Redmine
After=syslog.target
After=network.target
[Service]
Type=simple
User=redmine
Group=redmine
ip="$(/sbin/ip -o -4 addr list eno16777736 | awk '{print $4}' | cut -d/ -f1)"
ExecStart=/usr/bin/ruby /home/redmine/redmine/bin/rails server webrick -e production -b $ip -p 3000
# Дать разумное количество времени для запуска/остановки сервера
TimeoutSec=300
[Install]
WantedBy=multi-user.target
Как я могу выполнить эту команду внутри файла сервиса systemd
и получить её результат:
ip=$(/sbin/ip -o -4 addr list eno16777736 | awk '{print $4}' | cut -d/ -f1)
чтобы использовать его результат в инфо xxx.xxx.xxx.xxx
, что-то вроде этого:
ExecStart=/usr/bin/ruby /home/redmine/redmine/bin/rails server webrick -e production -p 3000 -b $ip
Получение имени хоста машины с помощью systemd
.
Хотя я не очень знаком с systemd
, просмотр этого учебного сайта, а также официальных руководств systemd
показывает, что вам нужно значение “specifier”:
Многие настройки разрешают спецификаторы, которые могут использоваться для написания универсальных файлов юнитов, ссылающихся на параметры запуска или параметры юнита, которые заменяются при загрузке файлов юнитов.
И конкретный спецификатор, который, по моему мнению, подойдет здесь, это %H
, который используется для “Имени хоста” и описывается как:
Имя хоста работающей системы в момент загрузки конфигурации юнита.
Таким образом, в вашем примере скрипта systemd
измените ваш блок [Service]
на следующий:
[Service]
Type=simple
User=redmine
Group=redmine
ExecStart=/usr/bin/ruby /home/redmine/redmine/bin/rails server webrick -e production -b %H -p 3000
Обратите внимание, что строка с присвоением ip=()
исчезает, а команда ExecStart
теперь использует %H
вместо $ip
.
Идея получения IP-адреса с помощью systemd
.
Стоит отметить, что systemd
предоставляет только имя хоста через спецификатор %H
. Что странно, если вы спросите меня. Поэтому, хотя у меня нет глубокого опыта работы с systemd
, я думаю, что понимаю, что можно сделать, чтобы достичь цели этого поста.
Ключевым моментом будет настройка EnvironmentFile
для чтения systemd
. Узнайте, как использовать файл EnvironmentFile
на этом сайте.
Итак, скажем, вы создали простой Bash скрипт, такой как этот; назовем его write_ip_to_file.sh
и не стесняйтесь изменять логику получения IP-адреса ip=$()
, чтобы она соответствовала вашей настройке:
#!/bin/bash
ip=$(/sbin/ifconfig eth0 | awk '/inet addr/ {split ($2,A,":"); print A[2]}');
echo IP=$ip > ~/ip.txt;
Все, что он делает, это выводит IP-адрес eth0
в текстовый файл с именем ip.txt
в вашем домашнем каталоге. Формат будет таким:
IP=123.456.789.0
Поняли? Отлично. Теперь в вашем скрипте systemd
измените ваш блок [Service]
на следующий; убедитесь, что вы установили [ваше имя пользователя]
, чтобы оно соответствовало имени пользователя каталога, в котором сохранен ip.txt
:
[Service]
Type=simple
User=redmine
Group=redmine
EnvironmentFile=/home/[ваше имя пользователя]/ip.txt
ExecStart=/usr/bin/ruby /home/redmine/redmine/bin/rails server webrick -e production -b $IP -p 3000
И это загрузит конфигурацию в ip.txt
и присвоит $IP
значение 123.456.789.0
. Я думаю, что это то, что вы ищете.
Ключевой фактор здесь – заставить write_ip_to_file.sh
запускаться при старте системы или, возможно, даже самим скриптом systemd
.
Еще одна идея для получения IP-адреса с помощью systemd
.
Но при этом у меня есть идея получше (если она работает): Переместить всю команду ExecStart
в Bash-файл под названием redmine_start.sh
и убедиться, что система может его читать и выполнять. Содержимое redmine_start.sh
будет следующим; не стесняйтесь изменить логику получения IP-адреса ip=$()
, чтобы она соответствовала вашей настройке:
#!/bin/bash
ip=$(/sbin/ifconfig eth0 | awk '/inet addr/ {split ($2,A,":"); print A[2]}');
/usr/bin/ruby /home/redmine/redmine/bin/rails server webrick -e production -b $IP -p 3000
А затем измените ваш блок [Service]
на примерно такое; убедитесь, что вы установили [ваше имя пользователя]
, чтобы оно соответствовало имени пользователя каталога, в котором сохранен redmine_start.sh
:
[Service]
Type=simple
User=redmine
Group=redmine
ExecStart=/home/[ваше имя пользователя]/redmine_start.sh
Если следовать логике, если вся логика ExecStart
содержится в redmine_start.sh
, то вы можете использовать этот Bash трюк для получения IP-адреса, присвоения его переменной и запуска Redmine там. Скрипт systemd
будет лишь управлять тем, когда и как его запускать.
Получение IP-адреса машины с помощью init.d
.
И для справки для пользователей init.d
, я использую Ubuntu, и когда мне нужно получить текущий рабочий IP-адрес системы в Bash или стартовом скрипте init.d
, я запускаю что-то вроде этого:
ip=$(/sbin/ifconfig eth0 | awk '/inet addr/ {split ($2,A,":"); print A[2]}')
Конечно, вам нужно изменить /sbin/ifconfig
, чтобы оно соответствовало расположению ifconfig
в вашей системе, и затем изменить eth0
, чтобы оно соответствовало сетевому интерфейсу, IP-адрес которого вы хотите получить.
Но как только вы подстроили это под ваш настройки и нужды, это успешно получает IP-адрес интерфейса и присваивает его переменной ip
, к которой затем можно получить доступ как $ip
в вашем скрипте.
Возможно, эта конструкция сработает. Попробуйте:
[Service]
Type=simple
User=redmine
Group=redmine
PermissionsStartOnly=true
ExecStartPre=/bin/bash -c "/bin/systemctl set-environment ip=$(/sbin/ip -o -4 addr list eno16777736 | awk '{print $4}' | cut -d/ -f1)"
ExecStart=/usr/bin/ruby /home/redmine/redmine/bin/rails server webrick -e production -b ${ip} -p 3000
Используйте IP-адреса хоста и EnvironmentFile
Вы можете записать IP-адреса вашего хоста в файл /etc/network-environment
с помощью утилиты setup-network-environment. Затем вы можете запустить свое приложение следующим образом:
[Unit]
Requires=setup-network-environment.service
After=setup-network-environment.service
[Service]
EnvironmentFile=/etc/network-environment
ExecStart=/opt/bin/kubelet --hostname_override=${DEFAULT_IPV4}
Источник: https://coreos.com/os/docs/latest/using-environment-variables-in-systemd-units.html
Используйте hostname -i
, чтобы получить ваш (первый) IP-адрес и сохранить его в окружении SystemD. Вы можете получить доступ к этой переменной с помощью ${HOST_IP}
.
Документация:
man hostname
для параметров -i и -Iman systemctl
для set-environmentman systemd.service
для ExecStartPre
Пример
[Service]
ExecStartPre=/bin/sh -c "systemctl set-environment HOST_IP=$(hostname -i)"
networkctl
может выводить JSON, если параметр --json=
установлен в pretty
или short
.
Поэтому вы можете извлечь значения с помощью jq
, nushell
или других парсеров JSON. Я использую nu
.
Для IPv4:
networkctl status main --json=short
| from json | get Addresses | where Family == 2
| get Address | each { str join '.' } | to text
Выводит 192.168.1.7
networkctl status main --json=short
Получить JSONfrom json
преобразовать JSON в данные nushellget Addresses
выбрать список со всеми адресами интерфейсаwhere Family == 2
Ipv4 здесьget Address
получить список одиночных адресовeach { str join '.' }
адрес представлен в виде списка октетов, соедините их в адрес в нотации через точкиto text
вернуть текст, который может быть разобран другими оболочками и т.д. Если присутствуют несколько адресов, они будут разделены новыми строками
Для IPv6 это немного сложнее, благодаря networkctl за “специальное”:
networkctl status dns --json=short
| from json | get Addresses | where Family == 10 | get Address
| each {
each {
$in | into int | into binary -c | encode hex --lower
} | chunks 2 | each {
str join | str trim -l -c '0'
} | str join ':'
} | str replace -r -a '::+' '::'
| filter { not ( $in starts-with "fe80" ) } | to text
Выводит fdad:cafe:face:20::2
– на этом интерфейсе нет GUA
-
where Family == 10
– ключ для IPv6 адресов -
IPv6 адреса представлены в виде списка неподготовленных десятичных октетов, таких как
[253, 173, ... 2]
, что не является чем-то, что большинство вещей примет как допустимое без обработки -
Чтобы превратить их в шестнадцатеричную нотацию, вам нужно пройти по ним несколько раз:
$in | into int
превратить строку значения в целое числоinto binary -c
затем в неподготовленное двоичноеencode hex --lower
наконец, в шестнадцатеричное в нижнем регистре
-
Теперь у вас есть адрес в шестнадцатеричном формате, но он в октетах –
fd:00
– вместо гексетов –fd00
– всё ещё не допустимый IPv6 в понимании большинства вещейchunks 2
разбить список на парыeach { str join | str trim -l -c '0' }
объединить каждую пару октетов в гекстанавливают и очистить ведущие 0. Обрезка необязательна, но делает её более читабельнойstr join ':'
соединить гекстанавливают в допустимый адрес
-
str replace -r -a '::+' '::'
regex от {{\’::::\’ -> ‘::’}}. Также опционально для читаемости -
Наконец, вы можете фильтровать типы адресов, которые вам нужны:
filter { not ( $in starts-with "fe80" ) }
чтобы исключить LLA(и)filter { $in starts-with "fd" }
получить ULA(и)filter { not ( $in starts-with "fe80" ) and not ( $in starts-with "fd" ) }
получить GUA(и)
Это старый вопрос, но он был верхним результатом, когда я искал, так что, надеюсь, это сэкономит кому-то время.
Благодаря дискорд-каналу nu за пару советов по обработке IPv6.
Всё это достижимо с помощью jq & sh, но я нахожу автономные nushell скрипты более переносимыми:
#!/usr/bin/env nu
# networkd-ip.nu
def "main help" []: nothing -> nothing {
print "
Usage:
v4 DEVICE Print IPv4 addresses of DEVICE, newline-separated
v6 DEVICE [OPTIONS ...]
Print IPv6 addresses of DEVICE, newline-separated
Options:
-t --type lla|ula|gua
The type of IPv6 addresses to return
help Show this help
"
}
def "main v4" [
iface: string
]: nothing -> any {
networkctl status $iface --json=short
| from json | get Addresses | where Family == 2
| get Address | each { str join '.' } | to text
}
def "main v6" [
iface: string
--type (-t): string
]: nothing -> any {
def v6 [iface: string]: nothing -> list {
networkctl status dns --json=short
| from json | get Addresses | where Family == 10 | get Address
| each {
each {
$in | into int | into binary -c | encode hex --lower
} | chunks 2 | each {
str join | str trim -l -c '0'
} | str join ':'
} | str replace -r -a '::+' '::'
}
if not ( $type == null ) {
match $type {
"lla" => {
v6 $iface | filter { $in.startswith "fe80" } | to text
},
"ula" => {
v6 $_iface | filter { $in.startswith "fd" } | to text
},
"gua" => {
v6 $iface | filter {
not ( $in.startswith "fe80" ) and not ( $in.startswith "fd" )
} | to text
},
_ => {
print $"Error: ($type) is not a valid IPv6 address type"
print " -t --type lla|ula|gua"
}
}
} else {
v6 $iface | to text
}
}
def main []: nothing -> nothing {
main help
}