diff options
28 files changed, 713 insertions, 105 deletions
diff --git a/_config.yml b/_config.yml index ad5c74aeb..6ee6be0e1 100644 --- a/_config.yml +++ b/_config.yml @@ -4,6 +4,8 @@ baseurl: / permalink: pretty markdown: kramdown url: http://code.alaveteli.org -exclude: [ node_modules ] +exclude: + - node_modules + - script gems: - jekyll-redirect-from diff --git a/_layouts/default.html b/_layouts/default.html index c8c18c111..b4e2b6faf 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -97,7 +97,8 @@ <div class="column"> <h3>Connect</h3> <ul> - <li><a href="https://groups.google.com/group/alaveteli-dev">Mailing list</a></li> + <li><a href="https://groups.google.com/group/alaveteli-users">Mailing list</a></li> + <li><a href="https://groups.google.com/group/alaveteli-dev">Developer Mailing list</a></li> <li><a href="https://github.com/mysociety/alaveteli">GitHub</a></li> <li><a href="http://www.irc.mysociety.org/">IRC</a></li> <li><a href="https://twitter.com/alaveteli">Twitter</a></li> diff --git a/_layouts/page.html b/_layouts/page.html index 8cee7ba11..0eb0d0dbf 100644 --- a/_layouts/page.html +++ b/_layouts/page.html @@ -66,6 +66,8 @@ layout: default <li><a href="{{ site.baseurl }}docs/running/">Running</a> <ul> <li><a href="{{ site.baseurl }}docs/running/admin_manual/">Admin manual</a></li> + <li><a href="{{ site.baseurl }}docs/running/redaction">Redaction</a></li> + <li><a href="{{ site.baseurl }}docs/running/security/">Security & Maintenance</a></li> <li><a href="{{ site.baseurl }}docs/running/server/">Server checklist</a></li> <li><a href="{{ site.baseurl }}docs/running/upgrading/">Upgrading</a></li> </ul> diff --git a/assets/img/redaction-address-outside-fence.png b/assets/img/redaction-address-outside-fence.png Binary files differnew file mode 100644 index 000000000..bbed100cf --- /dev/null +++ b/assets/img/redaction-address-outside-fence.png diff --git a/assets/img/redaction-address-quoted-redacted.png b/assets/img/redaction-address-quoted-redacted.png Binary files differnew file mode 100644 index 000000000..f5e2591d8 --- /dev/null +++ b/assets/img/redaction-address-quoted-redacted.png diff --git a/assets/img/redaction-automatically-added-id-number-censor-rule.png b/assets/img/redaction-automatically-added-id-number-censor-rule.png Binary files differnew file mode 100644 index 000000000..a05b6bc9d --- /dev/null +++ b/assets/img/redaction-automatically-added-id-number-censor-rule.png diff --git a/assets/img/redaction-domicile-censor-rule-applied.png b/assets/img/redaction-domicile-censor-rule-applied.png Binary files differnew file mode 100644 index 000000000..8933c409a --- /dev/null +++ b/assets/img/redaction-domicile-censor-rule-applied.png diff --git a/assets/img/redaction-domicile-censor-rule.png b/assets/img/redaction-domicile-censor-rule.png Binary files differnew file mode 100644 index 000000000..dcbae99ab --- /dev/null +++ b/assets/img/redaction-domicile-censor-rule.png diff --git a/assets/img/redaction-id-number-in-main-body-not-redacted.png b/assets/img/redaction-id-number-in-main-body-not-redacted.png Binary files differnew file mode 100644 index 000000000..215bc8051 --- /dev/null +++ b/assets/img/redaction-id-number-in-main-body-not-redacted.png diff --git a/assets/img/redaction-id-number-in-main-body-redacted.png b/assets/img/redaction-id-number-in-main-body-redacted.png Binary files differnew file mode 100644 index 000000000..437568733 --- /dev/null +++ b/assets/img/redaction-id-number-in-main-body-redacted.png diff --git a/assets/img/redaction-id-number-in-quoted-section.png b/assets/img/redaction-id-number-in-quoted-section.png Binary files differnew file mode 100644 index 000000000..f46fc0ebb --- /dev/null +++ b/assets/img/redaction-id-number-in-quoted-section.png diff --git a/assets/img/redaction-id-number-redacted.png b/assets/img/redaction-id-number-redacted.png Binary files differnew file mode 100644 index 000000000..2e8d2c8d8 --- /dev/null +++ b/assets/img/redaction-id-number-redacted.png diff --git a/assets/img/redaction-outgoing-message-with-general-law.png b/assets/img/redaction-outgoing-message-with-general-law.png Binary files differnew file mode 100644 index 000000000..80f9b3e53 --- /dev/null +++ b/assets/img/redaction-outgoing-message-with-general-law.png diff --git a/assets/img/redaction-outgoing-message-with-id-number.png b/assets/img/redaction-outgoing-message-with-id-number.png Binary files differnew file mode 100644 index 000000000..834ad3d08 --- /dev/null +++ b/assets/img/redaction-outgoing-message-with-id-number.png diff --git a/assets/img/redaction-pdf-redaction-as-html.png b/assets/img/redaction-pdf-redaction-as-html.png Binary files differnew file mode 100644 index 000000000..e98aba73e --- /dev/null +++ b/assets/img/redaction-pdf-redaction-as-html.png diff --git a/assets/img/redaction-pdf-redaction-download.png b/assets/img/redaction-pdf-redaction-download.png Binary files differnew file mode 100644 index 000000000..b24a50568 --- /dev/null +++ b/assets/img/redaction-pdf-redaction-download.png diff --git a/assets/img/redaction-sign-up-form.png b/assets/img/redaction-sign-up-form.png Binary files differnew file mode 100644 index 000000000..5a36a7304 --- /dev/null +++ b/assets/img/redaction-sign-up-form.png diff --git a/docs/customising/config.md b/docs/customising/config.md index 906af3702..8778b2edd 100644 --- a/docs/customising/config.md +++ b/docs/customising/config.md @@ -71,7 +71,7 @@ indentation correct. If in doubt, look at the examples already in the file, and <code><a href="#admin_username">ADMIN_USERNAME</a></code> <br> <code><a href="#admin_password">ADMIN_PASSWORD</a></code> -<br> <code><a href="#admin_username">DISABLE_EMERGENCY_USER</a></code> +<br> <code><a href="#disable_emergency_user">DISABLE_EMERGENCY_USER</a></code> <br> <code><a href="#skip_admin_auth">SKIP_ADMIN_AUTH</a></code> ### Email management: @@ -499,10 +499,25 @@ THEME_URLS: & <a name="admin_password"><code>ADMIN_PASSWORD</code></a> <br> - <a name="admin_username"><code>DISABLE_EMERGENCY_USER</code></a> + <a name="disable_emergency_user"><code>DISABLE_EMERGENCY_USER</code></a> </dt> <dd> - The emergency user. + Details for the + <a href="{{site.baseurl}}docs/glossary/#emergency" class="glossary__link">emergency user</a>. + <p> + This is useful for creating the initial admin users for your site: + <ul> + <li>Create a new user (using regular sign up on the site)</li> + <li>Log in as the emergency user</li> + <li>Promote the new account</li> + <li>Disable the emergency user</li> + </ul> + </p> + <p> + For details of this process, see + <a href="{{site.baseurl}}docs/installing/next_steps/#create-a-superuser-admin-account">creating + a superuser account</a>. + </p> <div class="more-info"> <p>Examples:</p> <ul class="examples"> diff --git a/docs/customising/themes.md b/docs/customising/themes.md index 29560502a..a51ed447e 100644 --- a/docs/customising/themes.md +++ b/docs/customising/themes.md @@ -190,3 +190,7 @@ necessary. ## Adding or overriding models and controllers If you need to extend the behaviour of Alaveteli at the controller or model level, see `alavetelitheme/lib/controller_patches.rb` and `alavetelitheme/lib/model_patches.rb` for examples. + +## Working with themes + +You can use [`script/switch-theme.rb`](https://github.com/mysociety/alaveteli/blob/master/script/switch-theme.rb) to set the current theme if you are working with multiple themes. This might be useful for switching between the default `alavetelitheme` and your own fork. diff --git a/docs/glossary.md b/docs/glossary.md index 0029cdc24..b5239a32a 100644 --- a/docs/glossary.md +++ b/docs/glossary.md @@ -23,6 +23,7 @@ Definitions <li><a href="#capistrano">Capistrano</a></li> <li><a href="#censor-rule">censor rule</a></li> <li><a href="#development">development site</a></li> + <li><a href="#emergency">emergency user</a></li> <li><a href="#foi">freedom of information</a></li> <li><a href="#git">git</a></li> <li><a href="#holding_pen">holding pen</a></li> @@ -231,6 +232,40 @@ Definitions </dd> <dt> + <a name="emergency">emergency user</a> + </dt> + <dd> + Alaveteli ships with a configuration setting for an <strong>emergency user</strong>. + This provides a username and password you can use to access the admin, even though + the user doesn't appear in the database. + <p> + When the system has been bootstrapped (that is, you've used the emergency user to + grant a user account full <em>super</em> privileges), you must disable the emergency + user. + </p> + <div class="more-info"> + <p>More information:</p> + <ul> + <li> + The username and password are defined by the configuration settings + <code><a href="{{site.baseurl}}docs/customising/config/#admin_username">ADMIN_USERNAME</a></code> + and + <code><a href="{{site.baseurl}}docs/customising/config/#admin_password">ADMIN_PASSWORD</a></code>. + </li> + <li> + For an example of using the emergency user, see + <a href="{{site.baseurl}}docs/installing/next_steps/#create-a-superuser-account-for-yourself">creating + a superuser account</a>. + </li> + <li> + Disable the emergency user by setting + <code><a href="{{site.baseurl}}docs/customising/config/#disable_emergency_user">DISABLE_EMERGENCY_USER:</a> true</code> + </li> + </ul> + </div> + </dd> + + <dt> <a name="foi">Freedom of Information</a> (also FOI) </dt> <dd> diff --git a/docs/installing/ami.md b/docs/installing/ami.md index cee4f88af..7008155f9 100644 --- a/docs/installing/ami.md +++ b/docs/installing/ami.md @@ -6,58 +6,155 @@ title: Installation from AMI # Installation on Amazon EC2 <p class="lead"> - We've made an Amazon Machine Image (AMI) so you can quickly deploy on Amazon EC2. This is handy if you just want to evaluate Alaveteli, for example. + We've made an Amazon Machine Image (AMI) so you can quickly deploy on Amazon + EC2. This is handy if you just want to evaluate Alaveteli, for example. </p> Note that there are [other ways to install Alaveteli]({{ site.baseurl }}docs/installing/). ## Installing from our AMI -To help people try out Alaveteli, we have created an AMI (Amazon Machine -Image) with a basic installation of Alaveteli (installed using the -[installation script]({{ site.baseurl }}docs/installing/script/)), which -you can use to create a running server on an Amazon EC2 instance. This -creates an instance that runs in development mode, so we wouldn't -recommend you use it for a production system without changing the -configuration. - -Unfortunately, Alaveteli will not run properly on a free Micro -instance due to the low amount of memory available on those -instances; you will need to use at least a Small instance, which -Amazon will charge for. - -The AMI can be found in the EU West (Ireland) region, with the ID ami-23519f54 -and name “Basic Alaveteli installation 2014-06-12”. You can launch an instance -based on that AMI with [this -link](https://console.aws.amazon.com/ec2/home?region=eu-west-1#launchAmi=ami-23519f54). - -When you create an EC2 instance based on that AMI, make sure that you choose -Security Groups that allows at least inbound HTTP, HTTPS, SSH and, if you want -to test incoming mail as well, SMTP. - -When your EC2 instance is launched, you will be able to log in as the `ubuntu` -user. This user can `sudo` freely to run commands as root. However, the code is -actually owned by (and runs as) the `alaveteli` user. After creating the -instance, you may want to edit a configuration file to customize the site's -configuration. That configuration file is -`/var/www/alaveteli/alaveteli/config/general.yml`, which can be edited with: +To help you try out Alaveteli, we have created an AMI with a basic installation +of Alaveteli, which you can use to create a running server on an Amazon EC2 +instance. This creates an instance that runs as a +<a href="{{ site.baseurl }}docs/glossary/#development" class="glossary__link">development site</a>. +If you want to use this for a +<a href="{{ site.baseurl }}docs/glossary/#production" class="glossary__link">production site</a>, +you must +[change the configuration]({{ site.baseurl }}docs/customising/config/#staging_site). + +<div class="attention-box"> + <p> + <strong>What's in the AMI?</strong> + The AMI gives you exactly the same thing as the + <a href="{{ site.baseurl}}docs/installing/script/">installation script</a> + does. You get an Alaveteli website powered by Rails running the Thin + application server under nginx, using a postgreSQL database. All this + running on Amazon's EC2 servers, ready to be + <a href="{{ site.baseurl }}docs/customising/">configured and customised</a>. + </p> +</div> + +Amazon instances are graded by size. Unfortunately, the *Micro* instance does +not have enough memory for Alaveteli to run -- and that's the only size +available on Amazon's free usage tier. You need to use a *Small* instance or +larger, which Amazon will charge you for. + +### Using Amazon web services + +To do this, you'll need: + + * an account with Amazon + * a SSL key pair (the Amazon web service screens guide you through this) + +If you don't have these already, you'll need to create them. See Amazon's +introduction on +[running a Virtual Server on AWS](http://docs.aws.amazon.com/gettingstarted/latest/awsgsg-intro/gsg-aws-virtual-server.html). + +### Launch the instance + +Once you're logged in to Amazon's service, and navigated to the **EC2 +Management Console**, you can launch the instance. If you prefer to do this +manually, you can find the AMI in the "EU West (Ireland)" region, with the ID +`ami-baf351cd` and name “Basic Alaveteli installation 2014-10-06”. +Alternatively, use this link: + +<p class="action-buttons"> + <a href="https://console.aws.amazon.com/ec2/home?region=eu-west-1#launchAmi=ami-baf351cd" class="button">launch + instance with Alaveteli installation AMI</a> +</p> + +When the instance launches, the first thing you need to choose is the instance +*type*. Remember that the *Micro* type does not have enough memory to run +Alaveteli, so you must choose at least *Small* or *Medium* -- note that these +are not available on Amazon's free usage tier. + +When the instance is created, the Amazon interface presents you with a lot of +choices about its configuration. You can generally accept the defaults for +everything, except the Security Groups. It's safe to click on **Review and +Launch** right away (rather than manually configuring all the instance details) +because you still get an opportunity to configure the security groups. Click on +**Edit Security Groups** on the summary page before you hit the big **Launch** +button. + +You must choose Security Groups that allow at least inbound HTTP, HTTPS, SSH +and, if you want to test incoming mail as well, SMTP. Amazon's settings here +let you specify the IP address(es) from which your instance will accept +requests. It's good practice to restrict these (if in doubt, choose a *Source* +of "My IP" for them all -- except incoming HTTP: for that, simpy to set +*Source* to "Anywhere"). You can change any of these settings later if you need +to. + +### Log into the server (shell) + +You need access to the server's command line shell to control and configure +your Alaveteli site. + +To access the server, use `ssh` and the `.pem` file from your SSL key pair. +Change the `.pem` file and instance ID to match your own in this command, which +connects to your server and logs you in as the user called `ubuntu`. Issue this +command from your own machine, to log in to the server: + + ssh -i path-to/your-key-pair.pem ubuntu@instance-id.eu-west-1.compute.amazonaws.com + +You won't be asked for a password, because the `.pem` file you supply with the +`-i` option contains the authorisation that matches the one at the other end, +on the server. You will be logged into the shell on your new Alaveteli server, +and can issue Unix commands to it. + +### Smoke test: start Alavateli + +You must configure your Alavateli site, but if you just want to see that you've +got your instance running OK, you *can* fire it up right away. Ideally, you +should skip this step and go straight to the configuration... but we know most +people like to see something in their browser first. ;-) + +On the command line shell, as the `ubuntu` user, start Alaveteli by doing: + + sudo service alaveteli start + +Find the "public DNS" URL of your EC2 instance from the AWS console, and look +at it in a browser. It will be of the form +`http://your-ec2-hostname.eu-west-1.compute.amazonaws.com`. You'll see your +Alaveteli site there. + +Your site isn't configured yet, so *this is insecure* (for example, you haven't +set your own passwords for access to the administration yet), so once you've +seen this running, bring the Alaveteli site down with: + + sudo service alaveteli stop + + +### Shell users: `ubuntu` and `alaveteli` + +When you log into your instance's command line shell, you must do so as the +`ubuntu` user. This user can `sudo` freely to run commands as root. However, +the code is actually owned by (and runs as) the `alaveteli` user. + +You will need to +[customise the site's configuration]({{ site.baseurl }}docs/customising/config/). +Do this by logging into your EC2 server and editing the `general.yml` +configuration file. + +The configuration file you need to edit is +`/var/www/alaveteli/alaveteli/config/general.yml`. For example, use the `nano` +editor (as the `alaveteli` user) like this: ubuntu@ip-10-58-191-98:~$ sudo su - alaveteli alaveteli@ip-10-58-191-98:~$ cd alaveteli alaveteli@ip-10-58-191-98:~/alaveteli$ nano config/general.yml -Then you should restart the Thin webserver with: +After making changes to that file, you'll need to start the application +server (use `restart` rather than `start` if it's already running): alaveteli@ip-10-58-191-98:~/alaveteli$ logout - ubuntu@ip-10-58-191-98:~$ sudo /etc/init.d/alaveteli restart + ubuntu@ip-10-58-191-98:~$ sudo service alaveteli start + +Your site will be running at the public URL again, which is of the form +`http://your-ec2-hostname.eu-west-1.compute.amazonaws.com`. -If you find the hostname of your EC2 instance from the AWS console, you should -then be able to see the site at -`http://your-ec2-hostname.eu-west-1.compute.amazonaws.com` +If you have any problems or questions, please ask on the [Alaveteli developer mailing list](https://groups.google.com/forum/#!forum/alaveteli-dev) or [report an issue](https://github.com/mysociety/alaveteli/issues?state=open). -If you have any problems or questions, please ask on the [Alaveteli Google -Group](https://groups.google.com/forum/#!forum/alaveteli-dev) or [report an -issue](https://github.com/mysociety/alaveteli/issues?state=open). ##What next? diff --git a/docs/installing/next_steps.md b/docs/installing/next_steps.md index 44660f5c8..4199daabf 100644 --- a/docs/installing/next_steps.md +++ b/docs/installing/next_steps.md @@ -8,32 +8,115 @@ title: Next Steps OK, you've installed a copy of Alaveteli, and can see the site in a browser. What next? </p> -## Load test data +## Create a superuser admin account + +Alaveteli ships with an +<a href="{{site.baseurl}}docs/glossary/#emergency" class="glossary__link">emergency user</a> +that has access to the admin. So when you've just created a new site, you +should sign up to create your own account, then log into admin as the emergency +user to promote your new account to be an administrator with *super* privileges. + +As soon as that's done, disable the emergency user, because you don't need to +use it any more: you've superseded it with your new admin account. + +Alaveteli ships with sample data that includes a dummy admin user called "Joe +Admin". If the sample data has been loaded into the database (this will depend on +how you installed), you must revoke Joe's administrator status too, because you +will be using your own admin account instead. + +### Step-by-step: + +First, in the browser: + +* Go to `/profile/sign_in` and create a user by signing up. +* Check your email and confirm your account. +* Go to `/admin?emergency=1`, log in with the username and password you specified in + [`ADMIN_USERNAME`]({{site.baseurl}}docs/customising/config/#admin_username) + and [`ADMIN_PASSWORD`]({{site.baseurl}}docs/customising/config/#admin_password). + You can find these settings in `config/general.yml`. +* You're now on the Alaveteli admin page. +* Click on **Users** (in the navigation menu across the top of the page), and + click on your name in the list of users. On *that* page, click **Edit**. +* Change your *Admin level* to "super" and click **Save**. +* From now on, when you are logged into your Alavateli site, you'll have access + to the admin (at `/admin`). Furthermore, you'll see links to admin pages off + the main site (which don't appear for regular users). + +If your installation has loaded the sample data, there will be a dummy user in +your database called "Joe Admin" who has admin status too. You should remove +this status so there's no risk of it being used to access your site admin. You +can either do this while you're still logged in as the emergency user... or +else, later, logged in as yourself: + +* Go to `/admin/users` or click on **Users** in the navigation menu on any + admin page. +* Find "Joe Admin" in the list of users, and click on the name to see the + user details. On *that* page, click **Edit**. +* Change the *Admin level* from "super" to "none" and click **Save**. +* Joe Admin no longer has admin status. + +Now that your account is a superuser admin, you don't need to allow the +emergency user access to the admin. On the command line shell, edit +`/var/www/alaveteli/alaveteli/config/general.yml`: + +* It's important that you change the emergency user's password (and, ideally, + the username too) from the values Alavateli ships with, because they are + public and hence insecure. In `general.yml`, change + [`ADMIN_PASSWORD`]({{site.baseurl}}docs/customising/config/#admin_password) + (and maybe [`ADMIN_USERNAME`]({{site.baseurl}}docs/customising/config/#admin_username) + too) to new, unique values. +* Additionally, you can totally disable the emergency user. Under normal + operation you don't need it, because from now on you'll be using the admin + user you've just created. + Set [`DISABLE_EMERGENCY_USER`]({{site.baseurl}}docs/customising/config/#disable_emergency_user) + to `true`. +* To apply these changes restart the service as a user with root privileges: + `sudo service alaveteli restart` + +You can use the same process (logged in as your admin account) to add or remove +superuser admin status to any users that are subsequently added to your site. +If you accidentally remove admin privilege from all accounts (try not to do +this, though!), you can enable the emergency user by editing the `general.yml` +file and restarting Alaveteli. + +## Load sample data If you want some dummy data to play with, you can try loading the fixtures that -the test suite uses into your development database. You can do this with: +the test suite uses into your development database. As the `alaveteli` user, do: script/load-sample-data -## Create a superuser account for yourself +If the sample data has already been loaded into the database, this command won't +do anything, but will instead <abbr +title='PG::Error: ERROR: permission denied: "RI_ConstraintTrigger_XXXXXX" is a system trigger'>fail +with an error</abbr>. -* Sign up for a new account on the site. You should receive a confirmation email. Click on the link in it to confirm the account. +If you have added the sample data, update the Xapian search index afterwards: -* Get access to the [admin interface]({{ site.baseurl}}docs/running/admin_manual/#administrator-privileges). You can find the -`general.yml` file you'll need to get the `ADMIN_USERNAME` and -`ADMIN_PASSWORD` credentials in the `config` subdirectory of the -directory Alaveteli was installed into. + script/update-xapian-index -* In the admin interface, go to the 'Users' section and find the account you just created. Promote the account you just created to superuser status by clicking the 'Edit' button and setting the 'Admin level' value to 'super'. +Remember that the sample data includes a user with admin access to your site. +You should revoke that status so it cannot be used to access your site -- +follow the steps described in the previous section. ## Test out the request process -* Create a new public authority in the admin interface - give it a name something like 'Test authority'. Set the request email to an address that you will receive. +* Create a new public authority in the admin interface -- give it a name like + "Test authority". Set the request email to an address that you will receive. * From the main interface of the site, make a request to the new authority. -* You should receive the request email - try replying to it. Your response email should appear in Alaveteli. Not working? Take a look at our [troubleshooting tips]({{ site.baseurl}}docs/installing/manual_install/#troubleshooting). If that doesn't sort it out, [get in touch]({{ site.baseurl}}community/) on the project mailing list or IRC -for help. +* You should receive the request email -- try replying to it. Your response + email should appear in Alaveteli. Not working? Take a look at our + [troubleshooting tips]({{ site.baseurl}}docs/installing/manual_install/#troubleshooting). + If that doesn't sort it out, [get in touch]({{ site.baseurl}}community/) on + the [developer mailing list](https://groups.google.com/forum/#!forum/alaveteli-dev) or [IRC](http://www.irc.mysociety.org/) for help. + +## Import Public Authorities + +Alaveteli can import a list of public authorities and their contact email addresses from a CSV file. + +You can find the uploader in under the "Authorities" tab of the admin section, or go straight to `/admin/body/import_csv`. ## Start thinking about customising Alaveteli diff --git a/docs/running/admin_manual.md b/docs/running/admin_manual.md index 567e6cf5e..d166cb859 100644 --- a/docs/running/admin_manual.md +++ b/docs/running/admin_manual.md @@ -13,6 +13,35 @@ title: Administrator's guide href="https://www.whatdotheyknow.com">whatdotheyknow.com</a>. </p> +In this guide: + +<ul class="toc"> + <li><a href="#whats-involved">What's involved?</a></li> + <li><a href="#user-support">User support</a> + <ul> + <li><a href="#dealing-with-email-thats-not-getting-through-to-the-authority">Dealing with email that's not getting through to the authority</a></li> + <li><a href="#requests-to-take-down-information">Requests to take down information</a></li> + <li><a href="#incorrectly-addressed">Incorrectly addressed</a></li> + <li><a href="#wants-advice">Wants advice</a></li> + <li><a href="#general-assistance-required">General assistance required</a></li> + <li><a href="#vexatious-users">Vexatious users</a></li> + <li><a href="#mail-import-errors">Mail import errors</a></li> + </ul> + <li><a href="#maintenance">Maintenance</a></li> + <ul> + <li><a href="#administrator-privileges-and-accessing-the-admin-interface">Administrator privileges and accessing the admin interface</a></li> + <li><a href="#removing-a-message-from-the-holding-pen">Removing a message from the 'Holding Pen'</a></li> + <li><a href="#editing-and-uploading-public-body-email-addresses">Editing and uploading public body email addresses</a></li> + <li><a href="#banning-a-user">Banning a user</a></li> + <li><a href="#deleting-a-request">Deleting a request</a></li> + <li><a href="#hiding-a-request">Hiding a request</a></li> + <li><a href="#hiding-an-incoming-or-outgoing-message">Hiding an incoming or outgoing message</a></li> + <li><a href="#editing-an-outgoing-message">Editing an outgoing message</a></li> + <li><a href="#hiding-certain-text-from-a-request-using-censor-rules">Hiding certain text from a request</a></li> + </ul> + </li> +</ul> + ## What's involved? The overhead in managing a successful FOI website is quite high. Richard, a @@ -24,7 +53,7 @@ WhatDoTheyKnow usually has about 3 active volunteers at any one time managing the support, plus a few other less active people who help out at different times. -Administration tasks can be split into **maintenance** and **user support**. +Administration tasks can be split into [**maintenance**]({{ site.baseurl }}docs/running/admin_manual/#maintenance) and [**user support**]({{ site.baseurl }}docs/running/admin_manual/#user-support). The boundaries of these tasks is in fact quite blurred; the main distinction is that the former happen exclusively through the web admin interface, whereas the latter are mediated by email directly with end users (but often result in @@ -49,7 +78,7 @@ During that week, the tasks broke down as follows: * 2 requests that have been marked as needing admin attention * 2 things marked as errors (message refused by server - spam, full mailbox, etc) to fix -### User interaction tasks +### User support tasks * 16 general, daily admin: i.e. things that resulted in admin actions on the site (bounces, misdelivered responses, etc) @@ -59,9 +88,9 @@ During that week, the tasks broke down as follows: * 3 requests to redact personal information * 2 requests to redact defamatory information -## Types of user interaction +## User support -There follows a breakdown of the most common kinds of user interaction. It's +There follows a breakdown of the most common kinds of user support. It's intended for use as a guide to the kind of policies and training that a support team might need to develop. @@ -253,13 +282,13 @@ Can be for many reasons, e.g. themselves * A reply has been automatically filed under the wrong request -## Vexatious users +### Vexatious users Some users persistently misuse the website. An alaveteli site should have a policy on banning users, for example giving them a first warning, informing them about moderation policy, etc. -## Mail import errors +### Mail import errors These are currently occurring at a rate of about two a month. Sometimes the root cause seems to be blocking in the database when two mails are received for @@ -274,9 +303,114 @@ the error sent to the site support address) in a file without the first "From" line, and piping the contents of that file into the mail handling script. e.g. ```cat missing_mail.txt | script/mailin``` -## Censor rules -Censor rules can be attached to a request or to a user and define bits of text +## Maintenance + +### Administrator privileges and accessing the admin interface + +The administrative interface is at the URL `/admin`. + +Only users with the `super` admin level can access the admin interface. Users +create their own accounts in the usual way, and then administrators can give +them `super` privileges. + +There is an emergency user account which can be accessed via +`/admin?emergency=1`, using the credentials `ADMIN_USERNAME` and +`ADMIN_PASSWORD`, which are set in `general.yml`. To bootstrap the +first `super` level accounts, you will need to log in as the emergency +user. You can disable the emergency user account by setting `DISABLE_EMERGENCY_USER` to `true` in `general.yml`. + +Users with the superuser role also have extra privileges in the website +front end, such as being able to categorise any request, being able to view +items that have been hidden from the search, and being presented with "admin" +links next to individual requests and comments in the front end. + +It is possible completely to override the administrator authentication by +setting `SKIP_ADMIN_AUTH` to `true` in `general.yml`. + +### Removing a message from the 'Holding Pen' + +The reason a message is in the holding pen is because the email can't be automatically associated with the request it is responding to. The email needs to be moved from the holding pen to the request it belongs with. + +First, log into the admin interface at `/admin`. You will see messages that are in the 'holding pen' under the title ‘Put misdelivered responses with the right request’. Click on the chevron to see the individual messages. + +If you click on a message in the holding pen, you may see a guess made by Alaveteli as to which request the message belongs to. Check this request. If it isn't the right one, or if Alaveteli hasn't made any guesses, you will need to look at the `To:` address of the raw email and the contents of the mail in order to figure out which request it belongs to. You can browse and search requests in the admin interface under the 'Requests' menu item. + +Once you have identified the request the message belongs to, you need to go back to the holding pen message page. Paste the request `id` or `url_title` into the box under 'Actions' in 'Incoming Message'. The request `id` can be found in the request URL in the admin interface - it is the part after `/show/`. In the admin request URL `/admin/request/show/118`, the request `id` is `118`. The `url_title` can be found in the request URL in the main interface - it is the part after `/request/`. In the URL `/request/documents_relating_to_meeting`, it is `documents_relating_to_meeting`. Then click on 'Redeliver to another request'. + +The message will now be associated with the correct request and will appear on the public request page. + +### Editing and uploading public body email addresses + + + +### Banning a user + +You may wish to completely ban a user from the website (such as a spammer or troll for example). You need to log into the admin interface at `/admin`. On the top row of links, locate and click on ‘Users’. + +Find the user you wish to ban on the list and click on their name. Once on the user page, select ‘edit’. + +Enter some text in the in the ‘Ban text’ box to explain why they have been banned. Please be aware, this is publicly viewable from the users' account. Then click on save and the user will be banned. + +### Deleting a request + +You can delete a request entirely using the admin interface. You will mainly only need to do this if someone has posted private information. Go to the admin page for the request by searching or browsing in the 'Requests' section of the admin interface. In the first section, click the 'Edit metadata' button. At the bottom of the next page, click the red 'Destroy request entirely' button. + +### Hiding a request + +You can hide an entire request from the admin interface. Log in to the +admin interface at `/admin`. On the top row of links, locate and click on +'Requests'. Search or browse to find the admin page for the request you +want to hide. You can also go directly to this page by following an +'admin' link from the public request page. You can hide a request in one +of two ways. + + * <strong>Hiding a vexatious or non-FOI request and notifying the + requester</strong> + Scroll down to the 'actions' section of the request + admin page. Select one of the options next to 'Hide the request and + notify the user:' and customise the text of the email that will be + sent to the user to let them know what you've done. When you're + ready, click the 'Hide request' button. + * <strong>Hiding a request or making it only visible to the + requester without notifying the requester</strong> + In the 'Request metadata' section of the request + admin page, click 'Edit metadata'. Change the 'Prominence' value to + 'requester_only' to only allow the requester to view the request, or + to 'hidden' to hide the request from everyone except site admins. + When you're ready, click 'Save changes' at the bottom of the 'Edit + metadata' section. No email will be sent to the requester to notify + them of what you've done. + +### Hiding an incoming or outgoing message + +You may need to hide a particular incoming or outgoing message from a +public request page, perhaps because someone has included personal +information in it. You can do this from the message's page in the admin +interface. You can get to a message's admin page either by following the +links from the "Outgoing messages" or "Incoming messages" sections of +the request's admin page, or directly from the public request page by +clicking on the 'admin' link on the message itself. Once you are on the +message's admin page, you can change it's prominence. Set the prominence +to 'hidden' to hide it from everyone except site admins, or to +'requester_only' to allow it to be viewed by the requester (and by site +admins). If you can, add some text in the box 'Reason for prominence'. +This will be displayed as part of the information that will appear on +the request page where the message used to be, telling people that it +has been hidden. + +### Editing an outgoing message + +You may find there is a need to edit an outgoing message because the requester has accidentally included personal information that they don't want to be published on the site. You can either follow one of the 'admin' links from the public request page on the site, or find the request from the admin interface by searching under 'Requests'. + +Scroll down to the 'Outgoing Messages' section, and click on 'Edit'. + +Then on the next page you will be able to edit the message accordingly and save it. The edited version will then appear on the Alaveteli website, although an unedited version will have been sent to the authority. + + +### Hiding certain text from a request using censor rules + +Censor rules can be attached to a request or to a user. These rules define bits of text to be removed (either from the request (and all associated files e.g. incoming message attachments) or from all requests associated with a user), and some replacement text. In binary files, the replacement text will always be a series @@ -301,24 +435,23 @@ hanging the application altogether), so please: * Restrict your use of them to cases that can't otherwise be easily covered. * Keep them as simple and specific as possible. -## Administrator privileges +<strong>To attach a censor rule to a request</strong>, go to the admin page for the +request, scroll to the bottom of the page, and click the "New censor +rule (for this request only)" button. On the following page, enter the +text that you want to replace e.g. 'some private info', the text you +wish to replace it with e.g. '[private info has been hidden]', and a +comment letting other admins know why you have hidden the information. + +<strong>To attach a censor rule to a user</strong>, so that it will be applied to all +requests that the user has made, go to the user page in the admin +interface. You can do this either by clicking on the admin heading +'Users' and browsing or searching to find the user you want, or by +following an 'admin' link for the user from the public interface. One +you are on the admin page for the user, scroll to the bottom of the page +and click the 'New censor rule' button. On the following page, enter the +text that you want to replace e.g. 'my real name is Bruce Wayne', the +text you wish to replace it with e.g. '[personal information has been +hidden]', and a comment letting other admins know why you have hidden +the information. -The administrative interface is at the URL `/admin`. -Only users with the `super` admin level can access the admin interface. Users -create their own accounts in the usual way, and then administrators can give -them `super` privileges. - -There is an emergency user account which can be accessed via -`/admin?emergency=1`, using the credentials `ADMIN_USERNAME` and -`ADMIN_PASSWORD`, which are set in `general.yml`. To bootstrap the -first `super` level accounts, you will need to log in as the emergency -user. You can disable the emergency user account by setting `DISABLE_EMERGENCY_USER` to `true` in `general.yml`. - -Users with the superuser role also have extra privileges in the website -front end, such as being able to categorise any request, being able to view -items that have been hidden from the search, and being presented with "admin" -links next to individual requests and comments in the front end. - -It is possible completely to override the administrator authentication by -setting `SKIP_ADMIN_AUTH` to `true` in `general.yml`. diff --git a/docs/running/index.md b/docs/running/index.md index 7257417ce..bbf30d3b9 100644 --- a/docs/running/index.md +++ b/docs/running/index.md @@ -16,10 +16,13 @@ site, you need to make sure day-to-day tasks get done too. Most Alaveteli sites are run by a team who allocate some time every day to user support and generally keeping the project up to date. -* the [administrator's guide]({{ site.baseurl }}docs/running/admin_manual/) describes +* The [administrator's guide]({{ site.baseurl }}docs/running/admin_manual/) describes what you need to do and know to run your site -* we've prepared a checklist of +* The [redaction guide]({{ site.baseurl }}docs/running/redaction/) includes some + examples of Alaveteli's ability to redact sensitive information + +* We've prepared a checklist of [things to consider]({{ site.baseurl }}docs/running/server/) when setting up your production server diff --git a/docs/running/redaction.md b/docs/running/redaction.md new file mode 100644 index 000000000..6ab8fed86 --- /dev/null +++ b/docs/running/redaction.md @@ -0,0 +1,135 @@ +--- +layout: page +title: Redacting Sensitive Information +--- + +# Redacting Sensitive Information + +In some countries, local requirements mean that requests need to contain personal information such as the address or ID number of the person asking for information. Usually requesters do not want this information to be displayed to the general public. + +Alaveteli has some ability to deal with this through the use of <a href="{{site.baseurl}}docs/glossary/#censor-rule" class="glossary__link">Censor Rules</a>. + +The [theme](https://github.com/mysociety/derechoapreguntar-theme) we'll use as an example requires a National Identity Card Number and what's known as General Law in Nicaragua (Date of Birth, Domicile, Occupation and Marital Status). + + + +## Identity Card Number + +We'll start off by looking at the National Identity Card Number (ID Number from here). Its a good example of something that is relatively easy to redact. It's unique for each user, and it has a specified format to match against. + +To send the ID Number to the authority we'll override the [initial request template](https://github.com/mysociety/alaveteli/blob/master/app/views/outgoing_mailer/initial_request.text.erb) (code snippet shortened): + + <%= raw @outgoing_message.body.strip %> + + ------------------------------------------------------------------- + + <%= _('Requestor details') %> + <%= _('Identity Card Number') %>: <%= @user_identity_card_number %> + +When a request is made the user's ID Number is now added to the footer of the outgoing email. + + + +At this point we haven't added any <a href="{{site.baseurl}}docs/glossary/#censor-rule" class="glossary__link">Censor Rules</a>. When the authority replies it is unlikely that the responder will remove the quoted section of the email: + + + +We could add a <a href="{{site.baseurl}}docs/glossary/#censor-rule" class="glossary__link">Censor Rule</a> for the individual request, but as every request will contain a user's ID Number its better to add some code to do do it automatically. + +To illustrate this we'll patch the `User` model with a callback that creates a <a href="{{site.baseurl}}docs/glossary/#censor-rule" class="glossary__link">Censor Rule</a> when the user is created and updated. + + # THEME_ROOT/lib/model_patches.rb + User.class_eval do + after_save :update_censor_rules + + private + + def update_censor_rules + censor_rules.where(:text => identity_card_number).first_or_create( + :text => identity_card_number, + :replacement => _('REDACTED'), + :last_edit_editor => THEME_NAME, + :last_edit_comment => _('Updated automatically after_save') + ) + end + end + +You can see the new <a href="{{site.baseurl}}docs/glossary/#censor-rule" class="glossary__link">Censor Rule</a> in the admin interface: + + + +Now the ID Number gets redacted: + + + +It also gets redacted if the public body use the ID Number in the main email body: + + + +A <a href="{{site.baseurl}}docs/glossary/#censor-rule" class="glossary__link">censor rule</a> added to a user only gets applied to correspondence on requests created by that user. It does not get applied to annotations made by the user. + +**Warning:** Redaction in this way requires the sensitive text to be in exactly the same format as the <a href="{{site.baseurl}}docs/glossary/#censor-rule" class="glossary__link">Censor Rule</a>. If it differs even slightly, the redaction can fail. If the public body was to remove the hyphens from the number it would not be redacted: + + + +**Warning:** Alaveteli also attempts to redact the text from any attachments. It can only do this if it can find the exact string, which is often not possible in binary formats such as PDF or Word. + +Alaveteli can usually redact the sensitive information when converting a PDF or text based attachment to HTML: + + + +This PDF does not contain the string in the raw binary so the redaction is _not_ applied when downloading the original PDF document: + + + +## General Law + +The General Law information is much harder to automatically redact. It is not as structured, and the information is unlikely to be unique (e.g. Domicile: London). + +We'll add the General Law information to the [initial request template](https://github.com/mysociety/alaveteli/blob/master/app/views/outgoing_mailer/initial_request.text.erb) in the same way as the ID Number: + + <%= _('Requestor details') %>: + <%-# !!!IF YOU CHANGE THE FORMAT OF THE BLOCK BELOW, ADD A NEW CENSOR RULE!!! -%> + =================================================================== + # <%= _('Name') %>: <%= @user_name %> + # <%= _('Identity Card Number') %>: <%= @user_identity_card_number %> + <% @user_general_law_attributes.each do |key, value| %> + # <%= _(key.humanize) %>: <%= value %> + <% end %> + =================================================================== + +Note that the information is now contained in a specially formatted block of text. + + + +This allows a <a href="{{site.baseurl}}docs/glossary/#censor-rule" class="glossary__link">Censor Rule</a> to match the special formatting and remove anything contained within. This <a href="{{site.baseurl}}docs/glossary/#censor-rule" class="glossary__link">Censor Rule</a> is global, so it will act on matches in all requests. + + # THEME_ROOT/lib/censor_rules.rb + # If not already created, make a CensorRule that hides personal information + regexp = '={67}\s*\n(?:[^\n]*?#[^\n]*?: ?[^\n]*\n){3,10}[^\n]*={67}' + + unless CensorRule.find_by_text(regexp) + Rails.logger.info("Creating new censor rule: /#{regexp}/") + CensorRule.create!(:text => regexp, + :allow_global => true, + :replacement => _('REDACTED'), + :regexp => true, + :last_edit_editor => THEME_NAME, + :last_edit_comment => 'Added automatically') + end + + + +**Warning:** Redacting unstructured information is a very fragile approach, as it relies on authorities always quoting the entire formatted block. + +In this case the authority has revealed the user's Date of Birth and Domicile: + + + +Its really difficult to add a <a href="{{site.baseurl}}docs/glossary/#censor-rule" class="glossary__link">Censor Rule</a> to remove this type of information. One suggestion might be to remove all mentions of the user's Date of Birth, but you would have to account for [every type of date format](http://en.wikipedia.org/wiki/Calendar_date#Date_format). Likewise, you could redact all occurrences of the user's Domicile, but if they a question about their local area (very likely) the request would become unintelligible. + + + +The redaction has been applied but there is no way of knowing the context that the use of the sensitive word is used. + + diff --git a/docs/running/security.md b/docs/running/security.md new file mode 100644 index 000000000..a22c4d636 --- /dev/null +++ b/docs/running/security.md @@ -0,0 +1,36 @@ +--- +layout: page +title: Security & Maintenance +--- + +# Security & Maintenance + +<p class="lead"> + Support of Alaveteli is divided into four groups: New features, bug fixes, security issues, and severe security issues. They are handled as follows: +</p> + +## New Features + +Only the [latest development branch](https://github.com/mysociety/alaveteli/tree/rails-3-develop/) gets new features which will be released in the next main release. + +## Bug Fixes + +- Only the current release will receive bug fixes +- Bug fixes will get a new release (e.g. `0.19.0` gets a new release to `0.19.1`) +- Bug fixes will be applied to current development branch + +## Security Issues + +- The current release, previous release and current development branch will receive fixes +- Security issues will get a new release (e.g. `0.19.0` gets a new release to `0.19.1`) for the current and previous releases +- Generic patch will be posted to the mailing list + +## Severe Security Issues + +- Severe is determined by the Alaveteli core team +- The current release, previous release and current development branch will receive fixes +- Severe security issues will get a new release (e.g. `0.19.0` gets a new release to `0.19.1`) for supported versions +- Generic patch will be posted to the mailing list +- All releases known to be in production will receive patches and every effort will be made to contact known re-users for a private disclosure + + diff --git a/docs/running/upgrading.md b/docs/running/upgrading.md index ab8db2385..533035892 100644 --- a/docs/running/upgrading.md +++ b/docs/running/upgrading.md @@ -12,6 +12,41 @@ Upgrading Alaveteli This page describes how to keep your site up-to-date </p> +## How to upgrade the code + +* If you're using Capistrano for deployment, + simply [deploy the code]({{site.baseurl}}docs/installing/deploy/#usage): + set the repo and branch in `deploy.yml` to be the version you want. + We recommend you set this to the explicit tag name (for example, + `0.18`, and not `master`) so there's no risk of you accidentally deploying + a new version before you're aware it's been released. +* otherwise, you can simply upgrade by running `git pull` + +## Run the post-deploy script + +Unless you're [using Capistrano for deployment]({{site.baseurl}}docs/installing/deploy/), +you should always run the script `scripts/rails-post-deploy` after each +deployment. This runs any database migrations for you, plus various other +things that can be automated for deployment. + +## Alaveteli Version Numbers + +Alaveteli uses a shifted version of [semver](http://semver.org). + +- Series `W` +- Major `X` +- Minor `Y` +- Patch `Z` + +At the time of writing the current release is `0.19.0.6`: + +- Series `0` +- Major `19` +- Minor `0` +- Patch `6` + +Alaveteli will transition to the [semver](http://semver.org) specification when it reaches `1.0.0`. + ## Master branch contains the latest stable release The developer team policy is that the `master` branch in git should always @@ -26,30 +61,16 @@ Upgrading may just require pulling in the latest code -- but it may also require other changes ("further action"). For this reason, for anything other than a *patch* (see below), always read the [`CHANGES.md`](https://github.com/mysociety/alaveteli/blob/master/doc/CHANGES.md) -document **before** doing an uprade. This way you'll be able to prepare for any +document **before** doing an upgrade. This way you'll be able to prepare for any other changes that might be needed to make the new code work. -## How to upgrade the code - -* If you're using Capistrano for deployment, - simply [deploy the code]({{site.baseurl}}docs/installing/deploy/#usage): - set the repo and branch in `deploy.yml` to be the version you want. - We recommend you set this to the explicit tag name (for example, - `0.18`, and not `master`) so there's no risk of you accidentally deploying - a new version before you're aware it's been released. -* otherwise, you can simply upgrade by running `git pull` - ## Patches -Patch version increases (e.g. 1.2.3 → 1.2.**4**) should not require any further -action on your part. +Patch version increases (e.g. 0.1.2.3 → 0.1.2.**4**) should not require any further action on your part. They will be backwards compatible with the current minor release version. ## Minor version increases -Minor version increases (e.g. 1.2.4 → 1.**3**.0) will usually require further -action. You should read the [`CHANGES.md`](https://github.com/mysociety/alaveteli/blob/master/doc/CHANGES.md) -document to see what's changed since your last deployment, paying special attention -to anything in the "Upgrade notes" sections. +Minor version increases (e.g. 0.1.2.4 → 0.1.**3**.0) will usually require further action. You should read the [`CHANGES.md`](https://github.com/mysociety/alaveteli/blob/master/doc/CHANGES.md) document to see what's changed since your last deployment, paying special attention to anything in the "Upgrade notes" sections. Any upgrade may include new translations strings, that is, new or altered messages to the user that need translating to your locale. You should visit Transifex @@ -59,10 +80,32 @@ your website in English by default. If your translations didn't make it to the latest release, you will need to download the updated `app.po` for your locale from Transifex and save it in the `locale/` folder. -## Run the post-deploy script +Minor releases will be backwards compatible with the current major release version. -Unless you're [using Capistrano for deployment]({{site.baseurl}}docs/installing/deploy/), -you should always run the script `scripts/rails-post-deploy` after each -deployment. This runs any database migrations for you, plus various other -things that can be automated for deployment. +## Major releases + +Major version increases (e.g. 0.1.2.4 → 0.2.0.0) will usually require further action. You should read the [`CHANGES.md`](https://github.com/mysociety/alaveteli/blob/master/doc/CHANGES.md) document to see what's changed since your last deployment, paying special attention to anything in the "Upgrade notes" sections. + +Only major releases may remove existing functionality. You will be warned about the removal of functionality with a deprecation notice in a minor release prior to the major release that removes the functionality. + +## Series releases + +Special instructions will accompany series releases. + +## Deprecation Notices + +You may start to see deprecation notices in your application log. They will look like: + + DEPRECATION WARNING: Object#id will be deprecated; use Object#object_id + +Deprecation notices allow us to communicate with you that some functionality will change or be removed in a later release of Alaveteli. + +### What to do if you see a deprecation notice + +You will usually see a deprecation notice if you have been using functionality in your theme that is now due to change or be removed. The notice should give you a fair explanation of what to do about it. Usually it will be changing or removing methods. The [changelog](https://github.com/mysociety/alaveteli/blob/rails-3-develop/doc/CHANGES.md) will include more detailed information about the deprecation and how to make the necessary changes. + +If you're ever unsure, don't hesitate to ask in the [developer mailing list](https://groups.google.com/group/alaveteli-dev) or [Alaveteli IRC channel](http://www.irc.mysociety.org/). + +### When will the change take place? +We introduce deprecation notices in a **minor** release. The following **major** release will make the change unless otherwise stated in the deprecation notice. diff --git a/script/translation-export b/script/translation-export new file mode 100755 index 000000000..5dba7455e --- /dev/null +++ b/script/translation-export @@ -0,0 +1,19 @@ +#!/bin/bash + +set -e + +TOP_DIR="$(dirname "$0")/.." +cd "$TOP_DIR" + +REPO="$(basename "$PWD")" +SHA="$(git rev-parse --short HEAD)" +TIMESTAMP=$(date '+%FT%T') +FILENAME="${REPO}_${SHA}_${TIMESTAMP}.tar" + +tar -cvf "$FILENAME" _includes/*.html +tar -uvf "$FILENAME" _layouts/*.html +tar -uvf "$FILENAME" $(ls -1 community/*.md) +tar -uvf "$FILENAME" $(find docs -type f -name "*.md") +tar -uvf "$FILENAME" --exclude README.md $(ls -1 *.md) + +gzip "$FILENAME" |