diff --git a/deploy/.gitignore b/deploy/.gitignore new file mode 100644 index 0000000..14e3745 --- /dev/null +++ b/deploy/.gitignore @@ -0,0 +1,3 @@ +*.retry +.venv/ +Pipfile.lock diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..c06cbb4 --- /dev/null +++ b/deploy/README.md @@ -0,0 +1,5 @@ +# Run ansble + +``` +ansible-playbook website.yml +``` diff --git a/deploy/ansible.cfg b/deploy/ansible.cfg new file mode 100644 index 0000000..b471aa9 --- /dev/null +++ b/deploy/ansible.cfg @@ -0,0 +1,4 @@ +[defaults] +inventory = inventory +nocows = 1 +vault_password_file = ~/.ansible-hkis-vault diff --git a/deploy/group_vars/all/vars b/deploy/group_vars/all/vars new file mode 100644 index 0000000..516f058 --- /dev/null +++ b/deploy/group_vars/all/vars @@ -0,0 +1,11 @@ +--- +ansible_python_interpreter: "/usr/bin/python3" +ansible_user: root + +gandi_api_key: !vault | + $ANSIBLE_VAULT;1.1;AES256 + 65306331316361633033373833316433313632336336653030623733363936643363393236393637 + 6635326137663133333038336665613566373365356364610a386437646238636336343965323730 + 61306638653939653437386530386663313338396266666136396637653061313036643730613335 + 6538613861643632310a386561343237303137633066343130333764353263663364326161653638 + 62313561333737313864303335626264636562626536613465326162323164666262 diff --git a/deploy/group_vars/all/vault b/deploy/group_vars/all/vault new file mode 100644 index 0000000..bc69efb --- /dev/null +++ b/deploy/group_vars/all/vault @@ -0,0 +1,17 @@ +$ANSIBLE_VAULT;1.1;AES256 +65356533653563626232346137616263306533353638653434623434373466373639316433653261 +6333616531306439383331313035306563656366303363610a646432353865323836396533343966 +37383835633362616466646138666335663437393463346535656136373266313161336464316462 +3335376432633766380a666437356434656564636536313162663630643034373338653366313334 +32346562386136353531373361346139363733626261383031633762326137383031326136646563 +63313939356236626138366538616361636230633530303633646562366237626462383736373262 +37636632393839636531333237356537646465613962353835636262653531333063386338366139 +63663563393134663064623461326537663935323832383730313863613130396633323533366566 +66313465383931393965386261653233333039383363333364613163393637306134393762396262 +37666164313661633866643037656466623136646161366531323433386639633334333236313337 +62343835303461636330326531333564376630633339343030336163643566363930383531663861 +32353961646336326238636236303539626661303864626135626638613865373738373131316365 +66313666393331646235643664633061653162633962303664336263643466316632303738303833 +36616439343238383463396139626134336131363666313164373033333964626630386463653134 +34623263666433326530376665313962373531383966646534336336363136336463633037633065 +35663537343339393866 diff --git a/deploy/inventory b/deploy/inventory new file mode 100644 index 0000000..5482df8 --- /dev/null +++ b/deploy/inventory @@ -0,0 +1,2 @@ +[website] +eqy.fr diff --git a/deploy/requirements.txt b/deploy/requirements.txt new file mode 100644 index 0000000..90d4055 --- /dev/null +++ b/deploy/requirements.txt @@ -0,0 +1 @@ +ansible diff --git a/deploy/roles/common/handlers/main.yml b/deploy/roles/common/handlers/main.yml new file mode 100644 index 0000000..8c1375d --- /dev/null +++ b/deploy/roles/common/handlers/main.yml @@ -0,0 +1,7 @@ +--- + +- name: sshd + service: name=sshd state=reloaded + +- name: nftables + service: name=nftables state=reloaded diff --git a/deploy/roles/common/tasks/common.yml b/deploy/roles/common/tasks/common.yml new file mode 100644 index 0000000..aa18607 --- /dev/null +++ b/deploy/roles/common/tasks/common.yml @@ -0,0 +1,93 @@ +--- + +- name: Configure hostname + hostname: + name: "{{ inventory_hostname_short }}" + +- name: Configure FQDN + lineinfile: + path: /etc/hosts + regexp: '^127\.0\.0\.1' + line: "127.0.0.1 {{ inventory_hostname }} {{ inventory_hostname_short }} localhost" + owner: root + group: root + mode: 0644 + +- name: apt-get some packages + apt: + state: present + name: + - aptitude + - fail2ban + - nftables + - python3 + - python3-dev + - python3-pip + - python3-venv + - rsync + +- name: nftable service is started + service: + name: nftables + enabled: yes + state: started + daemon_reload: yes + +- name: Copy nftables config + copy: + content: | + #!/usr/sbin/nft -f + + table inet filter + flush table inet filter + + table inet filter { + chain input { + type filter hook input priority 0; + iif lo accept + ct state established,related accept + tcp dport { ssh, http, https } ct state new accept + # accept neighbour discovery otherwise connectivity breaks: + icmpv6 type { echo-request, nd-neighbor-solicit, nd-router-advert, nd-neighbor-advert } accept + ip protocol icmp icmp type { destination-unreachable, echo-reply, echo-request, source-quench, time-exceeded } accept + counter drop + } + } + + dest: /etc/nftables.conf + owner: root + group: root + mode: 0755 + notify: nftables + +- name: Set some authorized keys + authorized_key: user=root key="{{item}}" + with_items: + - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDKA7DgTQ0G7+kdsX0lIUOAAOllwGSCu8s8TxPvr/61Y8q+pIO5mrZycI0xYcKP5NZaABqlFyXUUNfLj7RLqteBxqq2QZP4NOJ1MutYRIkzJ9YW0f565jHaOqSguz0MY+1sCHtuEPiUUZoNexkKN7SIx60SfoaMEvGjAj46txA7VFbJUuKcJtA1Yvmn0C0KoXUUQ/G+JqvjQ7QuKLQYdTZ8S9OEvNaqNfwNSwvy1/LCnuajFw0O+H5bz7AcS5Iuj+9k8wgHPK1a1rQEdteOcn2XBCvta/VOVlFLv6/9K3iU3EJ1pyaZ88UkuJef8aWnH/AJGaF2gLqUbBuL+UeXyD41 julien+yubikey4@palard.fr" + - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8vv8vwmbyhFEa0chj8LklnnY6DRLKj2OM0NgaMTd9SsrtBeLMqTt34pU+kKl6/9EIe9P8Z1/fWFyOiTsE7Khf3rkNsoILPmEV14i18Bvtp4nMtljqZaKVkAcRjPvo7flRWNxxL2Zbo+BEr3wVCl3Sc6YV8oQzCwVPKf34AB39b+PW4f3580Aqcd4Ci6zca0Ol95tLDv1slX1A7QcpoZAne8kj5h6bb4cC7FLBC9+xOSKmzoLOlP7LsyxaUUGRyi/FeMoma1VES65aIJ5U23GtZrzZI3tKz+vpQvOVaozNTDkNLiiJkjd3Ew1I10wArpZixjwSndP8CvGFyJc1XUXZ julien+yubikey5@palard.fr" + +- name: Drop mlocate or locate + apt: + name: ["mlocate", "locate"] + state: absent + +# From https://infosec.mozilla.org/guidelines/openssh +- name: SSHd hardening + blockinfile: + marker: "# {mark} ANSIBLE MANAGED BLOCK (KexAlgorithms, Ciphers, MACs)" + path: /etc/ssh/sshd_config + state: present + create: yes + block: | + KexAlgorithms curve25519-sha256@libssh.org,ecdh-sha2-nistp521,ecdh-sha2-nistp384,ecdh-sha2-nistp256,diffie-hellman-group-exchange-sha256 + Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr + MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com,hmac-sha2-512,hmac-sha2-256,umac-128@openssh.com + + HostKey /etc/ssh/ssh_host_ed25519_key + HostKey /etc/ssh/ssh_host_rsa_key + HostKey /etc/ssh/ssh_host_ecdsa_key + + AuthenticationMethods publickey + LogLevel VERBOSE + notify: sshd + tags: ssh diff --git a/deploy/roles/common/tasks/main.yml b/deploy/roles/common/tasks/main.yml new file mode 100644 index 0000000..3b24d19 --- /dev/null +++ b/deploy/roles/common/tasks/main.yml @@ -0,0 +1,7 @@ +--- + +- include_role: + name: exim4 + +- include_tasks: common.yml + tags: common diff --git a/deploy/roles/exim4/LICENSE.txt b/deploy/roles/exim4/LICENSE.txt new file mode 100644 index 0000000..248de00 --- /dev/null +++ b/deploy/roles/exim4/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Tobias Schifftner, ambimax® GmbH + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/deploy/roles/exim4/defaults/main.yml b/deploy/roles/exim4/defaults/main.yml new file mode 100644 index 0000000..03dfa72 --- /dev/null +++ b/deploy/roles/exim4/defaults/main.yml @@ -0,0 +1,23 @@ +--- +exim4_sendonly_fqdn: '{{ ansible_fqdn }}' + +exim4_aws_access_key_id: '' +exim4_aws_secret_access_key: '' + +exim4_aws_ses_region: 'eu-west-1' + +exim4_sendonly_enable_tls: true +exim4_sendonly_smarthost: '' +exim4_sendonly_username: '' +exim4_sendonly_password: '' + +exim4_sendonly_email_addresses: [] +# root: 'your@email.com' + +exim4_sendonly_email_aliases: [] +# - regexp: '^root:' +# line: 'root: your@email.com' + +exim4_sendonly_apt_packages: + - exim4-daemon-light + - mailutils diff --git a/deploy/roles/exim4/files/logrotate b/deploy/roles/exim4/files/logrotate new file mode 100644 index 0000000..0902d82 --- /dev/null +++ b/deploy/roles/exim4/files/logrotate @@ -0,0 +1,14 @@ +/var/mail/* { + su root mail + daily + missingok + rotate 8 + size 5M + maxage 30 + notifempty + compress + delaycompress + notifempty + create 0640 root root + sharedscripts +} diff --git a/deploy/roles/exim4/handlers/main.yml b/deploy/roles/exim4/handlers/main.yml new file mode 100644 index 0000000..130dd99 --- /dev/null +++ b/deploy/roles/exim4/handlers/main.yml @@ -0,0 +1,6 @@ +--- +- name: restart exim4 + service: + name: 'exim4' + state: restarted + enabled: yes diff --git a/deploy/roles/exim4/tasks/configure.yml b/deploy/roles/exim4/tasks/configure.yml new file mode 100644 index 0000000..e145798 --- /dev/null +++ b/deploy/roles/exim4/tasks/configure.yml @@ -0,0 +1,50 @@ +--- + +- name: Configure exim4 + notify: restart exim4 + template: + src: 'update-exim4.conf.conf' + dest: /etc/exim4/update-exim4.conf.conf + +- name: Update mailname + notify: restart exim4 + copy: + content: '{{ exim4_sendonly_fqdn }}' + dest: '/etc/mailname' + +- name: Define email aliases + notify: restart exim4 + lineinfile: + dest: /etc/aliases + regexp: "{{ item.regexp }}" + line: "{{ item.line }}" + with_items: '{{ exim4_sendonly_email_aliases }}' + when: exim4_sendonly_email_aliases|length + +- name: Define email addresses + notify: restart exim4 + template: + src: 'email-addresses.j2' + dest: '/etc/email-addresses' + when: exim4_sendonly_email_addresses|length + +- name: Set auth for relay host + notify: restart exim4 + template: + src: 'passwd.client' + dest: '/etc/exim4/passwd.client' + +- name: Enable TLS + notify: restart exim4 + template: + src: 'exim4.conf.localmacros' + dest: '/etc/exim4/exim4.conf.localmacros' + when: exim4_sendonly_enable_tls + +- name: 'Set logrotate for local user mails in /var/mail' + copy: + src: 'logrotate' + dest: /etc/logrotate.d/local_user_mails + owner: root + group: root + mode: '0644' diff --git a/deploy/roles/exim4/tasks/install.yml b/deploy/roles/exim4/tasks/install.yml new file mode 100644 index 0000000..f49321b --- /dev/null +++ b/deploy/roles/exim4/tasks/install.yml @@ -0,0 +1,7 @@ +--- + +- name: Install exim4 packages + apt: + name: '{{ exim4_sendonly_apt_packages }}' + state: present + cache_valid_time: 86400 diff --git a/deploy/roles/exim4/tasks/main.yml b/deploy/roles/exim4/tasks/main.yml new file mode 100644 index 0000000..f5f8bd7 --- /dev/null +++ b/deploy/roles/exim4/tasks/main.yml @@ -0,0 +1,15 @@ +--- + +- name: Install exim4 + import_tasks: install.yml + +- name: Configure exim4 + import_tasks: configure.yml + +- name: Start exim4 + service: + name: exim4 + state: started + enabled: true + changed_when: false + tags: ['always'] diff --git a/deploy/roles/exim4/templates/email-addresses.j2 b/deploy/roles/exim4/templates/email-addresses.j2 new file mode 100644 index 0000000..c7cbafe --- /dev/null +++ b/deploy/roles/exim4/templates/email-addresses.j2 @@ -0,0 +1,3 @@ +{% for user, email in exim4_sendonly_email_addresses.items() %} +{{ user }}: {{ email }} +{% endfor %} diff --git a/deploy/roles/exim4/templates/exim4.conf.localmacros b/deploy/roles/exim4/templates/exim4.conf.localmacros new file mode 100644 index 0000000..45836d7 --- /dev/null +++ b/deploy/roles/exim4/templates/exim4.conf.localmacros @@ -0,0 +1 @@ +MAIN_TLS_ENABLE = 1 \ No newline at end of file diff --git a/deploy/roles/exim4/templates/passwd.client b/deploy/roles/exim4/templates/passwd.client new file mode 100644 index 0000000..feeb0df --- /dev/null +++ b/deploy/roles/exim4/templates/passwd.client @@ -0,0 +1,10 @@ +# password file used when the local exim is authenticating to a remote +# host as a client. +# +# see exim4_passwd_client(5) for more documentation +# +# Example: +### target.mail.server.example:login:password +{% if exim4_sendonly_username != '' %} +*:{{ exim4_sendonly_username }}:{{ exim4_sendonly_password }} +{% endif %} diff --git a/deploy/roles/exim4/templates/update-exim4.conf.conf b/deploy/roles/exim4/templates/update-exim4.conf.conf new file mode 100644 index 0000000..0c29d5e --- /dev/null +++ b/deploy/roles/exim4/templates/update-exim4.conf.conf @@ -0,0 +1,30 @@ +# /etc/exim4/update-exim4.conf.conf +# +# Edit this file and /etc/mailname by hand and execute update-exim4.conf +# yourself or use 'dpkg-reconfigure exim4-config' +# +# Please note that this is _not_ a dpkg-conffile and that automatic changes +# to this file might happen. The code handling this will honor your local +# changes, so this is usually fine, but will break local schemes that mess +# around with multiple versions of the file. +# +# update-exim4.conf uses this file to determine variable values to generate +# exim configuration macros for the configuration file. +# +# Most settings found in here do have corresponding questions in the +# Debconf configuration, but not all of them. +# +# This is a Debian specific file +dc_eximconfig_configtype="{{ 'internet' if exim4_sendonly_smarthost == '' else 'satellite' }}" +dc_other_hostnames='{{ ansible_hostname }}; localhost.localdomain; localhost' +dc_local_interfaces='127.0.0.1' +dc_readhost='' +dc_relay_domains='' +dc_minimaldns='false' +dc_relay_nets='' +dc_smarthost='{{ exim4_sendonly_smarthost }}' +CFILEMODE='644' +dc_use_split_config='true' +dc_hide_mailname='true' +dc_mailname_in_oh='true' +dc_localdelivery='mail_spool' diff --git a/deploy/roles/letsencrypt/README.md b/deploy/roles/letsencrypt/README.md new file mode 100644 index 0000000..62b97b8 --- /dev/null +++ b/deploy/roles/letsencrypt/README.md @@ -0,0 +1,10 @@ +# Letsencrypt role + +This role uses the standalone mode of certbot if no webserver is +running (typically during the first installation), else uses the nginx +module. + +Note that existing certificates are renewed (using the nginx module) +as a cron task/systemd timer. + +It creates snippets in `/etc/nginx/snippets/letsencrypt-{{ fqdn }}.conf`. diff --git a/deploy/roles/letsencrypt/defaults/main.yml b/deploy/roles/letsencrypt/defaults/main.yml new file mode 100644 index 0000000..bd652a4 --- /dev/null +++ b/deploy/roles/letsencrypt/defaults/main.yml @@ -0,0 +1,3 @@ +--- + +domains: [example.com] diff --git a/deploy/roles/letsencrypt/tasks/main.yml b/deploy/roles/letsencrypt/tasks/main.yml new file mode 100644 index 0000000..6a0c823 --- /dev/null +++ b/deploy/roles/letsencrypt/tasks/main.yml @@ -0,0 +1,66 @@ +--- + +- name: Install ca-certificates + apt: + state: present + name: [cron, ca-certificates, nginx] + +- name: Setup or upgrade venv + command: python3 -m venv --upgrade-deps /root/certbot-venv/ + changed_when: false + +- name: Prepare certbot+gandi venv + pip: + chdir: /root/ + virtualenv_command: /usr/bin/python3 -m venv + virtualenv: /root/certbot-venv/ + state: latest + name: + - pip + +- name: Install certbot+gandi in venv + pip: + chdir: /root/ + state: latest + virtualenv_command: /usr/bin/python3 -m venv + virtualenv: /root/certbot-venv/ + name: + - certbot + - certbot-plugin-gandi + +- name: Setup Gandi credentials + copy: + content: "dns_gandi_api_key = {{ gandi_api_key }}" + mode: 0600 + dest: /root/gandi.ini + +- name: Create SSL dhparam + get_url: + url: https://ssl-config.mozilla.org/ffdhe2048.txt + dest: /etc/ssl/certs/dhparam.pem + mode: 0644 + +- name: Generate TLS certificates + command: /root/certbot-venv/bin/certbot certonly --cert-name {{ cert_name | quote }} -n --agree-tos -d {{ domains | join(",") | quote }} -m {{ admin_email | quote }} --authenticator dns-gandi --dns-gandi-credentials /root/gandi.ini + register: certbot + changed_when: '"no action taken." not in certbot.stdout' + +- name: Create letsencrypt snippets + template: + src: letsencrypt.conf.j2 + dest: '/etc/nginx/snippets/letsencrypt-{{ cert_name }}.conf' + +- name: Setup renewal cron + cron: + name: certbot + minute: "55" + hour: "8" + job: '/root/certbot-venv/bin/certbot -q renew' + +- name: Setup renewal hook to reload nginx + copy: + mode: 0755 + content: | + #!/bin/sh + systemctl reload nginx + dest: /etc/letsencrypt/renewal-hooks/deploy/01-reload-nginx diff --git a/deploy/roles/letsencrypt/templates/letsencrypt.conf.j2 b/deploy/roles/letsencrypt/templates/letsencrypt.conf.j2 new file mode 100644 index 0000000..2fc4dd4 --- /dev/null +++ b/deploy/roles/letsencrypt/templates/letsencrypt.conf.j2 @@ -0,0 +1,29 @@ +# https://ssl-config.mozilla.org/ + +ssl_certificate /etc/letsencrypt/live/{{ cert_name }}/fullchain.pem; +ssl_certificate_key /etc/letsencrypt/live/{{ cert_name }}/privkey.pem; + +ssl_session_timeout 1d; +ssl_session_cache shared:MozSSL:10m; # about 40000 sessions +ssl_session_tickets off; + +# curl https://ssl-config.mozilla.org/ffdhe2048.txt > /path/to/dhparam +ssl_dhparam /etc/ssl/certs/dhparam.pem; + +# intermediate configuration +ssl_protocols TLSv1.2 TLSv1.3; +ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; +ssl_prefer_server_ciphers off; + +# HSTS (ngx_http_headers_module is required) (63072000 seconds) +add_header Strict-Transport-Security "max-age=63072000" always; + +# OCSP stapling +ssl_stapling on; +ssl_stapling_verify on; + +# verify chain of trust of OCSP response using Root CA and Intermediate certs +ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt; + +# replace with the IP address of your resolver +resolver 155.133.140.130; diff --git a/deploy/roles/website/files/systemd/website.service b/deploy/roles/website/files/systemd/website.service new file mode 100644 index 0000000..9329e15 --- /dev/null +++ b/deploy/roles/website/files/systemd/website.service @@ -0,0 +1,19 @@ +[Unit] +Description=website daemon +After=network.target + +[Service] +PIDFile=/opt/website/website.pid +User=website +Group=website +WorkingDirectory=/opt/website/src +ExecStart=/opt/website/venv/bin/gunicorn --pid /opt/website/website.pid \ + --bind unix:/opt/website/website.sock --timeout 60 eqy_fr.wsgi \ + --access-logfile - +ExecReload=/bin/kill -s HUP $MAINPID +ExecStop=/bin/kill -s TERM $MAINPID +PrivateTmp=true +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/deploy/roles/website/handlers/main.yml b/deploy/roles/website/handlers/main.yml new file mode 100644 index 0000000..61d177f --- /dev/null +++ b/deploy/roles/website/handlers/main.yml @@ -0,0 +1,10 @@ +--- + +- name: restart website + systemd: name=website state=restarted daemon_reload=yes + +- name: reload nginx + service: name=nginx state=reloaded + +- name: reload psql + service: name=postgresql state=reloaded diff --git a/deploy/roles/website/meta/main.yml b/deploy/roles/website/meta/main.yml new file mode 100644 index 0000000..0aeeba0 --- /dev/null +++ b/deploy/roles/website/meta/main.yml @@ -0,0 +1,4 @@ +--- + +dependencies: + - role: letsencrypt diff --git a/deploy/roles/website/tasks/main.yml b/deploy/roles/website/tasks/main.yml new file mode 100644 index 0000000..6df3fe0 --- /dev/null +++ b/deploy/roles/website/tasks/main.yml @@ -0,0 +1,212 @@ +--- + +- name: Install dependencies + apt: + state: present + name: + - cron + - gettext + - git + - nginx + - postgresql + - postgresql-server-dev-all # To compile Python client. + - pgbadger + - python3 + - python3-pip + - python3-psycopg2 + - python3-venv + update_cache: true + tags: website + +- name: Add unix user website + user: + name: website + shell: /bin/false + system: yes + home: /opt/website + tags: website + +- name: install website.service (systemd) + copy: + src: systemd/website.service + dest: /etc/systemd/system/website.service + owner: root + group: root + mode: 0644 + notify: restart website + tags: website + +- name: add user website to pgsql + become: true + become_user: postgres + postgresql_user: + user: website + tags: website + +- name: add database media + become: true + become_user: postgres + postgresql_db: + name: media + owner: website + tags: website + +- name: Collect PostgreSQL version and extensions + become: yes + become_user: postgres + postgresql_info: + filter: ver* + register: db_info + +- name: Configure psql + notify: reload psql + copy: + dest: "/etc/postgresql/{{ db_info.version.major }}/main/conf.d/media.conf" + owner: postgres + group: postgres + mode: 0644 + content: | + log_min_duration_statement = 0 + log_checkpoints = on + log_connections = on + log_disconnections = on + log_lock_waits = on + log_temp_files = 0 + log_autovacuum_min_duration = 0 + log_error_verbosity = default + lc_messages='en_US.UTF-8' + lc_messages='C' + +- name: Synchronize source + ansible.posix.synchronize: + src: /home/mdk/eqy.fr/ + dest: /opt/website/src/ + notify: restart website + +- name: Creates a /opt/website/venv for virtual environments + file: + path: /opt/website/venv + state: directory + mode: 0755 + tags: website + +- name: Setup or upgrade venv + command: python3 -m venv --upgrade-deps /opt/website/venv + changed_when: false + +- name: Creates a /opt/website/locale for translations + file: + path: /opt/website/locale + state: directory + mode: 0755 + owner: root + group: root + tags: website + +- name: Creates a /opt/website/media for medias + file: + path: /opt/website/media + state: directory + mode: 0755 + owner: website + group: website + tags: website + +- name: Creates a /opt/website/static for static + file: + path: /opt/website/locale + state: directory + mode: 0755 + owner: root + group: root + tags: website + +- name: pip installs requirements + pip: + chdir: /opt/website/src + requirements: requirements.txt + virtualenv: /opt/website/venv + virtualenv_command: /usr/bin/python3 -m venv + tags: website + +- name: pip installs psycopg2 + pip: + chdir: /opt/website/src + name: psycopg2 + virtualenv: /opt/website/venv + virtualenv_command: /usr/bin/python3 -m venv + tags: website + +- name: pip installs gunicorn + pip: + chdir: /opt/website/src + name: gunicorn + virtualenv: /opt/website/venv + virtualenv_command: /usr/bin/python3 -m venv + tags: website + +- name: Install website configuration + template: + src: local_settings.py.j2 + dest: /opt/website/src/local_settings.py + owner: root + group: website + mode: 0640 + notify: restart website + tags: website + +- name: Migrate db + command: "/opt/website/venv/bin/python manage.py migrate" + args: + chdir: "/opt/website/src" + register: migrate_result + changed_when: '" Applying " in migrate_result.stdout' + run_once: true + become: true + become_user: website + tags: [website, test] + +- name: Collectstatic + command: "/opt/website/venv/bin/python manage.py collectstatic --noinput" + args: + chdir: "/opt/website/src" + register: collectstatic_result + changed_when: '"Copying " in collectstatic_result.stdout' + tags: [website, test] + +- name: Compile gettext + command: "/opt/website/venv/bin/python manage.py compilemessages" + args: + chdir: "/opt/website/src" + notify: restart website + tags: [website, test] + +- name: Ensure website is running + service: name=website state=started enabled=yes + tags: website + +- name: Configure nginx host + template: + src: nginx-vhost + dest: "/etc/nginx/sites-available/{{ website_vhost }}" + owner: root + group: root + mode: 0644 + notify: reload nginx + tags: website + +- name: Create symlink for API nginx site + file: + src: "/etc/nginx/sites-available/{{ website_vhost }}" + dest: "/etc/nginx/sites-enabled/{{ website_vhost }}" + state: link + notify: reload nginx + tags: website + +- name: Daily backup + cron: + user: website + name: "backup" + job: "/usr/bin/pg_dump --clean media > backup.sql" + hour: '2' + minute: '0' diff --git a/deploy/roles/website/templates/local_settings.py.j2 b/deploy/roles/website/templates/local_settings.py.j2 new file mode 100644 index 0000000..c398493 --- /dev/null +++ b/deploy/roles/website/templates/local_settings.py.j2 @@ -0,0 +1,22 @@ +DATABASES = { + "default": { + "ENGINE": "django.db.backends.postgresql", + "NAME": "media", + "USER": "website", + } +} + +STATIC_ROOT = "/opt/website/static/" +MEDIA_ROOT = "/opt/website/media/" + +DEBUG = False + +SERVER_EMAIL = "julien@palard.fr" + +ALLOWED_HOSTS = ["eqy.fr"] + +SECRET_KEY = r'ephae4Xo Aeve8aic eeph7Eed Sho9oloo johY4ae5 ish0Eela areiy3Ia Joechoo6' + +LOCALE_PATHS = ["/opt/website/locale"] + +SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTOCOL", "https") diff --git a/deploy/roles/website/templates/nginx-vhost b/deploy/roles/website/templates/nginx-vhost new file mode 100644 index 0000000..d489aff --- /dev/null +++ b/deploy/roles/website/templates/nginx-vhost @@ -0,0 +1,34 @@ +server { + listen [::]:80; + listen 80; + server_name {{ website_vhost }}; + + location / { + return 301 https://{{ website_vhost }}$request_uri; + } +} + +server { + listen 443 http2 ssl; + listen [::]:443 http2 ssl; + server_name {{ website_vhost }}; + + client_max_body_size 666M; + + include snippets/letsencrypt-{{ cert_name }}.conf; + + location /static/ { + alias /opt/website/static/; + } + + location /media/ { + alias /opt/website/media/; + } + + location / { + proxy_pass http://unix:/opt/website/website.sock; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-Protocol $scheme; + } +} diff --git a/deploy/website.yml b/deploy/website.yml new file mode 100644 index 0000000..0820eef --- /dev/null +++ b/deploy/website.yml @@ -0,0 +1,12 @@ +--- + +- hosts: website + roles: + - common + - website + vars: + website_vhost: eqy.fr + admin_email: julien+photos@palard.fr + cert_name: eqy + domains: + - eqy.fr diff --git a/eqy_fr/settings.py b/eqy_fr/settings.py index 7c8819b..f150db1 100644 --- a/eqy_fr/settings.py +++ b/eqy_fr/settings.py @@ -123,3 +123,8 @@ MEDIA_URL = 'media/' # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +try: + from local_settings import * +except ImportError: + pass diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d3e4ba5 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +django