Большая переработка

- внедрён iptvc
- скорректированы докерфайлы
- php8.3 => 8.4
- переписан скрипт iptv
- скорректирован композ и енвы
This commit is contained in:
2025-05-11 12:13:30 +08:00
parent 3a624ba8d1
commit 833c5218eb
15 changed files with 1031 additions and 108 deletions

View File

@@ -1,5 +1,13 @@
IPTV_ENV=dev
REDIS_UID=1000
REDIS_GID=1000
REDIS_PORT=6379
KEYDB_UID=1000
KEYDB_GID=1000
KEYDB_PORT=6379
KEYDB_USERNAME=
KEYDB_PASSWORD=
CHECKER_DB=0
CHECKER_TTL=1800
CHECKER_WAIT=60
CHECKER_INIFILE=/app/playlists.ini
CHECKER_TAGFILE=/app/channels.json

18
.gitignore vendored
View File

@@ -1,16 +1,16 @@
/.idea
/.vscode
downloaded/
/svc-*
/tools
/tmp
/.idea/
/.vscode/
/iptvc/
/web/
/playlists/
/tools/
/.profile/
/tmp/
*.log
.env
*.log
*.m3u
*.m3u.*
*.m3u8
*.m3u8.*
*.rdb
!/**/.gitkeep

View File

@@ -1,38 +1,54 @@
# Инфраструктурный слой проекта iptv.axenov.dev
> **Адрес**: https://iptv.axenov.dev
> **FAQ**: https://iptv.axenov.dev/faq
> **Исходный код**: https://git.axenov.dev/IPTV
Docker-окружение для работы проекта iptv.axenov.dev.
Содержит docker-окружение для запуска проекта iptv.axenov.dev.
> **Веб-сайт:** [iptv.axenov.dev](https://iptv.axenov.dev)
> **Зеркало:** [m3u.su](https://m3u.su)
> Исходный код: [git.axenov.dev/IPTV](https://git.axenov.dev/IPTV)
> Telegram-канал: [@iptv_aggregator](https://t.me/iptv_aggregator)
> Обсуждение: [@iptv_aggregator_chat](https://t.me/iptv_aggregator_chat)
> Дополнительные сведения: [git.axenov.dev/IPTV/.profile](https://git.axenov.dev/IPTV/.profile)
## Использованный стек
* [docker compose](https://docs.docker.com/compose/)
* [php8.3-fpm](https://www.php.net/releases/8.3/ru.php)
* [php8.4-fpm](https://www.php.net/releases/8.4/ru.php)
* [nginx](https://nginx.org/ru/)
* [keydb](https://docs.keydb.dev/docs/)
* [iptvc](https://git.axenov.dev/IPTV/iptvc)
* bash
## Установка и настройка
```
git clone https://git.axenov.dev/IPTV/docker.git iptv
cp .env.example .env
git clone https://git.axenov.dev/IPTV/svc-main.git
cp svc-main/.env.example svc-main/.env
docker exec -it iptv-php composer i
docker compose up -d --build
wget -O - https://git.axenov.dev/IPTV/iptv-docker/raw/branch/master/iptv | bash -s - init
```
### Описание переменных окружения
##рипт [`iptv`](./iptv)
Это инструмент, который позволяет быстро управлять локальной средой `lis-docker`:
* инициализировать с нуля, как в примере выше;
* управлять образами и контейнерами среды.
> Управление средой не всегда удобно через команды git и docker, поэтому рекомендуется использовать `./iptv`.
Набери `./iptv help` для справки по использованию.
При доработке используй [линтер](https://www.shellcheck.net): `shellcheck -s bash iptv`
## Описание переменных окружения
* `IPTV_ENV` -- окружение для развёртывания: это имена директорий и/или префиксы имён конфигов, которые будут проброшены в контейнеры;
* `REDIS_UID`, `REDIS_GID` -- ID поьзователя/группы для разрешения владельца файлов и директорий keydb;
* `REDIS_PORT` -- порт keydb, который будет проброшен на хост.
* `KEYDB_UID`, `KEYDB_GID` -- ID пользователя/группы для разрешения владельца файлов и директорий keydb;
* `KEYDB_PORT` -- порт keydb, который будет проброшен на хост.
* `KEYDB_USERNAME`, `KEYDB_PASSWORD` -- реквизиты доступа к keydb;
* `CHECKER_DB` -- БД keydb для хранения кеша проверенных плейлистов;
* `CHECKER_TTL` -- время жизни кеша проверенных плейлистов;
* `CHECKER_WAIT` -- кол-во секунд между запусками iptvc;
* `CHECKER_INIFILE` -- путь к файлу списка плейлистов внутри контейнера;
* `CHECKER_TAGFILE` -- путь к файлу списка тегов внутри контейнера.
### Reverse-proxy
## Reverse-proxy
На сервере опционально можно настроить реверс-прокси до контейнера веб-сервиса, например, чтобы настроить доступ по доменному имени, изменить порт, подключить SSL-сертификаты или др.

View File

@@ -20,26 +20,53 @@ services:
<<: *common-attributes
container_name: iptv-keydb
image: eqalpha/keydb:latest
user: "${REDIS_UID}:${REDIS_GID}"
user: "${KEYDB_UID}:${KEYDB_GID}"
volumes:
- ./docker/keydb/keydb.conf:/etc/keydb/keydb.conf
- ./docker/keydb/data/:/data:rw
- ./log/keydb:/var/log/keydb/:rw
ports:
- "${REDIS_PORT:-6379}:6379"
- "${KEYDB_PORT:-6379}:6379"
php:
web:
<<: *common-attributes
container_name: iptv-php
container_name: iptv-web
build:
dockerfile: dockerfile.web.${IPTV_ENV}
environment:
- PHP_IDE_CONFIG=serverName=iptv.local
build:
dockerfile: docker/php/${IPTV_ENV}/dockerfile
volumes:
- ./docker/php/${IPTV_ENV}/www.conf:/usr/local/etc/php-fpm.d/www.conf:ro
- ./docker/php/${IPTV_ENV}/php.ini:/usr/local/etc/php/conf.d/php.ini:ro
- ./playlists/playlists.ini:/var/www/config/playlists.ini
# - ./playlists/channels.json:/var/www/config/channels.json
- ./log/php:/var/log/php:rw
- ./svc-main:/var/www:rw
- ./web:/var/www:rw
depends_on:
- keydb
checker:
<<: *common-attributes
container_name: iptv-checker
build:
dockerfile: ./dockerfile.checker
environment:
- CACHE_ENABLED=true
# - CACHE_HOST=localhost
- CACHE_HOST=iptv-keydb
- CACHE_PORT=${KEYDB_PORT:-6379}
- CACHE_USERNAME=${KEYDB_USERNAME}
- CACHE_PASSWORD=${KEYDB_PASSWORD}
- CACHE_DB=${CHECKER_DB:-0}
- CACHE_TTL=${CHECKER_TTL:-1800}
- CHECKER_WAIT=${CHECKER_WAIT:-60}
- CHECKER_INIFILE=${CHECKER_INIFILE:-/app/playlists.ini}
- CHECKER_TAGFILE=${CHECKER_TAGFILE:-/app/channels.json}
volumes:
- ./docker/checker/entrypoint.sh:/entrypoint.sh
- ./iptvc/:/app/
- ./playlists/playlists.ini:${CHECKER_INIFILE:-/app/playlists.ini}
- ./playlists/channels.json:${CHECKER_TAGFILE:-/app/channels.json}
depends_on:
- keydb
@@ -50,10 +77,10 @@ services:
volumes:
- ./docker/nginx/vhost.conf:/etc/nginx/conf.d/default.conf:ro
- ./log/nginx:/var/log/nginx:rw
- ./svc-main:/var/www:ro
- ./web:/var/www:ro
ports:
- "8080:80"
links:
- php
- web
depends_on:
- php
- web

24
docker/checker/entrypoint.sh Executable file
View File

@@ -0,0 +1,24 @@
#!/bin/bash
echo "CHECKER_WAIT=$CHECKER_WAIT"
echo "CHECKER_INIFILE=$CHECKER_INIFILE"
echo "CHECKER_TAGFILE=$CHECKER_TAGFILE"
binary="/app/bin/linux_amd64/iptvc"
args="check -i $CHECKER_INIFILE -t $CHECKER_TAGFILE"
go get
make linux
if [ ! -f "$binary" ]; then
echo "Not found: $binary"
exit 1
fi
while true; do
echo
echo "Running: $binary $args"
$binary $args
echo "Waiting $CHECKER_WAIT seconds"
sleep $CHECKER_WAIT
done

View File

@@ -20,7 +20,7 @@ server {
}
location ~ \.php$ {
try_files $uri /index.php =404;
fastcgi_pass php:9000;
fastcgi_pass web:9000;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

View File

@@ -1,21 +0,0 @@
FROM php:8.3-fpm
RUN apt update && \
apt upgrade -y && \
apt install -y git unzip 7zip
# https://pecl.php.net/package/xdebug
# https://pecl.php.net/package/redis
RUN pecl channel-update pecl.php.net && \
pecl install xdebug-3.4.1 redis && \
docker-php-ext-enable redis && \
mkdir -p /var/run/php && \
mkdir -p /var/log/php && \
chmod -R 777 /var/log/php
COPY --from=composer /usr/bin/composer /usr/local/bin/composer
EXPOSE 9000
WORKDIR /var/www
CMD composer install
ENTRYPOINT php-fpm

View File

@@ -1,5 +1,5 @@
[PHP]
error_reporting = E_ALL
error_reporting = E_ALL & ~E_NOTICE & ~E_DEPRECATED
expose_php = Off
file_uploads = Off
max_execution_time=-1

View File

@@ -19,3 +19,4 @@ php_flag[display_errors] = on
php_admin_value[error_log] = /var/log/php/$pool.error.log
php_admin_flag[log_errors] = on
php_admin_value[memory_limit] = 512M
php_admin_value[error_reporting] = E_ALL & ~E_NOTICE & ~E_DEPRECATED

View File

@@ -1,21 +0,0 @@
FROM php:8.3-fpm
RUN apt update && \
apt upgrade -y && \
apt install -y git
# https://pecl.php.net/package/redis
RUN pecl channel-update pecl.php.net && \
pecl install redis && \
docker-php-ext-enable redis && \
mkdir -p /var/log/php && \
chmod -R 777 /var/log/php && \
git config --global --add safe.directory /var/www
COPY --from=composer /usr/bin/composer /usr/local/bin/composer
USER www-data
EXPOSE 9000
WORKDIR /var/www
CMD composer install --no-dev --optimize-autoloader
ENTRYPOINT php-fpm

View File

@@ -19,3 +19,4 @@ php_flag[display_errors] = on
php_admin_value[error_log] = /var/log/php/$pool.error.log
php_admin_flag[log_errors] = on
php_admin_value[memory_limit] = 512M
php_admin_value[error_reporting] = E_ALL & ~E_NOTICE & ~E_DEPRECATED

14
dockerfile.checker Normal file
View File

@@ -0,0 +1,14 @@
FROM alpine:3.21 AS iptvc-compiler
RUN apk --no-cache add \
bash \
tzdata \
go \
make
RUN mkdir /app && \
chmod 777 /app
WORKDIR /app
ENTRYPOINT ["/entrypoint.sh"]

37
dockerfile.web.dev Normal file
View File

@@ -0,0 +1,37 @@
FROM php:8.4-fpm AS iptv-php-dev
RUN apt update && \
apt upgrade -y && \
apt install -y \
git \
unzip \
7zip \
cron \
zlib1g-dev \
imagemagick \
libpng-dev \
libjpeg-dev
# https://pecl.php.net/package/xdebug
# https://pecl.php.net/package/redis
RUN pecl channel-update pecl.php.net && \
pecl install \
xdebug-3.4.1 \
redis-6.1.0
RUN docker-php-ext-enable redis && \
docker-php-ext-configure gd --with-jpeg && \
docker-php-ext-install gd
RUN mkdir -p /var/run/php && \
mkdir -p /var/log/php && \
chmod -R 777 /var/log/php
COPY --from=composer /usr/bin/composer /usr/local/bin/composer
RUN git config --global --add safe.directory /var/www
EXPOSE 9000
WORKDIR /var/www
CMD composer install && \
php-fpm --nodaemonize

34
dockerfile.web.prod Normal file
View File

@@ -0,0 +1,34 @@
FROM php:8.4-fpm AS iptv-php-prod
RUN apt update && \
apt upgrade -y && \
apt install -y \
git \
unzip \
7zip \
cron \
zlib1g-dev \
imagemagick \
libpng-dev \
libjpeg-dev
# https://pecl.php.net/package/redis
RUN pecl channel-update pecl.php.net && \
pecl install redis-6.1.0
RUN docker-php-ext-enable xdebug redis && \
docker-php-ext-configure gd --with-jpeg && \
docker-php-ext-install gd
RUN mkdir -p /var/run/php && \
mkdir -p /var/log/php && \
chmod -R 777 /var/log/php
COPY --from=composer /usr/bin/composer /usr/local/bin/composer
RUN git config --global --add safe.directory /var/www
EXPOSE 9000
WORKDIR /var/www
RUN composer install
ENTRYPOINT php-fpm --nodaemonize

855
iptv
View File

@@ -1,32 +1,835 @@
#!/bin/bash
# https://gist.github.com/anthonyaxenov/89c99e09ddb195985707e2b24a57257d
#!/usr/bin/env bash
##########################################################################################
# Скрипт управления проектом iptv.axenov.dev
#
# Copyright (c) 2025 Антон Аксенов <anthonyaxenov@gmail.com>
# MIT License, see LICENSE file for more info.
##########################################################################################
CONTAINER="iptv-php" # the name of the container in which to 'exec' something
CONFIG="$(dirname $([ -L $0 ] && readlink -f $0 || echo $0))/docker-compose.yml" # path to compose yml file
CMD="docker compose -f $CONFIG" # docker-compose command
APP_URL='http://localhost:8080/'
# shellcheck disable=SC2015,SC2103,SC2164,SC2155
set -o pipefail
open_browser() {
if which xdg-open > /dev/null; then
xdg-open "$1" </dev/null >/dev/null 2>&1 & disown
elif which gnome-open > /dev/null; then
gnome-open "$1" </dev/null >/dev/null 2>&1 & disown
########################################################
# Служебные исходные переменные
########################################################
if [[ "${BASH_SOURCE[*]}" ]]; then
[[ "${BASH_SOURCE[0]}" != "$0" ]] && echo "*** Команда source iptv запрещена ***" && exit 40
ROOT_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
else
# script running from stdin
ROOT_PATH="$(pwd)"
fi
IPTV_PROJECTS=("iptvc" "web" "playlists")
IPTV_GITEA_URL_SSH="git@git.axenov.dev:IPTV"
IPTV_GITEA_URL_HTTPS="https://git.axenov.dev/IPTV"
IPTV_DOCKER_URL_SSH="$IPTV_GITEA_URL_SSH/iptv-docker.git"
for sig in SIGHUP SIGINT SIGQUIT SIGABRT SIGKILL SIGTERM SIGTSTP; do
# https://faculty.cs.niu.edu/~hutchins/csci480/signals.htm
# shellcheck disable=SC2064
trap "set +x && echo && echo && echo '*** Прервано сигналом $sig, остановка ***' && exit" $sig
done
[[ $IPTV_DEBUG == 1 ]] && DEBUG_MODE=1
[[ $IPTV_DEBUG -gt 1 ]] && set -x
RAW_ARGS=("$@")
COMMAND="$1"
shift
########################################################
# Ввод/вывод
########################################################
which tput > /dev/null 2>&1 && [ "$(tput -T"$TERM" colors)" -gt 8 ] && CAN_USE_COLORS=1 || CAN_USE_COLORS=0
IPTV_COLORS=${IPTV_COLORS:-$CAN_USE_COLORS}
[[ $IPTV_COLORS == 1 ]] && FBOLD="$(tput bold)" || FBOLD=''
[[ $IPTV_COLORS == 1 ]] && FDIM="$(tput dim)" || FDIM=''
[[ $IPTV_COLORS == 1 ]] && FRESET="$(tput sgr0)" || FRESET=''
ask() {
IFS= read -rp "$(print "${FBOLD}$1" ): " "$2"
}
print() {
echo -e "$*${FRESET}"
}
debug() {
[[ "$DEBUG_MODE" != 1 ]] && return
if [ "$2" ]; then
print "${FDIM}> ${FUNCNAME[1]:-?}():${BASH_LINENO:-?}\t$* ${FRESET}" >&2
else
print "${FDIM}> $* ${FRESET}" >&2
fi
}
case "$1" in
'' | 'help' ) echo -e "Provide one of operations: \t init, start, stop, up, down, restart, rebuild, open";
echo "Otherwise all args will passed to 'docker exec -ti $CONTAINER ...'" ;;
'init' ) cp src/.env.example src/.env && \
./iptv up && \
./iptv composer i && \
echo "Project started successfully! $APP_URL" ;;
'up' ) $CMD up -d --build && ./iptv open ;; # build and start containers
'down' ) $CMD down --remove-orphans ;; # stop and remove containers
'start' ) $CMD start ;; # start containers
'stop' ) $CMD stop ;; # stop containers
'restart' ) $CMD stop && $CMD start ;; # restart containers
'rebuild' ) $CMD down --remove-orphans && $CMD up -d --build ;; # rebuild containers
'open' ) open_browser $APP_URL && echo -e "\nYou're welcome!\n\t$APP_URL" ;;
* ) docker exec -ti $CONTAINER $* ;; # exec anything else in container
var_dump() {
debug "$1 = ${!1}"
}
inform() {
print "$*${FRESET}"
}
subtitle() {
echo
inform "${FBOLD}$*${FRESET}"
}
title() {
subtitle "$@"
echo
}
success() {
print "${FBOLD}$*${FRESET}"
}
warn() {
print "${FBOLD}Внимание!${FRESET} $*${FRESET}"
}
error() {
print "${FBOLD}Ошибка:${FRESET} $*${FRESET}" >&2
print_stacktrace
}
die() {
error "${1:-halted}"
exit "${2:-255}"
}
print_stacktrace() {
[[ "$DEBUG_MODE" != 1 ]] && return
local i
local stack_size=${#FUNCNAME[@]}
debug "Callstack:"
# for (( i=$stack_size-1; i>=1; i-- )); do
for (( i=1; i<stack_size; i++ )); do
local func="${FUNCNAME[$i]}"
[ "$func" = "" ] && func=MAIN
local linen="${BASH_LINENO[$(( i - 1 ))]}"
local src="${BASH_SOURCE[$i]}"
[ "$src" = "" ] && src=non_file_source
debug " at $func $src:$linen"
done
}
########################################################
# Базовые хелперы
########################################################
# Возвращает абсолютный путь до $1
abspath() {
realpath -q "$1"
}
# Проверяет, указывает ли путь $1 на директорию
is_dir() {
[ -d "$(abspath "$1")" ]
}
# Проверяет, указывает ли путь $1 на файл
is_file() {
[ -f "$(abspath "$1")" ]
}
# Проверяет существование функции с именем $1
is_function() {
declare -F "$1" > /dev/null
}
# Проверяет соответствие строки $1 регулярному выражению $2
regex_match() {
[[ "$1" =~ ^$2$ ]]
}
# Проверяет соответствие строки $1 регулярному выражению $2 с помощью grep
grep_match() {
printf "%s" "$1" | grep -qE "$2" >/dev/null 2>&1
}
# Возвращает название текущей ОС
get_os() {
case "$(uname -s)" in
Linux*) echo Linux ;;
Darwin*) echo Macos ;;
CYGWIN*) echo Cygwin ;;
MINGW*) echo MinGw ;;
MSYS_NT*) echo Git ;;
*) return 1 ;;
esac
}
# Обрезает пробельные символы с начала и с конца строки
trim() {
echo "$1" | xargs
}
########################################################
# Функции парсинга аргументов
# https://gist.axenov.dev/anthony/sh-args
########################################################
# Парсит короткий аргумент
arg() {
[ "$1" ] || { echo "Argument name is not specified!" >&2 && exit 1; }
local arg_name="${1:0:1}" # first character of argument name to find
local is_flag="$2" || 0 # 1 if we need just find a flag, 0 to get a value
local var_name="$3" || 0 # variable name to return value into or 0 to echo it in stdout
local value= # initialize empty value to check if we found one later
local arg_found=0 # marker of found argument
for idx in "${!RAW_ARGS[@]}"; do # going through all args
local arg_search=${RAW_ARGS[idx]} # get current argument
# skip $arg_search if it starts with '--' or letter
grep_match "$arg_search" "^(\w|--)" && continue
# clear $arg_search from special and duplicate characters, e.g. 'fas-)dfs' will become 'fasd'
local arg_chars="$(printf "%s" "$arg_search" \
| tr -s "[$arg_search]" 2>/dev/null \
| tr -d "[:punct:][:blank:]" 2>/dev/null)"
# if $arg_name is not one of $arg_chars the skip it
grep_match "-$arg_name" "^-[$arg_chars]$" || continue
arg_found=1
# then return '1'|'0' back into $value if we need flag or next arg value otherwise
[[ "$is_flag" == 1 ]] && value=1 || value="${RAW_ARGS[idx+1]}"
break
done
[[ "$is_flag" == 1 ]] && [[ -z "$value" ]] && value=0;
# if value we found is empty or looks like another argument then exit with error message
if [ "$arg_found" = 1 ] && ! grep_match "$value" "^[[:graph:]]+$" || grep_match "$value" "^--?\w+$"; then
echo "ERROR: Argument '-$arg_name' must have correct value!" >&2 && exit 1
fi
# return '$value' back into $var_name (if exists) or echo in stdout
[ "$var_name" ] && eval "$var_name='$value'" || echo "$value"
}
# Парсит длинный аргумент
argl() {
[ "$1" ] || { echo "Argument name is not specified!" >&2 && exit 1; }
local arg_name="$1" # argument name to find
local is_flag="$2" || 0 # 1 if we need just find a flag, 0 to get a value
local var_name="$3" || 0 # variable name to return value into or 0 to echo it in stdout
local value= # initialize empty value to check if we found one later
local arg_found=0 # marker of found argument
for idx in "${!RAW_ARGS[@]}"; do # going through all args
local arg_search="${RAW_ARGS[idx]}" # get current argument
if [ "$arg_search" = "--$arg_name" ]; then # if current arg begins with two dashes
# then return '1' back into $value if we need flag or next arg value otherwise
[[ "$is_flag" == 1 ]] && value=1 || value="${RAW_ARGS[idx+1]}"
break # stop the loop
elif grep_match "$arg_search" "^--$arg_name=.+$"; then # check if $arg like '--foo=bar'
# then return '1' back into $value if we need flag or part from '=' to arg's end as value otherwise
[[ "$is_flag" == 1 ]] && value=1 || value="${arg_search#*=}"
break # stop the loop
fi
done
[[ "$is_flag" == 1 ]] && [[ -z "$value" ]] && value=0;
# if value we found is empty or looks like another argument then exit with error message
if [ "$arg_found" = 1 ] && ! grep_match "$value" "^[[:graph:]]+$" || grep_match "$value" "^--?\w+$"; then
echo "ERROR: Argument '--$arg_name' must have correct value!" >&2 && exit 1;
fi
# return '$value' back into $var_name (if exists) or echo in stdout
[ "$var_name" ] && eval "$var_name='$value'" || echo "$value"
}
########################################################
# Функции контроля системных пакетов
########################################################
# Проверяет наличие команды в системе
installed() {
command -v "$1" >/dev/null 2>&1
}
# Проверяет наличие пакета в системе
installed_pkg() {
dpkg --list | grep -qw "ii $1"
}
# Требует наличие пакета в системе
require() {
sw=()
for package in "$@"; do
# if ! installed "$package" && ! installed_pkg "$package"; then
if ! installed "$package"; then
sw+=("$package")
fi
done
if [ ${#sw[@]} -gt 0 ]; then
die "Это ПО должно быть установлено в системе:\n${sw[*]}" 200
fi
}
########################################################
# Функции для работы с git
########################################################
# Проверяет, является ли директория git-репозиторием
git.is_repo() {
require git
is_dir "$1/" && is_dir "$1/.git/"
}
# Клонирует репозиторий
git.clone() {
require git
cmd="git clone $*"
debug "Команда: $cmd"
$cmd
}
########################################################
# Функции для работы с docker
########################################################
# Вызывает корректную команду docker compose
docker.compose() {
require docker
args=${*/--profiles=[a-zA-Z_,0-9]*/}
if docker compose &>/dev/null; then
local cmd="docker compose $args"
elif installed_pkg "docker-compose"; then
local cmd="docker-compose $args"
warn
warn "docker-compose v1 устарел и не поддерживается, его поведение непредсказуемо."
warn "Обнови docker согласно документации: https://docs.docker.com/engine/install/"
warn
else
error "Должен быть установлен docker-compose-plugin!"
die "Установи docker согласно документации: https://docs.docker.com/engine/install/" 2
fi
debug "Команда: $cmd"
$cmd
}
# Выводит информацию о контейнере
docker.inspect() {
cmd="docker inspect $*"
debug "Команда: $cmd"
$cmd 2>/dev/null
}
# Строит образы согласно файлов iptv-docker/docker-compose*.yml
docker.build_base_images() {
# for file in compose*.yml; do
# subtitle "Построение базовых образов: $file"
# docker.compose -f "$file" build
# done
subtitle "Построение образов"
docker.compose build
success "Базовые образы построены"
}
# Выполняет команду в контейнере от имени root
docker.exec() {
cmd="docker exec -u root -it $*"
debug "Команда: $cmd"
$cmd
}
# Выполняет команду в контейнере от имени www-data
docker.exec_www() {
cmd="docker exec -u www-data -it $*"
debug "Команда: $cmd"
$cmd
}
########################################################
# Хелперы для обработки команд
########################################################
# Возвращает ssh-адрес к репозиторию проекта
project_url_ssh() {
echo "$IPTV_GITEA_URL_SSH/${IPTV_PROJECTS[$1]}.git"
}
# Возвращает https-адрес к репозиторию проекта
project_url_https() {
echo "$IPTV_GITEA_URL_HTTPS/${IPTV_PROJECTS[$1]}"
}
# Копирует .env.example в .env, если возможно
prepare_dot_env() {
if is_file .env.example && ! is_file .env; then
debug "Копирование .env.example => .env"
cp .env.example .env
fi
}
# Клонирует проект
project_clone() {
local repo_url="$1"; shift
local repo_path="$1"; shift
if git.is_repo "$repo_path"; then
debug "Репозиторий уже существует: $repo_path"
else
debug "Репозиторий будет склонирован: $repo_path"
git.clone "$repo_url" "$repo_path" "$@"
fi
}
# Проверяет передачу флагов -h|--help в команду и выводит справку
process_help_arg() {
command="${FUNCNAME[1]}"
need_help=$(arg help 1)
[[ "$need_help" -eq 0 ]] && need_help=$(argl help 1)
[[ "$need_help" -eq 1 ]] && help "$command"
}
# Выводит список сервисов
list_services_compose() {
services=$(docker.compose --profile full config --services | sort)
IFS=$'\n' sorted=("${services[@]}")
unset IFS
for name in "${sorted[@]}"; do
print " $name"
done
}
# Возвращает корректное название сервиса из композа
find_service_compose() {
svc="$1"
[ -z "$svc" ] && die "неизвестный сервис" 2
for known in $(docker.compose config --services); do
if [ "$known" = "$svc" ]; then
debug "Сервис '$svc' найден в композе"
echo "iptv-$known"
exit
fi
done
debug "Сервис '$svc' не найден в композе"
return 1
}
# Возвращает корректные названия сервисов из композа
find_services_compose() {
local services=''
[ "$*" ] && for svc in "$@"; do
grep_match "$svc" "^--?.*" && continue
var_dump svc
svc="$(find_service_compose "$svc")"
services="$services iptv-$svc"
done
trim "$services"
}
########################################################
# Главные функции обработки команд
########################################################
# Инициализирует репозиторий iptv-docker и все вложенные в него
init() {
process_help_arg
local docker_repo_path=$ROOT_PATH
local basename=$(basename "$docker_repo_path")
if [ ! "$basename" = "iptv-docker" ]; then
# это наиболее надёжный способ понять откуда запускается скрипт
docker_repo_path="$docker_repo_path/iptv-docker"
fi
#TODO установка ПО
subtitle "Подготовка локальной среды..."
project_clone "$IPTV_DOCKER_URL_SSH" "$docker_repo_path"
cd "$docker_repo_path"
prepare_dot_env
local counter=1
local repo_count=${#IPTV_PROJECTS[@]}
for repo_name in "${IPTV_PROJECTS[@]}"; do
local project_repo_path="$docker_repo_path/$repo_name"
subtitle "[$counter/$repo_count] Подготовка репозитория ${FBOLD}$repo_name${FRESET}..."
local project_repo_url=$(project_url_ssh "$repo_name")
debug "Известная ссылка на репозиторий: $project_repo_url"
project_clone "$project_repo_url" "$project_repo_path"
cd "$project_repo_path"
prepare_dot_env
cd - >/dev/null
success "Репозиторий $repo_name готов"
counter=$((counter+1))
done
success "Локальная среда развёрнута: $docker_repo_path"
cd "$docker_repo_path"
docker.build_base_images && up
success "Проверь корректность значений, указанных в файлах .env, и выполни ${FBOLD}./iptv rebuild${FRESET} при необходимости."
}
# Создаёт и запускает контейнеры
up() {
process_help_arg
subtitle "Создание и запуск контейнеров"
argl profiles 0 profiles
local services=''
[ "$*" ] && services="$(find_services_compose "$@")"
COMPOSE_PROFILES="$profiles" docker.compose up "$services" --build --detach --remove-orphans && \
success 'Среда запущена успешно'
}
# Запускает созданные контейнеры
start() {
process_help_arg
subtitle "Запуск созданных ранее контейнеров"
profiles="$(argl profiles 0)"
local services=''
[ "$*" ] && services="$(find_services_compose "$@")"
COMPOSE_PROFILES="$profiles" docker.compose start "$services" && \
success 'Среда запущена успешно'
}
# Останавливает и удаляет контейнеры
down() {
process_help_arg
subtitle "Остановка и удаление контейнеров"
argl profiles 0 profiles
[[ -z "$profiles" ]] && profiles="full"
local services=''
[ "$*" ] && services="$(find_services_compose "$@")"
COMPOSE_PROFILES="$profiles" docker.compose down "$services" --remove-orphans && \
success 'Среда остановлена успешно'
}
# Останавливает контейнеры
stop() {
process_help_arg
subtitle "Остановка контейнеров"
profiles=$(argl profiles 0)
[[ -z "$profiles" ]] && profiles="full"
local services=''
[ "$*" ] && services="$(find_services_compose "$@")"
COMPOSE_PROFILES="$profiles" docker.compose stop "$services" && \
success 'Среда остановлена успешно'
}
# Останавливает, удаляет, создаёт и запускает контейнеры
rebuild() {
process_help_arg
is_full=$(arg full 1)
[ "$is_full" = 0 ] && is_full=$(argl full 1)
[ -n "$*" ] && down "$@"
[ "$is_full" = 1 ] && docker.build_base_images
up "$@"
}
# Останавливает и запускает созданные контейнеры
restart() {
process_help_arg
stop "$@" && start "$@"
}
# Останавливает среду, удаляет все контейнеры и образы
purge() {
process_help_arg
subtitle "Удаление docker-образов и контейнеров"
down
docker rmi "$(docker image list | grep iptv- | awk '{print $3}')"
docker system prune --all --force
success 'Образы удалены успешно'
}
# Выводит логи сервиса
logs() {
process_help_arg
[ -z "$1" ] && die "не указан сервис" 8
svc=$(find_service_compose "$1")
[ -z "$svc" ] && die "неизвестный сервис" 3
subtitle "Логи сервиса $svc"
shift
cmd="docker logs $svc $*"
debug "Команда: $cmd"
$cmd
}
# Выводит статистику потребления ресурсов контейнерами
stats() {
subtitle "Состояние контейнеров"
docker.compose stats "$@"
}
########################################################
# Команды справки
########################################################
help() {
print "${FBOLD}Среда разработки iptv.axenov.dev"
is_function "help.$1" && help."$1" && exit
print "Использование:"
print " ./iptv КОМАНДА [АРГУМЕНТЫ]"
print
print "Доступные КОМАНДЫ:"
print " h|help - вывести это сообщение и выйти"
print " init - инициализировать окружение"
print " u|up - построить образы, контейнеры и запустить их"
print " start - запустить построенные ранее контейнеры"
print " d|down - остановить и удалить контейнеры"
print " stop - остановить контейнеры"
print " r|rebuild - выполнить 'down' и 'up'"
print " restart - выполнить 'stop' и 'start'"
print " purge - удалить контейнеры и образы"
print " update - обновить репозитории"
print " info - вывести версии ПО контейнера сервиса"
print " f|flame - сгенерировать флеймграф из трейса xdebug"
print " fix - исправить некоторые проблемы окружения"
print " exec - выполнить произвольную команду в контейнере"
print " logs - вывести логи контейнера"
print " profiles - вывести список профилей"
print " services - вывести список сервисов профиля"
print " info - вывести информацию о ПО в контейнере"
print " status - вывести статус сервиса и его репозитория"
print " stats - вывести информацию о потреблении ресурсов окружением"
print
print "Доступные АРГУМЕНТЫ:"
print " -h|--help - вывести справку по любой КОМАНДЕ"
print
print "Любая КОМАНДА может также иметь свои АРГУМЕНТЫ."
print "Некоторые КОМАНДЫ могут также поддерживать АРГУМЕНТЫ 'docker' и его субсомманд."
print
print "См. './iptv help КОМАНДА' или './iptv КОМАНДА -h|--help' для справки."
}
help.help() {
print "КОМАНДА: help"
print "Отображает справку по скрипту и его командам."
print
print "Использование:"
print " ./iptv help [КОМАНДА]"
print " ./iptv КОМАНДА [-h|--help]"
print
print "Если КОМАНДА не указана, будет отображена справка по скрипту со списком допустимых КОМАНД."
}
help.init() {
print "КОМАНДА: init"
print "Инициализирует окружение разработки. Клонирует репозиторий iptv-docker, клонирует"
print "внутрь исходники всех сервисов и создаёт заготовки файлов .env для них. Можно"
print "использовать для подгрузки свежих сервисов, ещё не развёрнутых локально."
print
print "Использование:"
print " ./iptv init [АРГУМЕНТЫ]"
print
print "Доступные АРГУМЕНТЫ:"
print " -n|--name ИМЯ - установить ИМЯ в качестве имени пользователя git"
print " -e|--email ПОЧТА - установить ПОЧТУ в качестве email пользователя git"
print " -h|--help - вывести это сообщение и выйти"
print
print "Если --name и/или --email не переданы, то данные будут взяты из глобального конфига git."
print
print "ИМЯ и ПОЧТА проверяются на валидность:"
print " - ИМЯ должно содержать имя и фамилию на кириллице, разделённые пробелом;"
print " - ПОЧТА должна быть корпоративной (@bars.group или @bars-open.ru)."
print
print "Если ИМЯ или ПОЧТА некорректны, КОМАНДА завершится с ошибкой и предложением передать"
print "явно ИМЯ и ПОЧТУ с помощью аргументов."
print
print "После выполнения КОМАНДЫ необходимо указать корректные данные в файлах .env сервисов."
}
help.up() {
print "КОМАНДА: up"
print "Выполняет перестроение образов (включая базовые, при необходимости)"
print "создание и запуск контейнеров."
print
print "Использование:"
print " ./iptv up [АРГУМЕНТЫ] [СЕРВИСЫ...]"
print
print "Доступные АРГУМЕНТЫ:"
print " -h|--help - вывести это сообщение и выйти"
print
print "Если указан один и более СЕРВИСОВ, то КОМАНДА будет применена только к ним."
print
print "СЕРВИСЫ можно передавать без префикса 'iptv-'"
}
help.start() {
print "КОМАНДА: start"
print "Выполняет запуск построенных ранее контейнеров."
print
print "Использование:"
print " ./iptv start [АРГУМЕНТЫ] [СЕРВИСЫ...]"
print
print "Доступные АРГУМЕНТЫ:"
print " -h|--help - вывести это сообщение и выйти"
print
print "Если указан один и более СЕРВИСОВ, то КОМАНДА будет применена только к ним."
print
print "СЕРВИСЫ можно передавать без префикса 'iptv-'"
}
help.down() {
print "КОМАНДА: down"
print "Выполняет остановку и удаление запущенных контейнеров."
print
print "Использование:"
print " ./iptv down [АРГУМЕНТЫ] [СЕРВИСЫ...]"
print
print "Доступные АРГУМЕНТЫ:"
print " -h|--help - вывести это сообщение и выйти"
print
print "Если указан один и более СЕРВИСОВ, то КОМАНДА будет применена только к ним."
print
print "СЕРВИСЫ можно передавать без префикса 'iptv-'"
}
help.stop() {
print "КОМАНДА: stop"
print "Выполняет остановку запущенных контейнеров."
print
print "Использование:"
print " ./iptv stop [АРГУМЕНТЫ] [СЕРВИСЫ...]"
print
print "Доступные АРГУМЕНТЫ:"
print " -h|--help - вывести это сообщение и выйти"
print
print "Если указан один и более СЕРВИСОВ, то КОМАНДА будет применена только к ним."
print
print "СЕРВИСЫ можно передавать без префикса 'iptv-'"
}
help.rebuild() {
print "КОМАНДА: rebuild"
print "Выполняет 'down' и 'up'."
print
print "Использование:"
print " ./iptv rebuild [АРГУМЕНТЫ] [СЕРВИСЫ...]"
print
print "Доступные АРГУМЕНТЫ:"
print " -h|--help - вывести это сообщение и выйти"
print
print "Если указан один и более СЕРВИСОВ, то КОМАНДА будет применена только к ним."
print
print "СЕРВИСЫ можно передавать без префикса 'iptv-'"
}
help.restart() {
print "КОМАНДА: restart"
print "Выполняет 'stop' и 'start'."
print
print "Использование:"
print " ./iptv restart [АРГУМЕНТЫ] [СЕРВИСЫ...]"
print
print "Доступные АРГУМЕНТЫ:"
print " -h|--help - вывести это сообщение и выйти"
print
print "Если указан один и более СЕРВИСОВ, то КОМАНДА будет применена только к ним."
print
print "СЕРВИСЫ можно передавать без префикса 'iptv-'"
}
help.purge() {
print "КОМАНДА: purge"
print "Удаляет все образы и контейнеры 'iptv-*'."
print
print "Использование:"
print " ./iptv purge [АРГУМЕНТЫ]"
print
print "Доступные АРГУМЕНТЫ:"
print " -h|--help - вывести это сообщение и выйти"
}
help.logs() {
print "КОМАНДА: logs"
print "Выводит логи указанного СЕРВИСА."
print
print "Использование:"
print " ./iptv logs СЕРВИС [АРГУМЕНТЫ]"
print
print "Доступные АРГУМЕНТЫ:"
print " -h|--help - вывести это сообщение и выйти"
print
print "Поддерживаются АРГУМЕНТЫ 'docker logs'."
print
print "СЕРВИС можно передавать без префикса 'iptv-'"
}
help.stats() {
print "КОМАНДА: stats"
print "Выводит потребление ресурсов окружением разработки."
print
print "Использование:"
print " ./iptv stats [АРГУМЕНТЫ]"
print
print "Доступные АРГУМЕНТЫ:"
print " -h|--help - вывести это сообщение и выйти"
print
print "Поддерживаются АРГУМЕНТЫ 'docker compose stats'."
print "Рекомендуется использовать вместе с '--no-stream'."
}
########################################################
# Точка входа
########################################################
debug "Директория выполнения: $ROOT_PATH/$IPTV_DIR"
case "$COMMAND" in
h|help ) help "$1" ;;
init ) init "$@" ;;
u|up ) up "$@" ;;
start ) start "$@" ;;
d|down ) down "$@" ;;
stop ) stop "$@" ;;
r|rebuild ) rebuild "$@" ;;
restart ) restart "$@" ;;
purge ) purge ;;
logs ) logs "$@" ;;
stats ) stats "$@" ;;
* ) die "неизвестная команда. Смотри './iptv help' для справки." ;;
esac