Скрипт bash для корректного обновления платформы 1С &НаСервере. Рецепты от Капитана

Скрипт bash для корректного обновления серверной части платформы 1С Предприятия (сохраняет и восстанавливает настройки служб, даже если их несколько, изменяет версию платформы во всех веб-публикациях и делает еще много полезных мелочей не включенных в штатный инсталлятор) Иными словами “обновление под ключ” или “нажал и забыл”
Телефонный опрос, проведенный утром 1 января, дал следующие результаты:2% - "да?" 3% - "алло?" 95% - затруднились ответить
www.anekdot.ru©
Новый Год у сисадмина отличается от обычных дней тем, что на столе рядом с пивом лежат мандаринки ...
сетевой фольклор©
Ведь именно новогодние каникулы, это то время, пока коллеги лежат носом в оливье, ИТ-шники всех стран обновляют сервера, сворачивают базы, переходят на новые релизы.
В ноябре уже пора об этом задумываться.

Внимание! Все действия нужно производить осознанно и понимая их последствия
Скрипт предоставляется бесплатно по принципу "AS IS" / "Как есть".
Автор гарантирует, что никаких действий, кроме обновления серверной части платформы 1С, скрипт не производит, но не несёт ответственности за ваши данные.
Постановка задачи
Установщики платформы 1С от вендора для linux, да и для windows тоже, как виде для пакетного менеджера, так в виде программы установки сделанной VMWare InstallBuilder не умеют и скорее всего не будут уметь, сохранять настройки для службы сервера 1С сделанные вручную, особенно если серверов 1С несколько на разных портах на одном логическом сервере.
Тем более обновлять версии платформы во всех веб-публикациях.
Для не совсем внимательного пользователя это может быть проблемой.
Тем более если этот пользователь работает на linux.
Импортозамещение шагает по стране уже два года, но для linux пока не нашел ничего достойного.
Поэтому привел свой скрипт в удобоваримый вид (не обошлось без помощи gemini) и просто положу его здесь.
Решение
Скрипт на bash, который проверяет.
что запущен с правами администратора
наличие архива установки или распакованного файла установки
если выбрано обновление, то соответствие установленной версии платформы и заданной ключом командной строки
сохраняет файлы старых служб сервера 1С и ras с возможными нетиповыми настройками
создает новые файлы служб сервера 1С и ras с перенесенными настройками
останавливает службу сервера 1С и ras
устанавливает новую версию платформы 1С
переносит в ее каталог новые файлы служб сервера 1С и ras
запускает службы сервера 1С и ras
при успешном запуске удаляет старую версию платформы 1С
во всех найденных веб-публикациях изменяет версию платформы 1С
перезапускает веб-сервер
Выполняется как любой скрипт.
Может обновлять 8.3.х.х на 8.5.х.х и обратно, что не менее актуально в настоящий момент см. багтрекер 1С.
Любители острых ощущений могут его даже в cron запланировать на ночь и утром удивиться результату.

Использование скрипта: $PROGNAME параметр1 параметр2
Первый параметр это номер обновляемой версии 1С, если 0 то чистая установка
Второй параметр это номер новой версии 1С
Файл установки 1С должен находиться в одном каталоге со скриптом
Если указан номер обновляемой версии 1С, то должен существовать каталог установки обновляемой версии 1С
Пример: $PROGNAME 0 8.3.24.1586
Пример: $PROGNAME 8.3.23.1586 8.3.24.1586

Поскольку настроящий сисадмин должен быть не только умный, но и ленивый, не буду заставлять его ходить на github, а положу весь скрипт под кат. Но кто сходит, тот найдет еще пару скриптов: на 1С Исполнитель и bash для обновления только веб-публикаций для IIS и Apache
Лицензия MIT
#!/bin/bash
# Copyright (c) 2026 [@capitan_nemo_spb]
#
# Данная лицензия разрешает лицам, получившим копию данного программного
# обеспечения и сопутствующей документации (в дальнейшем именуемого "Программное
# обеспечение"), безвозмездно использовать Программное обеспечение без ограничений,
# включая неограниченное право на использование, копирование, изменение, слияние,
# публикацию, распространение, сублицензирование и/или продажу копий Программного
# обеспечения, а также лицам, которым предоставляется данное Программное
# обеспечение, при соблюдении следующих условий:
#
# Указанное выше уведомление об авторских правах и данные условия лицензии должны
# быть включены во все копии или значимые части Программного обеспечения.
#
# ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ "КАК ЕСТЬ", БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ,
# ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, ВКЛЮЧАЯ НЕ ОГРАНИЧИВАЯСЬ ГАРАНТИЯМИ ТОВАРНОЙ
# ПРИГОДНОСТИ, СООТВЕТСТВИЯ ПО НАЗНАЧЕНИЮ И НЕНАРУШЕНИЯ ПРАВ. НИ В КАКОМ СЛУЧАЕ
# АВТОРЫ ИЛИ ПРАВООБЛАДАТЕЛИ НЕ НЕСУТ ОТВЕТСТВЕННОСТИ ПО КАКИМ-ЛИБО ИСКАМ, ЗА
# УЩЕРБ ИЛИ ПО ИНЫМ ТРЕБОВАНИЯМ, ВОЗНИКШИМ В СВЯЗИ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ ИЛИ
# ИСПОЛЬЗОВАНИЕМ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ.
set -euo pipefail
PROGNAME="$(basename "$0")"
SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)"
usage() {
cat <<EOF
Использование программы: $PROGNAME параметр1 параметр2
Первый параметр это номер обновляемой версии 1С, если 0 то чистая установка
Второй параметр это номер новой версии 1С
Файл установки 1С должен находиться в одном каталоге со скриптом
Если указан номер обновляемой версии 1С, то должен существовать каталог установки
Пример: $PROGNAME 0 8.3.24.1586
Пример: $PROGNAME 8.3.23.1586 8.3.24.1586
EOF
}
log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*"
}
err() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] ОШИБКА: $*" >&2
}
replace_version_in_file() {
local target_file="$1"
if [[ -f "$target_file" ]]; then
perl -pE "s/\Q${old_version}\E/${new_version}/g" -i "$target_file"
fi
}
http_replace_version() {
local conf_file="$1"
if grep -qF "${old_version}" "${conf_file}"; then
log "Изменяется версия в файле веб-публикации: ${conf_file}"
perl -i".bak_${current_date}" -pE "s/\Q${old_version}\E/${new_version}/g" "${conf_file}"
fi
}
if [[ "${1:-}" =~ ^(-h|--help)$ ]]; then
usage
exit 0
fi
if [[ "$(id -u)" -ne 0 ]]; then
err "Скрипт должен быть запущен с правами администратора"
exit 1
fi
if [[ $# -ne 2 ]]; then
usage
exit 1
fi
old_version=$1
new_version=$2
current_date=$(date +'%Y%m%d_%H%M%S')
bak_dir="${SCRIPT_DIR}/bak_${current_date}"
log "Проверяется существование архива/файла установки"
new_archive="${SCRIPT_DIR}/server64_${new_version//./_}.zip"
shopt -s nullglob
installer_matches=( "${SCRIPT_DIR}"/*-"${new_version}"-*.run )
shopt -u nullglob
if [[ ${#installer_matches[@]} -eq 0 ]]; then
if [[ -f "${new_archive}" ]]; then
log "Разархивируется ${new_archive}..."
unzip -o "${new_archive}" -d "${SCRIPT_DIR}" "*${new_version}*.run"
shopt -s nullglob
installer_matches=( "${SCRIPT_DIR}"/*-"${new_version}"-*.run )
shopt -u nullglob
else
err "Не обнаружен файл установки или архива новой версии 1С ${new_archive}"
exit 1
fi
fi
if [[ ${#installer_matches[@]} -eq 0 || ! -f "${installer_matches[0]}" ]]; then
err "Не обнаружен файл установки новой версии 1С"
exit 1
fi
new_file="${installer_matches[0]}"
chmod +x "${new_file}"
old_dir="/opt/1cv8/x86_64/${old_version}"
new_dir="/opt/1cv8/x86_64/${new_version}"
srv_suffixes=()
ras_suffixes=()
if [[ "${old_version}" != "0" ]]; then
log "Старая версия не 0 (${old_version}), проверяется существование каталога установки"
if [[ ! -f "${old_dir}/uninstaller-full" ]]; then
err "Не обнаружен каталог обновляемой версии 1C ${old_version}"
exit 1
fi
mkdir -p "${bak_dir}"
log "Сохраняются файлы старых служб с возможными нетиповыми настройками"
# Поиск всех служб сервера 1С вида srv1cv8-OLD_VER@.service, srv1cv8-OLD_VER@2541.service и т.д.
shopt -s nullglob
old_srv_services=( "${old_dir}"/srv1cv8-"${old_version}"@*.service )
shopt -u nullglob
if [[ ${#old_srv_services[@]} -eq 0 ]]; then
log "Службы srv1cv8 не найдены в ${old_dir}, используется шаблон по умолчанию"
srv_suffixes+=( "" )
else
for srv_path in "${old_srv_services[@]}"; do
srv_file="$(basename "${srv_path}")"
# Извлекаем суффикс после @ и до .service (например: "" или "2541")
suffix="${srv_file#srv1cv8-${old_version}@}"
suffix="${suffix%.service}"
srv_suffixes+=( "${suffix}" )
new_srv_name="srv1cv8-${new_version}@${suffix}.service"
log "Резервное копирование и адаптация службы: ${srv_file} -> ${new_srv_name}"
cp "${srv_path}" "${bak_dir}/${new_srv_name}"
replace_version_in_file "${bak_dir}/${new_srv_name}"
done
fi
# Поиск всех служб RAS вида ras-OLD_VER.service, ras-OLD_VER-2545.service, ras-OLD_VER@.service и т.д.
shopt -s nullglob
old_ras_services=( "${old_dir}"/ras-"${old_version}"*.service )
shopt -u nullglob
if [[ ${#old_ras_services[@]} -eq 0 ]]; then
log "Службы RAS не найдены в ${old_dir}"
else
for ras_path in "${old_ras_services[@]}"; do
ras_file="$(basename "${ras_path}")"
# Извлекаем суффикс после ras-${old_version} и до .service (например: "" для ras-8.3.23.service, "-2545" или "@2545")
suffix="${ras_file#ras-${old_version}}"
suffix="${suffix%.service}"
ras_suffixes+=( "${suffix}" )
new_ras_name="ras-${new_version}${suffix}.service"
log "Резервное копирование и адаптация службы RAS: ${ras_file} -> ${new_ras_name}"
cp "${ras_path}" "${bak_dir}/${new_ras_name}"
replace_version_in_file "${bak_dir}/${new_ras_name}"
done
fi
log "Останавливается старая версия платформы 1С"
for suffix in "${srv_suffixes[@]}"; do
inst="default"
[[ -n "${suffix}" ]] && inst="${suffix}"
srv_unit="srv1cv8-${old_version}@${suffix}.service"
srv_inst="srv1cv8-${old_version}@${inst}.service"
systemctl stop "${srv_inst}" 2>/dev/null || true
systemctl disable "${srv_inst}" 2>/dev/null || true
for unit_link in \
"/etc/systemd/system/${srv_unit}" \
"/etc/systemd/system/${srv_inst}" \
"/etc/systemd/system/multi-user.target.wants/${srv_inst}"
do
if [[ -e "${unit_link}" || -L "${unit_link}" ]]; then
rm -f "${unit_link}"
fi
done
done
log "Останавливаются старые службы RAS"
for suffix in "${ras_suffixes[@]}"; do
ras_unit="ras-${old_version}${suffix}.service"
# Если служба шаблонная (содержит @), определяем имя инстанса
if [[ "${suffix}" == *"@"* ]]; then
inst_suffix="${suffix#*@}"
[[ -z "${inst_suffix}" ]] && inst_suffix="default"
ras_inst="ras-${old_version}@${inst_suffix}.service"
else
ras_inst="${ras_unit}"
fi
systemctl stop "${ras_inst}" 2>/dev/null || true
systemctl disable "${ras_inst}" 2>/dev/null || true
for unit_link in \
"/etc/systemd/system/${ras_unit}" \
"/etc/systemd/system/${ras_inst}" \
"/etc/systemd/system/multi-user.target.wants/${ras_inst}"
do
if [[ -e "${unit_link}" || -L "${unit_link}" ]]; then
rm -f "${unit_link}"
fi
done
done
systemctl daemon-reload
systemctl reset-failed "srv1cv8-*" "ras-*" 2>/dev/null || true
else
srv_suffixes+=( "" )
ras_suffixes+=( "" )
fi
log "Производится установка новой версии платформы 1С (${new_version})..."
"${new_file}" --mode unattended --enable-components "server,ws,server_admin,additional_admin_functions,liberica_jre,ru"
log "Проверяется, что новая версия установилась..."
new_service="${new_dir}/srv1cv8-${new_version}@.service"
if [[ ! -f "${new_service}" ]]; then
err "Некорректно установилась новая версия 1C: файл ${new_service} не найден"
exit 1
fi
if [[ "${old_version}" != "0" ]]; then
log "Удаляется старая версия платформы 1С (${old_version})..."
"${old_dir}/uninstaller-full" --mode unattended
log "Восстановление конфигураций служб srv1cv8..."
for suffix in "${srv_suffixes[@]}"; do
target_srv="srv1cv8-${new_version}@${suffix}.service"
bak_srv_file="${bak_dir}/${target_srv}"
dest_srv_file="${new_dir}/${target_srv}"
if [[ -f "${bak_srv_file}" ]]; then
if [[ -f "${dest_srv_file}" ]]; then
mv "${dest_srv_file}" "${bak_dir}/${target_srv}.dist"
fi
log "Копирование файла службы: ${target_srv} -> ${new_dir}/"
cp "${bak_srv_file}" "${dest_srv_file}"
fi
done
log "Восстановление конфигураций служб RAS..."
for suffix in "${ras_suffixes[@]}"; do
target_ras="ras-${new_version}${suffix}.service"
bak_ras_file="${bak_dir}/${target_ras}"
dest_ras_file="${new_dir}/${target_ras}"
if [[ -f "${bak_ras_file}" ]]; then
if [[ -f "${dest_ras_file}" ]]; then
mv "${dest_ras_file}" "${bak_dir}/${target_ras}.dist"
fi
log "Копирование файла службы RAS: ${target_ras} -> ${new_dir}/"
cp "${bak_ras_file}" "${dest_ras_file}"
fi
done
fi
log "Запускаются службы 1С..."
systemctl daemon-reload
for suffix in "${srv_suffixes[@]}"; do
inst="default"
[[ -n "${suffix}" ]] && inst="${suffix}"
srv_unit_file="${new_dir}/srv1cv8-${new_version}@${suffix}.service"
srv_inst_name="srv1cv8-${new_version}@${inst}"
if [[ -f "${srv_unit_file}" ]]; then
systemctl link "${srv_unit_file}"
systemctl enable "${srv_inst_name}" --now
else
err "Файл службы ${srv_unit_file} не найден для регистрации!"
fi
done
for suffix in "${ras_suffixes[@]}"; do
ras_unit_file="${new_dir}/ras-${new_version}${suffix}.service"
if [[ "${suffix}" == *"@"* ]]; then
inst_suffix="${suffix#*@}"
[[ -z "${inst_suffix}" ]] && inst_suffix="default"
ras_inst_name="ras-${new_version}@${inst_suffix}"
else
ras_inst_name="ras-${new_version}${suffix}.service"
fi
if [[ -f "${ras_unit_file}" ]]; then
systemctl link "${ras_unit_file}"
systemctl enable "${ras_inst_name}" --now
fi
done
log "Ожидание запуска служб..."
for ((i=0; i<15; i++)); do
all_active=true
for suffix in "${srv_suffixes[@]}"; do
inst="default"
[[ -n "${suffix}" ]] && inst="${suffix}"
if ! systemctl is-active --quiet "srv1cv8-${new_version}@${inst}"; then
all_active=false
break
fi
done
if [[ "${all_active}" == "true" ]]; then
for suffix in "${ras_suffixes[@]}"; do
ras_unit_file="${new_dir}/ras-${new_version}${suffix}.service"
[[ ! -f "${ras_unit_file}" ]] && continue
if [[ "${suffix}" == *"@"* ]]; then
inst_suffix="${suffix#*@}"
[[ -z "${inst_suffix}" ]] && inst_suffix="default"
ras_inst_name="ras-${new_version}@${inst_suffix}"
else
ras_inst_name="ras-${new_version}${suffix}.service"
fi
if ! systemctl is-active --quiet "${ras_inst_name}"; then
all_active=false
break
fi
done
fi
if [[ "${all_active}" == "true" ]]; then
log "Все службы успешно перешли в активное состояние."
break
fi
sleep 2
done
for suffix in "${srv_suffixes[@]}"; do
inst="default"
[[ -n "${suffix}" ]] && inst="${suffix}"
systemctl status "srv1cv8-${new_version}@${inst}" --no-pager -l || true
done
for suffix in "${ras_suffixes[@]}"; do
ras_unit_file="${new_dir}/ras-${new_version}${suffix}.service"
[[ ! -f "${ras_unit_file}" ]] && continue
if [[ "${suffix}" == *"@"* ]]; then
inst_suffix="${suffix#*@}"
[[ -z "${inst_suffix}" ]] && inst_suffix="default"
ras_inst_name="ras-${new_version}@${inst_suffix}"
else
ras_inst_name="ras-${new_version}${suffix}.service"
fi
systemctl status "${ras_inst_name}" --no-pager -l || true
done
if [[ "${old_version}" != "0" ]]; then
web_conf_dirs=()
[[ -d '/etc/httpd' ]] && web_conf_dirs+=( '/etc/httpd' )
[[ -d '/etc/apache2' ]] && web_conf_dirs+=( '/etc/apache2' )
if [[ ${#web_conf_dirs[@]} -gt 0 ]]; then
for conf_dir in "${web_conf_dirs[@]}"; do
readarray -d '' -t find_files < <(find "${conf_dir}" -name '*.conf' -size +1c -type f -print0)
for file_path in "${find_files[@]}"; do
http_replace_version "${file_path}"
done
done
log "Перезапускается веб-сервер..."
if systemctl list-unit-files 2>/dev/null | grep -qE '^(httpd|apache2)\.service'; then
systemctl restart httpd 2>/dev/null || systemctl restart apache2 2>/dev/null || true
fi
fi
fi
log "Обновление/установка платформы 1С успешно завершена!"
Будет интересно, напишу про обновление клиентов.
Статья продолжает серию публикаций:
Графическая утилита для очистки кэша 1С (с возможностью выбора базы) для ОС Linux
Установка комьюнити-лицензии разработчика на сервер 1С. Рецепты от Капитана
Платформа 1C: Предприятие 8.3 в корпоративной сети РедОС. Тысяча мелочей
Бесплатный вебинар «В облаке, как дома: Как настроить рабочее место пользователя 1С на базе РЕД ОС»
Сервер 1С: Предприятие на Ubuntu 16.04 и PostgreSQL 9.6, для тех, кто хочет узнать его вкус. Рецепт от Капитана обратите внимание на дату публикации
Серия «Рецепты от Капитана» на всякий случай
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.