From dbf150816cf508e960141dced68015d08c4d2973 Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Wed, 26 May 2021 23:27:48 +0200 Subject: [PATCH 01/14] remove obsolete letsencrypt stuff --- README.md | 7 ----- roles/common/tasks/letsencrypt.yml | 34 --------------------- roles/common/tasks/main.yml | 2 -- roles/common/templates/letsencrypt-renew.j2 | 16 ---------- 4 files changed, 59 deletions(-) delete mode 100644 roles/common/tasks/letsencrypt.yml delete mode 100644 roles/common/templates/letsencrypt-renew.j2 diff --git a/README.md b/README.md index 7624846..9efff90 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,8 @@ This playbook helps you set up and manage your Uberspace(s). -It configures a few common things that I find essential for Uberspaces and it is extensible for other stuff. - ## Current features -- [Let's Encrypt SSL certificates](https://wiki.uberspace.de/webserver:https#let_s-encrypt-zertifikate) - [WordPress](https://wordpress.org/) using the awesome [Bedrock](https://roots.io/bedrock/) boilerplate - [Ruby on Rails](http://rubyonrails.org/) apps @@ -26,10 +23,6 @@ It configures a few common things that I find essential for Uberspaces and it is If you have an SSH keypair and your public key is installed in `~/.ssh/id_rsa.pub` on your local computer, the key will be stored in `~/.ssh/authorized_keys` on your Uberspace and you won't need the `--ask-pass` argument in subsequent runs. -### Let's Encrypt - -Nothing to do or configure here. This works automagically for all your domains. - ### WordPress 1. To set up a WordPress instance, simply create an entry under `wordpress_instances` in your `host_vars` file (see `host_vars/UBERSPACE_NAME.UBERSPACE_HOST.uberspace.de.example` for an example) diff --git a/roles/common/tasks/letsencrypt.yml b/roles/common/tasks/letsencrypt.yml deleted file mode 100644 index 43a26ca..0000000 --- a/roles/common/tasks/letsencrypt.yml +++ /dev/null @@ -1,34 +0,0 @@ ---- -- name: Run uberspace-letsencrypt - shell: uberspace-letsencrypt creates=~/.config/letsencrypt/cli.ini - -- name: Add domains to ~/.config/letsencrypt/cli.ini - lineinfile: - dest: ~/.config/letsencrypt/cli.ini - line: domains = {{ domains | join(',') }} - regexp: ^domains\s*= - -- name: Add agree-tos and renew-by-default to ~/.config/letsencrypt/cli.ini - lineinfile: - dest: ~/.config/letsencrypt/cli.ini - line: "{{item}}" - with_items: - - "agree-tos = True" - - "renew-by-default = True" - -- name: Create script ~/bin/letsencrypt-renew - template: - src : templates/letsencrypt-renew.j2 - dest: ~/bin/letsencrypt-renew - mode: ugo+x - -- name: Create cron job for ~/bin/letsencrypt-renew - cron: - name: letsencrypt-renew - job: ~/bin/letsencrypt-renew - special_time: daily - -- name: Run ~/bin/letsencrypt-renew for the first time - shell: ~/bin/letsencrypt-renew - register: result - changed_when: "'Done renewing certificates.' in result.stdout" diff --git a/roles/common/tasks/main.yml b/roles/common/tasks/main.yml index 6ed7ed9..df676ee 100644 --- a/roles/common/tasks/main.yml +++ b/roles/common/tasks/main.yml @@ -1,4 +1,2 @@ --- - include: common.yml -- include: letsencrypt.yml - when: domains is defined diff --git a/roles/common/templates/letsencrypt-renew.j2 b/roles/common/templates/letsencrypt-renew.j2 deleted file mode 100644 index e6346fe..0000000 --- a/roles/common/templates/letsencrypt-renew.j2 +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -# {{ ansible_managed }} - -PATH=/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/sbin - -openssl x509 -checkend 1728000 -in ~/.config/letsencrypt/live/{{ domains[0] }}/cert.pem > /dev/null - -if [ $? != 0 ]; then - # run let's encrypt - letsencrypt certonly - # import certificate - uberspace-add-certificate \ - -k ~/.config/letsencrypt/live/{{ domains[0] }}/privkey.pem \ - -c ~/.config/letsencrypt/live/{{ domains[0] }}/cert.pem - echo 'Done renewing certificates.' -fi From 0e3796c07be8df0fc15929206af9f4ce5eef2fa8 Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Thu, 27 May 2021 09:21:08 +0200 Subject: [PATCH 02/14] start adapting stuff for U7, rename common to uberspace --- roles/common/tasks/common.yml | 37 ------------------- roles/common/tasks/main.yml | 2 - roles/{common => uberspace}/handlers/main.yml | 0 roles/uberspace/tasks/domains.yml | 21 +++++++++++ roles/uberspace/tasks/main.yml | 28 ++++++++++++++ .../templates/gitconfig.j2 | 0 6 files changed, 49 insertions(+), 39 deletions(-) delete mode 100644 roles/common/tasks/common.yml delete mode 100644 roles/common/tasks/main.yml rename roles/{common => uberspace}/handlers/main.yml (100%) create mode 100644 roles/uberspace/tasks/domains.yml create mode 100644 roles/uberspace/tasks/main.yml rename roles/{common => uberspace}/templates/gitconfig.j2 (100%) diff --git a/roles/common/tasks/common.yml b/roles/common/tasks/common.yml deleted file mode 100644 index 08cd871..0000000 --- a/roles/common/tasks/common.yml +++ /dev/null @@ -1,37 +0,0 @@ ---- -- name: Upload local user's public key for SSH - authorized_key: - user: "{{ansible_user}}" - key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}" - -- name: Generate SSH key pair for remote user on Uberspace - user: name={{ ansible_user }} generate_ssh_key=yes ssh_key_bits=4096 ssh_key_file=.ssh/id_rsa - -- name: Fetch remote user's public key (e.g. for Git access) - fetch: src=~/.ssh/id_rsa.pub dest=public_keys/{{ ansible_host }} flat=yes - -- name: Configure Git - template: - src: templates/gitconfig.j2 - dest: ~/.gitconfig - -- name: Run uberspace-add-domain - shell: uberspace-add-domain -w -d {{item}} - with_items: '{{ domains }}' - ignore_errors: yes - -- name: Enable Strict Transport Security - shell: uberspace-configure-webserver enable hsts - register: result - changed_when: "'new configuration will be live' in result.stdout" - -- name: Enable daemontools - shell: uberspace-setup-svscan - args: - creates: ~/service - -- name: Create ~/www symlink - file: - path: ~/www - src: /var/www/virtual/{{ ansible_user }}/ - state: link diff --git a/roles/common/tasks/main.yml b/roles/common/tasks/main.yml deleted file mode 100644 index df676ee..0000000 --- a/roles/common/tasks/main.yml +++ /dev/null @@ -1,2 +0,0 @@ ---- -- include: common.yml diff --git a/roles/common/handlers/main.yml b/roles/uberspace/handlers/main.yml similarity index 100% rename from roles/common/handlers/main.yml rename to roles/uberspace/handlers/main.yml diff --git a/roles/uberspace/tasks/domains.yml b/roles/uberspace/tasks/domains.yml new file mode 100644 index 0000000..12047e9 --- /dev/null +++ b/roles/uberspace/tasks/domains.yml @@ -0,0 +1,21 @@ +--- +- name: "Enable web for {{ item.name }}" + command: "uberspace web domain add {{ item.name }}" + #ignore_errors: yes + register: web_domain + failed_when: web_domain.rc == 1 and web_domain.stderr != "Can't add domain to the configuration. It is already configured for your Uberspace account." + changed_when: ("The webserver's configuration has been adapted." in web_domain.stdout_lines) + when: item.web or (item.web is not defined and item.mail is not defined) +- name: "Enable mail for {{ item.name }}" + command: "uberspace mail domain add {{ item.name }}" + #ignore_errors: yes + register: mail_domain + failed_when: mail_domain.rc == 1 and mail_domain.stderr != "Can't add domain to the configuration. It is already configured for your Uberspace account." + changed_when: ("The mailserver's configuration has been adapted." in mail_domain.stdout_lines) + when: item.mail or (item.web is not defined and item.mail is not defined) +- name: "Create symlink for {{ item.name }}" + file: + path: ~/www + src: "/var/www/virtual/{{ ansible_env.USER }}/" + state: link + when: item.link or item.link is not defined diff --git a/roles/uberspace/tasks/main.yml b/roles/uberspace/tasks/main.yml new file mode 100644 index 0000000..f457f4a --- /dev/null +++ b/roles/uberspace/tasks/main.yml @@ -0,0 +1,28 @@ +--- +#- name: Upload local user's public key for SSH +# authorized_key: +# user: "{{ ansible_env.USER }}" +# key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}" + +- name: Generate SSH key pair for remote user on Uberspace + user: + name: "{{ ansible_env.USER }}" + generate_ssh_key: yes + ssh_key_bits: 4096 + ssh_key_file: .ssh/id_rsa + +#- name: Fetch remote user's public key (e.g. for Git access) +# fetch: +# src: ~/.ssh/id_rsa.pub +# dest: "public_keys/{{ ansible_host }}" +# flat: yes + +#- name: Configure Git +# template: +# src: templates/gitconfig.j2 +# dest: ~/.gitconfig + +- name: Add domains + include_tasks: domains.yml + with_items: "{{ domains }}" + when: domains is defined diff --git a/roles/common/templates/gitconfig.j2 b/roles/uberspace/templates/gitconfig.j2 similarity index 100% rename from roles/common/templates/gitconfig.j2 rename to roles/uberspace/templates/gitconfig.j2 From c774af09006f8d7e24468968f2f5cdd9b0cca6aa Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Thu, 27 May 2021 09:22:03 +0200 Subject: [PATCH 03/14] remove rails role, because I have no clue and am useless for that --- README.md | 78 +-------------------------- roles/rails/tasks/main.yml | 70 ------------------------ roles/rails/templates/database.yml.j2 | 7 --- 3 files changed, 1 insertion(+), 154 deletions(-) delete mode 100644 roles/rails/tasks/main.yml delete mode 100644 roles/rails/templates/database.yml.j2 diff --git a/README.md b/README.md index 9efff90..ec9f5fc 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ This playbook helps you set up and manage your Uberspace(s). ## Current features +- Registering domains for web and mail, and optionally symlinking docroot to the home directory - [WordPress](https://wordpress.org/) using the awesome [Bedrock](https://roots.io/bedrock/) boilerplate -- [Ruby on Rails](http://rubyonrails.org/) apps ## Requirements @@ -51,82 +51,6 @@ curl -s 'https://julia.eridanus.uberspace.de/cgi-bin/wordpress-update-example_bl Or if you use [Planio](https://plan.io), simply enter your URL via **Settings** → **Repositories** → *your repo* → **Edit** → **Post-Receive webhook URL** -### Ruby on Rails apps - -Setting up and deploying your Ruby on Rails apps involves a little bit more work, but it's definitely worth it. Where else do you get such awesome Rails hosting for the price? Let's get started: - -#### Configure your playbook - -1. To set up a Rails app, create an entry under `rails_apps` in your `host_vars` file (see `host_vars/UBERSPACE_NAME.UBERSPACE_HOST.uberspace.de.example` for an example) -2. Make sure to give it a `name` which should be only characters, numbers and maybe the underscore character – no spaces! -3. Add the domains through which your Rails app should be accessible -4. Make sure to add these domains to the top-level `domains` section in the `host_vars` file as well! -5. For `secret_key_base` generate a secret using `rake secret` in your Rails app -6. For `port` choose an unused port on your Uberspace between 32000 and 65000 - -That's it for Ansible. You can now run your playbook using `ansible-playbook site.yml`. - -#### Configure your Rails app - -To actually deploy your app, we're going to use [Capistrano](http://capistranorb.com/). Git clone your Rails app on your local computer and perform the following modifications: - -##### Modify your `Gemfile` - -1. Add or uncomment `gem 'capistrano-rails', group: :development` -2. Run `bundle install` and then `bundle exec cap install` - -##### Mofify your `Capfile` - -1. Add or uncomment `require 'capistrano/bundler'` -2. Add or uncomment `require 'capistrano/rails/migrations'` if your app is using MySQL - -Your `Capfile` should now look similar to this: - -```ruby -require "capistrano/setup" -require "capistrano/deploy" -require 'capistrano/bundler' -require 'capistrano/rails/migrations' - -Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r } -``` - -###### Modify your `config/deploy.rb` - -1. Find the line `set :application, 'my_app_name'` and replace `my_app_name` with the *exact same* name you chose earlier for the `rails_apps` entry in your `host_vars` file. -2. Find the line `set :repo_url, 'git@example.com:me/my_repo.git'` and add your Rails app's repo URL. If your repo is private, please add the keys you find in `public_keys/` within your playbook as deploy keys to your repository hoster. -3. Find the line `# set :deploy_to, '/var/www/my_app_name'`, uncomment it and set the value to `"~/www/rails/#{fetch :application}"` (notice the double quotes!) -4. Add the line `set :linked_files, fetch(:linked_files, []).push('config/database.yml')` -5. Add the line `after :publishing, :restart { execute :svc, "-du ~/service/rails-app-#{fetch :application}" }` within the `namespace :deploy` block - -Your `config/deploy.rb` should now look similar to this: - -```ruby -lock '3.5.0' - -set :application, 'example_app' -set :repo_url, 'git@example.plan.io:example-app.git' -set :deploy_to, "~/www/rails/#{fetch :application}" -set :linked_files, fetch(:linked_files, []).push('config/database.yml') - -namespace :deploy do - after :publishing, :restart { execute :svc, "-du ~/service/rails-app-#{fetch :application}" } -end -``` - - -##### Modify your `config/deploy/production.rb` - -1. Find and uncomment the line `# server 'example.com', user: 'deploy', roles: %w{app db web}, my_property: :my_value` and replace `example.com` with the hostname of your Uberspace, e.g. `eridanus.uberspace.de` and `deploy` with your Uberspace username. - -Your `config/deploy/production.rb` should now look similar to this: - -```ruby -server 'eridanus.uberspace.de', user: 'julia', roles: %w{app db web} -``` - -That's it. You should be able to deploy your app using `bundle exec cap production deploy`. After some time, your Rails app should be humming nicely on your configured domain. - ### Cleanup As the Uberspace Playbook is still in development, it would make sense for you to run the cleanup tasks after every update from this repo. The cleanup tasks remove any files/configurations on your Uberspace which previous versions of the playbook may have installed but which are no longer needed. You can run the cleanup tasks like so: diff --git a/roles/rails/tasks/main.yml b/roles/rails/tasks/main.yml deleted file mode 100644 index 8a85ac6..0000000 --- a/roles/rails/tasks/main.yml +++ /dev/null @@ -1,70 +0,0 @@ ---- -- name: Create directories for capistrano deployment - file: - dest: '~/www/rails/{{ item.name }}/shared/config' - state: directory - with_items: '{{ rails_apps }}' - -- name: Create MySQL database(s) - mysql_db: - name: "{{ ansible_user }}_{{ item.name }}" - collation: utf8_unicode_ci - encoding: utf8 - state: present - with_items: '{{ rails_apps }}' - -- name: Read MySQL database password from ~/.my.cnf - shell: sed -n -e '/\[client\]/,$p' ~/.my.cnf | grep password | head -1 | cut -d= -f2 | cut -d " " -f1 warn=no - register: database_password - changed_when: False - -- name: Create database.yml - template: - src : templates/database.yml.j2 - dest: '~/www/rails/{{ item.name }}/shared/config/database.yml' - with_items: '{{ rails_apps }}' - -- name: Create daemontools jobs for unicorns - shell: 'uberspace-setup-service rails-app-{{ item.name }} bundle exec unicorn --host 127.0.0.1 --port {{ item.port }}' - args: - creates: '~/etc/run-rails-app-{{ item.name }}/run' - with_items: '{{ rails_apps }}' - -- name: Configure daemontools jobs for rails - blockinfile: - args: - dest: '~/etc/run-rails-app-{{ item.name }}/run' - insertbefore: '^exec' - block: | - export RAILS_ENV=production - export RAILS_SERVE_STATIC_FILES=true - export SECRET_KEY_BASE={{ item.secret_key_base }} - cd ~/www/rails/{{ item.name }}/current - with_items: '{{ rails_apps }}' - -- name: Create web directory for rails apps - file: - path: '/var/www/virtual/{{ ansible_user }}/rails/{{ item.name }}/web' - state: directory - with_items: '{{ rails_apps }}' - -- name: Set up .htaccess for Rails apps and redirect all traffic to main domain - blockinfile: - dest: '/var/www/virtual/{{ ansible_user }}/rails/{{ item.name }}/web/.htaccess' - create: yes - insertbefore: BOF - block: | - RewriteEngine On - RewriteCond %{HTTP_HOST} !={{ item.domains[0] }} - RewriteRule (.*) https://{{ item.domains[0] }}/$1 [R=301,L] - RewriteRule (.*) http://localhost:{{ item.port }}/$1 [P] - with_items: '{{ rails_apps }}' - -- name: Create domain symlinks for rails apps - file: - dest: /var/www/virtual/{{ ansible_user }}/{{ item.1 }} - src: /var/www/virtual/{{ ansible_user }}/rails/{{ item.0.name }}/web - state: link - with_subelements: - - "{{ rails_apps }}" - - domains diff --git a/roles/rails/templates/database.yml.j2 b/roles/rails/templates/database.yml.j2 deleted file mode 100644 index 8301c73..0000000 --- a/roles/rails/templates/database.yml.j2 +++ /dev/null @@ -1,7 +0,0 @@ -# {{ ansible_managed }} -production: - adapter: mysql2 - encoding: utf8 - database: {{ ansible_user }}_{{ item.name }} - username: {{ ansible_user }} - password: {{ database_password.stdout }} From 2b2d8da00e5842444e03e9f7bca8f028e0529452 Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Thu, 27 May 2021 13:56:54 +0200 Subject: [PATCH 04/14] new top level definition structure though role vars directly in playbook, support uploading different ssh keys --- .gitignore | 2 +- common.yml | 8 ----- ...E_NAME.UBERSPACE_HOST.uberspace.de.example | 31 ------------------- roles/uberspace/tasks/domains.yml | 4 +-- roles/uberspace/tasks/main.yml | 11 ++++--- site.yml | 2 -- site.yml.example | 19 ++++++++++++ 7 files changed, 29 insertions(+), 48 deletions(-) delete mode 100644 common.yml delete mode 100644 host_vars/UBERSPACE_NAME.UBERSPACE_HOST.uberspace.de.example delete mode 100644 site.yml create mode 100644 site.yml.example diff --git a/.gitignore b/.gitignore index 25087f1..5073244 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ host_vars/* public_keys/* -!host_vars/UBERSPACE_NAME.UBERSPACE_HOST.uberspace.de.example uberspaces +uberspaces.yml diff --git a/common.yml b/common.yml deleted file mode 100644 index 2489137..0000000 --- a/common.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -- hosts: all - roles: - - role: common - - role: wordpress - when: wordpress_instances is defined - - role: rails - when: rails_apps is defined diff --git a/host_vars/UBERSPACE_NAME.UBERSPACE_HOST.uberspace.de.example b/host_vars/UBERSPACE_NAME.UBERSPACE_HOST.uberspace.de.example deleted file mode 100644 index 73942a1..0000000 --- a/host_vars/UBERSPACE_NAME.UBERSPACE_HOST.uberspace.de.example +++ /dev/null @@ -1,31 +0,0 @@ ---- -domains: - - example.com - - www.example.com - - blog.example.com - -wordpress_instances: - - name: example_blog - domains: - - blog.example.com - - # Fork this repo and adapt to your requirements. Make sure to add your - # Uberspace's public SSH key as a "deploy key" to your Git repo. The - # playbook will download your Uberspace's public keys to the public_keys/ - # dir for you. - bedrock_repo: https://github.com/yeah/bedrock.git - - # For deployments via webhooks. Your webhook URL will be: - # https://UBERSPACE_NAME.UBERSPACE_HOST.uberspace.de/cgi-bin/wordpress-update-example_blog.cgi?secretsauce123 - webhook_key: secretsauce123 - -rails_apps: - - name: example_app - domains: - - myapp.example.com - - # generate a secure random string for this, e.g. by running `rake secret` in your app - secret_key_base: 0123456789abcdef - - # choose a random free port on your Uberspace, between 32000 and 65000 - port: 54321 diff --git a/roles/uberspace/tasks/domains.yml b/roles/uberspace/tasks/domains.yml index 12047e9..905e3c8 100644 --- a/roles/uberspace/tasks/domains.yml +++ b/roles/uberspace/tasks/domains.yml @@ -1,18 +1,18 @@ --- - name: "Enable web for {{ item.name }}" command: "uberspace web domain add {{ item.name }}" - #ignore_errors: yes register: web_domain failed_when: web_domain.rc == 1 and web_domain.stderr != "Can't add domain to the configuration. It is already configured for your Uberspace account." changed_when: ("The webserver's configuration has been adapted." in web_domain.stdout_lines) when: item.web or (item.web is not defined and item.mail is not defined) + - name: "Enable mail for {{ item.name }}" command: "uberspace mail domain add {{ item.name }}" - #ignore_errors: yes register: mail_domain failed_when: mail_domain.rc == 1 and mail_domain.stderr != "Can't add domain to the configuration. It is already configured for your Uberspace account." changed_when: ("The mailserver's configuration has been adapted." in mail_domain.stdout_lines) when: item.mail or (item.web is not defined and item.mail is not defined) + - name: "Create symlink for {{ item.name }}" file: path: ~/www diff --git a/roles/uberspace/tasks/main.yml b/roles/uberspace/tasks/main.yml index f457f4a..4770be4 100644 --- a/roles/uberspace/tasks/main.yml +++ b/roles/uberspace/tasks/main.yml @@ -1,8 +1,10 @@ --- -#- name: Upload local user's public key for SSH -# authorized_key: -# user: "{{ ansible_env.USER }}" -# key: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}" +- name: Upload local public key + authorized_key: + user: "{{ ansible_env.USER }}" + key: "{{ item }}" + with_items: upload_ssh_keys + when: upload_ssh_keys is defined and upload_ssh_keys - name: Generate SSH key pair for remote user on Uberspace user: @@ -10,6 +12,7 @@ generate_ssh_key: yes ssh_key_bits: 4096 ssh_key_file: .ssh/id_rsa + when: generate_ssh_key #- name: Fetch remote user's public key (e.g. for Git access) # fetch: diff --git a/site.yml b/site.yml deleted file mode 100644 index df676ee..0000000 --- a/site.yml +++ /dev/null @@ -1,2 +0,0 @@ ---- -- include: common.yml diff --git a/site.yml.example b/site.yml.example new file mode 100644 index 0000000..16f4ac4 --- /dev/null +++ b/site.yml.example @@ -0,0 +1,19 @@ +--- +- hosts: julia.eridanus.uberspace.de + roles: + - role: uberspace + vars: + upload_ssh_keys: + - "{{ lookup('file', '~/.ssh/id_rsa.pub') }}" + generate_ssh_key: true + domains: + - name: example.com + web: true + mail: true + link: true + - name: www.example.com + web: true + link: true + - name: blog.example.com + web: true + link: true From 32b32409dc2a994e2d735ac9c2a69011842df798 Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Thu, 27 May 2021 19:48:00 +0200 Subject: [PATCH 05/14] report DNS entries when changed --- roles/uberspace/handlers/main.yml | 4 ++++ roles/uberspace/tasks/domains.yml | 22 ++++++++++++++++++++-- roles/uberspace/tasks/main.yml | 6 +++++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/roles/uberspace/handlers/main.yml b/roles/uberspace/handlers/main.yml index ed97d53..922292e 100644 --- a/roles/uberspace/handlers/main.yml +++ b/roles/uberspace/handlers/main.yml @@ -1 +1,5 @@ --- +- name: Report DNS entries + debug: + msg: "{{ web_domains | default([]) | combine(mail_domains | default([]), recursive=true) }}" + listen: Report DNS entries diff --git a/roles/uberspace/tasks/domains.yml b/roles/uberspace/tasks/domains.yml index 905e3c8..090c2b3 100644 --- a/roles/uberspace/tasks/domains.yml +++ b/roles/uberspace/tasks/domains.yml @@ -4,14 +4,32 @@ register: web_domain failed_when: web_domain.rc == 1 and web_domain.stderr != "Can't add domain to the configuration. It is already configured for your Uberspace account." changed_when: ("The webserver's configuration has been adapted." in web_domain.stdout_lines) - when: item.web or (item.web is not defined and item.mail is not defined) + notify: + - Report DNS entries + when: + - item.web or (item.web is not defined and item.mail is not defined) + - ansible_facts.ansible_local.users[ansible_env.USER].domains[item.name].web is not defined + +- name: "Remember web facts for {{ item.name }}" + set_fact: + web_domains: "{{ (web_domains | default([])) + [{'name': item.name, 'A': web_domain.stdout | regex_search('\\sA\\s+->\\s+([0-9.]+)'), 'AAAA': web_domain.stdout | regex_search('\\sAAAA\\s+->\\s+([a-f0-9:]+)')}] }}" + when: web_domain.changed - name: "Enable mail for {{ item.name }}" command: "uberspace mail domain add {{ item.name }}" register: mail_domain failed_when: mail_domain.rc == 1 and mail_domain.stderr != "Can't add domain to the configuration. It is already configured for your Uberspace account." changed_when: ("The mailserver's configuration has been adapted." in mail_domain.stdout_lines) - when: item.mail or (item.web is not defined and item.mail is not defined) + notify: + - Report DNS entries + when: + - item.mail or (item.web is not defined and item.mail is not defined) + - ansible_facts.ansible_local.users[ansible_env.USER].domains[item.name].mail is not defined + +- name: "Remember mail facts for {{ item.name }}" + set_fact: + mail_domains: "{{ (mail_domains | default([])) + [{'name': item.name, 'MX': mail_domain.stdout | regex_search('\\sMX\\s+->\\s+([^\n]+)'), 'TXT': mail_domain.stdout | regex_search('\\sTXT\\s+->\\s+([^\n]+)')}] }}" + when: mail_domain.changed - name: "Create symlink for {{ item.name }}" file: diff --git a/roles/uberspace/tasks/main.yml b/roles/uberspace/tasks/main.yml index 4770be4..8fe07e9 100644 --- a/roles/uberspace/tasks/main.yml +++ b/roles/uberspace/tasks/main.yml @@ -3,7 +3,7 @@ authorized_key: user: "{{ ansible_env.USER }}" key: "{{ item }}" - with_items: upload_ssh_keys + with_items: "{{ upload_ssh_keys }}" when: upload_ssh_keys is defined and upload_ssh_keys - name: Generate SSH key pair for remote user on Uberspace @@ -25,6 +25,10 @@ # src: templates/gitconfig.j2 # dest: ~/.gitconfig +- name: "Already registered domains:" + debug: + msg: "{{ ansible_facts.ansible_local.users[ansible_env.USER].domains }}" + - name: Add domains include_tasks: domains.yml with_items: "{{ domains }}" From bfe959462d6bb20043a3b6c6e1ac7f0470743ac0 Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Fri, 28 May 2021 09:54:49 +0200 Subject: [PATCH 06/14] use python3 interpreter --- uberspaces.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uberspaces.example b/uberspaces.example index 165d765..a97523b 100644 --- a/uberspaces.example +++ b/uberspaces.example @@ -1 +1 @@ -julia.eridanus.uberspace.de ansible_user=julia +eridanus.uberspace.de ansible_user=julia ansible_python_interpreter=/usr/bin/python3 From b0366aa0e1141ac620a3462620114d46207d9718 Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Wed, 2 Jun 2021 09:54:51 +0200 Subject: [PATCH 07/14] wordpress_bedrock role, TODO: updates/hooks --- .gitignore | 6 +- .../handlers/main.yml | 0 .../tasks/install.yml | 185 ++++++++++++++++++ .../tasks/main.yml | 7 + .../templates/wordpress-update.cgi.j2 | 2 +- .../templates/wordpress-update.j2 | 2 +- roles/wordpress/tasks/main.yml | 142 -------------- site.yml.example | 11 ++ 8 files changed, 209 insertions(+), 146 deletions(-) rename roles/{wordpress => uberspace_wordpress_bedrock}/handlers/main.yml (100%) create mode 100644 roles/uberspace_wordpress_bedrock/tasks/install.yml create mode 100644 roles/uberspace_wordpress_bedrock/tasks/main.yml rename roles/{wordpress => uberspace_wordpress_bedrock}/templates/wordpress-update.cgi.j2 (74%) rename roles/{wordpress => uberspace_wordpress_bedrock}/templates/wordpress-update.j2 (72%) delete mode 100644 roles/wordpress/tasks/main.yml diff --git a/.gitignore b/.gitignore index 5073244..a8418bc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ host_vars/* public_keys/* -uberspaces -uberspaces.yml +/*.yml +!/uberspaces.example +!/uberspaces.yml.example +!/site.yml.example diff --git a/roles/wordpress/handlers/main.yml b/roles/uberspace_wordpress_bedrock/handlers/main.yml similarity index 100% rename from roles/wordpress/handlers/main.yml rename to roles/uberspace_wordpress_bedrock/handlers/main.yml diff --git a/roles/uberspace_wordpress_bedrock/tasks/install.yml b/roles/uberspace_wordpress_bedrock/tasks/install.yml new file mode 100644 index 0000000..aa2f254 --- /dev/null +++ b/roles/uberspace_wordpress_bedrock/tasks/install.yml @@ -0,0 +1,185 @@ +--- +- name: Determine docroot + set_fact: + _determined_docroot: "{{ instance.docroot | default(default_root) }}" + vars: + default_root: "/var/www/virtual/{{ ansible_env.USER }}/wordpress/{{ instance.name }}" + +- name: Create PyMySQL venv + pip: + name: + - PyMySQL + virtualenv: ~/.pymysql + virtualenv_command: /usr/bin/python3 -m venv + +- name: Read .my.cnf + block: + - name: Create temporary local file + local_action: + module: tempfile + state: directory + register: mycnf_dir + changed_when: false + + - name: Retrieve .my.cnf + fetch: + src: ~/.my.cnf + dest: "{{ mycnf_dir.path }}" + register: mycnf + changed_when: false + + +- name: Download WP-CLI + block: + - name: Get SHA512 checksum + get_url: + url: https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar.sha512 + dest: ~/tmp/wp-cli.phar.sha512 + + - name: Read SHA512 checksum + slurp: + src: ~/tmp/wp-cli.phar.sha512 + register: checksum + + - name: Download WP-CLI + get_url: + url: https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar + dest: ~/bin/wp + checksum: "sha512:{{ checksum.content | b64decode }}" + + +- name: Make WP-CLI executable + file: + path: ~/bin/wp + mode: ugo+x + +- name: Install some WP-CLI packages + command: + cmd: "~/bin/wp package install {{ item }}" + creates: "~/.wp-cli/packages/vendor/{{ item }}" + register: install_packages + changed_when: ("Nothing to install, update or remove" not in install_packages.stdout_lines) + with_items: + - aaemnnosttv/wp-cli-dotenv-command:^0.2 + +- name: Create cron job for ~/bin/wp cli update + cron: + name: WP-CLI self-update + job: ~/bin/wp cli update --yes --quiet + special_time: daily + + +#- name: Clone fresh Bedrock (WordPress boilerplate) +# git: +# repo: "{{ instance.bedrock_repo | default('https://github.com/yeah/bedrock.git') }}" +# dest: "{{ _determined_docroot }}" +# version: "{{ instance.bedrock_version | default('master') }}" +# force: yes + +- name: Check if project already exists + stat: + path: "{{ _determined_docroot }}/composer.json" + register: composer_json + +- name: Ensure project dir is created + file: + path: "{{ _determined_docroot }}" + state: directory + when: not composer_json.stat.exists + +- name: Install wordpress bedrock + composer: + command: create-project + arguments: "roots/bedrock {{ _determined_docroot }}" + working_dir: "{{ _determined_docroot }}" + when: not composer_json.stat.exists + +- name: Create MySQL database + mysql_db: + name: "{{ ansible_env.USER }}_{{ instance.name }}" + collation: utf8_unicode_ci + encoding: utf8 + state: present + vars: + ansible_python_interpreter: ~/.pymysql/bin/python3 + +- name: Create .env template with salts + command: + cmd: ~/bin/wp dotenv init --with-salts + chdir: "{{ _determined_docroot }}" + #ignore_errors: yes + register: result + failed_when: ('Environment file already exists' in result.stdout) + changed_when: "'already exists' not in result.stdout" + +- name: Set environment variables in .env + command: + cmd: "~/bin/wp dotenv set {{ item.key }} {{ item.val }}" + chdir: "{{ _determined_docroot }}" + with_items: + - key: "DB_NAME" + val: "{{ ansible_env.USER }}_{{ instance.name }}" + - key: "DB_USER" + val: "{{ lookup('ini', 'user section=client file=' + mycnf.dest) }}" + - key: "DB_PASSWORD" + val: "{{ lookup('ini', 'password section=client file=' + mycnf.dest) }}" + - key: "WP_ENV" + #val: "production" + val: "development" + - key: "WP_HOME" + val: "https://{{ instance.entry_points[0] }}" + - key: "WP_SITEURL" + val: "https://{{ instance.entry_points[0] }}/wp" + + +#- name: Create script ~/bin/wordpress-update +# template: +# src: templates/wordpress-update.j2 +# dest: ~/bin/wordpress-update +# mode: ugo+x +# +#- name: Create runwhen jobs for ~/bin/wordpress-update +# command: +# cmd: "runwhen-conf ~/etc/run-wordpress-update-{{ instance.name }} ~/bin/wordpress-update {{ instance.name }}" +# creates: "~/etc/run-wordpress-update-{{ instance.name }}/run" +# +#- name: Configure runwhen jobs for ~/bin/wordpress-update +# lineinfile: +# dest: ~/etc/run-wordpress-update-{{ instance.name }}/run +# regexp: "^RUNWHEN=" +# line: "RUNWHEN=\",H=1\"" +# +#- name: Activate runwhen jobs for ~/bin/wordpress-update +# file: +# path: "~/service/wordpress-update-{{ instance.name }}" +# src: "~/etc/run-wordpress-update-{{ instance.name }}" +# state: link +# +#- name: Run wordpress-update for the first time +# command: "svc -a ~/service/wordpress-update-{{ instance.name }}" +# +# +#- name: Create webhook for WordPress updates +# template: +# src: templates/wordpress-update.cgi.j2 +# dest: "~/cgi-bin/wordpress-update-{{ instance.name }}.cgi" +# mode: 0755 +# when: "{{ instance.webhook_key is defined }}" + + +- name: Set up .htaccess to redirect all traffic to main domain + blockinfile: + dest: "{{ _determined_docroot }}/web/.htaccess" + create: yes + insertbefore: BOF + block: | + RewriteEngine On + RewriteCond %{HTTP_HOST} !={{ instance.entry_points[0] | regex_replace('/.*') }} + RewriteRule (.*) https://{{ instance.entry_points[0] | regex_replace('/.*') }}/$1 [R=301,L] + +- name: Create domain symlinks + file: + path: "/var/www/virtual/{{ ansible_env.USER }}/{{ item }}" + src: "{{ _determined_docroot }}/web" + state: link + with_items: "{{ instance.entry_points }}" diff --git a/roles/uberspace_wordpress_bedrock/tasks/main.yml b/roles/uberspace_wordpress_bedrock/tasks/main.yml new file mode 100644 index 0000000..b5db11c --- /dev/null +++ b/roles/uberspace_wordpress_bedrock/tasks/main.yml @@ -0,0 +1,7 @@ +--- + +- name: Install wordpress bedrock + include_tasks: install.yml + with_items: "{{ instances }}" + loop_control: + loop_var: instance diff --git a/roles/wordpress/templates/wordpress-update.cgi.j2 b/roles/uberspace_wordpress_bedrock/templates/wordpress-update.cgi.j2 similarity index 74% rename from roles/wordpress/templates/wordpress-update.cgi.j2 rename to roles/uberspace_wordpress_bedrock/templates/wordpress-update.cgi.j2 index 7c8fa27..2aa1517 100644 --- a/roles/wordpress/templates/wordpress-update.cgi.j2 +++ b/roles/uberspace_wordpress_bedrock/templates/wordpress-update.cgi.j2 @@ -5,7 +5,7 @@ echo 'Content-type: text/plain' if [ "${QUERY_STRING}" == "{{ item.webhook_key }}" ]; then echo "Status: 200 OK"; echo - svc -a "/home/{{ ansible_user }}/service/wordpress-update-{{ item.name }}" + svc -a "/home/{{ ansible_env.USER }}/service/wordpress-update-{{ item.name }}" echo "OK." else echo "Status: 403 Forbidden"; echo diff --git a/roles/wordpress/templates/wordpress-update.j2 b/roles/uberspace_wordpress_bedrock/templates/wordpress-update.j2 similarity index 72% rename from roles/wordpress/templates/wordpress-update.j2 rename to roles/uberspace_wordpress_bedrock/templates/wordpress-update.j2 index c3cfd96..3d576ac 100644 --- a/roles/wordpress/templates/wordpress-update.j2 +++ b/roles/uberspace_wordpress_bedrock/templates/wordpress-update.j2 @@ -1,7 +1,7 @@ #!/bin/sh # {{ ansible_managed }} -cd "/var/www/virtual/{{ ansible_user }}/wordpress/$1" +cd "/var/www/virtual/{{ ansible_env.USER }}/wordpress/$1" git fetch origin git reset --hard --quiet origin/master ~/bin/composer -q -n update diff --git a/roles/wordpress/tasks/main.yml b/roles/wordpress/tasks/main.yml deleted file mode 100644 index dbe2951..0000000 --- a/roles/wordpress/tasks/main.yml +++ /dev/null @@ -1,142 +0,0 @@ ---- -- name: Check if Composer is installed - stat: path=~/bin/composer - register: composer - -- name: Download Composer installer - get_url: url=https://getcomposer.org/installer dest=~/ansible-composer-setup.php - when: composer.stat.exists == False - -- name: Install Composer - shell: php ~/ansible-composer-setup.php --install-dir=$HOME/bin --filename=composer - when: composer.stat.exists == False - -- name: Remove Composer installer - file: path=~/ansible-composer-setup.php state=absent - -- name: Create cron job for ~/bin/composer self-update - cron: - name: composer self-update - job: ~/bin/composer -q -n self-update - special_time: daily - -- name: Download WP-CLI - get_url: url=https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar dest=~/bin/wp force=no - -- name: Make WP-CLI executable - file: - path: ~/bin/wp - mode: ugo+x - -- name: Install some WP-CLI packages - shell: ~/bin/wp package install {{ item }} - args: - creates: ~/.wp-cli/packages/vendor/{{ item }} - with_items: - - aaemnnosttv/wp-cli-dotenv-command:^0.2 - -- name: Create cron job for ~/bin/wp cli update - cron: - name: WP-CLI self-update - job: ~/bin/wp cli update --yes --quiet - special_time: daily - - -- name: Clone fresh Bedrock (WordPress boilerplate) - git: - repo: "{{ item.bedrock_repo }}" - dest: /var/www/virtual/{{ ansible_user }}/wordpress/{{ item.name }} - force: yes - with_items: '{{ wordpress_instances }}' - -- name: Create MySQL database(s) - mysql_db: - name: "{{ ansible_user }}_{{ item.name }}" - collation: utf8_unicode_ci - encoding: utf8 - state: present - with_items: '{{ wordpress_instances }}' - -- name: Create .env template with salts - shell: ~/bin/wp dotenv init --with-salts - args: - chdir: /var/www/virtual/{{ ansible_user }}/wordpress/{{ item.name }} - ignore_errors: yes - register: result - changed_when: "'already exists' not in result.stdout" - with_items: '{{ wordpress_instances }}' - -- name: Read MySQL database password from ~/.my.cnf - shell: sed -n -e '/\[client\]/,$p' ~/.my.cnf | grep password | head -1 | cut -d= -f2 | cut -d " " -f1 warn=no - register: database_password - changed_when: False - -- name: Set environment variables in .env - shell: "~/bin/wp dotenv set DB_NAME {{ ansible_user }}_{{ item.name }}; \ - ~/bin/wp dotenv set DB_USER {{ ansible_user }}; \ - ~/bin/wp dotenv set DB_PASSWORD {{ database_password.stdout }}; \ - ~/bin/wp dotenv set WP_ENV production; \ - ~/bin/wp dotenv set WP_HOME https://{{ item.domains[0] }}; \ - ~/bin/wp dotenv set WP_SITEURL https://{{ item.domains[0] }}/wp;" - args: - chdir: /var/www/virtual/{{ ansible_user }}/wordpress/{{ item.name }} - with_items: '{{ wordpress_instances }}' - -- name: Create script ~/bin/wordpress-update - template: - src: templates/wordpress-update.j2 - dest: ~/bin/wordpress-update - mode: ugo+x - -- name: Create runwhen jobs for ~/bin/wordpress-update - shell: 'runwhen-conf ~/etc/run-wordpress-update-{{ item.name }} ~/bin/wordpress-update {{ item.name }}' - args: - creates: '~/etc/run-wordpress-update-{{ item.name }}/run' - with_items: '{{ wordpress_instances }}' - -- name: Configure runwhen jobs for ~/bin/wordpress-update - lineinfile: - args: - dest: ~/etc/run-wordpress-update-{{ item.name }}/run - regexp: '^RUNWHEN=' - line: 'RUNWHEN=",H=1"' - with_items: '{{ wordpress_instances }}' - -- name: Activate runwhen jobs for ~/bin/wordpress-update - file: - path: '~/service/wordpress-update-{{ item.name }}' - src: '~/etc/run-wordpress-update-{{ item.name }}' - state: link - with_items: '{{ wordpress_instances }}' - -- name: Run wordpress-update for the first time - shell: 'svc -a ~/service/wordpress-update-{{ item.name }}' - with_items: '{{ wordpress_instances }}' - -- name: Create webhook for WordPress updates - template: - src: templates/wordpress-update.cgi.j2 - dest: "~/cgi-bin/wordpress-update-{{ item.name }}.cgi" - mode: 0755 - when: '{{ item.webhook_key is defined }}' - with_items: '{{ wordpress_instances }}' - -- name: Set up .htaccess to redirect all traffic to main domain - blockinfile: - dest: /var/www/virtual/{{ ansible_user }}/wordpress/{{ item.name }}/web/.htaccess - create: yes - insertbefore: BOF - block: | - RewriteEngine On - RewriteCond %{HTTP_HOST} !={{ item.domains[0] | regex_replace('/.*') }} - RewriteRule (.*) https://{{ item.domains[0] | regex_replace('/.*') }}/$1 [R=301,L] - with_items: '{{ wordpress_instances }}' - -- name: Create domain symlinks for wordpresses - file: - path: /var/www/virtual/{{ ansible_user }}/{{ item.1 }} - src: /var/www/virtual/{{ ansible_user }}/wordpress/{{ item.0.name }}/web - state: link - with_subelements: - - "{{ wordpress_instances }}" - - domains diff --git a/site.yml.example b/site.yml.example index 16f4ac4..dcffc31 100644 --- a/site.yml.example +++ b/site.yml.example @@ -17,3 +17,14 @@ - name: blog.example.com web: true link: true + - role: uberspace_wordpress_bedrock + vars: + instances: + - name: example-blog + entry_points: + - blog.example.com + - www.example.com/blog/ + - name: test + docroot: ~/test + bedrock_repo: https://github.com/yeah/bedrock.git + bedrock_version: master From 8d1cc547f0432e853f36eae793c70743b7d75831 Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Wed, 2 Jun 2021 09:55:31 +0200 Subject: [PATCH 08/14] rewrite the README a bit --- README.md | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index ec9f5fc..1c25549 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ This playbook helps you set up and manage your Uberspace(s). ## Current features - Registering domains for web and mail, and optionally symlinking docroot to the home directory -- [WordPress](https://wordpress.org/) using the awesome [Bedrock](https://roots.io/bedrock/) boilerplate +- [WordPress](https://wordpress.org/) using the [Bedrock](https://roots.io/bedrock/) boilerplate ## Requirements @@ -15,20 +15,9 @@ This playbook helps you set up and manage your Uberspace(s). ## Usage 1. Copy `uberspaces.example` to `uberspaces` and add your Uberspace host(s) and username(s) -2. Copy `host_vars/UBERSPACE_NAME.UBERSPACE_HOST.uberspace.de.example` to a new file named without the `.example` suffix and replace `UBERSPACE_NAME` with your username, e.g. `julia` and `UBERSPACE_HOST` with your Uberspace host, e.g. `eridanus`. -3. Add the domains you'd like to run on the respective Uberspace to the file created in step 2. -4. Repeat steps 2 and 3 for all your Uberspaces. -5. Run the playbook using `ansible-playbook --ask-pass site.yml`. -6. Enjoy! - -If you have an SSH keypair and your public key is installed in `~/.ssh/id_rsa.pub` on your local computer, the key will be stored in `~/.ssh/authorized_keys` on your Uberspace and you won't need the `--ask-pass` argument in subsequent runs. - -### WordPress - -1. To set up a WordPress instance, simply create an entry under `wordpress_instances` in your `host_vars` file (see `host_vars/UBERSPACE_NAME.UBERSPACE_HOST.uberspace.de.example` for an example) -2. Use the default `bedrock_repo` from `https://github.com/yeah/bedrock.git` or use your own forked repo of the boilerplate (Your Uberspace's public keys will be conveniently downloaded for you to `public_keys/` so you can use them as deploy keys for your private Git repos.) -3. Add the domains through which your WordPress should be accessible -4. Make sure to add these domains to the top-level `domains` section in the `host_vars` file as well! +2. Copy `site.yml.example` to `site.yml` or any other playbook name you want and start adapting it to your needs +3. The `uberspace` role handles uploading local ssh keys, generating a key for the managed machine and registering domains for web or mail against the uberspace host with an optional symlink to home +4. The `uberspace_wordpress_bedrock` role installs wordpress bedrock, creating the database for it and linking the docroot of the instance to the specified entry points #### Bonus: Deploy hooks @@ -51,12 +40,6 @@ curl -s 'https://julia.eridanus.uberspace.de/cgi-bin/wordpress-update-example_bl Or if you use [Planio](https://plan.io), simply enter your URL via **Settings** → **Repositories** → *your repo* → **Edit** → **Post-Receive webhook URL** -### Cleanup - -As the Uberspace Playbook is still in development, it would make sense for you to run the cleanup tasks after every update from this repo. The cleanup tasks remove any files/configurations on your Uberspace which previous versions of the playbook may have installed but which are no longer needed. You can run the cleanup tasks like so: - -`ansible-playbook cleanup.yml` - ## License MIT. @@ -67,4 +50,6 @@ To contribute something you usually configure on your Uberspace, please fork thi ## Credits -[I](http://jan.sh) built this. By myself. On my computer. +[Jan](http://jan.sh) built this. By himself. On his computer. + +Then U7 came along with breaking changes and Peter Nerlich eventually tried to rewrite this for the new version. Though in the process, he self righteously changed stuff he deemed weird and removed some features he didn't use himself, completely destroying any backwards compatibility. From e30c7ad122a4a4c471f4fa321d0d7af619711ec6 Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Wed, 9 Jun 2021 09:07:24 +0200 Subject: [PATCH 09/14] draft mailbox setup --- .gitignore | 55 +++++++++++++++++++ roles/uberspace/filter_plugins/normalize.py | 60 +++++++++++++++++++++ roles/uberspace/tasks/mailboxes.yml | 53 ++++++++++++++++++ 3 files changed, 168 insertions(+) create mode 100644 roles/uberspace/filter_plugins/normalize.py create mode 100644 roles/uberspace/tasks/mailboxes.yml diff --git a/.gitignore b/.gitignore index a8418bc..42307a3 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,58 @@ public_keys/* !/uberspaces.example !/uberspaces.yml.example !/site.yml.example + + + + +# venv & app stuff + +bin/ +lib64 +pyvenv.cfg + +cache/ + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# pyenv +.python-version + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ diff --git a/roles/uberspace/filter_plugins/normalize.py b/roles/uberspace/filter_plugins/normalize.py new file mode 100644 index 0000000..753b222 --- /dev/null +++ b/roles/uberspace/filter_plugins/normalize.py @@ -0,0 +1,60 @@ +from typing import Callable +from collections.abc import Iterable +from typing import Any, Union, Optional +from types import FunctionType +from ansible.plugins.lookup.password import LookupModule as Password +from ansible.parsing.dataloader import DataLoader + + +class FilterModule(object): + ''' jinja2 filters ''' + pw = Password(DataLoader()) + + def filters(self): + return { + 'normalize_item_to_dict': self.normalize_item_to_dict, + 'normalize_items_to_dicts': self.normalize_items_to_dicts, + 'normalize_mailboxes': self.normalize_mailboxes, + } + + def normalize_items_to_dicts(self, items: Iterable, default_key: str, additional_keys: Optional[Iterable[Union[str,Iterable[str,Any]]]]): + def per_item(x): + return self.normalize_item_to_dict(x, default_key, additional_keys) + + return type(items)(map(per_item, items)) + + def normalize_item_to_dict(self, item: Any, default_key: str, additional_keys: Optional[Iterable[Union[str,Iterable[str,Any]]]]): + """ + Apply python string formatting on an object: + .. sourcecode:: jinja + {{ "%s - %s"|format("Hello?", "Foo!") }} + -> Hello? - Foo! + """ + print('normalize_item_to_dict({}, {}, {})'.format(item, default_key, additional_keys)) + out = dict() + attrs = additional_keys or [] + + #print('normalize_item_to_dict() type(item) is {} (isinstance(item, dict) is {})'.format(type(item), isinstance(item, dict))) + if not isinstance(item, dict): + out[default_key] = item + #print('normalize_item_to_dict() out set to {}'.format(out)) + else: + attrs = [default_key] + attrs + + for additional in attrs: + key = additional if isinstance(additional, str) else additional[0] + val = None if isinstance(additional, str) else additional[1] + + if isinstance(val, FunctionType): + try: + val = val() + except Exception as e: + print('[{}.{}] running function failed! {}'.format(item, key, e)) + pass + + out[key] = item[key] if key in item else val + + return out + + def normalize_mailboxes(self, items: Iterable[Union[str,tuple[str,Any]]], password_spec='/dev/null chars=ascii_letters,digits,.:;-_$%&=# length=16'): + return self.normalize_items_to_dicts(items, 'name', [('password', lambda: self.pw.run([password_spec], [])[0])]) diff --git a/roles/uberspace/tasks/mailboxes.yml b/roles/uberspace/tasks/mailboxes.yml new file mode 100644 index 0000000..d08cbd6 --- /dev/null +++ b/roles/uberspace/tasks/mailboxes.yml @@ -0,0 +1,53 @@ +--- +- name: Gather facts + block: + - name: Gather existing mailboxes + command: + cmd: uberspace mail user list + changed_when: false + register: _existing_boxes + + - name: Gather catchall + command: + cmd: uberspace mail catchall status + changed_when: false + register: _catchall + + +- name: "Generate new passwords" + set_fact: + mail: "{{ mail | combine({'boxes': mail['boxes'] | normalize_mailboxes}) }}" + +- name: Create new mailboxes + command: + cmd: "uberspace mail user add --password {{ item.password }} {{ item.name }}" + register: _password + failed_when: ("ERROR:" in _password.stdout) + with_items: "{{ mail.boxes | difference(_existing_boxes.stdout_lines) }}" + +- name: Check currently set forward + command: + cmd: "uberspace mail user forward list {{ item.name }}" + register: item._current + when: item.name in _existing_boxes.stdout_lines + with_items: "{{ mail.forwards }}" + +- name: Set forwards + command: + cmd: "uberspace mail user forward set {{ item.name }} {{ item.target }}" + register: _set_forward + changed_when: item._current is defined and item.target != item._current.stdout_lines[0] + failed_when: ("ERROR:" in _set_forward.stdout) + with_items: "{{ mail.forwards }}" + +- name: Set catchall + command: + cmd: "uberspace mail catchall set {{ mail.catchall }}" + changed_when: mail.catchall != _catchall.stdout + when: mail.catchall is defined and mail.catchall is not false + +- name: Unset catchall + command: + cmd: uberspace mail catchall del + changed_when: ("No catchall configured." not in _catchall.stdout_lines) + when: mail.catchall is defined and mail.catchall is false From 4dfdf72625f46e579df3ac3a66bf09c1c39a86df Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Wed, 16 Jun 2021 23:35:44 +0200 Subject: [PATCH 10/14] allow specifying mailboxes and simple sieve scripts --- roles/uberspace/defaults/main.yml | 2 + roles/uberspace/filter_plugins/normalize.py | 3 +- roles/uberspace/handlers/main.yml | 7 ++ roles/uberspace/tasks/domains.yml | 10 +- roles/uberspace/tasks/mailboxes.yml | 38 +++++-- roles/uberspace/tasks/main.yml | 3 + roles/uberspace/tasks/sieve.yml | 41 +++++++ .../templates/simple-sieve-script.sieve.j2 | 102 ++++++++++++++++++ site.yml.example | 18 ++++ 9 files changed, 206 insertions(+), 18 deletions(-) create mode 100644 roles/uberspace/defaults/main.yml create mode 100644 roles/uberspace/tasks/sieve.yml create mode 100644 roles/uberspace/templates/simple-sieve-script.sieve.j2 diff --git a/roles/uberspace/defaults/main.yml b/roles/uberspace/defaults/main.yml new file mode 100644 index 0000000..cbeb29d --- /dev/null +++ b/roles/uberspace/defaults/main.yml @@ -0,0 +1,2 @@ +--- +report_new_mail_passwords: false diff --git a/roles/uberspace/filter_plugins/normalize.py b/roles/uberspace/filter_plugins/normalize.py index 753b222..b9c07e2 100644 --- a/roles/uberspace/filter_plugins/normalize.py +++ b/roles/uberspace/filter_plugins/normalize.py @@ -30,7 +30,6 @@ def normalize_item_to_dict(self, item: Any, default_key: str, additional_keys: O {{ "%s - %s"|format("Hello?", "Foo!") }} -> Hello? - Foo! """ - print('normalize_item_to_dict({}, {}, {})'.format(item, default_key, additional_keys)) out = dict() attrs = additional_keys or [] @@ -57,4 +56,4 @@ def normalize_item_to_dict(self, item: Any, default_key: str, additional_keys: O return out def normalize_mailboxes(self, items: Iterable[Union[str,tuple[str,Any]]], password_spec='/dev/null chars=ascii_letters,digits,.:;-_$%&=# length=16'): - return self.normalize_items_to_dicts(items, 'name', [('password', lambda: self.pw.run([password_spec], [])[0])]) + return self.normalize_items_to_dicts(items, 'name', [('password', lambda: self.pw.run([password_spec], [])[0]), ('simple_rules', None)]) diff --git a/roles/uberspace/handlers/main.yml b/roles/uberspace/handlers/main.yml index 922292e..62eab3a 100644 --- a/roles/uberspace/handlers/main.yml +++ b/roles/uberspace/handlers/main.yml @@ -3,3 +3,10 @@ debug: msg: "{{ web_domains | default([]) | combine(mail_domains | default([]), recursive=true) }}" listen: Report DNS entries + +- name: Report new mail passwords + debug: + #msg: "{{ mail_passwords | default([]) }}" + msg: "{{ mail_passwords.results | selectattr('changed') | map(attribute='item') }}" + when: report_new_mail_passwords + listen: Report new mail passwords diff --git a/roles/uberspace/tasks/domains.yml b/roles/uberspace/tasks/domains.yml index 090c2b3..0454a74 100644 --- a/roles/uberspace/tasks/domains.yml +++ b/roles/uberspace/tasks/domains.yml @@ -17,9 +17,9 @@ - name: "Enable mail for {{ item.name }}" command: "uberspace mail domain add {{ item.name }}" - register: mail_domain - failed_when: mail_domain.rc == 1 and mail_domain.stderr != "Can't add domain to the configuration. It is already configured for your Uberspace account." - changed_when: ("The mailserver's configuration has been adapted." in mail_domain.stdout_lines) + register: _mail_domain + failed_when: _mail_domain.rc == 1 and _mail_domain.stderr != "Can't add domain to the configuration. It is already configured for your Uberspace account." + changed_when: ("The mailserver's configuration has been adapted." in _mail_domain.stdout_lines) notify: - Report DNS entries when: @@ -28,8 +28,8 @@ - name: "Remember mail facts for {{ item.name }}" set_fact: - mail_domains: "{{ (mail_domains | default([])) + [{'name': item.name, 'MX': mail_domain.stdout | regex_search('\\sMX\\s+->\\s+([^\n]+)'), 'TXT': mail_domain.stdout | regex_search('\\sTXT\\s+->\\s+([^\n]+)')}] }}" - when: mail_domain.changed + mail_domains: "{{ (mail_domains | default([])) + [{'name': item.name, 'MX': _mail_domain.stdout | regex_search('\\sMX\\s+->\\s+([^\n]+)'), 'TXT': _mail_domain.stdout | regex_search('\\sTXT\\s+->\\s+([^\n]+)')}] }}" + when: _mail_domain.changed - name: "Create symlink for {{ item.name }}" file: diff --git a/roles/uberspace/tasks/mailboxes.yml b/roles/uberspace/tasks/mailboxes.yml index d08cbd6..99fff7b 100644 --- a/roles/uberspace/tasks/mailboxes.yml +++ b/roles/uberspace/tasks/mailboxes.yml @@ -14,40 +14,56 @@ register: _catchall -- name: "Generate new passwords" +- name: Generate new passwords set_fact: mail: "{{ mail | combine({'boxes': mail['boxes'] | normalize_mailboxes}) }}" +- name: Enable the spamfolder + command: + cmd: uberspace mail spamfolder enable + register: _spamfolder + changed_when: ('already' not in _spamfolder.stdout) + failed_when: _spamfolder.rc != 0 and 'The spam folder is already enabled.' not in _spamfolder.stdout_lines + - name: Create new mailboxes command: cmd: "uberspace mail user add --password {{ item.password }} {{ item.name }}" - register: _password - failed_when: ("ERROR:" in _password.stdout) - with_items: "{{ mail.boxes | difference(_existing_boxes.stdout_lines) }}" + register: mail_passwords + changed_when: ("ERROR:" not in mail_passwords.stderr) + failed_when: ("ERROR:" in mail_passwords.stderr and "already exists." not in mail_passwords.stderr) + when: item.name not in _existing_boxes.stdout_lines + with_list: "{{ mail.boxes }}" + notify: Report new mail passwords - name: Check currently set forward command: cmd: "uberspace mail user forward list {{ item.name }}" - register: item._current - when: item.name in _existing_boxes.stdout_lines - with_items: "{{ mail.forwards }}" + register: _current + changed_when: false + failed_when: false + with_list: "{{ mail.forwards }}" - name: Set forwards command: - cmd: "uberspace mail user forward set {{ item.name }} {{ item.target }}" + cmd: "uberspace mail user forward set {{ item[0].name }} {{ item[0].target }}" register: _set_forward - changed_when: item._current is defined and item.target != item._current.stdout_lines[0] + changed_when: item[1] is defined and item[0].target != item[1].stdout_lines[0] failed_when: ("ERROR:" in _set_forward.stdout) - with_items: "{{ mail.forwards }}" + when: item[1] is not defined or item[0].target != item[1].stdout_lines[0] + with_list: "{{ mail.forwards | zip(_current.results) }}" - name: Set catchall command: cmd: "uberspace mail catchall set {{ mail.catchall }}" changed_when: mail.catchall != _catchall.stdout - when: mail.catchall is defined and mail.catchall is not false + when: mail.catchall is defined and mail.catchall is not false and mail.catchall != _catchall.stdout - name: Unset catchall command: cmd: uberspace mail catchall del changed_when: ("No catchall configured." not in _catchall.stdout_lines) when: mail.catchall is defined and mail.catchall is false + +- name: Set mail rules + include_tasks: sieve.yml + with_list: "{{ mail.boxes }}" diff --git a/roles/uberspace/tasks/main.yml b/roles/uberspace/tasks/main.yml index 8fe07e9..37aabba 100644 --- a/roles/uberspace/tasks/main.yml +++ b/roles/uberspace/tasks/main.yml @@ -33,3 +33,6 @@ include_tasks: domains.yml with_items: "{{ domains }}" when: domains is defined + +- name: Set up mailboxes + include_tasks: mailboxes.yml diff --git a/roles/uberspace/tasks/sieve.yml b/roles/uberspace/tasks/sieve.yml new file mode 100644 index 0000000..5beaffa --- /dev/null +++ b/roles/uberspace/tasks/sieve.yml @@ -0,0 +1,41 @@ +- block: + - name: Create sieve directory + file: + path: ~/users/{{ item['name'] }}/sieve + state: directory + + - name: Set sieve script + template: + src: simple-sieve-script.sieve.j2 + dest: ~/users/{{ item['name'] }}/sieve/ansible-managed.sieve.test + + - name: Check sieve script validity + command: + cmd: sievec ~/users/{{ item['name'] }}/sieve/ansible-managed.sieve.test + changed_when: false + + - name: Copy tested script over + copy: + src: ~/users/{{ item['name'] }}/sieve/ansible-managed.sieve.test + dest: ~/users/{{ item['name'] }}/sieve/ansible-managed.sieve + remote_src: true + + - name: Activate sieve script + file: + src: ~/users/{{ item['name'] }}/sieve/ansible-managed.sieve + dest: ~/users/{{ item['name'] }}/.dovecot.sieve + state: link + when: ('simple_rules' in item and item.simple_rules is not none) + +- block: + - name: Check active script + stat: + path: ~/users/{{ item['name'] }}/.dovecot.sieve + register: _active_script + + - name: Deactivate sieve script + file: + path: ~/users/{{ item['name'] }}/.dovecot.sieve + state: absent + when: _active_script.stat.exists and _active_script.stat.islnk and _active_script.stat.lnk_target.endswith('/ansible-managed.sieve') + when: ('simple_rules' not in item or item.simple_rules is none) diff --git a/roles/uberspace/templates/simple-sieve-script.sieve.j2 b/roles/uberspace/templates/simple-sieve-script.sieve.j2 new file mode 100644 index 0000000..8db8e57 --- /dev/null +++ b/roles/uberspace/templates/simple-sieve-script.sieve.j2 @@ -0,0 +1,102 @@ +# {{ ansible_managed }} + +{% macro get_required(rules) -%} +{%- if list_required(rules).split(',') | difference(['']) | length > 0 -%} +require [{{ [] | union(list_required(rules).split(',')) | difference(['']) | join(",") }}]; +{% endif -%} +{%- endmacro -%} + +{%- macro list_required(rules) -%} +{%- for rule in rules -%} + +{%- if 'if' in rule or 'elif' in rule or 'elsif' in rule or 'elseif' in rule -%} +{%- if (rule.if|default(rule.elif)|default(rule.elsif)|default(rule.elseif)).startswith('envelope ') -%} +"envelope", +{%- endif -%} +{%- if ' :comparator ' in rule.if|default(rule.elif)|default(rule.elsif)|default(rule.elseif) -%} + {%- if rule.if|default(rule.elif)|default(rule.elsif)|default(rule.elseif) | regex_findall(' :comparator "([^"]+)" ')|first not in ["i;octet", "i;ascii-casemap"] -%} +"comparator-{{ rule.if|default(rule.elif)|default(rule.elsif)|default(rule.elseif) | regex_findall(' :comparator "([^"]+)" ')|first }}", + {%- endif -%} +{%- endif -%} +{%- if ' :value ' in rule.if|default(rule.elif)|default(rule.elsif)|default(rule.elseif) -%} +"relational", +{%- endif -%} +{%- if (rule.if|default(rule.elif)|default(rule.elsif)|default(rule.elseif)).startswith('spamtest ') -%} +"spamtestplus", +{%- endif -%} +{%- if (rule.if|default(rule.elif)|default(rule.elsif)|default(rule.elseif)).startswith('virustest ') -%} +"virustest", +{%- endif -%} +{%- if ' :detail ' in rule.if|default(rule.elif)|default(rule.elsif)|default(rule.elseif) -%} +"subaddress", +{%- endif -%} +{%- if ' :regex ' in rule.if|default(rule.elif)|default(rule.elsif)|default(rule.elseif) -%} +"regex", +{%- endif -%} +{{ list_required(rule.block) }} + +{%- elif 'else' in rule -%} +{{ list_required(rule.block) }} + +{%- elif 'reject' in rule -%} +"reject", +{%- elif 'fileinto' in rule -%} +"fileinto", +{%- elif 'setflag' in rule -%} +"imap4flags", +{%- elif 'vacation' in rule -%} +"vacation", + +{%- endif -%} +{%- endfor -%} +{%- endmacro -%} + + +{%- macro indent(n) -%} +{{ ' '*n }} +{%- endmacro -%} + +{%- macro makerules(rules, ind) -%} +{%- for subrule in rules -%} +{{ makerule(subrule, ind) }} +{%- endfor -%} +{%- endmacro -%} + +{%- macro makerule(rule, ind) -%} + +{% if 'if' in rule %} + +{{ indent(ind) }}if {{ rule.if }} { +{{ makerules(rule.block, ind+1) }}{{ indent(ind) }}} + +{% elif 'elif' in rule or 'elsif' in rule or 'elseif' in rule %} +{{ indent(ind) }}elsif {{ rule.elif|default(rule.elsif)|default(rule.elseif) }} { +{{ makerules(rule.block, n+1) }}{{ indent(ind) }}} + +{% elif 'else' in rule %} +{{ indent(ind) }}else {{ rule.else }} { +{{ makerules(rule.block, ind+1) }}{{ indent(ind) }}} + +{% elif 'reject' in rule -%} +{{ indent(ind) }}reject "{{ rule.reject }}"; +{% elif 'redirect' in rule -%} +{{ indent(ind) }}redirect "{{ rule.redirect }}"; +{% elif 'keep' in rule -%} +{{ indent(ind) }}keep; +{% elif 'discard' in rule -%} +{{ indent(ind) }}discard; +{% elif 'fileinto' in rule -%} +{{ indent(ind) }}fileinto "{{ rule.fileinto }}"; +{% elif 'setflag' in rule -%} +{{ indent(ind) }}setflag "{{ rule.setflag }}"; +{% elif 'vacation' in rule -%} +{{ indent(ind) }}setflag "{{ rule.vacation }}"; +{% elif 'stop' in rule -%} +{{ indent(ind) }}stop; +{% endif -%} + +{%- endmacro -%} + + +{{ get_required(item.simple_rules) }} +{{ makerules(item.simple_rules, 0) }} diff --git a/site.yml.example b/site.yml.example index dcffc31..4ebd44a 100644 --- a/site.yml.example +++ b/site.yml.example @@ -17,6 +17,24 @@ - name: blog.example.com web: true link: true + mail: + boxes: + - name: admin + password: VeryComplicatedPassw0rd + simple_rules: + - if: header :value "ge" :comparator "i;ascii-numeric" "X-Rspamd-Score" "4" + block: + - fileinto: "Spam" + - stop: + - hello + forwards: + - name: hallo + target: hello@example.com + - name: my_dad + target: dad@other.com + catchall: admin + report_new_mail_passwords: true + - role: uberspace_wordpress_bedrock vars: instances: From e2aa49276152bee17909686afa02c85d41e3f64c Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Wed, 16 Jun 2021 23:41:54 +0200 Subject: [PATCH 11/14] add mail_pw_reset role to deploy password reset application --- .../uberspace_mail_pw_reset/defaults/main.yml | 10 ++++ .../uberspace_mail_pw_reset/handlers/main.yml | 5 ++ roles/uberspace_mail_pw_reset/tasks/main.yml | 51 +++++++++++++++++++ roles/uberspace_mail_pw_reset/tasks/setup.yml | 0 .../uberspace_mail_pw_reset/templates/env.j2 | 18 +++++++ .../templates/flask.ini.j2 | 3 ++ .../templates/uwsgi.ini.j2 | 9 ++++ site.yml.example | 6 +++ 8 files changed, 102 insertions(+) create mode 100644 roles/uberspace_mail_pw_reset/defaults/main.yml create mode 100644 roles/uberspace_mail_pw_reset/handlers/main.yml create mode 100644 roles/uberspace_mail_pw_reset/tasks/main.yml create mode 100644 roles/uberspace_mail_pw_reset/tasks/setup.yml create mode 100644 roles/uberspace_mail_pw_reset/templates/env.j2 create mode 100644 roles/uberspace_mail_pw_reset/templates/flask.ini.j2 create mode 100644 roles/uberspace_mail_pw_reset/templates/uwsgi.ini.j2 diff --git a/roles/uberspace_mail_pw_reset/defaults/main.yml b/roles/uberspace_mail_pw_reset/defaults/main.yml new file mode 100644 index 0000000..a35ef00 --- /dev/null +++ b/roles/uberspace_mail_pw_reset/defaults/main.yml @@ -0,0 +1,10 @@ +--- +project_path: "/home/{{ ansible_env.USER }}/mail_pw_reset" +mail_domain: example.com + +server_name: "{{ ansible_env.USER }}.uber.space" +backend_entrypoint: /mail_reset +backend_port: 1024 + +git_repo: 'https://github.com/PeterNerlich/uberspace_mail_pw_reset.git' +git_version: master diff --git a/roles/uberspace_mail_pw_reset/handlers/main.yml b/roles/uberspace_mail_pw_reset/handlers/main.yml new file mode 100644 index 0000000..b8634c9 --- /dev/null +++ b/roles/uberspace_mail_pw_reset/handlers/main.yml @@ -0,0 +1,5 @@ +--- +- name: Restart flask application + community.general.supervisorctl: + name: uberspace_mail_pw_reset_flask + state: restarted diff --git a/roles/uberspace_mail_pw_reset/tasks/main.yml b/roles/uberspace_mail_pw_reset/tasks/main.yml new file mode 100644 index 0000000..508c704 --- /dev/null +++ b/roles/uberspace_mail_pw_reset/tasks/main.yml @@ -0,0 +1,51 @@ +--- +- name: Fetch flask project + ansible.builtin.git: + repo: "{{ git_repo }}" + dest: "{{ project_path }}" + force: true + version: "{{ git_version }}" + notify: Restart flask application + +- name: Create venv + pip: + requirements: "{{ project_path }}/requirements.txt" + virtualenv: "{{ project_path }}" + virtualenv_command: /usr/bin/python3.6 -m venv + notify: Restart flask application + +- name: Create env file + template: + src: env.j2 + dest: "{{ project_path }}/.env" + vars: + secret_key: "{{ lookup('password', '/dev/null chars=ascii_letters,digits,hexdigits,punctuation length=16') }}" + notify: Restart flask application + +- name: Template uwsgi.ini + template: + src: uwsgi.ini.j2 + dest: "{{ project_path }}/uwsgi.ini" + notify: Restart flask application + +- name: Template flask.ini + template: + src: flask.ini.j2 + dest: ~/etc/services.d/uberspace_mail_pw_reset_flask.ini + notify: Restart flask application + +- name: Start application + community.general.supervisorctl: + name: uberspace_mail_pw_reset_flask + state: started + +- name: Set backend + command: "uberspace web backend set {{ backend_entrypoint }} --http --port {{ backend_port }}" + register: backend + failed_when: backend.rc == 1 and not backend.stdout.startswith("Set backend for ") + changed_when: backend.stdout.startswith("Set backend for ") + when: + - backend_entrypoint not in ansible_facts.ansible_local.users[ansible_env.USER].global_backends + - ansible_facts.ansible_local.users[ansible_env.USER].global_backends[backend_entrypoint].port != backend_port + - ansible_facts.ansible_local.users[ansible_env.USER].global_backends[backend_entrypoint].remove_prefix != false + - ansible_facts.ansible_local.users[ansible_env.USER].global_backends[backend_entrypoint].transport != 'http' diff --git a/roles/uberspace_mail_pw_reset/tasks/setup.yml b/roles/uberspace_mail_pw_reset/tasks/setup.yml new file mode 100644 index 0000000..e69de29 diff --git a/roles/uberspace_mail_pw_reset/templates/env.j2 b/roles/uberspace_mail_pw_reset/templates/env.j2 new file mode 100644 index 0000000..495bb27 --- /dev/null +++ b/roles/uberspace_mail_pw_reset/templates/env.j2 @@ -0,0 +1,18 @@ +DATABASE_URI=sqlite:///uberspace_mail_pw_reset.sqlite3 + +SECRET_KEY={{ secret_key }} + +BLACKLISTED_MAILBOXES={{ blacklisted_mailboxes|join(',') }} + +PREFERRED_URL_SCHEME=https +SERVER_NAME={{ server_name }} +APP_ROOT={{ backend_entrypoint }} +SCRIPT_NAME={{ backend_entrypoint }} +MAIL_SENDER=noreply@{{ mail_domain }} +MAIL_RECEIVER_DOMAIN={{ mail_domain }} +MAIL_HOSTNAME={{ ansible_facts.hostname }}.{{ ansible_facts.domain }} + +TOKEN_SECONDS_VALID=3600 # 1 hour +TOKEN_MAX_DELAY_TO_RESET=300 # 5 min + +LANGUAGES=en,de diff --git a/roles/uberspace_mail_pw_reset/templates/flask.ini.j2 b/roles/uberspace_mail_pw_reset/templates/flask.ini.j2 new file mode 100644 index 0000000..7c688b4 --- /dev/null +++ b/roles/uberspace_mail_pw_reset/templates/flask.ini.j2 @@ -0,0 +1,3 @@ +[program:uberspace_mail_pw_reset_flask] +directory={{ project_path }} +command={{ project_path }}/bin/uwsgi {{ project_path }}/uwsgi.ini diff --git a/roles/uberspace_mail_pw_reset/templates/uwsgi.ini.j2 b/roles/uberspace_mail_pw_reset/templates/uwsgi.ini.j2 new file mode 100644 index 0000000..5cdad6f --- /dev/null +++ b/roles/uberspace_mail_pw_reset/templates/uwsgi.ini.j2 @@ -0,0 +1,9 @@ +[uwsgi] +mount = {{ backend_entrypoint }}=main:app +manage-script-name = true +pidfile = uberspace_mail_pw_reset.pid +master = true +processes = 1 +http-socket = :{{ backend_port }} +chmod-socket = 660 +vacuum = true diff --git a/site.yml.example b/site.yml.example index 4ebd44a..4bbca9f 100644 --- a/site.yml.example +++ b/site.yml.example @@ -35,6 +35,12 @@ catchall: admin report_new_mail_passwords: true + - role: uberspace_mail_pw_reset + vars: + mail_domain: example.com + blacklisted_mailboxes: + - admin + - role: uberspace_wordpress_bedrock vars: instances: From 81e70097dea970589347aa03cfafd479c927fa44 Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Tue, 22 Jun 2021 19:14:50 +0200 Subject: [PATCH 12/14] handle missing mail.boxes and mail.forwards as empty list --- roles/uberspace/tasks/mailboxes.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/roles/uberspace/tasks/mailboxes.yml b/roles/uberspace/tasks/mailboxes.yml index 99fff7b..1534237 100644 --- a/roles/uberspace/tasks/mailboxes.yml +++ b/roles/uberspace/tasks/mailboxes.yml @@ -16,7 +16,7 @@ - name: Generate new passwords set_fact: - mail: "{{ mail | combine({'boxes': mail['boxes'] | normalize_mailboxes}) }}" + mail: "{{ mail | combine({'boxes': mail['boxes']|default([]) | normalize_mailboxes}) }}" - name: Enable the spamfolder command: @@ -32,7 +32,7 @@ changed_when: ("ERROR:" not in mail_passwords.stderr) failed_when: ("ERROR:" in mail_passwords.stderr and "already exists." not in mail_passwords.stderr) when: item.name not in _existing_boxes.stdout_lines - with_list: "{{ mail.boxes }}" + with_list: "{{ mail.boxes|default([]) }}" notify: Report new mail passwords - name: Check currently set forward @@ -41,7 +41,7 @@ register: _current changed_when: false failed_when: false - with_list: "{{ mail.forwards }}" + with_list: "{{ mail.forwards|default([]) }}" - name: Set forwards command: @@ -50,7 +50,7 @@ changed_when: item[1] is defined and item[0].target != item[1].stdout_lines[0] failed_when: ("ERROR:" in _set_forward.stdout) when: item[1] is not defined or item[0].target != item[1].stdout_lines[0] - with_list: "{{ mail.forwards | zip(_current.results) }}" + with_list: "{{ mail.forwards|default([]) | zip(_current.results) }}" - name: Set catchall command: @@ -66,4 +66,4 @@ - name: Set mail rules include_tasks: sieve.yml - with_list: "{{ mail.boxes }}" + with_list: "{{ mail.boxes|default([]) }}" From fd6630106ad7b98adbd3c82c9f394a3861e3f282 Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Tue, 22 Jun 2021 19:16:11 +0200 Subject: [PATCH 13/14] mail_pw_reset: use python3.9, generate longer secret key, give initial token valid time --- roles/uberspace_mail_pw_reset/tasks/main.yml | 6 ++++-- roles/uberspace_mail_pw_reset/templates/env.j2 | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/roles/uberspace_mail_pw_reset/tasks/main.yml b/roles/uberspace_mail_pw_reset/tasks/main.yml index 508c704..20af9d3 100644 --- a/roles/uberspace_mail_pw_reset/tasks/main.yml +++ b/roles/uberspace_mail_pw_reset/tasks/main.yml @@ -11,7 +11,9 @@ pip: requirements: "{{ project_path }}/requirements.txt" virtualenv: "{{ project_path }}" - virtualenv_command: /usr/bin/python3.6 -m venv + virtualenv_command: /usr/bin/python3.9 -m venv + environment: + CPATH: /usr/include/python3.9 notify: Restart flask application - name: Create env file @@ -19,7 +21,7 @@ src: env.j2 dest: "{{ project_path }}/.env" vars: - secret_key: "{{ lookup('password', '/dev/null chars=ascii_letters,digits,hexdigits,punctuation length=16') }}" + secret_key: "{{ lookup('password', '/dev/null chars=ascii_letters,digits,hexdigits,punctuation length=64') }}" notify: Restart flask application - name: Template uwsgi.ini diff --git a/roles/uberspace_mail_pw_reset/templates/env.j2 b/roles/uberspace_mail_pw_reset/templates/env.j2 index 495bb27..80a45e9 100644 --- a/roles/uberspace_mail_pw_reset/templates/env.j2 +++ b/roles/uberspace_mail_pw_reset/templates/env.j2 @@ -13,6 +13,7 @@ MAIL_RECEIVER_DOMAIN={{ mail_domain }} MAIL_HOSTNAME={{ ansible_facts.hostname }}.{{ ansible_facts.domain }} TOKEN_SECONDS_VALID=3600 # 1 hour +INITIAL_TOKEN_SECONDS_VALID=172800 # 48 hours TOKEN_MAX_DELAY_TO_RESET=300 # 5 min LANGUAGES=en,de From 1dc8ef01fbfd9f090dfa8751b98c9bdd9bef3abf Mon Sep 17 00:00:00 2001 From: Peter Nerlich Date: Sat, 26 Jun 2021 08:33:40 +0200 Subject: [PATCH 14/14] mail_pw_reset: properly initialize translations --- roles/uberspace_mail_pw_reset/handlers/main.yml | 5 +++++ roles/uberspace_mail_pw_reset/tasks/main.yml | 4 +++- roles/uberspace_mail_pw_reset/templates/env.j2 | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/roles/uberspace_mail_pw_reset/handlers/main.yml b/roles/uberspace_mail_pw_reset/handlers/main.yml index b8634c9..6a0bb1b 100644 --- a/roles/uberspace_mail_pw_reset/handlers/main.yml +++ b/roles/uberspace_mail_pw_reset/handlers/main.yml @@ -1,4 +1,9 @@ --- +- name: Build translations + ansible.builtin.command: + cmd: "{{ project_path }}/update_and_build_translations.sh" + chdir: "{{ project_path }}" + - name: Restart flask application community.general.supervisorctl: name: uberspace_mail_pw_reset_flask diff --git a/roles/uberspace_mail_pw_reset/tasks/main.yml b/roles/uberspace_mail_pw_reset/tasks/main.yml index 20af9d3..baefaff 100644 --- a/roles/uberspace_mail_pw_reset/tasks/main.yml +++ b/roles/uberspace_mail_pw_reset/tasks/main.yml @@ -5,7 +5,9 @@ dest: "{{ project_path }}" force: true version: "{{ git_version }}" - notify: Restart flask application + notify: + - Build translations + - Restart flask application - name: Create venv pip: diff --git a/roles/uberspace_mail_pw_reset/templates/env.j2 b/roles/uberspace_mail_pw_reset/templates/env.j2 index 80a45e9..e90f030 100644 --- a/roles/uberspace_mail_pw_reset/templates/env.j2 +++ b/roles/uberspace_mail_pw_reset/templates/env.j2 @@ -17,3 +17,4 @@ INITIAL_TOKEN_SECONDS_VALID=172800 # 48 hours TOKEN_MAX_DELAY_TO_RESET=300 # 5 min LANGUAGES=en,de +DEFAULT_LOCALE=de