diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..2feec03c1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +ansible/inventory +ansible/passwords/ diff --git a/README.md b/README.md index d54c2875c..2283ee464 100644 --- a/README.md +++ b/README.md @@ -64,14 +64,32 @@ Made with [Rust](https://www.rust-lang.org), [Actix](https://actix.rs/), [Infern ## Install -### Docker +### Ansible (recommended) -Make sure you have both docker and docker-compose(>=`1.24.0`) installed. +First, you need to [install Ansible on your local computer](https://docs.ansible.com/ansible/latest/installation_guide/intro_installation.html), +eg using `sudo apt install ansible`, or the equivalent for you platform. + +Then run the following commands on your local computer: +```bash +git clone https://github.com/dessalines/lemmy.git +cd lemmy/ansible/ +cp inventory.example inventory +nano inventory # enter your server, domain, contact email +ansible-playbook lemmy.yml +``` + +### Manual + +Make sure you have both docker and docker-compose installed. ``` mkdir lemmy/ cd lemmy/ wget https://raw.githubusercontent.com/dessalines/lemmy/master/docker/prod/docker-compose.yml +wget https://raw.githubusercontent.com/dessalines/lemmy/master/docker/prod/env -O .env +wget https://raw.githubusercontent.com/dessalines/lemmy/master/docker/prod/nginx.conf +# you need to edit .env and nginx.conf to replace the indicated {{ variables }} +sudo mv nginx.conf /etc/nginx/sites-enabled/lemmy.conf docker-compose up -d ``` diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg new file mode 100644 index 000000000..960a7c40f --- /dev/null +++ b/ansible/ansible.cfg @@ -0,0 +1,5 @@ +[defaults] +inventory=inventory + +[ssh_connection] +pipelining = True diff --git a/ansible/inventory.example b/ansible/inventory.example new file mode 100644 index 000000000..52b45d3c3 --- /dev/null +++ b/ansible/inventory.example @@ -0,0 +1,6 @@ +[lemmy] +# define the username and hostname that you use for ssh connection, and specify the domain +myuser@example.com domain=example.com letsencrypt_contact_email=your@email.com + +[all:vars] +ansible_connection=ssh diff --git a/ansible/lemmy.yml b/ansible/lemmy.yml new file mode 100644 index 000000000..4ba80e90a --- /dev/null +++ b/ansible/lemmy.yml @@ -0,0 +1,70 @@ +--- +- hosts: all + + # Install python if required + # https://www.josharcher.uk/code/ansible-python-connection-failure-ubuntu-server-1604/ + gather_facts: False + pre_tasks: + - name: install python for Ansible + raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal python-setuptools) + args: + executable: /bin/bash + register: output + changed_when: output.stdout != "" + - setup: # gather facts + + tasks: + - name: install dependencies + apt: + pkg: ['nginx', 'docker-compose', 'docker.io', 'certbot', 'python-certbot-nginx'] + + - name: request initial letsencrypt certificate + command: certbot certonly --nginx --agree-tos -d '{{ domain }}' -m '{{ letsencrypt_contact_email }}' + args: + creates: '/etc/letsencrypt/live/{{domain}}/privkey.pem' + + - name: create lemmy folder + file: path={{item.path}} state=directory + with_items: + - { path: '/lemmy/' } + - { path: '/lemmy/volumes/' } + + - name: add all template files + template: src={{item.src}} dest={{item.dest}} + with_items: + - { src: 'templates/env', dest: '/lemmy/.env' } + - { src: '../docker/prod/docker-compose.yml', dest: '/lemmy/docker-compose.yml' } + - { src: 'templates/nginx.conf', dest: '/etc/nginx/sites-enabled/lemmy.conf' } + vars: + postgres_password: "{{ lookup('password', 'passwords/{{ inventory_hostname }}/postgres chars=ascii_letters,digits') }}" + jwt_password: "{{ lookup('password', 'passwords/{{ inventory_hostname }}/jwt chars=ascii_letters,digits') }}" + + - name: set env file permissions + file: + path: "/lemmy/.env" + state: touch + mode: 0600 + access_time: preserve + modification_time: preserve + + - name: enable and start docker service + systemd: + name: docker + enabled: yes + state: started + + - name: start docker-compose + docker_compose: + project_src: /lemmy/ + state: present + pull: yes + + - name: reload nginx with new config + shell: nginx -s reload + + - name: certbot renewal cronjob + cron: + special_time=daily + name=certbot-renew-lemmy + user=root + job="certbot certonly --nginx -d '{{ domain }}' --deploy-hook 'docker-compose -f /peertube/docker-compose.yml exec nginx nginx -s reload'" diff --git a/ansible/templates/env b/ansible/templates/env new file mode 100644 index 000000000..12ff85066 --- /dev/null +++ b/ansible/templates/env @@ -0,0 +1,4 @@ +DOMAIN={{ domain }} +DATABASE_PASSWORD={{ postgres_password }} +DATABASE_URL=postgres://lemmy:{{ postgres_password }}@db:5432/lemmy +JWT_SECRET={{ jwt_password }} diff --git a/ansible/templates/nginx.conf b/ansible/templates/nginx.conf new file mode 100644 index 000000000..21560b5f5 --- /dev/null +++ b/ansible/templates/nginx.conf @@ -0,0 +1,61 @@ +server { + listen 80; + server_name {{ domain }}; + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + location / { + return 301 https://$host$request_uri; + } +} + +server { + listen 443 ssl http2; + server_name {{ domain }}; + + ssl_certificate /etc/letsencrypt/live/{{domain}}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/{{domain}}/privkey.pem; + + # Various TLS hardening settings + # https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; + ssl_session_timeout 10m; + ssl_session_cache shared:SSL:10m; + ssl_session_tickets off; + ssl_stapling on; + ssl_stapling_verify on; + + # Hide nginx version + server_tokens off; + + # Enable compression for JS/CSS/HTML bundle, for improved client load times. + # It might be nice to compress JSON, but leaving that out to protect against potential + # compression+encryption information leak attacks like BREACH. + gzip on; + gzip_types text/css application/javascript; + gzip_vary on; + + # Only connect to this site via HTTPS for the two years + add_header Strict-Transport-Security "max-age=63072000"; + + # Various content security headers + add_header Referrer-Policy "same-origin"; + add_header X-Content-Type-Options "nosniff"; + add_header X-Frame-Options "DENY"; + add_header X-XSS-Protection "1; mode=block"; + + location / { + rewrite (\/(user|u|inbox|post|community|c|login|search|sponsors|communities|modlog|home)+) /static/index.html break; + proxy_pass http://0.0.0.0:8536; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # WebSocket support + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } +} diff --git a/docker/prod/docker-compose.yml b/docker/prod/docker-compose.yml index 271054fd5..d55b28088 100644 --- a/docker/prod/docker-compose.yml +++ b/docker/prod/docker-compose.yml @@ -1,32 +1,31 @@ -version: '2.4' +version: "3.3" services: + db: image: postgres:12-alpine restart: always environment: - POSTGRES_USER: rrr - POSTGRES_PASSWORD: rrr - POSTGRES_DB: rrr + - POSTGRES_USER=lemmy + - POSTGRES_PASSWORD=${DATABASE_PASSWORD} + - POSTGRES_DB=lemmy volumes: - - db:/var/lib/postgresql/data + - ./volumes/db:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U rrr"] + test: ["CMD-SHELL", "pg_isready -U lemmy"] interval: 5s timeout: 5s retries: 20 + lemmy: - image: dessalines/lemmy:v0.0.7.3 + image: dessalines/lemmy:v0.0.7 .3 + restart: always ports: - "8536:8536" environment: - LEMMY_FRONT_END_DIR: /app/dist - DATABASE_URL: postgres://rrr:rrr@db:5432/rrr - JWT_SECRET: changeme - HOSTNAME: rrr - restart: always - depends_on: - db: - condition: service_healthy -volumes: - db: + - LEMMY_FRONT_END_DIR=/app/dist + - DATABASE_URL=${DATABASE_URL} + - JWT_SECRET=${JWT_SECRET} + - HOSTNAME=${DOMAIN} + depends_on: + - db diff --git a/docker/prod/env b/docker/prod/env new file mode 100644 index 000000000..06f3cfe21 --- /dev/null +++ b/docker/prod/env @@ -0,0 +1,4 @@ +DOMAIN={{your domain}} +DATABASE_PASSWORD={{a random password for postgres}} +DATABASE_URL=postgres://lemmy:{{ the same postgres password again }}@db:5432/lemmy +JWT_SECRET={{ a random password for jwt}} diff --git a/docker/prod/nginx.conf b/docker/prod/nginx.conf new file mode 100644 index 000000000..918851a0f --- /dev/null +++ b/docker/prod/nginx.conf @@ -0,0 +1,61 @@ +server { + listen 80; + server_name {{ your domain }}; + location /.well-known/acme-challenge/ { + root /var/www/certbot; + } + location / { + return 301 https://$host$request_uri; + } +} + +server { + listen 443 ssl http2; + server_name {{ your domain }}; + + ssl_certificate /etc/letsencrypt/live/{{ your domain }}/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/{{ your domain }}/privkey.pem; + + # Various TLS hardening settings + # https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; + ssl_session_timeout 10m; + ssl_session_cache shared:SSL:10m; + ssl_session_tickets off; + ssl_stapling on; + ssl_stapling_verify on; + + # Hide nginx version + server_tokens off; + + # Enable compression for JS/CSS/HTML bundle, for improved client load times. + # It might be nice to compress JSON, but leaving that out to protect against potential + # compression+encryption information leak attacks like BREACH. + gzip on; + gzip_types text/css application/javascript; + gzip_vary on; + + # Only connect to this site via HTTPS for the two years + add_header Strict-Transport-Security "max-age=63072000"; + + # Various content security headers + add_header Referrer-Policy "same-origin"; + add_header X-Content-Type-Options "nosniff"; + add_header X-Frame-Options "DENY"; + add_header X-XSS-Protection "1; mode=block"; + + location / { + rewrite (\/(user|u|inbox|post|community|c|login|search|sponsors|communities|modlog|home)+) /static/index.html break; + proxy_pass http://0.0.0.0:8536; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # WebSocket support + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } +}