Add deployment scripts
This commit is contained in:
		
							parent
							
								
									3f31086dd8
								
							
						
					
					
						commit
						9cb0c6f676
					
				
					 3 changed files with 305 additions and 0 deletions
				
			
		
							
								
								
									
										147
									
								
								deploy/basics.yml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								deploy/basics.yml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,147 @@ | ||||||
|  | # Basic Ansible playbook to set up security essentials: Nginx dhparams, fail2ban, | ||||||
|  | # unattended-upgrades, history logging, firewall, no SSH keys and Postfix | ||||||
|  | # relay/rewriting/aliases. | ||||||
|  | # | ||||||
|  | # Run with: | ||||||
|  | # ANSIBLE_STDOUT_CALLBACK=debug ansible-playbook deploy/basics.yml -i deploy/inventory.yml --verbose | ||||||
|  | - hosts: web | ||||||
|  |   become: true | ||||||
|  |   vars: | ||||||
|  |     ansible_ssh_pipelining: true | ||||||
|  |   tasks: | ||||||
|  |     - name: Generate dhparams file for HTTP2 | ||||||
|  |       ansible.builtin.command: | ||||||
|  |         cmd: openssl dhparam -out /etc/nginx/dhparam.pem 2048 | ||||||
|  |         creates: /etc/nginx/dhparam.pem | ||||||
|  |     - name: Install fail2ban | ||||||
|  |       apt: | ||||||
|  |         pkg: fail2ban | ||||||
|  | 
 | ||||||
|  |     - name: Install unattended-upgrades | ||||||
|  |       apt: | ||||||
|  |         pkg: unattended-upgrades | ||||||
|  | 
 | ||||||
|  |     - name: Configure unattended upgrades overrides | ||||||
|  |       # See defaults in 50unattended-upgrades. | ||||||
|  |       copy: | ||||||
|  |         dest: /etc/apt/apt.conf.d/20auto-upgrades | ||||||
|  |         content: | | ||||||
|  |           APT::Periodic::Update-Package-Lists "1"; | ||||||
|  |           APT::Periodic::Unattended-Upgrade "1"; | ||||||
|  |           Unattended-Upgrade::Automatic-Reboot "true"; | ||||||
|  |           Unattended-Upgrade::Automatic-Reboot-Time "02:00"; | ||||||
|  |           Unattended-Upgrade::Mail "root"; | ||||||
|  | 
 | ||||||
|  |     - name: Add extensive history logging | ||||||
|  |       blockinfile: | ||||||
|  |         path: /etc/bash.bashrc | ||||||
|  |         block: | | ||||||
|  |           # Write to history file immediately (rather than only when shell is | ||||||
|  |           # closed). For setting history length see HISTSIZE and HISTFILESIZE in | ||||||
|  |           # bash(1). | ||||||
|  |           shopt -s histappend | ||||||
|  |           PROMPT_COMMAND='history -a' | ||||||
|  |           HISTSIZE=1000000 | ||||||
|  |           HISTFILESIZE=1000000 | ||||||
|  |         insertafter: EOF | ||||||
|  | 
 | ||||||
|  |     - name: Install `netfilter-persistent` && `iptables-persistent` packages | ||||||
|  |       apt: | ||||||
|  |         pkg: | ||||||
|  |           - iptables-persistent | ||||||
|  |           - netfilter-persistent | ||||||
|  | 
 | ||||||
|  |     - name: Flush existing firewall rules | ||||||
|  |       iptables: | ||||||
|  |         flush: true | ||||||
|  | 
 | ||||||
|  |     - name: Firewall rule - allow all loopback traffic | ||||||
|  |       iptables: | ||||||
|  |         action: append | ||||||
|  |         chain: INPUT | ||||||
|  |         in_interface: lo | ||||||
|  |         jump: ACCEPT | ||||||
|  | 
 | ||||||
|  |     - name: Firewall rule - allow established connections | ||||||
|  |       iptables: | ||||||
|  |         chain: INPUT | ||||||
|  |         ctstate: ESTABLISHED,RELATED | ||||||
|  |         jump: ACCEPT | ||||||
|  | 
 | ||||||
|  |     - name: Firewall rule - allow port ping traffic | ||||||
|  |       iptables: | ||||||
|  |         chain: INPUT | ||||||
|  |         jump: ACCEPT | ||||||
|  |         protocol: icmp | ||||||
|  | 
 | ||||||
|  |     - name: Firewall rule - allow port 22/SSH traffic | ||||||
|  |       iptables: | ||||||
|  |         chain: INPUT | ||||||
|  |         destination_port: '22' | ||||||
|  |         jump: ACCEPT | ||||||
|  |         protocol: tcp | ||||||
|  | 
 | ||||||
|  |     - name: Firewall rule - allow port 80/HTTP traffic | ||||||
|  |       iptables: | ||||||
|  |         chain: INPUT | ||||||
|  |         destination_port: '80' | ||||||
|  |         jump: ACCEPT | ||||||
|  |         protocol: tcp | ||||||
|  | 
 | ||||||
|  |     - name: Firewall rule - allow port 443/HTTPS traffic | ||||||
|  |       iptables: | ||||||
|  |         chain: INPUT | ||||||
|  |         destination_port: '443' | ||||||
|  |         jump: ACCEPT | ||||||
|  |         protocol: tcp | ||||||
|  | 
 | ||||||
|  |     - name: Firewall rule - drop any traffic without rule | ||||||
|  |       iptables: | ||||||
|  |         chain: INPUT | ||||||
|  |         jump: DROP | ||||||
|  | 
 | ||||||
|  |     - name: Disable SSH password authentication | ||||||
|  |       lineinfile: | ||||||
|  |         path: /etc/ssh/sshd_config | ||||||
|  |         line: 'PasswordAuthentication no' | ||||||
|  |         regexp: 'PasswordAuthentication ' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     # Postfix | ||||||
|  |     - name: Postfix | ||||||
|  |       apt: | ||||||
|  |         pkg: | ||||||
|  |           - postfix | ||||||
|  |           - mailutils | ||||||
|  | 
 | ||||||
|  |     ## Commented because you only want this on first run ever. | ||||||
|  |     # - name: Add file for SMTP credentials | ||||||
|  |     #   copy: | ||||||
|  |     #     dest: /etc/postfix/sasl_passwd | ||||||
|  |     #     content: |- | ||||||
|  |     #       # After updating, run `sudo postmap hash:/etc/postfix/sasl_passwd`. | ||||||
|  |     #       [pine.sfconservancy.org]:587 conference@sfconservancy.org:PASSWORD | ||||||
|  | 
 | ||||||
|  |     - name: Configure Postfix envelope rewriting | ||||||
|  |       copy: | ||||||
|  |         dest: /etc/postfix/canonical | ||||||
|  |         content: |- | ||||||
|  |           /./ conference@sfconservancy.org | ||||||
|  | 
 | ||||||
|  |     - name: Configure Postfix From header rewriting | ||||||
|  |       copy: | ||||||
|  |         dest: /etc/postfix/header_checks | ||||||
|  |         content: |- | ||||||
|  |           /^From:.*/ REPLACE From: conference@sfconservancy.org | ||||||
|  | 
 | ||||||
|  |     - name: Configure Postfix for relaying | ||||||
|  |       copy: | ||||||
|  |         src: postfix/main.cf | ||||||
|  |         dest: /etc/postfix/main.cf | ||||||
|  | 
 | ||||||
|  |     - name: Alias mail to root | ||||||
|  |       copy: | ||||||
|  |         dest: /etc/aliases | ||||||
|  |         content: |- | ||||||
|  |           postmaster: root | ||||||
|  |           root: sysadmin@sfconservancy.org, sysadmin@sturm.com.au | ||||||
							
								
								
									
										66
									
								
								deploy/postfix/main.cf
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								deploy/postfix/main.cf
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,66 @@ | ||||||
|  | # See /usr/share/postfix/main.cf.dist for a commented, more complete version | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # Debian specific:  Specifying a file name will cause the first | ||||||
|  | # line of that file to be used as the name.  The Debian default | ||||||
|  | # is /etc/mailname. | ||||||
|  | #myorigin = /etc/mailname | ||||||
|  | 
 | ||||||
|  | smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU) | ||||||
|  | biff = no | ||||||
|  | 
 | ||||||
|  | # appending .domain is the MUA's job. | ||||||
|  | append_dot_mydomain = no | ||||||
|  | 
 | ||||||
|  | # Uncomment the next line to generate "delayed mail" warnings | ||||||
|  | #delay_warning_time = 4h | ||||||
|  | 
 | ||||||
|  | readme_directory = no | ||||||
|  | 
 | ||||||
|  | # See http://www.postfix.org/COMPATIBILITY_README.html -- default to 2 on | ||||||
|  | # fresh installs. | ||||||
|  | compatibility_level = 2 | ||||||
|  | 
 | ||||||
|  | # TLS parameters | ||||||
|  | smtpd_tls_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem | ||||||
|  | smtpd_tls_key_file=/etc/ssl/private/ssl-cert-snakeoil.key | ||||||
|  | smtpd_use_tls=yes | ||||||
|  | smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache | ||||||
|  | smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache | ||||||
|  | 
 | ||||||
|  | # See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for | ||||||
|  | # information on enabling SSL in the smtp client. | ||||||
|  | 
 | ||||||
|  | smtpd_relay_restrictions = permit_mynetworks permit_sasl_authenticated defer_unauth_destination | ||||||
|  | myhostname = symposion.sfconservancy.org | ||||||
|  | alias_maps = hash:/etc/aliases | ||||||
|  | alias_database = hash:/etc/aliases | ||||||
|  | myorigin = /etc/mailname | ||||||
|  | mydestination = $myhostname, symposion.novalocal, symposion, localhost | ||||||
|  | relayhost = [pine.sfconservancy.org]:587 | ||||||
|  | mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128 | ||||||
|  | mailbox_size_limit = 0 | ||||||
|  | recipient_delimiter = + | ||||||
|  | inet_interfaces = loopback-only | ||||||
|  | inet_protocols = all | ||||||
|  | 
 | ||||||
|  | # Relay to Conservancy | ||||||
|  | # | ||||||
|  | smtp_sasl_auth_enable = yes | ||||||
|  | smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd | ||||||
|  | smtp_sasl_security_options = noanonymous | ||||||
|  | smtp_tls_security_level = secure | ||||||
|  | smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt | ||||||
|  | 
 | ||||||
|  | # Increase default limit of 10M to 50M | ||||||
|  | message_size_limit = 51200000 | ||||||
|  | 
 | ||||||
|  | # This configuration rewrites both envelope sender and From header to a single | ||||||
|  | # fixed address so that all outgoing mail, including cron emails, can be | ||||||
|  | # delivered through a single user-grade SMTP account. | ||||||
|  | # | ||||||
|  | # Envelope | ||||||
|  | canonical_maps = regexp:/etc/postfix/canonical | ||||||
|  | canonical_classes = envelope_sender | ||||||
|  | # From header | ||||||
|  | smtp_header_checks = regexp:/etc/postfix/header_checks | ||||||
							
								
								
									
										92
									
								
								fabfile.py
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								fabfile.py
									
										
									
									
										vendored
									
									
										Normal file
									
								
							|  | @ -0,0 +1,92 @@ | ||||||
|  | """Automated deployment with Fabric. | ||||||
|  | 
 | ||||||
|  | To deploy to production: | ||||||
|  | 
 | ||||||
|  |   python3 -m pip install --user vps-deploy | ||||||
|  |   export FABRIC_HOSTS=user@hostname | ||||||
|  |   fab deploy | ||||||
|  | """ | ||||||
|  | 
 | ||||||
|  | import os | ||||||
|  | 
 | ||||||
|  | from fabric import task  # type: ignore | ||||||
|  | from invoke.collection import Collection  # type: ignore | ||||||
|  | from patchwork.files import exists | ||||||
|  | from vps_deploy import django_fabric2 as df2  # type: ignore | ||||||
|  | 
 | ||||||
|  | hosts = os.environ['FABRIC_HOSTS'].split(',') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def install_essentials(c): | ||||||
|  |     # ImageMagick (convert) and Inkscape required for generating badges. | ||||||
|  |     c.run('sudo apt-get install -yy git python3-dev python3-venv python3-wheel build-essential python3-cairocffi python3-psycopg2 postgresql uwsgi-emperor uwsgi-plugin-python3 memcached netcat nginx certbot libmemcached-dev xmlsec1 imagemagick inkscape') | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @task(hosts=hosts) | ||||||
|  | def deploy(c): | ||||||
|  |     install_essentials(c) | ||||||
|  |     df2.transfer_files_git(c) | ||||||
|  |     df2.init(c) | ||||||
|  |     if not exists(c, c.env.virtualenv): | ||||||
|  |         c.sudo(f'mkdir -p $(dirname {c.env.virtualenv})') | ||||||
|  |         c.sudo(f'chown {c.user} $(dirname {c.env.virtualenv})') | ||||||
|  |         c.run('{env.python} -m venv --system-site-packages {env.virtualenv}'.format(env=c.env)) | ||||||
|  |     with c.cd(c.env.project_dir): | ||||||
|  |         c.run('{env.virtualenv}/bin/python -m pip install -c constraints.txt -r requirements.txt'.format(env=c.env)) | ||||||
|  |         # Fixes "AttributeError: install_layout". See: | ||||||
|  |         # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1003252 | ||||||
|  |         # https://github.com/pypa/setuptools/issues/3278 | ||||||
|  |         c.run('SETUPTOOLS_USE_DISTUTILS=stdlib {env.virtualenv}/bin/python -m pip install -c constraints.txt -r vendored_requirements.txt'.format(env=c.env)) | ||||||
|  |     df2.prepare_django(c, fail_level='ERROR') | ||||||
|  |     df2.fix_permissions( | ||||||
|  |         c, | ||||||
|  |         read=['pinaxcon', 'vendor', 'static'], | ||||||
|  |         read_write=[], | ||||||
|  |     ) | ||||||
|  |     df2.reload_uwsgi(c) | ||||||
|  |     df2.flush_memcached(c) | ||||||
|  |     df2.update_nginx(c) | ||||||
|  |     df2.install_scheduled_jobs( | ||||||
|  |         c, | ||||||
|  |         periodic_jobs=[ | ||||||
|  |             'deploy/cron/cron.daily', | ||||||
|  |         ], | ||||||
|  |     ) | ||||||
|  |     df2.check_site_online(c) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # The "ns" appears to be a magic name. | ||||||
|  | ns = Collection( | ||||||
|  |     deploy, | ||||||
|  |     task(df2.download_postgres_db, hosts=hosts), | ||||||
|  |     task(df2.mirror_postgres_db, hosts=hosts), | ||||||
|  |     task(df2.mirror_media, hosts=hosts), | ||||||
|  |     task(df2.django_shell, hosts=hosts), | ||||||
|  |     task(df2.bash, hosts=hosts), | ||||||
|  | ) | ||||||
|  | ns.configure({ | ||||||
|  |     # Built-in Fabric config. | ||||||
|  |     'run': { | ||||||
|  |         'echo': True, | ||||||
|  |         # Needed so local commands work. Can also use FABRIC_RUN_REPLACE_ENV. | ||||||
|  |         'replace_env': False, | ||||||
|  |     }, | ||||||
|  | 
 | ||||||
|  |     # Our custom project config. | ||||||
|  |     'env': { | ||||||
|  |         'branch': 'fossy2023', | ||||||
|  |         'app_user': 'www-data', | ||||||
|  |         'db_name': 'symposion', | ||||||
|  |         'project_dir': '/srv/symposion_app', | ||||||
|  |         'media_dir': 'media', | ||||||
|  |         'virtualenv': '/srv/venvs/symposion-django-py39', | ||||||
|  |         'site_name': 'symposion', | ||||||
|  |         'requirements': 'requirements.txt', | ||||||
|  |         'settings': 'pinaxcon.settings', | ||||||
|  |         'uwsgi_conf': 'deploy/uwsgi.ini', | ||||||
|  |         'nginx_conf': 'deploy/nginx.conf', | ||||||
|  |         'python': '/usr/bin/python3.9', | ||||||
|  |         'url': 'https://2023.fossy.us/', | ||||||
|  |         'domain': '2023.fossy.us', | ||||||
|  |     }, | ||||||
|  | }) | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue