× К оглавлению На главную Об авторе

Дата и время публикации:    

Проблема и решение

1. Cуть проблемы

Допустим, что вы разработчик и работает над собственными проектами, некоторые из которых находятся в совместной разработке по всему миру и находятся на GitHub, или других крупных системах контроля версий (versioning system). Однако, некоторые проекты могут быть настолько маленькими и закрытыми для публичного доступа, что размещение на публичных репозитариях является не желательным, потому что они могут быть пропроетарными или предназначенными только для внутреннего использования.[3.1]

Тем не менее, такая потребность есть всегда, поэтому для примера возьмем два проекта: первый — публичный, размещенный на GitHub проект procps-ptree [3.2], а второй – распространяемый в виде TAR-болла с тестовым примером для Flawfinder программы на С [3.3], оформленный мной в виде файла example-1.1.tar.gz, содержащий проект исходного кода, собираемый автоматически с помощью GNU Automake [3.4].

2. Решение

2.1 Удаленный репозитарий (remote repo)

На локальном узле создадим пользователя USER=git, вместе с назначением ему домашней директории /home/$USER и командной оболочки git-shell, как показано в дампе 2.1.1

Дамп 2.2.1

user@debian-11uni: ~/example-1.1$ git init -b v1.1
Initialized empty Git repository in /home/user/Build/yocto-poky/git-server/example-1.1/.git/
user@debian-11uni: ~/example-1.1$ git remote add local_proj /home/git/repos/example.git
user@debian-11uni: ~/example-1.1$ git add README.md
user@debian-11uni: ~/example-1.1$ git commit -m 'Store the file README.md'
[v1.1 (root-commit) ef6f2c6] Store the file README.md
 1 file changed, 10 insertions(+)
 create mode 100644 README.md
user@debian-11uni: ~/example-1.1$ git push --set-upstream origin v1.1
fatal: '/git/repos/example.git' does not appear to be a git repository
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

Для исправления ошибки "fatal: '/git/repos/example.git' does not appear to be a git repository" нужно выполнить действия по созданию локальной директории /home/git/repos/example.git и проинициализировать пустой с веткой master, как показано в дампе 2.2.2

Дамп 2.2.2

user@debian-11uni: ~/example-1.1$ sudo -u mkdir -m 755 -p /home/git/repos/example.git && cd /home/git/repos/example.git
user@debian-11uni:/home/git/repos/example.git$ sudo -u git git init .
Initialized empty Git repository in /home/git/repos/example.git/.git/
user@debian-11uni:/home/git/repos/example.git$ sudo -u git git config receive.denyCurrentBranch ignore

После чего, можно повторить git-push(1) в точке положения исходного кода ~/example-1.1, как было показано ранее в 2.2.1, но с некоторыми ньюансами [3.7], которые показаны в дампе 2.2.3

Дамп 2.2.3

user@debian-11uni: ~/$ git clone /home/git/repos/example.git && cd example
user@debian-11uni: ~/example$ cp ../example-1.1/README.md .
user@debian-11uni: ~/example$ git add README.md
user@debian-11uni: ~/example$ git commit -m 'Store the file README.md'
$ git push --set-upstream origin master
Перечисление объектов: 3, готово.
Подсчет объектов: 100% (3/3), готово.
При сжатии изменений используется до 8 потоков
Сжатие объектов: 100% (2/2), готово.
Запись объектов: 100% (3/3), 469 байтов | 469.00 КиБ/с, готово.
Всего 3 (изменений 0), повторно использовано 0 (изменений 0), повторно использовано пакетов 0
error: сбой при внешней распаковке unable to create temporary object directory
To /home/git/repos/example.git
 ! [remote rejected] master -> master (unpacker error)
error: не удалось отправить некоторые ссылки в «/home/git/repos/example.git»

Для парирования "unable to create temporary object directory, для удаленной директории /home/git/repos/example.git/ выполним команду: sudo -u git chmod a=rwx -r. После выполнения которой, файл README.md будет успешно отправлен в репозитарий удаленной директории /home/git/repos/example.git, как показано в дампе 2.2.4

Дамп 2.2.4

user@debian-11uni: ~/example$ git push --set-upstream origin master
Перечисление объектов: 3, готово.
Подсчет объектов: 100% (3/3), готово.
При сжатии изменений используется до 8 потоков
Сжатие объектов: 100% (2/2), готово.
Запись объектов: 100% (3/3), 469 байтов | 469.00 КиБ/с, готово.
Всего 3 (изменений 0), повторно использовано 0 (изменений 0), повторно использовано пакетов 0
To /home/git/repos/example.git
 * [new branch]      master -> master
Ветка «master» отслеживает внешнюю ветку «master» из «origin».

Отсюда, главный недостаток локального протокола — полный доступ сторонних и поту стронних субъектов. Именно это обстоятельство, заставило меня снять ограничение прав доступа для постронних пользователей коим является пользователь с именем «user» поотношению к "git".

Для проверки, работоспособности репозитария и спустя како-то время добавил оставшиеся файлы проекта example из example-1.1.tar.gz, как показано в дампе 2.2.5

Дамп 2.2.5

user@debian-11uni: ~/example$ git
$ git commit -m 'add the rest of the files in the project'
[master 1cac386] add the rest of the files in the project
 28 files changed, 26500 insertions(+)
 create mode 100644 Makefile
 create mode 100644 Makefile.am
 create mode 100644 Makefile.in
 create mode 100644 aclocal.m4
 create mode 100644 autom4te.cache/output.0
 create mode 100644 autom4te.cache/output.1
 create mode 100644 autom4te.cache/requests
 create mode 100644 autom4te.cache/traces.0
 create mode 100644 autom4te.cache/traces.1
 create mode 100755 compile
 create mode 100644 config.h
 create mode 100644 config.h.in
 create mode 100644 config.h.in~
 create mode 100644 config.log
 create mode 100755 config.status
 create mode 100755 configure
 create mode 100644 configure.ac
 create mode 100755 depcomp
 create mode 100644 docs/Flawfinder-results.pdf
 create mode 100755 install-sh
 create mode 100755 missing
 create mode 100644 src/.deps/example.Po
 create mode 100644 src/Makefile
 create mode 100644 src/Makefile.am
 create mode 100644 src/Makefile.am~
 create mode 100644 src/Makefile.in
 create mode 100644 src/example.c
 create mode 100644 stamp-h1
user@home2:~/Build/yocto-poky/git-server/example$ git push --set-up
Перечисление объектов: 33, готово.
Подсчет объектов: 100% (33/33), готово.
При сжатии изменений используется до 8 потоков
Сжатие объектов: 100% (29/29), готово.
Запись объектов: 100% (32/32), 1.26 MiB | 14.63 MiB/s, готово.
Total 32 (delta 6), reused 0 (delta 0), pack-reused 0
To /home/git/repos/example.git
   a61a50c..1cac386  master -> master
Ветка «master» отслеживает внешнюю ветку «master» из «origin».

Для примера, ниже привожу импортирование проекта procps_ptree [3.2] из GitHub в локальный репозитарий, как показано в дампе 2.2.6

Дамп 2.2.6

user@home2:~$ git clone --bare https://github.com/rjaan/procps_ptree.git
Клонирование в голый репозиторий «procps_ptree.git»…
remote: Enumerating objects: 228, done.
remote: Counting objects: 100% (228/228), done.
remote: Compressing objects: 100% (206/206), done.
remote: Total 228 (delta 127), reused 76 (delta 20), pack-reused 0
Получение объектов: 100% (228/228), 168.54 KiB | 1.64 MiB/s, готово.
Определение изменений: 100% (127/127), готово.

Далее создал пустой голый репозитарий /home/git/repos/procps_ptree.git, как показано в дампе 2.2.7

Дамп 2.2.7

user@home2:/home/git/repos$ sudo -u git mkdir -m 777 -p procps_ptree.git && cd  procps_ptree.git
user@home2:/home/git/repos/$ sudo -u git git init --bare procps_ptree.git
user@home2:/home/git/repos$ sudo -u git find ./procps_ptree.git -type d -print | sudo -u git xargs chmod a=wrx
user@home2:/home/git/repos$ sudo -u git find ./procps_ptree.git -type f -print | sudo -u git xargs chmod a=wr

После чего, выполнил клонирование ~/procps_ptree.git в локальный репозитарий /home/git/repos/procps_ptree.git с использованием опции "mirror", как показано в дампе 2.2.8

Дамп 2.2.8

user@home2:~/procps_ptree.git$ $ git push --mirror /home/git/repos/procps_ptree.git
Перечисление объектов: 228, готово.
Подсчет объектов: 100% (228/228), готово.
При сжатии изменений используется до 8 потоков
Сжатие объектов: 100% (99/99), готово.
Запись объектов: 100% (228/228), 168.54 KiB | 15.32 MiB/s, готово.
Total 228 (delta 127), reused 228 (delta 127), pack-reused 0
remote: Определение изменений: 100% (127/127), готово.
To /home/git/repos/procps_ptree.git
 * [new branch]      coverity_scan -> coverity_scan
 * [new branch]      master -> master

2.3 Протокол HTTP

Подразумевает наличие программы сервера HTTP, с ролью которой легко может справиться програмный модуль http.server бинарного интерпретатора кода Python, пример запуска которого приведен в дампе 2.3.1

Дамп 2.3.1

sudo -u git python3 -m http.server --cgi --bind 127.0.0.1 8080 --directory /home/git/repos/

При этом в указанной директории /home/git-repos/ мне потребовалось создать поддиректорию cgi-bin/, т.к. опция --cgi модуля http.server требует этого [3.8], кроме указанных вместе с ней опций --bind для связывания с портом и адресом или доменным именем) и --directory, чтобы указать корневую директорию /home/git-repos/ — хранилища Git

Поддержку протоколов http:// и https:// обеспечивается программным модулем git-http-backend, вызываемый клиентом git командой http-backend, который является простой программой CGI и предназначенной для обслуживания, обеспечения доступа клиентов Git к хранилищу репозитариев Git

Самый простой способ, чтобы посмотреть что за диковинка WebGit — это запустить простенький HTTP-север webrick с помощью команды git instaweb, как показано в дампе 2.3.2

Дамп 2.3.2

user@debian-11uni:/home/git/repos$ sudo -u git git init .
user@debian-11uni:/home/git/repos$ sudo -u git git instaweb --httpd=webrick -p 8080 --start

В результате, при обращении по ссылке: http://127.0.0.1:8080/ будет выполнен сервис CGI, называемый GitWeb [3.10], который запускается из корня /home/git/repos и позволяет просматривать в нем содержимое корневой директории и/или поддиректории (вложенные репозитории Git), как показано на рисунке 2.3.3

Рисунок 2.3.3

На котором показан пустой и созданный для организации просмотра /home/git/repos и два вложенных в него репозитария example.git и procps_ptree.git

При этом, все бы хорошо, но функциональность написанный на Ruby север webrick желает оставлять лучшего, т.к. запускает CGI сервер с помощью файла обертки .git/gitweb/webrick/wrapper.sh всегда обращается напрямую /usr/share/gitweb/gitweb.cgi, листинг .git/gitweb/webrick/wrapper.sh показан в листинге 2.3.4

Дамп 2.3.4

#!/bin/sh
# we use this shell script wrapper around the real gitweb.cgi since
# there appears to be no other way to pass arbitrary environment variables
# into the CGI process
GIT_EXEC_PATH=/usr/lib/git-core GIT_DIR=/home/git/repos/.git GITWEB_CONFIG=/home/git/repos/.git/gitweb/gitweb_config.perl
export GIT_EXEC_PATH GIT_DIR GITWEB_CONFIG
exec /usr/share/gitweb/gitweb.cgi

При этом, читающим эту статью нужно учитывать, что её автор использует Debian, который вместе с пакетом git устанавливает CGI-скрипт GitWeb в директории /usr/share/gitweb (выделена жирным в листинге 2.3.4).

Поэтому для выполнения запросов из консоли с помощью клиента git(1), нам понадобится развернуть Apache, как это предлагает настоятельно руководство к WebGit, потому что для клонирования или получения порции данных от запрашиваемого проекта нужен полноценный HTTP сервер, а не запускающий один-единственный скрипт CGI, когда запускали webrick

Для этого используется CGI программа git-http-backend, которая находится в Debian находится /usr/lib/git-core/ и обеспечивает доступ к содержимому Git-репозитария по протоколам http:// и https:// . Эта программа поддерживает загрузку данных репозитария Git клиентам по обеим редакциям протокола HTTP — современной HTTP Smart и более старой — безответного, обеспечивающего обратную совместимость HTTP Dumb [

