diff --git a/.editorconfig b/.editorconfig index 6a8fde0..56a5255 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,7 +6,7 @@ insert_final_newline = true indent_style = space indent_size = 4 -[*.{yml}] +[*.yml] indent_size = 2 [Makefile] diff --git a/Makefile b/Makefile index cdbe9e9..3910e0c 100644 --- a/Makefile +++ b/Makefile @@ -3,3 +3,10 @@ configure: install-roles: ansible-galaxy install -r "ansible/requirements.yml" + +test-rebuild: + vagrant destroy -f && vagrant up + +lint-configuration: + ansible-lint "./ansible/configuration.yml" --exclude="./ansible/galaxy.roles/" -v || true + ansible-lint "./ansible/roles/ssl-certificate/tasks/main.yml" -v || true diff --git a/ansible/configuration.yml b/ansible/configuration.yml index c0ba02a..de7d081 100644 --- a/ansible/configuration.yml +++ b/ansible/configuration.yml @@ -13,20 +13,6 @@ dbname: notes_db dbuser: notes_user dbpassword: Sf6tp6LKeCyrjVZ2YGKYUd - www: '/var/www/notes' - - app_envs: - NOTES_SECRET_TOKEN: qJqFNP5B9RP2EfqgpTPyZe - NOTES_DATABASE_HOST: 127.0.0.1 - NOTES_DATABASE_PORT: 3306 - NOTES_DATABASE_NAME: "{{ apps.notes.dbname }}" - NOTES_DATABASE_USER: "{{ apps.notes.dbuser }}" - NOTES_DATABASE_PASSWORD: "{{ apps.notes.dbpassword }}" - NOTES_MAILER_HOST: smtp.timeweb.ru - NOTES_MAILER_PORT: 25 - NOTES_MAILER_USER: noreply@anwinged.ru - NOTES_MAILER_PASSWORD: C5DkD5gs - SYMFONY_ENV: prod timezone: UTC @@ -36,7 +22,7 @@ # php settings - php_version: "7.1" + php_version: '7.1' php_packages: - php7.1 - php7.1-curl @@ -49,23 +35,17 @@ - php7.1-mysql php_webserver_daemon: nginx php_enable_php_fpm: true - php_date_timezone: "{{ timezone }}" - php_fpm_listen: /var/run/php{{ php_version }}-fpm.sock + php_date_timezone: '{{ timezone }}' # mysql settings mysql_databases: - - name: "{{ apps.notes.dbname }}" + - name: '{{ apps.notes.dbname }}' mysql_users: - - name: "{{ apps.notes.dbuser }}" + - name: '{{ apps.notes.dbuser }}' host: '127.0.0.1' - password: "{{ apps.notes.dbpassword }}" - priv: "{{ apps.notes.dbname }}.*:ALL" - - letsencrypt_webroot_path: /var/www/letsencrypt - letsencrypt_email: anwinged@ya.ru - letsencrypt_cert_domains: - - notes.anwinged.ru + password: '{{ apps.notes.dbpassword }}' + priv: '{{ apps.notes.dbname }}.*:ALL' pre_tasks: - name: Ensure that PHP PPA is added. @@ -83,14 +63,8 @@ - zip - name: Add deploy user user: - name: "{{ deploy_user }}" + name: '{{ deploy_user }}' groups: www-data - - name: "Set authorized key took from file for {{ deploy_user }}" - authorized_key: - user: "{{ deploy_user }}" - state: present - key: "{{ lookup('file', 'id_rsa.pub') }}" - when: from_vagrant roles: - yatesr.timezone @@ -98,55 +72,32 @@ - geerlingguy.php-versions - geerlingguy.php - geerlingguy.mysql + - role: static-site static_site_name: anwinged static_site_domain: anwinged.ru + - role: static-site static_site_name: s2photo static_site_domain: s2photo.ru - tasks: - - name: Create system environment variables. - lineinfile: - dest: /etc/environment - regexp: '^{{ item.key }}=' - line: '{{ item.key }}="{{ item.value }}"' - with_dict: "{{ app_envs }}" - - - name: Generate dhparams. - shell: openssl dhparam -out /etc/nginx/dhparams.pem 2048 - args: - creates: /etc/nginx/dhparams.pem - - - name: Create letsencrypt directory. - file: - name: /var/www/letsencrypt - state: directory - - - name: Copy notes acme server config. - template: - src: notes-acme.vhost.j2 - dest: "/etc/nginx/sites-enabled/notes-acme.conf" - notify: restart nginx - - - name: Restart nginx. - service: name=nginx state=restarted - - - name: Configure Lest Encrypt certificate. - include_role: - name: thefinn93.ansible-letsencrypt - - - name: Copy notes server config. - template: - src: notes.vhost.j2 - dest: "/etc/nginx/sites-enabled/notes.conf" - notify: restart nginx - - - name: 'Create folder for {{ apps.notes.name }}.' - file: - path: "{{ apps.notes.www }}" - state: directory - owner: "{{ deploy_user }}" - group: www-data - recurse: yes - notify: restart nginx + - role: symfony-app + app_name: notes + app_user: notes_owner + app_user_ssh_keys: ['{{ lookup("file", "av_id_rsa.pub") }}'] + app_domains: ['notes.anwinged.ru'] + app_cert: yes + app_cert_type: 'letsencrypt' + app_cert_email: anwinged@ya.ru + app_envs: + NOTES_SECRET_TOKEN: qJqFNP5B9RP2EfqgpTPyZe + NOTES_DATABASE_HOST: 127.0.0.1 + NOTES_DATABASE_PORT: 3306 + NOTES_DATABASE_NAME: '{{ apps.notes.dbname }}' + NOTES_DATABASE_USER: '{{ apps.notes.dbuser }}' + NOTES_DATABASE_PASSWORD: '{{ apps.notes.dbpassword }}' + NOTES_MAILER_HOST: smtp.timeweb.ru + NOTES_MAILER_PORT: 25 + NOTES_MAILER_USER: noreply@anwinged.ru + NOTES_MAILER_PASSWORD: C5DkD5gs + SYMFONY_ENV: prod diff --git a/ansible/files/av_id_rsa.pub b/ansible/files/av_id_rsa.pub new file mode 100644 index 0000000..03abf82 --- /dev/null +++ b/ansible/files/av_id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDxqDV6RWsmTgWmgKGwL0B9NdNH3zdRIo5dZrLK8rRvvOKVUwHxK8V0i0qaxBho/hVTuI2Jk3dt+/3/E7CsK9qxTci0272nIizkJd4nzicTIrT2K7NQQLrvhnNvDx3g2KGLqChcaDrICgHsCv2VTH1Cm64pvE4cqom0xJz/tG7ijqzBzGDybubC4TAItkNDmtp7F4Ia06yzfL2CExBz8zxeTG4oT0sy5e0j/NjxP2MYPrQW5tL60r65VFy9a1x+8dp6IqrZkM3z6oDER0Gzhl0zfB/EAp4KhN06Bs+2UyhaQbi4+owIUVNTP+amFicyZFSu6VAeVr4JWsmrsWaKYVMD av@sol diff --git a/ansible/roles/ssl-certificate/defaults/main.yml b/ansible/roles/ssl-certificate/defaults/main.yml new file mode 100644 index 0000000..c619a08 --- /dev/null +++ b/ansible/roles/ssl-certificate/defaults/main.yml @@ -0,0 +1,24 @@ +--- +# Required, allowed: self-signed, letsencrypt +cert_type: 'self-signed' + +# Required, name for ssl-certificate configuration +cert_name: '' + +# Required: domain owner email +cert_email: '' + +# Required: domains for lets encrypt certificate creation +cert_domains: [] + +# Parameters to store generated keys +cert_directory: '/opt/ssl-certificates/{{ cert_name }}' +cert_key: '{{ cert_directory }}/ssl.key' +cert_request: '{{ cert_directory }}/ssl.csr' +cert_certificate: '{{ cert_directory }}/ssl.crt' + +# DH parameters +cert_dhparam: '/etc/nginx/dhparam.pem' +cert_dhparam_n: 2048 + +cert_le_webroot_path: /var/www/letsencrypt diff --git a/ansible/roles/ssl-certificate/tasks/letsencrypt.yml b/ansible/roles/ssl-certificate/tasks/letsencrypt.yml new file mode 100644 index 0000000..dce4f6e --- /dev/null +++ b/ansible/roles/ssl-certificate/tasks/letsencrypt.yml @@ -0,0 +1,33 @@ +--- +- name: Check required parameters. + fail: + msg: You must set up domain and email. + when: not cert_domains or not cert_email + +- name: Create letsencrypt web root directory. + file: + name: '{{ cert_le_webroot_path }}' + state: directory + +- name: Copy notes acme server config. + template: + src: vhost.conf.j2 + dest: "/etc/nginx/sites-enabled/{{ cert_name }}_letsencrypt.conf" + +- name: Restart nginx. + service: + name: nginx + state: restarted + +- name: Configure Lest Encrypt certificate. + include_role: + name: thefinn93.ansible-letsencrypt + private: yes + vars: + letsencrypt_webroot_path: '{{ cert_le_webroot_path }}' + letsencrypt_email: '{{ cert_email }}' + letsencrypt_cert_domains: '{{ cert_domains }}' + letsencrypt_renewal_command_args: '--renew-hook "systemctl restart nginx"' + ssl_certificate: '{{ cert_certificate }}' + ssl_certificate_key: '{{ cert_key }}' + when: False diff --git a/ansible/roles/ssl-certificate/tasks/main.yml b/ansible/roles/ssl-certificate/tasks/main.yml new file mode 100644 index 0000000..09f5750 --- /dev/null +++ b/ansible/roles/ssl-certificate/tasks/main.yml @@ -0,0 +1,16 @@ +--- +- name: Ensure certificate storage exists. + file: + path: '{{ cert_directory }}' + state: directory + +- include: self-signed.yml + when: cert_type == 'self-signed' + +- include: letsencrypt.yml + when: cert_type == 'letsencrypt' + +- name: Generate dhparams. + shell: 'openssl dhparam -out {{ cert_dhparam }} {{ cert_dhparam_n }}' + args: + creates: '{{ cert_dhparam }}' diff --git a/ansible/roles/ssl-certificate/tasks/self-signed.yml b/ansible/roles/ssl-certificate/tasks/self-signed.yml new file mode 100644 index 0000000..60fb73a --- /dev/null +++ b/ansible/roles/ssl-certificate/tasks/self-signed.yml @@ -0,0 +1,33 @@ +--- +- name: Check certificate params. + fail: + msg: You must setup certificate file params. + when: not cert_certificate or not cert_key + +- name: Generate self signed ssl key. + shell: | + openssl genrsa \ + -aes256 \ + -passout pass:client11 \ + -out {{ cert_directory }}/ssl.pass.key \ + 1024 + + openssl rsa \ + -passin pass:client11 \ + -in {{ cert_directory }}/ssl.pass.key \ + -out {{ cert_key }} + + openssl req \ + -new \ + -key {{ cert_key }} \ + -out {{ cert_request }} \ + -subj "/CN=localhost" + + openssl x509 \ + -req \ + -days 365 \ + -in {{ cert_request }} \ + -signkey {{ cert_key }} \ + -out {{ cert_certificate }} + args: + creates: '{{ cert_certificate }}' diff --git a/ansible/templates/notes-acme.vhost.j2 b/ansible/roles/ssl-certificate/templates/vhost.conf.j2 similarity index 59% rename from ansible/templates/notes-acme.vhost.j2 rename to ansible/roles/ssl-certificate/templates/vhost.conf.j2 index 150dc0b..4b8637c 100644 --- a/ansible/templates/notes-acme.vhost.j2 +++ b/ansible/roles/ssl-certificate/templates/vhost.conf.j2 @@ -1,11 +1,9 @@ server { listen 80; - server_name notes.anwinged.ru; + server_name {{ cert_domains|join(' ') }}; - # For Lets Encrypt verify - # include acme; location /.well-known { - root /var/www/letsencrypt; + root {{ cert_le_webroot_path }}; try_files $uri $uri/ =404; } diff --git a/ansible/roles/symfony-app/defaults/main.yml b/ansible/roles/symfony-app/defaults/main.yml new file mode 100644 index 0000000..f33c9a1 --- /dev/null +++ b/ansible/roles/symfony-app/defaults/main.yml @@ -0,0 +1,44 @@ +--- +app_name: '' + + +# ПОЛЬЗОАВТЕЛЬ + +app_user: '{{ app_name }}' +app_group: '{{ app_user }}' +app_user_ssh_keys: [] + + +# ОКРУЖЕНИЕ + +# Переменные окружения приложения. +# Необходимо указывать в виде пар ключ-значение, +# где ключ - имя переменной (обычно в верхнем регистре). +app_envs: {} + + +# ВЕБ-СЕРВЕР + +app_directory: '/var/www/{{ app_name }}' +app_domains: ['{{ app_name }}.loc'] +app_web_root: '/var/www/{{ app_name }}/current/web' +app_web_listen: 'unix:/var/run/php-fpm-{{ app_name }}.sock' + + +# СЕРТИФИКАТ + +app_cert: no +app_cert_type: 'self-signed' +app_cert_email: '' +app_cert_directory: '/opt/ssl-certificates/{{ app_name }}' +app_cert_certificate: '/opt/ssl-certificates/{{ app_name }}/ssl.crt' +app_cert_key: '/opt/ssl-certificates/{{ app_name }}/ssl.key' +app_dhparam_file: '/opt/ssl-certificates/{{ app_name }}/dhparam.pem' + + +# PHP-FPM + +app_php_version: '{{ php_version | default("7.0") }}' +app_fpool_name: '{{ app_name }}' +app_fpool_listen: '/var/run/php-fpm-{{ app_name }}.sock' +app_fpool_slowlog: '/var/www/{{ app_name }}/shared/logs/' diff --git a/ansible/roles/symfony-app/tasks/main.yml b/ansible/roles/symfony-app/tasks/main.yml new file mode 100644 index 0000000..1cef058 --- /dev/null +++ b/ansible/roles/symfony-app/tasks/main.yml @@ -0,0 +1,67 @@ +--- +- name: 'Check app requirements for {{ app_name }}.' + fail: + msg: You must set app name. + when: not app_name + +- name: 'Create group "{{ app_group }}" for {{ app_name }}.' + group: + name: '{{ app_group }}' + state: present + +- name: 'Create user "{{ app_user }}" for {{ app_name }}.' + user: + name: '{{ app_user }}' + comment: '{{ app_name }} application owner' + group: '{{ app_group }}' + shell: /bin/bash + +- name: 'Set up user ssh keys for {{ app_name }}.' + authorized_key: + user: '{{ app_user }}' + key: '{{ item }}' + state: present + with_items: '{{ app_user_ssh_keys }}' + +- name: 'Set up system environment variables for {{ app_name }}.' + lineinfile: + dest: /etc/environment + regexp: '^{{ item.key }}=' + line: '{{ item.key }}="{{ item.value }}"' + with_dict: '{{ app_envs }}' + +- name: 'Create ssl certificate for {{ app_name }}.' + include_role: + name: ssl-certificate + private: yes + vars: + cert_type: '{{ app_cert_type }}' + cert_name: '{{ app_name }}' + cert_email: '{{ app_cert_email }}' + cert_domains: '{{ app_domains }}' + cert_directory: '{{ app_cert_directory }}' + cert_key: '{{ app_cert_key }}' + cert_certificate: '{{ app_cert_certificate }}' + cert_dhparam: '{{ app_dhparam_file }}' + when: app_cert + +- name: 'Create web directory for {{ app_name }}.' + file: + state: directory + path: '{{ app_directory }}' + owner: '{{ app_user }}' + group: '{{ app_group }}' + recurse: yes + notify: restart nginx + +- name: 'Create nginx config for {{ app_name }}.' + template: + src: app.conf.j2 + dest: '/etc/nginx/sites-enabled/{{ app_name }}.conf' + notify: restart nginx + +- name: 'Creates php-fpm pool config for {{ app_name }}.' + template: + src: fpm-pool.conf.j2 + dest: '/etc/php/{{ app_php_version }}/fpm/pool.d/{{ app_name }}.conf' + notify: restart php-fpm diff --git a/ansible/roles/symfony-app/templates/app.conf.j2 b/ansible/roles/symfony-app/templates/app.conf.j2 new file mode 100644 index 0000000..d8c79fa --- /dev/null +++ b/ansible/roles/symfony-app/templates/app.conf.j2 @@ -0,0 +1,50 @@ +server { + server_name {{ app_domains | join(" ") }}; + + {% if app_cert %} + listen 443 ssl http2 deferred; + {% else %} + listen 80; + {% endif %} + + {% if app_cert %} + {% include './ssl.j2' %} + {% endif %} + + root {{ app_web_root }}; + + location / { + # try to serve file directly, fallback to app.php + try_files $uri /app.php$is_args$args; + } + + location ~ ^/app\.php(/|$) { + fastcgi_pass {{ app_web_listen }}; + fastcgi_split_path_info ^(.+\.php)(/.*)$; + include fastcgi_params; + + # When you are using symlinks to link the document root to the + # current version of your application, you should pass the real + # application path instead of the path to the symlink to PHP + # FPM. + # Otherwise, PHP's OPcache may not properly detect changes to + # your PHP files (see https://github.com/zendtech/ZendOptimizerPlus/issues/126 + # for more information). + fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; + fastcgi_param DOCUMENT_ROOT $realpath_root; + + # Prevents URIs that include the front controller. This will 404: + # http://domain.tld/app.php/some-path + # Remove the internal directive to allow URIs like this + internal; + } + + # return 404 for all other php files not matching the front controller + # this prevents access to other php files you don't want to be accessible. + location ~ \.php$ { + return 404; + } + + error_log /var/log/nginx/{{ app_name }}_error.log; + access_log /var/log/nginx/{{ app_name }}_access.log; +} diff --git a/ansible/roles/symfony-app/templates/fpm-pool.conf.j2 b/ansible/roles/symfony-app/templates/fpm-pool.conf.j2 new file mode 100644 index 0000000..8e3b0bc --- /dev/null +++ b/ansible/roles/symfony-app/templates/fpm-pool.conf.j2 @@ -0,0 +1,28 @@ +[{{ app_fpool_name }}] + +listen = {{ app_fpool_listen }} +listen.allowed_clients = 127.0.0.1 +listen.backlog = -1 + +user = {{ app_user }} +group = {{ app_group }} + +; request_slowlog_timeout = 5s +; slowlog = /var/log/php-fpm/slowlog-blog.log + +pm = dynamic +pm.max_children = 4 +pm.start_servers = 2 +pm.min_spare_servers = 1 +pm.max_spare_servers = 3 +pm.max_requests = 200 +pm.status_path = /status + +request_terminate_timeout = 120s +rlimit_files = 131072 +rlimit_core = unlimited +catch_workers_output = yes + +{% for name, value in app_envs.iteritems() %} +env[{{ name }}]={{ value }} +{% endfor %} diff --git a/ansible/roles/symfony-app/templates/ssl.j2 b/ansible/roles/symfony-app/templates/ssl.j2 new file mode 100644 index 0000000..56e82fc --- /dev/null +++ b/ansible/roles/symfony-app/templates/ssl.j2 @@ -0,0 +1,15 @@ +ssl on; +ssl_certificate {{ app_cert_certificate }}; +ssl_certificate_key {{ app_cert_key }}; +ssl_trusted_certificate {{ app_cert_certificate }}; + +ssl_session_cache shared:SSL:50m; +ssl_session_timeout 5m; +ssl_stapling on; +ssl_stapling_verify on; + +ssl_protocols TLSv1 TLSv1.1 TLSv1.2; +ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4"; + +ssl_dhparam {{ app_dhparam_file }}; +ssl_prefer_server_ciphers on; diff --git a/ansible/templates/notes.vhost.j2 b/ansible/templates/notes.vhost.j2 deleted file mode 100644 index 4f1ac76..0000000 --- a/ansible/templates/notes.vhost.j2 +++ /dev/null @@ -1,62 +0,0 @@ -server { - listen 443 ssl http2 deferred; - server_name notes.anwinged.ru; - - ssl on; - ssl_certificate /etc/letsencrypt/live/notes.anwinged.ru/fullchain.pem; - ssl_certificate_key /etc/letsencrypt/live/notes.anwinged.ru/privkey.pem; - ssl_trusted_certificate /etc/letsencrypt/live/notes.anwinged.ru/fullchain.pem; - - ssl_session_cache shared:SSL:50m; - ssl_session_timeout 5m; - ssl_stapling on; - ssl_stapling_verify on; - - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4"; - - ssl_dhparam /etc/nginx/dhparams.pem; - ssl_prefer_server_ciphers on; - - root /var/www/notes/current/web; - - location / { - # try to serve file directly, fallback to app.php - try_files $uri /app.php$is_args$args; - } - - # PROD - location ~ ^/app\.php(/|$) { - fastcgi_pass unix:/var/run/php{{ php_version }}-fpm.sock; - fastcgi_split_path_info ^(.+\.php)(/.*)$; - include fastcgi_params; - - # When you are using symlinks to link the document root to the - # current version of your application, you should pass the real - # application path instead of the path to the symlink to PHP - # FPM. - # Otherwise, PHP's OPcache may not properly detect changes to - # your PHP files (see https://github.com/zendtech/ZendOptimizerPlus/issues/126 - # for more information). - fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; - fastcgi_param DOCUMENT_ROOT $realpath_root; - - {% for name, value in app_envs.iteritems() %} - fastcgi_param {{ name }} "{{ value }}"; - {% endfor %} - - # Prevents URIs that include the front controller. This will 404: - # http://domain.tld/app.php/some-path - # Remove the internal directive to allow URIs like this - internal; - } - - # return 404 for all other php files not matching the front controller - # this prevents access to other php files you don't want to be accessible. - location ~ \.php$ { - return 404; - } - - error_log /var/log/nginx/{{ apps.notes.name }}_error.log; - access_log /var/log/nginx/{{ apps.notes.name }}_access.log; -}