АйТи Фреш
Главная / Статьи / Серверы и хостинг
Серверы и хостинг

«Repository time shift detected» в Veeam 13.1: почему после долгого выключения репозитория пропадает защита от удаления

Автор: Семёнов Евгений Сергеевич, директор ООО «АйТи-Фреш» · · ~23 мин чтения
«Repository time shift detected» в Veeam 13.1: почему после долгого выключения репозитория пропадает защита от удаления
Иллюстрация к статье ««Repository time shift detected» в Veeam 13.1: почему после долгого выключения репозитория пропадает защита от удаления».

Задание отработало, точка восстановления на месте, а в логе жёлтая строчка про сдвиг времени. Выглядит как мелочь про часы — на деле это стоп-кран: сервис иммутабельности перестаёт и ставить, и снимать флаг защиты — как на новых файлах, так и на существующих. Ниже — механика срабатывания по KB4482 и Help Center, способ за пять минут понять, сколько точек уже лежит без защиты, и три разных сценария сброса: для Veeam Software Appliance / Hardened Repository на JeOS 13.x, для старого VHR ISO 12.x и для самосборного Linux-репозитория. Плюс разбор случая в книжном магазине «КнигоДом», где 61 час простоя стоил 19 незащищённых файлов бэкапа.

Что именно ломается, когда вы видите это предупреждение

Полная формулировка в логе задания, по KB4482: «A problem occurred during setting the immutable flag: repository time shift detected, immutability flag cannot be set. Please refer to KB4482 for more details». Это именно предупреждение (Warning) в сессии задания Backup или Backup Copy, а не ошибка: Veeam не блокирует сами задания. Данные записались, точка восстановления создана, в консоли всё почти зелёное. И именно поэтому предупреждение живёт неделями: администратор видит «бэкап есть» и идёт дальше. А свойство, ради которого вы вообще городили hardened repository — невозможность удалить файл до истечения срока, — на новых файлах отсутствует.

Под капотом всё устроено так. На Linux-репозитории работает Veeam Immutability Service (veeamimmureposvc, дочерний процесс veeamtransport), и при его старте создаётся файл /etc/veeam/immureposvc/timeLog. Раз в 10 минут сервис дописывает туда текущее время UTC (systemTime) и значение аппаратных часов RTC (hwTime). По системному времени считается moveTime — насколько разница между соседними отметками UTC разошлась с интервалом проверки. По аппаратным часам — accelerationTime: насколько разошлись приращения RTC и UTC. Как только moveTime или accelerationTime превышает 86 400 секунд (сутки), создаётся файл retainLock с информацией о сдвиге. Сам retainLock создаётся иммутабельным, то есть с атрибутом i на файловой системе: просто rm его не возьмёт.

Пока retainLock лежит на месте, сервис не меняет состояние иммутабельности ни для существующих файлов бэкапа, ни для новых. Help Center формулирует это прямо: Veeam Immutability Service не ставит и не снимает атрибут immutable. Последствия двусторонние. Новые .vbk и .vib приезжают на XFS обычными файлами без атрибута i. А старые точки, у которых срок уже истёк, не разблокируются — атрибут с них никто не снимет, и ретеншен не сможет их удалить. Получается худшее из двух миров: свежие бэкапы беззащитны, а устаревшие занимают место, пока репозиторий не упрётся в ёмкость.

Ключевое слово в механике — «накапливается». Это не «часы съехали ровно на сутки», а сумма аномальных расхождений. Порог в 24 часа можно набрать десятком мелких скачков или одним крупным. KB4482 относится к веткам 12.1–13.1, логика одна и та же, только в 13.x сброс для appliance переехал в Veeam Host Management.

Warning в задании — не косметика. Пока висит retainLock, новые .vbk и .vib ложатся на диск обычными файлами, а просроченные не удаляются. Первые удалит кто угодно с правами на каталог, включая шифровальщика, дошедшего до root на репозитории; вторые тихо съедят место. Ровно тот сценарий, от которого hardened repository и строят.
Цифры и версии: Что именно ломается, когда вы видите это предупреждение — схема
Цифры и версии: Что именно ломается, когда вы видите это предупреждение. Открыть схему в полном размере

Почему это стреляет именно после долгого выключения

Логика детектора не различает «злоумышленник крутит часы» и «сервер стоял выключенным». Пока машина обесточена, сервис не пишет timeLog. При следующем старте он сравнивает последнюю запись с текущим временем и получает дельту, равную всему простою. Help Center прямо предупреждает: если выключить репозиторий больше чем на 24 часа, операции ретеншена будут заблокированы. Выключили в пятницу вечером, подняли в понедельник утром — это 60+ часов, retainLock появится вскоре после загрузки, ещё до первого задания.

KB4482 перечисляет четыре штатных триггера. Первый — длительное выключение. Второй — остановка службы VeeamTransport, которая и отслеживает время: обновление VBR, обслуживание, ручной systemctl stop veeamtransport «на полчасика», растянувшийся на сутки. Третий — добавление в VBR hardened repository, который уже использовался раньше и на котором остался старый timeLog: переставили диски, переустановили ОС поверх, а журнал приехал вместе с каталогом. Четвёртый — намеренная смена системного времени администратором, в том числе через плейбук или скрипт первичной настройки.

Отдельная история — аппаратные часы. По умолчанию проверка RTC включена (checkHwTime="1"), и детектор сравнивает приращения RTC с приращениями системного времени. Help Center отдельно оговаривает: для точного детектирования RTC должен идти в UTC; если RTC нет или он отключён, accelerationTime игнорируется и проверяется только moveTime. На виртуалке «аппаратные» часы — это виртуальный RTC, который гипервизор синхронизирует по своим правилам: восстановление из снапшота, миграция между хостами с разными часами или долгий простой дают скачок hwTime независимо от systemTime. На физическом сервере с севшей батарейкой CMOS RTC после каждого обесточивания сбрасывается на заводскую дату — retainLock будет возвращаться после каждого холодного старта, пока батарейку не поменяют.

Мой вывод из практики: если ваш hardened repository — виртуалка, ждите этого предупреждения регулярно. В требованиях к hardened repository Veeam рекомендует физическую машину с локальными дисками — формально ради сокращения поверхности атаки, но и с точки зрения времени физический сервер с живым NTP ведёт себя куда предсказуемее.

Проверьте очевидное до того, как лезть в файлы: если на репозитории вообще не настроен NTP, вы будете ловить retainLock снова и снова, а сброс защиты превратится в еженедельный ритуал.
«Repository time shift detected» в Veeam 13.1: почему после долгого выключения репозитория пропадает защита от удаления — схема
Схема к статье. Открыть схему в полном размере

Разбор из практики: 61 час простоя и 19 незащищённых файлов

Книжный магазин «КнигоДом»: торговый зал, склад и бэк-офис, 29 рабочих мест, учётная база, кассовый сервер, файловый сервер и почтовые архивы. Контур бэкапа небольшой, но сделан правильно: VBR 13.1 на отдельной машине, hardened repository на физическом tower-сервере — 6 дисков по 4 ТБ в RAID6, Rocky Linux 9, XFS с reflink=1 (нужен для fast clone на синтетических full), immutability period 14 дней, четыре задания. Сервер стоит в серверном шкафу на складе, SSH закрыт, учётка Veeam добавлена как single-use credentials, root по паролю выключен.

В здании на выходные отключали электричество — плановые работы по вводному щиту. Репозиторий погасили в пятницу в 19:40, подняли в понедельник в 09:15. Простой — 61 час 35 минут, то есть около 221 700 секунд, в два с половиной раза больше порога. В понедельник вечером в отчёте: 3 задания из 4 со статусом Warning, у всех одна и та же строка про time shift (четвёртое, недельное, в эти дни не запускалось). На репозитории файл /etc/veeam/immureposvc/retainLock создан в 09:22 — через несколько минут после загрузки ОС, до старта первого задания.

Дальше самое интересное — масштаб. Прогнали lsattr по каталогам заданий и посчитали файлы без атрибута i. Получилось 19 штук: инкременты за понедельник и вторник (задания успели отработать дважды, пока разбирались, плюс ночные прогоны по кассовой базе) и один синтетический full, попавший в окно. То есть почти двое суток бэкапы писались без защиты — и ни одно задание не упало, ни одно письмо не пришло, потому что уведомления были настроены на Failed, а не на Warning. Это типичная дыра: у многих небольших компаний warning-статусы никуда не отправляются.

Что сделали. Сверили время: timedatectl показал корректный UTC и «RTC in local TZ: no», chronyc tracking — offset меньше миллисекунды, источник живой. Убедились по журналу, что скачок объясняется именно плановым отключением, а не чем-то подозрительным в /var/log/secure. Остановили задания, сняли блокировку, перезапустили veeamtransport, дождались нового timeLog и проверили, что retainLock не появился заново. Затем не стали ждать следующего инкремента, а вручную запустили active full по двум критичным заданиям — учётной и кассовой базе. От «увидели warning» до «есть свежая полная копия с флагом i» прошло около двух с половиной часов, из них полтора — сам active full примерно на 900 ГБ.

Отдельно посчитали, что было бы, если бы не заметили. retainLock сам не исчезает. Новые точки продолжали бы приходить без защиты, а старые, у которых вышел срок, так и оставались бы с атрибутом i — ретеншен не смог бы их удалить. Через пару недель магазин получил бы репозиторий, где свежие бэкапы можно стереть одной командой, а место забито устаревшими цепочками, — при почти зелёном дашборде. Вот эта комбинация «всё работает, защиты нет» и есть настоящая опасность предупреждения.

Первым делом после сброса запускайте active full по критичным заданиям. Не рассчитывайте, что уже записанные без защиты файлы кто-то задним числом сделает иммутабельными: документация этого не обещает, и проверять на боевых данных я не советую.
Цифры и версии: Разбор из практики: 61 час простоя и 19 незащищённых файлов — схема
Цифры и версии: Разбор из практики: 61 час простоя и 19 незащищённых файлов. Открыть схему в полном размере

Как за пять минут понять реальный масштаб

Консоль VBR тут плохой свидетель. Она показывает вычисленную дату «immutable until», которую посчитал сервер бэкапа, а не то, что реально лежит на файловой системе репозитория. Единственный источник правды — атрибуты файлов. Заходим на репозиторий и смотрим три вещи: есть ли блокировка, что с часами, и какие файлы остались без флага.

Первое — сама блокировка и журнал времени. Второе — состояние часов, чтобы не сбрасывать защиту при реально кривом времени. Третье — сплошная проверка каталогов заданий (путь /mnt/veeam/repo01 замените на свой):

# 1. Есть ли блокировка и что в ней записано
ls -la /etc/veeam/immureposvc/
sudo cat /etc/veeam/immureposvc/retainLock
sudo tail -n 5 /etc/veeam/immureposvc/timeLog

# 2. Состояние часов (RTC должен быть в UTC: "RTC in local TZ: no")
timedatectl
chronyc tracking
sudo hwclock --show

# 3. Какие файлы бэкапа остались без флага иммутабельности
lsattr /mnt/veeam/repo01/BackupJob01/*.vbk /mnt/veeam/repo01/BackupJob01/*.vib
sudo lsattr -R /mnt/veeam/repo01 2>/dev/null | grep -E '\.(vbk|vib)$' | grep -vE '^-{4}i' | wc -l

В выводе lsattr атрибут i стоит на пятой позиции: строка вида ----i---------e------- означает, что файл защищён, а --------------e------- — что нет. По Help Center, для каждого файла бэкапа Veeam создаёт служебный .veeam.N.lock со сроком иммутабельности, а на сам файл вешает расширенный атрибут (xattr) с тем же сроком — его видно через getfattr -d. Если .lock и xattr есть, а атрибута i на .vbk нет, значит файл попал ровно в окно блокировки.

И нюанс, который регулярно ломает людям арифметику: фактический срок блокировки файла обычно дольше заданного в свойствах репозитория. Отсчёт immutability period идёт не от создания конкретного файла, а от последней точки восстановления в активной цепочке, и срок продлевается для всех файлов этой цепочки. Пример из документации: full 12 января, инкременты 13 и 14 января, период 10 дней — вся цепочка неизменяема до 24 января. Ещё два факта оттуда же: флаг ставится только после завершения сессии задания, а если сессия упала и следующий прогон сделал новый full, файлы неудачного прогона сами иммутабельными не станут — для этого есть командлет Set-VBRImmutabilityLockExpirationDate. Закладывайте всё это в расчёт ёмкости.

Не полагайтесь на колонку «Immutable until» в консоли VBR. Она отражает намерение сервера бэкапа, а не факт на диске. Проверяйте lsattr — это тридцать секунд работы и совсем другой уровень уверенности.

Сброс защиты: три разных контура, не перепутайте

Способ сброса зависит от того, как развёрнут репозиторий, и KB4482 разводит три сценария. Статьи в интернете часто смешивают их в одну кучу. Перед любым сбросом обязательный шаг, который Veeam прописывает первой строкой решения: убедиться, что сдвиг времени был ожидаемым и что текущее время правильное. Если время кривое — сначала чините часы, потом снимаете блокировку, иначе получите её обратно.

Вариант первый, для 13.x — Hardened Repository, развёрнутый из JeOS-образа (Veeam Infrastructure Appliance), и встроенный репозиторий Veeam Software Appliance. Сброс делается в Veeam Host Management TUI: логинитесь как Host Administrator, в главном меню выбираете Host configuration → Reset time shift protection. Пункт стоит в одном ряду с прочими обслуживающими операциями — управлением службами, экспортом конфигурации, выгрузкой логов. Шелл не нужен, и это правильно: SSH на appliance по умолчанию выключен, а если при установке настроен Security Officer, root-шелл TUI выдаётся только по запросу с его одобрением, на 8 часов с первого входа и только с локальной или виртуальной консоли. Держите контакты Security Officer под рукой.

Вариант второй — репозиторий, собранный из Veeam Hardened Repository ISO ветки 12.x. Открываете консоль машины, входите под vhradmin и в главном меню стрелками выбираете Reset time lock. Вариант третий — самосборный Linux-репозиторий из обычного дистрибутива. Только здесь уместна работа с файлами под root, и по KB4482 последовательность такая: сначала остановить и отключить задания, которые пишут в этот репозиторий (шаг можно пропустить, но файлы, которые пишутся в момент сброса, получат иммутабельность только при следующем запуске):

# Только для самосборного Linux-репозитория (НЕ для appliance!)
# 0. Остановить/отключить задания на этот репозиторий в консоли VBR
sudo chattr -i /etc/veeam/immureposvc/retainLock
sudo rm /etc/veeam/immureposvc/retainLock
# новый timeLog появится при старте службы или в течение ~8 минут;
# рестарт прервёт задания, которые ещё пишут в репозиторий
sudo systemctl restart veeamtransport

# Контроль через 15 минут: новый timeLog есть, retainLock нет
ls -la /etc/veeam/immureposvc/

Порядок важен: сначала снимаем атрибут i, только потом удаляем файл — обычный rm на иммутабельном файле вернёт «Operation not permitted» даже под root. После удаления retainLock иммутабельность не назначается, пока VeeamTransport не создаст новый timeLog: это происходит при старте службы или в течение 8 минут, поэтому можно либо перезапустить службу, либо подождать. timeLog руками не трогаем — процедура KB этого не предусматривает, служба сама ведёт журнал. После сброса включите задания обратно и через полчаса проверьте, что retainLock не вернулся. Если вернулся — у вас реально едет время, и разбираться надо с NTP и RTC, а не с Veeam.

Не лезьте руками в файловую систему appliance. Veeam Software Appliance — это заблокированная ОС с преднастроенным хардненингом; обход TUI через выпрошенный root-шелл вы потом будете объяснять и вендору при обращении в поддержку, и себе при следующем аудите. Есть штатный пункт меню — пользуйтесь им.

Чтобы не повторялось: NTP, регламент выключения и мониторинг

Первое и самое скучное — время. На hardened repository NTP должен быть настроен и работать, а не «быть установленным». Я ставлю chrony с двумя-тремя источниками, с makestep для первой синхронизации после загрузки и с rtcsync, чтобы RTC подтягивался за системными часами. RTC держу в UTC — Help Center прямо называет это условием точного детектирования. Тот же источник времени задаю и серверу бэкапа VBR: у него и у репозитория не должно быть расхождений, иначе разбор инцидентов по журналам превращается в гадание. Для машин в сегменте без выхода наружу — внутренний NTP на контроллере домена или шлюзе, но тогда следите, чтобы сам источник был правильным.

# /etc/chrony.conf
server ntp1.vniiftri.ru iburst
server ntp2.vniiftri.ru iburst
pool ru.pool.ntp.org iburst
makestep 1.0 3
rtcsync
driftfile /var/lib/chrony/drift
sudo timedatectl set-local-rtc 0   # RTC в UTC
sudo systemctl enable --now chronyd
chronyc sources -v

Второе — регламент планового выключения. Если репозиторий гасится дольше чем на сутки, вы гарантированно получите retainLock при следующем старте. Это не повод не выключать сервер, это повод внести сброс защиты в чек-лист работ: включили → проверили время → сбросили защиту → проверили lsattr → запустили active full. Пять пунктов, десять минут. Из практики: когда этого нет в чек-листе, предупреждение живёт до первой серьёзной проверки, а проверка обычно случается в неудачный момент.

Третье — порог. По KB4482 детектор настраивается файлом /etc/veeam/immureposvc/config: создать его нужно самому, владелец root, права 600; если файла нет или он оформлен неверно, действуют встроенные значения. Диапазон maxDeltaValueInSec — от 60 до 604 800 секунд, по умолчанию 86 400. Моя позиция: значение по умолчанию не трогать. Для стендов, где машины постоянно выключаются, можно поднять до 172 800 — компромисс, который я готов защищать. А вот disableCheck="1« — это выключение той самой защиты от манипуляций временем. checkHwTime=»0" имеет смысл только там, где RTC действительно нет. После создания или правки конфига обязателен рестарт veeamtransport.

<!-- /etc/veeam/immureposvc/config  —  root:root, chmod 600 -->
<TimeDefenderConfig disableCheck="0" checkHwTime="1" maxDeltaValueInSec="86400" />

Допустимый диапазон maxDeltaValueInSec: минимум 60, по умолчанию 86400, максимум 604800.

Четвёртое — мониторинг. Он должен ловить две вещи: наличие файла retainLock и статус Warning у заданий бэкапа. Первое проверяется однострочником и заводится в Zabbix за пять минут. Второе — вопрос настройки уведомлений в самом VBR: по умолчанию у многих в почту улетают только Failed, и именно поэтому такие истории тянутся неделями.

# UserParameter для Zabbix: 1 = защита заблокирована, 0 = норма
UserParameter=veeam.retainlock,test -e /etc/veeam/immureposvc/retainLock && echo 1 || echo 0
disableCheck="1" в конфиге детектора — это добровольный отказ от защиты от манипуляций временем. Единственный случай, когда я считаю это допустимым, — изолированный тестовый стенд, где данные никому не нужны. На боевом репозитории такого параметра быть не должно.

Что делать со старыми точками и чего делать точно не надо

После сброса сервис снова управляет иммутабельностью. По Help Center он проверяет атрибуты файлов каждые 20 минут и ставит или снимает атрибут по сроку, записанному для файла, — так что просроченные точки начнут разблокироваться и уходить по ретеншену, а новые получат флаг штатно. Встанет ли флаг на файлы, записанные в окно блокировки, документация явно не обещает, поэтому я не делаю вид, что знаю ответ для всех сборок: проверяю lsattr через полчаса-час после сброса. Практический ход, который работает всегда: active full по критичным заданиям, чтобы получить заведомо защищённую полную точку.

Теперь то, чего делать нельзя. Не удаляйте timeLog «чтобы обнулить счётчик» — процедура Veeam этого не предусматривает, а старый или чужой журнал сам по себе входит в список триггеров, так что эксперименты с ним ни к чему хорошему не ведут. Не пытайтесь «починить» проблему переводом системного времени назад, к моменту последней записи: это ещё одна аномальная дельта и искажённые сроки для уже защищённых файлов. Не удаляйте служебные .veeam.N.lock рядом с файлами бэкапа. И не форматируйте репозиторий в панике, чтобы «начать с чистого листа»: проблема решится вместе с историей восстановления за несколько месяцев.

И организационный вывод, который важнее любых команд. Hardened repository — одна копия из трёх в схеме 3-2-1-1-0, а не серебряная пуля. Если неделя бэкапов ушла без флага иммутабельности и вы узнали об этом постфактум — вопрос не «как теперь их защитить», а «почему нет второй копии, где эти данные лежат независимо». Для магазина уровня «КнигоДома» вторая копия обычно уезжает во внешнее облачное хранилище или на отдельную площадку, и именно она вытаскивает в таких ситуациях. Иммутабельность защищает от удаления, но не от того, что неделю никто не смотрел на статусы заданий.

Если свести всё к одной фразе: любое длительное выключение Linux-репозитория Veeam — это не «сервер постоял», а «защита от удаления выключена, пока вы её руками не включите обратно». Заведите этот пункт в регламент, и предупреждение из проблемы превратится в рутинную строчку чек-листа.

Правильная последовательность целиком: подтвердить причину скачка → проверить время и RTC → остановить задания → сбросить защиту штатным способом → дождаться нового timeLog → lsattr → active full → пункт в чек-лист планового выключения.
Порядок действий: Что делать со старыми точками и чего делать точно не надо — схема
Порядок действий: Что делать со старыми точками и чего делать точно не надо. Открыть схему в полном размере

Частые вопросы

Бэкап завершился с Warning — данные-то целы?

Да, данные записаны, точка восстановления рабочая, восстановиться из неё можно. Veeam не блокирует задания — он блокирует операции с атрибутом иммутабельности: новые файлы остаются без флага i, а со старых флаг не снимается, поэтому ретеншен не может удалить просроченные точки. Задание при этом не падает, и именно поэтому предупреждение так часто пропускают.

Можно ли просто удалить retainLock командой rm?

Нет, файл создаётся с атрибутом i, и обычный rm вернёт «Operation not permitted» даже под root. На самосборном репозитории по KB4482: остановить задания, chattr -i, удалить retainLock, перезапустить veeamtransport или подождать до 8 минут, пока появится новый timeLog. На Hardened Repository из JeOS 13.x и Veeam Software Appliance есть штатный пункт Host configuration → Reset time shift protection в Host Management TUI, на VHR ISO 12.x — Reset time lock под vhradmin.

После сброса старые незащищённые файлы станут иммутабельными?

Документация этого явно не обещает. Сервис после сброса проверяет атрибуты каждые 20 минут и работает по записанным срокам, но я не рекомендую на это рассчитывать — проверьте lsattr через полчаса-час. Надёжный ход — сразу запустить active full по критичным заданиям и получить заведомо защищённую полную точку.

Можно поднять порог, чтобы блокировка не срабатывала после плановых выключений?

Технически да: параметр maxDeltaValueInSec в /etc/veeam/immureposvc/config принимает значения от 60 до 604800 секунд при значении по умолчанию 86400. Практически — на боевом репозитории я это значение не трогаю. Для стендов, где машины гасятся регулярно, разумный потолок 172800. А disableCheck="1" — это уже отключение защиты от манипуляций временем, на продуктиве ему не место.

Почему файлы держатся дольше, чем указано в настройке immutability period?

Потому что отсчёт immutability period идёт от последней точки восстановления в активной цепочке, и срок продлевается для всех файлов цепочки. Пример из Help Center: full 12 января, последний инкремент 14 января, период 10 дней — вся цепочка неизменяема до 24 января. Для недельной цепочки с периодом 14 дней полный бэкап держится заметно дольше двух недель — это надо закладывать в расчёт ёмкости.

Как поймать проблему автоматически, а не через месяц?

Два простых чека. Первый — проверка наличия файла /etc/veeam/immureposvc/retainLock, заводится в Zabbix одной строкой UserParameter. Второй — настроить уведомления VBR не только на Failed, но и на Warning: в большинстве инсталляций, куда я прихожу, warning-статусы никуда не отправляются, и это главная причина, почему такие истории тянутся неделями.

Нужен ли NTP на сервере бэкапа, или достаточно на репозитории?

Детектор time shift работает на самом Linux-репозитории и смотрит на его системные и аппаратные часы, поэтому критичен именно NTP репозитория и RTC в UTC. Но я всегда настраиваю один и тот же источник времени и для сервера VBR: так сроки в консоли, журналы заданий и журналы репозитория сходятся, и разбор инцидента не превращается в сверку часовых поясов.

Столкнулись с похожей задачей? Обращайтесь — решим

Если у вас происходит что-то из описанного в этой статье — или любая другая проблема с ИТ-инфраструктурой, — обращайтесь в любое время. Мои специалисты и я лично разберём ситуацию, найдём настоящую причину и доведём до решения.

Возьмёмся и за разовую задачу, и за постоянное обслуживание. Первичная консультация — бесплатно и без обязательств.

📞 +7 903 729-62-41 💬 MAX: +7 903 729-62-41 ✈ Telegram @ITfresh_Boss

С уважением, Семёнов Евгений Сергеевич, директор «АйТи Фреш» — IT-аутсорсинг для компаний до 50 рабочих мест, 15+ лет практики

Источники

© ООО «АйТи-Фреш» · Москва · Все статьи