3.9].

При этом, HTTP Smart работает схожим образом c протоколами SSH и Git, но откланяется от стандарта портов HTTPS и может использовать различные механизмы аутентификации HTTP, что иногда бывает легче для пользователя чем в случае с протоколом SSH. Потому что в этом случае можно использовать аутентификационные данные — пару имя пользователя и пароль вместо того, чтобы создавать ключи SSH.

Так, если сервер не отвечает на запросы сервиса Git по протоколу HTTP Smart , клиент git попытается переключится на более простую версию протокола HTTP Dump, который предполагает, что клиент будет получать голый Git репозитарий от сервера, наподобие как тот передает обычные файлы. Для того, чтобы просто получатть голый репозитарий Git, нужно в директории с метаданными Git установить хук post-update в git/hooks/post-update . Для этого нужно просто выполнить команду переименования, как показано в дампе 2.3.5

Дамп 2.3.5

user@debian-11uni:/home/git/repos/example.git$ sudo -u git mv .git/hooks/post-update .git/hooks/post-update.sample         

Теперь приступим к настройке полноценного сервера Apache с поддержкой протокола HTTP(S) . Для чего, сперва установим требуемые пакеты, как показано в дампе 2.3.6

Дамп 2.3.6

user@debian-11uni:/home/git$ sudo apt-get update
user@debian-11uni:/home/git$ sudo apt-get install apache2

Как показано в дампе 2.3.6, до установки Apache произвести обновление кэша пакетов APT, обновить пакеты, если есть такие имеются.

Для запуска сервера Apache от имени пользователя git произвел соответствующие изменения в файле /etc/apache2/envvars [3.12], как показано в дампе 2.3.7

Дамп 2.3.7

...
user@debian-11uni:/home/git/$ sudo nano /etc/apache2/envvars
...
# Since there is no sane way to get the parsed apache2 config in scripts, some
# settings are defined via environment variables and then used in apache2ctl,
# /etc/init.d/apache2, /etc/logrotate.d/apache2, etc.
#export APACHE_RUN_USER=www-data
#export APACHE_RUN_GROUP=www-data
export APACHE_RUN_USER=git
export APACHE_RUN_GROUP=git
...

Выключил поддержку SSL, как показано в дампе 2.3.8

Дамп 2.3.8

$ sudo a2enmod cgi alias rewrite env
...
Enabling module socache_shmcb.
Enabling module ssl.
...

Apache должен быть сконфигурирован с поддержкой CGI в той директории, в которой gitweb установлен был установлен. В данном случае, такой директорией является /home/git/repos/cgi-bin/ , где /home/git/repos/ — корневая сервера WEB, а gitweb был установлен в её поддиректорию cgi-bin, как показано в дампе 2.3.9

Дамп 2.3.9

user@debian-11uni:/home/git/$ sudo -u git git clone git://git.kernel.org/pub/scm/git/git.git ./gitweb-source
[sudo] пароль для user: 
Клонирование в «./gitweb-source»…
remote: Enumerating objects: 2845, done.
remote: Counting objects: 100% (2845/2845), done.
remote: Compressing objects: 100% (486/486), done.
remote: Total 321876 (delta 2522), reused 2543 (delta 2359), pack-reused 319031
Получение объектов: 100% (321876/321876), 105.36 MiB | 2.70 MiB/s, готово.
Определение изменений: 100% (240570/240570), готово.
user@debian-11uni:/home/git/$ sudo -u git mkdir -m 755 -p /home/git/repos/cgi-bin
user@debian-11uni:/home/git/$ sudo -u git mkdir -m 755 -p /home/git/repos/static

user@debian-11uni:/home/git/$ cd ./gitweb-source
user@debian-11uni:/home/git/gitweb-source/$ sudo -u git make GITWEB_PROJECTROOT="/home/git/repos/cgi-bin" prefix=/usr gitweb
GIT_VERSION = 2.35.1.46.g38062e73e0
    SUBDIR gitweb
    SUBDIR ../
make[2]: «GIT-VERSION-FILE» не требует обновления.
    GEN gitweb.cgi
    GEN static/gitweb.js
user@debian-11uni:/home/git/gitweb-source/$ sudo -u git cp -Rf gitweb/gitweb.* /home/git/repos/cgi-bin
user@debian-11uni:/home/git/gitweb-source/$ sudo -u git cp -Rf gitweb/static /home/git/gitweb-static

В котором показано получение из доверенного источника GitWeb в назначенную пользователем директорию /home/git/repos/git-source и установка скриптов gitweb в CGI директорию /home/git/repos/cgi-bin/ [3.11].

После чего, приступил к настройке совместного использования WebGit с Apache. Для чего определился с со структурой удаленного репозитария, который показан в дампе 2.3.10

Дамп 2.3.10

user@debian-11uni:/home/git/repos$ exa --tree -D .
.
├── cgi-bin
├── example.git
├── git-shell-commands
├── procps_ptree.git
│  ├── branches
│  ├── hooks
│  ├── info
│  ├── objects
│  │  ├── info
│  │  └── pack
│  └── refs
│     ├── heads
│     └── tags
└── static
   └── js
      └── lib

Поэтому в директории /home/git/ определяем файл конфигурации для скрипта gitweb.cgi, содержимое которого показано в листинге 2.3.11

Дамп 2.3.11

# gitweb configuration file for http://git.example.org
#
our $projectroot = "/home/git/repos" ; # FHS recommendation
our $site_name = 'My locale projects';
@stylesheets = ("/static/gitweb.css");
$logo = ("/static/git-logo.png");
$favicon = ("/static/git-favicon.png");
$projects_list = $projectroot;
$per_request_config = 1;
$home_link = "/";
$site_header = "/home/git/gitweb-static/html-pages/siteheader.html";

После настройки конфигурационного файла webgit.conf скрипта gitweb.cgi, основные настройки Apache касаются программного компонента Rewrite engine(движок перезаписи), который выполняет процедуру перезаписи над унифицированным указателем ресурсов (Uniform Resource Locators, URLs) и используемый для реализации некой абстракции между генерирующими страницу WEB файлами и URLs, которая доступна из вне и имеющая внутреннею связь с ними. А так же откуда брать HTML код для заголовка, который состоит из картинки с эмблемой "домашних разработчиков-тихушников" и заголовка "Git Repos".

По умолчанию, Apache создает конфигурацию виртуального узла, который доступен для всех IP на порту 80, в файле /etc/apache2/sites-available/000-default.conf , поправленное содержимое которого показано в листинге 2.3.12

Дамп 2.3.12

 <VirtualHost *:80>
...
    DocumentRoot /home/git/repos
    SetEnv       GITWEB_CONFIG   /home/git/gitweb.conf

    #smart http protocol
    # by default allows GET only, not push.
    # we serve it over the same URLs as the normal gitweb URLs!
    SetEnv GIT_PROJECT_ROOT /home/git/repos
    SetEnv GIT_HTTP_EXPORT_ALL
    ScriptAliasMatch \
              "(?x)^/(.*git/(HEAD | \
                        info/refs | \
                        objects/(info/[^/]+ | \
                                 [0-9a-f]{2}/[0-9a-f]{38} | \
                                 pack/pack-[0-9a-f]{40}\.(pack|idx)) | \
                        git-(upload|receive)-pack))$" \
               /usr/lib/git-core/git-http-backend/$1
    ScriptAlias /git/ /var/www/cgi-bin/gitweb.cgi/
    ScriptAlias /cgi-bin/ /home/git/repos/cgi-bin/

    <Directory "/home/git/repos">
       Options +FollowSymLinks +ExecCGI
	   AddHandler cgi-script .cgi
       Require all granted
 
	   DirectoryIndex /cgi-bin/gitweb.cgi
 
       RewriteEngine On
       # next two of the rewrite conditions on that requested file or directory exists  
	   RewriteCond %{REQUEST_FILENAME} !-f
	   RewriteCond %{REQUEST_FILENAME} !-d
	   RewriteRule ^.* /gitweb.cgi/$0  [L,PT] 
       # make access for "dumb clients" work
       RewriteRule ^/(.*\.git/(?!/?(HEAD|info|objects|refs)).*)?$ \
                      /cgi-bin/gitweb.cgi%{REQUEST_URI}  [L,PT]
       # Make shortcut for a repostitory, so URL like <http://localhost/my_repository.git>
       # loads proper repository in Gitweb
       RewriteRule ^/(\w+\.git)$ /?p=$1 [L,P]  
    </Directory>
 
    <Directory "/usr/lib/git-core/">
        <Files "git-http-backend">
              Options +ExecCGI
              Require all granted
        </Files>
    </Directory>
 
    <Location "/home/git/gitweb.conf">
           Require all denied
     </Location>
...

В листинге 2.3.12, представлена конфигурация, которая поддерживает два загрузки по одному и тому же URL, который можно использовать в адресной строке браузера и клиента Git, выполняющий команды подобные git clone или git pull , как показано в дамп ее 2.3.13

Дамп 2.3.13

$ rm -rf example && git clone http://localhost/example.git
Клонирование в «example»…
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Распаковка объектов: 100% (3/3), 449 bytes | 449.00 KiB/s, готово.

При этом машинка в браузере (запуск http://localhost) не хотела работать без явного указания через дерективу ScriptAlias внутреннего отображения /cgi-bin/ в URL к ее фактичекому расположению в файловой системе, директории /home/git/repos/cgi-bin/

На финальной стадии настройки выполнил активацию поддержку CGI, внутреннего отображения URL к фактическому в файловой системе и переменных окружения, которые широко используются в моей конфигурации, выполнил команду a2enmod вместе с перезапуском Apache, как показано в дампе 2.3.14

Дамп 2.3.14

sudo a2enmod cgi alias rewrite env
sudo service apache2 restart 

2.4 Протокол Git

Реализует модулем git-daemon, который поднимает простой сервер Git на сетевом порту 9418/tcp, как показано в дампе 2.4.1

Дамп 2.4.1

user@debian-11uni:/home/git/repos$ sudo -u git git daemon --base-path=/home/git/repos --export-all --enable=receive-pack --reuseaddr --informative-errors --verbose
[282321] Ready to rumble
[282331] Connection from [::1]:43496
[282331] Extended attribute "host": localhost
[282331] Extended attribute "protocol": version=2
[282331] Request upload-pack for '/procps_ptree.git'
[282321] [282331] Disconnected

В дампе 2.4.1 осуществляется доступ к локальному хранилищу /home/git/repos репозитария Git в режиме только чтения, и т.е. выполнения команд git clone и git pull, но для реализации записи, т.е. выполнения таких команд как git push или git remote add нужно добавить в этот набор переменных опцию --enable=receive-pack .

Для подхвата менеджером запуска Systemd, воспользовался рецептом, предложенным Джерими Си Фостер (Jeremiah C Foster)[3.18] и адаптированным мной к условиям функционирования на системах с Debian-11

Для получения GitHub Gits серверной конфигурации блока Systemd и запуска, выполните команду, показанную в дампе 2.4.1, и следуйте затем инструкциям в README.md

Дамп 2.4.2

git clone https://gist.github.com/702b7b79faf34ba3ff2194d0f404760b.git git-daemon && \
cd git-daemon

Библиография

3.1 How to Set up the HTTP Git Server for Private Projects

3.2 procps_ptree

3.3 Flawfinder

3.4How to use make and automake

3.5 Git Guide book. 4.1 Git on the Server - The Protocols

3.6 WiKiPedia. The file URI scheme

3.7 refusing to update checked out branch: refs/heads/master error #20

3.8 Python module http.server

3.9 Git Guide book. git-http-backend

3.10 Git Via HTTP on Fedora

3.11 How do you clone a Git repository into a specific folder?

3.12 How to run apache as an alternate user

3.13 Git Guide book.gitweb.conf

3.14 WiKiPedia. Rewrite engine

3.15 .htaccess: Invalid command 'RewriteEngine', perhaps misspelled or defined by a module not included in the server configuration

3.16 git-http-backend on Apache 2.4

3.17 Taming The Git-Daemon To Quickly Share Git Repository

3.18 git-daemon

3.19 ARCHIVED: Lightweight Public Git Self Hosting

3.20 gitweb.conf.txt

Сайт разработан в соответствии с рекомендациями консорциума W3C для языка разметки HTML5.

Об авторе можно прочитать здесь.

Copyright © 2015-2019 Андрей Ржавсков