diff --git a/public/2015/04/24/setting-up-a-mail-server/index.html b/public/2015/04/24/setting-up-a-mail-server/index.html new file mode 100644 index 0000000..9fa266f --- /dev/null +++ b/public/2015/04/24/setting-up-a-mail-server/index.html @@ -0,0 +1,1435 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Setting up a mail server · Sysadmining. All day. Every day. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ +
+ + + +
+ + + + +
+ +
+

Setting up a mail server

+ + + +
+ +
+ + +

In this first tutorial, I’ll explain how I’ve configured my mail server using the following :

+ +
    +
  • A server running Linux Debian (jessie) ;
  • +
  • Postfix ;
  • +
  • Postfix-policyd-spf-python ;
  • +
  • Dovecot ;
  • +
  • Spamassassin ;
  • +
  • OpenDKIM ;
  • +
  • OpenDMARC ;
  • +
  • Monit ;
  • +
  • Rainloop.
  • +
+ +

I’m assuming you have some basic knowledge of Linux and DNS configuration.

+ +

You can host this server at home, but you might have issues with your ISP not allowing outbound traffic on TCP port 25, and your emails might be considered to be spam by other providers if your IP is dynamic and/or you can’t configure a reverse DNS record on it.

+ +

The cheapest VMs from DigitalOcean or Vultr are powerful enough to have this configuration running smoothly.

+ +

We’ll also need a SSL certificate for this configuration. You can create an auto-signed one or get a free valid one from StartSSL. For the purpose of this tutorial, I’ll consider you’ve chosen the latter.

+ +

You’ll also need a domain name. I’ve chosen Namecheap as a registrar. I won’t go into details on how to configure it, but you’ll need at the very least a A record on your server’s IP as well as a MX record pointing to it.

+ +

I use the captainark.net domain as an example throughout this tutorial. You’ll have to use your actual domain for your configuration to work !

+ +

Note: links in this section are sponsored.

+ +

Initial configuration

+ +

Installing the required packages

+ +

First thing first, we need to install the packages we’ll need for this configuration :

+ +
apt update
+
+apt install mysql-server mysql-client postfix postfix-mysql \
+postfix-policyd-spf-python dovecot-core dovecot-imapd dovecot-lmtpd \
+dovecot-mysql dovecot-sieve dovecot-managesieved dovecot-antispam \
+opendkim opendkim-tools monit opendmarc spamassassin spamc
+
+ +

During its installation, Postfix will prompt you with configuration questions. Choose “Internet Site”, and when asked about your System mail name, provide it with your server’s FQDN (it should be the output of the hostname -f command on your server).

+ +

You’ll also have to set-up a password for the MySQL root user.

+ +

Additional configuration

+ +

The PTR records on your server’s IPv4 and/or IPv6 should match your server’s FQDN (a dig -x on your server’s IP should match a hostname -f on your server).

+ +

You’ll have to open the following TCP ports on your server for this configuration to work : 25, 465, 587 and 993.

+ +

If you don’t want to have to remember the root user MySQL password, you can create a .my.cnf file in your current user home directory containing the following lines :

+ +
[client]
+host     = localhost
+user     = root
+password = myverysecurepassword
+socket   = /var/run/mysqld/mysqld.sock
+
+ +

Once it has been created, change the permissions on the file to make sure no other user can read it :

+ +
chmod 600 ~/.my.cnf
+
+ +

I also like to change the default MySQL shell to see what database I’m using at any given time. Since I use bash, I achieve this the following way :

+ +
echo 'export MYSQL_PS1="[\u@\h] (\d)> "' > ~/.bash_aliases
+
+ +

You’ll have to logout from the current shell for the modification to be taken into account (if you’re using SSH, log out and back into your server).

+ +

You should now be able to log into MySQL without specifying a password, and it should look like this :

+ +
:~$ mysql mysql
+[...]
+[root@localhost] (mysql)>
+
+ +

Configuring the MySQL database

+ +

Initial configuration

+ +

We now need to configure the MySQL database Postfix and Dovecot will be using. In this tutorial, we’ll be calling it “mail”, but you can name it whatever you want.

+ +

First, in a mysql shell, let’s create the MySQL database :

+ +
CREATE DATABASE mail;
+
+ +

Now, we are going to create the user that Postfix and Dovecot will be using to access the database. We will only be granting this user select permission :

+ +
GRANT SELECT ON mail.* TO 'mail'@'localhost' IDENTIFIED BY 'mailpassword';
+FLUSH PRIVILEGES;
+
+ +

We are now going to create the necessary tables for our needs. Let’s first use the mail database :

+ +
USE mail;
+
+ +

The first table we are going to create will contain the domains we will be using with our mail server :

+ +
CREATE TABLE `virtual_domains` (
+`id`  INT NOT NULL AUTO_INCREMENT,
+`name` VARCHAR(50) NOT NULL,
+PRIMARY KEY (`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+ +

Then, we are going to create the table that will contain our users and their password :

+ +
CREATE TABLE `virtual_users` (
+`id` INT NOT NULL AUTO_INCREMENT,
+`domain_id` INT NOT NULL,
+`password` VARCHAR(106) NOT NULL,
+`email` VARCHAR(120) NOT NULL,
+PRIMARY KEY (`id`),
+UNIQUE KEY `email` (`email`),
+FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+ +

Finally, the last table we are going to create will contain our mail aliases :

+ +
CREATE TABLE `virtual_aliases` (
+`id` INT NOT NULL AUTO_INCREMENT,
+`domain_id` INT NOT NULL,
+`source` varchar(100) NOT NULL,
+`destination` varchar(100) NOT NULL,
+PRIMARY KEY (`id`),
+FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+ +

Domains, users and aliases management

+ +

We are now going to add data to the tables we have created.

+ +

First, let’s add a domain to the virtual_domains table :

+ +
INSERT INTO virtual_domains (`name`) VALUES ('captainark.net');
+
+ +

We can now create users associated with this domain in the virtual_users table :

+ +
INSERT INTO virtual_users (`domain_id`, `password` , `email`) VALUES
+('1', ENCRYPT('notanactualpassword', CONCAT('$6$', SUBSTRING(SHA(RAND()), -16))),
+'example@captainark.net');
+
+ +

This is not mandatory, but we can also create our first mail alias :

+ +
INSERT INTO virtual_aliases (`domain_id`, `source`, `destination`) VALUES
+('1', 'alias@captainark.net', 'example@captainark.net');
+
+ +

Now, all messages sent to alias@captainark.net will be forwarded to example@captainark.net.

+ +

Use the same syntax to create additional domains, users and aliases. If you have more than one domains configured, be sure to associate your users and aliases with the correct domain_id.

+ +

Configuring Postfix

+ +

Next, we are going to configure Postfix.

+ +

Configuration backup

+ +

First, let’s backup the original configuration files :

+ +
cp /etc/postfix/main.cf /etc/postfix/main.cf.orig
+cp /etc/postfix/master.cf /etc/postfix/master.cf.orig
+
+ +

User and group creation

+ +

We are now going to create a user and group called vmail that will be used by both Postfix and Dovecot :

+ +
groupadd -g 5000 vmail
+useradd -g vmail -u 5000 vmail -d /var/mail -m -s /bin/false
+
+ +

SSL certificates

+ +

Next, we are going to create the folder where we will store the SSL certificates :

+ +
mkdir /etc/postfix/ssl
+chown root: /etc/postfix/ssl && chmod 600 /etc/postfix/ssl
+
+ +

Purists will probably want to store their certificates in /etc/ssl/private. If you choose to do so, you’ll have to adapt the path of those files for the remainder of this tutorial.

+ +

If you’ve decided to create a certificate with StartSSL, you’ll end up with two files, a .crt and a .key. I’ll name those files server.crt and server-with-passphrase.key. Put both these files in the folder we’ve just created.

+ +

Now, let’s remove the passphrase from the key :

+ +
cd /etc/postfix/ssl
+openssl rsa -in server-with-passphrase.key -out server.key
+
+ +

You’ll be prompted for the passphrase you chose during the certificate generation.

+ +

Next, we have to download the appropriate intermediate certificate :

+ +
wget -O /etc/postfix/ssl/sub.class1.server.ca.pem \
+http://www.startssl.com/certs/sub.class1.server.ca.pem
+
+ +

We now have to make sure that the permissions on those files are correct :

+ +
chown root: /etc/postfix/ssl/* && chmod 600 /etc/postfix/ssl/*
+
+ +

The last thing we have to do here is to generate Diffie-Hellman keys for Perfect Forward Secrecy (PFS) :

+ +
openssl gendh -out /etc/postfix/dh_512.pem -2 512
+openssl gendh -out /etc/postfix/dh_1024.pem -2 1024
+
+ +

Postifx configuration

+ +

First, let’s edit the /etc/postfix/main.cf file. It should end up looking something like that :

+ +
smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU)
+biff = no
+
+broken_sasl_auth_clients = yes
+config_directory = /etc/postfix
+disable_vrfy_command = yes
+smtpd_data_restrictions = reject_unauth_pipelining, permit
+smtpd_helo_required = yes
+
+queue_directory = /var/spool/postfix
+append_dot_mydomain = no
+readme_directory = no
+
+smtpd_use_tls=yes
+smtpd_tls_auth_only = yes
+smtpd_tls_cert_file=/etc/postfix/ssl/server.crt
+smtpd_tls_key_file=/etc/postfix/ssl/server.key
+smtpd_tls_CAfile=/etc/postfix/ssl/sub.class1.server.ca.pem
+smtpd_tls_mandatory_protocols=!SSLv2,!SSLv3
+smtpd_tls_protocols=!SSLv2,!SSLv3
+smtpd_tls_mandatory_ciphers=high
+smtpd_tls_dh1024_param_file = /etc/postfix/dh_1024.pem
+smtpd_tls_dh512_param_file = /etc/postfix/dh_512.pem
+smtpd_tls_eecdh_grade = strong
+smtpd_tls_loglevel = 1
+smtpd_tls_received_header = yes
+smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
+
+tls_preempt_cipherlist = yes
+tls_random_source = dev:/dev/urandom
+
+smtpd_data_restrictions = reject_unauth_pipelining, permit
+smtpd_helo_required = yes
+
+smtp_tls_CAfile = $smtpd_tls_CAfile
+smtp_tls_mandatory_protocols=!SSLv2,!SSLv3
+smtp_tls_protocols=!SSLv2,!SSLv3
+smtp_tls_security_level = may
+smtp_tls_loglevel = 1
+smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
+
+smtpd_milters =
+non_smtpd_milters = $smtpd_milters
+milter_protocol = 2
+milter_default_action = accept
+
+smtpd_recipient_restrictions =
+  reject_invalid_hostname,
+  reject_non_fqdn_hostname,
+  reject_non_fqdn_sender,
+  reject_non_fqdn_recipient,
+  reject_unknown_sender_domain,
+  reject_unknown_recipient_domain,
+  permit_mynetworks,
+  permit_sasl_authenticated,
+  reject_unauth_destination,
+  permit
+
+smtpd_sasl_auth_enable = yes
+smtpd_sasl_local_domain = $myhostname
+smtpd_sasl_security_options = noanonymous
+smtpd_sasl_tls_security_options = $smtpd_sasl_security_options
+smtpd_tls_auth_only = yes
+smtpd_sasl_type = dovecot
+smtpd_sasl_path = private/auth
+
+myhostname = myserver.captainark.net ### CHANGE THIS
+alias_maps = hash:/etc/aliases
+alias_database = hash:/etc/aliases
+myorigin = /etc/mailname
+mydestination = localhost, myserver.captainark.net ### CHANGE THIS
+relayhost =
+mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
+mailbox_size_limit = 0
+recipient_delimiter = +
+default_transport = smtp
+relay_transport = smtp
+inet_interfaces = all
+inet_protocols = all
+
+virtual_transport = lmtp:unix:private/dovecot-lmtp
+virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
+virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
+virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-alias-maps.cf
+
+ +

The variable “myhostname” has to be defined to you server’s FQDN. The file /etc/mailname should contain your server’s FQDN as well.

+ +

Next, we need to edit the /etc/postfix/master.cf file. You need to uncomment the following lines :

+ +
submission inet n       -       -       -       -       smtpd
+  -o syslog_name=postfix/submission
+  -o smtpd_tls_security_level=encrypt
+  -o tls_preempt_cipherlist=yes
+  -o smtpd_sasl_auth_enable=yes
+  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
+
+smtps     inet  n       -       -       -       -       smtpd
+  -o syslog_name=postfix/smtps
+  -o smtpd_tls_wrappermode=yes
+  -o smtpd_sasl_auth_enable=yes
+  -o smtpd_client_restrictions=permit_sasl_authenticated,reject
+
+ +

You also have to add the following lines at the end of the file :

+ +
dovecot    unix  -       n       n       -       -       pipe
+  flags=DRhu user=vmail:vmail argv=/usr/lib/dovecot/deliver -f ${sender} -d ${recipient}
+
+ +

MySQL access for Postfix

+ +

We now need to allow Postfix to connect to the MySQL database we have created earlier. To that end, we must create three files.

+ +

/etc/postfix/mysql-virtual-mailbox-domains.cf should contain the following lines :

+ +
user = mail
+password = mailpassword
+hosts = 127.0.0.1
+dbname = mail
+query = SELECT 1 FROM virtual_domains WHERE name='%s'
+
+ +

/etc/postfix/mysql-virtual-mailbox-maps.cf should contain the following lines :

+ +
user = mail
+password = mailpassword
+hosts = 127.0.0.1
+dbname = mail
+query = SELECT 1 FROM virtual_users WHERE email='%s'
+
+ +

/etc/postfix/mysql-virtual-alias-maps.cf should contain the following lines :

+ +
user = mail
+password = mailpassword
+hosts = 127.0.0.1
+dbname = mail
+query = SELECT destination FROM virtual_aliases WHERE source='%s'
+
+ +

Since these files contain a password, let’s make sure they are not world-readable :

+ +
chown root: /etc/postfix/mysql* && chmod 600 /etc/postfix/mysql*
+
+ +

You can use the command postmap to confirm that everything is working properly :

+ +
postmap -q captainark.net mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
+
+postmap -q example@captainark.net mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
+
+postmap -q alias@captainark.net mysql:/etc/postfix/mysql-virtual-alias-maps.cf
+
+ +

Let’s restart postfix for our modifications to be taken into account :

+ +
systemctl restart postfix
+
+ +

That’s it for Postfix, for now ; Dovecot is next !

+ +

Configuring Dovecot

+ +

Dovecot global configuration

+ +

By default, on Debian, Dovecot uses multiple configuration files in /etc/dovecot/conf.d. I found it annoying to maintain, and I ended up only using the /etc/doveconf.conf file.

+ +

As always, let’s start by backing up the original configuration file :

+ +
mv /etc/dovecot/dovecot.conf /etc/dovecot/dovecot.conf.orig
+
+ +

Next, we are going to create a new /etc/dovecot/dovecot.conf file. It should contain the following lines :

+ +
!include_try /usr/share/dovecot/protocols.d/*.protocol
+protocols = imap lmtp sieve
+
+mail_location = maildir:/var/mail/%d/%n
+mail_privileged_group = vmail
+mail_plugin_dir = /usr/lib/dovecot/modules
+mail_plugins =
+
+disable_plaintext_auth = yes
+auth_mechanisms = plain login
+
+service director {
+  unix_listener login/director {
+  }
+  fifo_listener login/proxy-notify {
+  }
+  unix_listener director-userdb {
+  }
+  inet_listener {
+  }
+}
+
+namespace inbox {
+  inbox = yes
+  type = private
+    mailbox Drafts {
+      auto = subscribe
+      special_use = \Drafts
+    }
+    mailbox Junk {
+      auto = subscribe
+      special_use = \Junk
+    }
+    mailbox Sent {
+      auto = subscribe
+      special_use = \Sent
+    }
+    mailbox Trash {
+      auto = subscribe
+      special_use = \Trash
+    }
+}
+
+service imap-login {
+  inet_listener imap {
+    port = 0
+  }
+  inet_listener imaps {
+    port = 993
+    ssl = yes
+  }
+}
+
+service pop3-login {
+  inet_listener pop3 {
+    port = 0
+  }
+  inet_listener pop3s {
+    port = 0
+  }
+}
+
+service lmtp {
+  unix_listener /var/spool/postfix/private/dovecot-lmtp {
+    mode = 0600
+    user = postfix
+    group = postfix
+  }
+}
+
+service imap {
+}
+
+service pop3 {
+}
+
+service auth {
+  unix_listener /var/spool/postfix/private/auth {
+    mode = 0666
+    user = postfix
+    group = postfix
+  }
+  unix_listener auth-userdb {
+    mode = 0600
+    user = vmail
+  }
+  user = dovecot
+}
+
+service auth-worker {
+  user = vmail
+}
+
+service dict {
+  unix_listener dict {
+  }
+}
+
+ssl = required
+ssl_cert = </etc/postfix/ssl/server.crt
+ssl_key = </etc/postfix/ssl/server.key
+ssl_ca = </etc/postfix/ssl/sub.class1.server.ca.pem
+ssl_protocols = !SSLv2 !SSLv3
+ssl_cipher_list = AES128+EECDH:AES128+EDH:!aNULL;
+protocol lda {
+  mail_plugins = $mail_plugins sieve
+}
+
+protocol imap {
+  mail_plugins = $mail_plugins
+}
+protocol lmtp {
+  mail_plugins = $mail_plugins sieve
+}
+
+plugin {
+  sieve = /var/mail/sieve/users/%u.sieve
+  sieve_after = /var/mail/sieve/after
+  sieve_before = /var/mail/sieve/before
+  sieve_global_dir = /var/lib/dovecot/sieve/
+  sieve_dir = ~/sieve
+}
+
+passdb {
+  driver = sql
+  args = /etc/dovecot/sql.conf
+}
+userdb {
+  driver = static
+  args = uid=vmail gid=vmail home=/var/mail/%d/%n
+}
+
+ +

Dovecot will use the same SSL certificate as Postfix.

+ +

Using this configuration, your virtual users’ emails will be stored in /var/mail/$domain/$user/ and will be owned by the vmail user.

+ +

For this to work, we have to create the domain folder :

+ +
mkdir -p /var/mail/captainark.net
+chown vmail: /var/mail/captainark.net && chmod 770 /var/mail/captainark.net
+
+ +

Dovecot will create the virtual users’ folders automatically.

+ +

Dovecot access to the MySQL database

+ +

We now need to allow Dovecot to connect to the mail database we have populated earlier. To do so, we are going to create a /etc/dovecot/sql.conf file with the following content :

+ +
driver = mysql
+connect = host=localhost dbname=mail user=mail password=mailpassword
+default_pass_scheme = SHA512-CRYPT
+password_query = SELECT email as user, password FROM virtual_users WHERE email='%u';
+
+ +

You’ll have to change the password to the one you have defined earlier. Since this file contains a password, let’s make sure it’s not world-readable :

+ +
chown root: /etc/dovecot/sql.conf && chmod 600 /etc/dovecot/sql.conf
+
+ +

Configuring Sieve

+ +

The last thing we need to configure here is sieve. The idea is to have all messages flagged as spam automatically moved to the mailbox Junk folder.

+ +

To do so, let’s first create the required folders :

+ +
mkdir -p /var/mail/sieve/before
+mkdir /var/mail/sieve/after
+mkdir /var/mail/sieve/users
+chown -R vmail: /var/mail/sieve && chmod -R 770 /var/mail/sieve
+
+ +

If you want to have sieve rules for a specific user, simply create $user@$domain.sieve file in the users folder (example@captainark.net in my case).

+ +

All .sieve files in the before folder will be used for all your virtual users, before their individual configuration ; the .sieve files in the after folder will be used, well, you guessed it, after.

+ +

Let’s create a filter.sieve file in the /var/mail/sieve/before folder with the following content :

+ +
require ["envelope", "fileinto", "imap4flags", "regex"];
+
+if not header :regex "message-id" ".*@.*\." {
+      fileinto "Junk";
+}
+
+if header :contains "X-Spam-Level" "*****" {
+      fileinto "Junk";
+}
+
+ +

Last thing we have to do is to change the permissions on the newly created file :

+ +
chown vmail: /var/mail/sieve/before/filter.sieve && \
+chmod 660 /var/mail/sieve/before/filter.sieve
+
+ +

That’s all ; now, all email we receive that is flagged as spam by SpamAssassin will be moved to the Junk folder.

+ +

Let’s restart dovecot :

+ +
systemctl restart dovecot
+
+ +

We now have a working mail server !

+ +

To connect to it and access your mailbox, configure your email client as follow :

+ +
    +
  • Username: example@captainark.net ;
  • +
  • Password: the password you chose for your virtual user ;
  • +
  • IMAP: your server’s FQDN, port 993 (SSL/TLS with normal password) ;
  • +
  • SMTP: your server’s FQDN, port 465 (SSL/TLS with normal password).
  • +
+ +

Configuring SpamAssassin

+ +

The alternatives

+ +

Next thing we have to do is to configure the actual anti-spam. I tried a few, but I ended up sticking with SpamAssassin. Here’s why :

+ + + +

The actual configuration

+ +

SpamAssassin’s configuration is pretty straightforward. First, let’s edit the /etc/default/spamassassin file :

+ +
ENABLED=1
+[...]
+CRON=1
+
+ +

Before the cron runs for the first time, we have to manually update SpamAssassin’s ruleset :

+ +
sa-learn
+
+ +

Next, as usual, let’s back up the original configuration file :

+ +
mv /etc/spamassassin/local.cf /etc/spamassassin/local.cf.orig
+
+ +

Let’s create a new /etc/spamassassin/local.cf file with the following content :

+ +
rewrite_header Subject [SPAM]
+report_safe 0
+required_score 5.0
+use_bayes 1
+bayes_auto_learn 1
+
+whitelist_from *@captainark.net
+
+ +

Next, to have Postfix send incoming emails through SpamAssassin, we have to edit the /etc/postfix/master.cf file. At the very beginning, we have to add a line under the smtp definition :

+ +
smtp      inet  n       -       -       -       -       smtpd
+  -o content_filter=spamassassin
+
+ +

At the very end of the same file, we have to add the following lines :

+ +
spamassassin unix -     n       n       -       -       pipe
+  user=debian-spamd argv=/usr/bin/spamc -f -e /usr/sbin/sendmail -oi -f ${sender} ${recipient}
+
+ +

Let’s restart SpamAssassin and Postfix :

+ +
systemctl restart postfix
+systemctl restart spamassassin
+
+ +

That’s all for SpamAssassin ! To check if it is working, send yourself an email from another provider. You should see the following headers in it :

+ +
X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on
+	myserver.captainark.net
+X-Spam-Level:
+
+ +

Configuring SPF

+ +

Allowing your server to send emails for your domain

+ +

SPF (Sender Policy Framework) is a mechanism that confirms that your server’s IP is allowed to send emails for your domain. Technically, it is a TXT DNS record which looks something like this :

+ +
captainark.net IN TXT "v=spf1 mx ~all"
+
+ +

This DNS record lets other mail servers know that hosts that have a MX record for my domain are also allowed to send emails for it.

+ +

For more information on SPF syntax, you can consult the official documentation.

+ +

Without a properly configured SPF record, other mail servers might flag your emails as spam or outright drop them.

+ +

Checking SPF record for inbound mail

+ +

Now that we have set up our own SPF record, let’s configure Postfix to check that other mail servers communicating with us have done the same.

+ +

First, let’s add the two following lines at the end of /etc/postfix-policyd-spf-python/policyd-spf.conf :

+ +
Header_Type = AR
+Authserv_Id = "<server's FQDN>"
+
+ +

Then, let’s edit the /etc/postfix/master.cf file and add the following lines at the end :

+ +
policy-spf  unix  -       n       n       -       -       spawn
+  user=nobody argv=/usr/bin/policyd-spf
+
+ +

Let’s now edit the /etc/postfix/main.cf. In the “smtpd_recipient_restrictions” section, add the “check_policy_service” line as seen below :

+ +
smtpd_recipient_restrictions =
+[...]
+  reject_unauth_destination,
+  check_policy_service unix:private/policy-spf,
+  permit
+
+ +

We now have to restart postfix :

+ +
systemctl restart postfix
+
+ +

Our server is now checking other mail server’s SPF records.

+ +

To make sure that it is working, send yourself an email from another provider. You should see the following header in it :

+ +
Authentication-Results: myserver.captainark.net; spf=pass (sender SPF authorized)
+[...] receiver=example@captainark.net)
+
+ +

Configuring OpenDKIM

+ +

DKIM (DomainKeys Identified Mail) is a mechanism that validates a domain name identity for an email through cryptographic authentication.

+ +

While not mandatory, setting up DKIM improves the odds of emails sent from your server not being flagged as spam by other providers.

+ +

With this configuration, OpenDKIM will also check the key for inbound emails.

+ +

Software side

+ +

First, let’s backup the original configuration file and create a folder for the configuration files :

+ +
mv /etc/opendkim.conf /etc/opendkim.conf.orig
+mkdir /etc/opendkim.d
+
+ +

We now have to create a /etc/opendkim.conf file with the following content :

+ +
AutoRestart             Yes
+AutoRestartRate         10/1h
+UMask                   002
+Syslog                  yes
+SyslogSuccess           Yes
+LogWhy                  Yes
+
+OversignHeaders         From
+AlwaysAddARHeader       yes
+
+Canonicalization        relaxed/simple
+
+ExternalIgnoreList      refile:/etc/opendkim.d/TrustedHosts
+InternalHosts           refile:/etc/opendkim.d/dkim/TrustedHosts
+KeyTable                refile:/etc/opendkim.d/dkim/KeyTable
+SigningTable            refile:/etc/opendkim.d/dkim/SigningTable
+
+Mode                    sv
+PidFile                 /run/opendkim/opendkim.pid
+SignatureAlgorithm      rsa-sha256
+
+UserID                  opendkim:opendkim
+
+Socket                  local:/var/spool/postfix/opendkim/opendkim.sock
+
+ +

Let’s then create the necessary folders :

+ +
mkdir -p /etc/opendkim.d/keys/captainark.net/
+
+ +

Now, we are going to create the /etc/opendkim.d/TrustedHosts file with the following content :

+ +
localhost
+127.0.0.1
+::1
+captainark.net
+
+ +

This file contains the hosts and domains for which OpenDKIM should sign emails.

+ +

Next, let’s create the /etc/opendkim.d/KeyTable :

+ +
mail._domainkey.captainark.net captainark.net:mail:/etc/opendkim.d/keys/captainark.net/mail.private
+
+ +

This file tells OpenDKIM which key it should use for each selector.

+ +

Finally, let’s create the /etc/opendkim.d/SigningTable file :

+ +
*@captainark.net mail._domainkey.captainark.net
+
+ +

This file tells OpenDKIM which selector it should use for each domain.

+ +

We now have to generate the private/public key pair for our domain :

+ +
cd /etc/opendkim.d/keys/captainark.net/
+opendkim-genkey -s mail -d captainark.net
+
+ +

This creates two files ; mail.private contains our private key, mail.txt contains our public key.

+ +

Let’s change the permissions on those files :

+ +
chown -R opendkim: /etc/opendkim.d/keys
+chmod -R 700 /etc/opendkim.d/keys
+chmod 600 /etc/opendkim.d/captainark.net/*
+
+ +

Postfix integration

+ +

The last thing we have to do is to configure Postfix to communicate with OpenDKIM.

+ +

First, let’s create the necessary folders :

+ +
mkdir /var/spool/postfix/opendkim
+chown opendkim: /var/spool/postfix/opendkim
+
+ +

We also have to add the postfix user to the opendkim group :

+ +
useradd -G opendkim postfix
+
+ +

Now, let’s edit the /etc/postfix/master.cf file, like so :

+ +
smtpd_milters = unix:/opendkim/opendkim.sock
+
+ +

We now have to restart OpenDKIM and Postfix :

+ +
systemctl restart opendkim
+systemctl restart postfix
+
+ +

DNS side

+ +

For DKIM to work, you have to configure a DNS TXT record in your zone. This record was automatically generated by OpenDKIM in the mail.txt file mentioned earlier :

+ +
mail._domainkey IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCkJq0CW3tl2XHZ1CN5XdbqRDU7KfXOJ70nlwI09bHmDU63/Yz3J5rl863S0t2ncVHfIudZANj0OaiJe5HRR7WCsjuNIhQFfPFGIWLNClpxqdQVQURI38sAGeyn7Ed/Cor1AiWABzFWzel0kvXILw8K/NTzxaAPeSa9ttwQEgSmowIDAQAB" ; ----- DKIM key mail for captainark.net
+
+ +

All you have to do is to copy and paste this record in your DNS zone file.

+ +

To make sure that OpenDKIM is working, you can send an empty email to check-auth@verifier.port25.com. You should receive a response with the following content :

+ +
==========================================================
+Summary of Results
+==========================================================
+SPF check:          pass
+DomainKeys check:   neutral
+DKIM check:         pass
+Sender-ID check:    pass
+SpamAssassin check: ham
+
+ +

Configuring OpenDMARC

+ +

DMARC (Domain-based Message Authentication, Reporting & Conformance) standardizes SPF and DKIM authentication mechanisms.

+ +

It lets the owner of a domain name indicate that his email is protected by SPF and/or DKIM and what other providers should do with emails that do not pass those checks.

+ +

Software side

+ +

Once again, let’s backup the original configuration file :

+ +
mv /etc/opendmarc.conf /etc/opendmarc.conf.orig
+
+ +

We now have to create a /etc/opendmarc.conf file with the following content :

+ +
AutoRestart             Yes
+AutoRestartRate         10/1h
+UMask                   0002
+Syslog                  true
+
+AuthservID              "<your server's FQDN>"
+TrustedAuthservIDs      "<your server's FQDN>"
+IgnoreHosts             /etc/opendkim.d/TrustedHosts
+
+RejectFailures          false
+
+UserID                  opendmarc:opendmarc
+PidFile                 /run/opendmarc.pid
+Socket                  local:/var/spool/postfix/opendmarc/opendmarc.sock
+
+ +

Postfix integration

+ +

The last thing we have to do is to configure Postfix to communicate with OpenDMARC.

+ +

First, let’s create the necessary folders :

+ +
mkdir /var/spool/postfix/opendmarc
+chown opendmarc: /var/spool/postfix/opendmarc
+
+ +

We also have to add the postfix user to the opendmarc group :

+ +
useradd -G opendmarc postfix
+
+ +

Now, let’s edit the /etc/postfix/master.cf file, like so :

+ +
smtpd_milters = unix:/opendkim/opendkim.sock, unix:/opendmarc/opendmarc.sock
+
+ +

We now have to restart OpenDMARC and Postfix :

+ +
systemctl restart opendmarc
+systemctl restart postfix
+
+ +

You should now see the following headers in your incoming emails :

+ +
Authentication-Results: myserver.captainark.net; dmarc=pass header.from=gmail.com
+
+ +

DNS side

+ +

DMARC, like SPF and DKIM, is based on DNS TXT records.

+ +

Here is how I configured it for the captainark.net domain :

+ +
_dmarc IN TXT "v=DMARC1; p=none; rua=mailto:postmaster@captainark.net; ruf=mailto:postmaster@captainark.net"
+
+ +

This tells other providers to not reject or quarantine emails should a SPF or DKIM check fail, but to send a daily report of those checks to postmaster@captainark.net.

+ +

For more information on the DMARC syntax, here is an article from Google.

+ +

Configuring Monit

+ +

Monit is a daemon that makes sure that other daemons are running. If they crash, it restarts them automatically. Is is not directly related to a mail server per say, but it’s pretty easy to set up.

+ +

First, as always, let’s backup the original configuration file :

+ +
mv /etc/monit/monitrc /etc/monit/monitrc.orig
+
+ +

We now have to create a new /etc/monit/monitrc file with the following content :

+ +
set daemon 30
+set logfile syslog facility log_daemon
+
+set httpd port 2812 and
+use address localhost
+allow localhost
+
+set mailserver localhost
+with timeout 30 seconds
+using hostname myserver.captainark.net
+
+set mail-format { from: monit@captainark.net }
+
+include /etc/monit/conf.d/*
+
+ +

Then, we are going to create a /etc/monit/conf.d/mail file with the following content :

+ +
check process postfix
+  with pidfile "/var/spool/postfix/pid/master.pid"
+  start program = "/bin/systemctl start postfix"
+  stop program = "/bin/systemctl stop postfix"
+  alert monit@captainark.net
+  group mail
+
+check process dovecot
+  with pidfile "/run/dovecot/master.pid"
+  start program = "/bin/systemctl start dovecot"
+  stop program = "/bin/systemctl stop dovecot"
+  alert monit@captainark.net
+  group mail
+  depends on postfix
+
+check process spamassassin
+  with pidfile "/run/spamassassin.pid"
+  start program = "/bin/systemctl start spamassassin"
+  stop program = "/bin/systemctl stop spamassassin"
+  alert monit@captainark.net
+  group mail
+  depends on postfix, dovecot
+
+check process opendkim
+  with pidfile "/run/opendkim/opendkim.pid"
+  start program = "/bin/systemctl start opendkim"
+  stop program = "/bin/systemctl stop opendkim"
+  alert monit@captainark.net
+  group mail
+  depends on postfix, dovecot
+
+check process opendmarc
+  with pidfile "/run/opendmarc/opendmarc.pid"
+  start program = "/bin/systemctl start opendmarc"
+  stop program = "/bin/systemctl stop opendmarc"
+  alert monit@captainark.net
+  group mail
+  depends on postfix, dovecot
+
+ +

Let’s make sure that permissions on the file are correct :

+ +
chown root: /etc/monit/conf.d/mail && chmod 600 /etc/monit/conf.d/mail
+
+ +

Then, we have to reload the monit daemon :

+ +
monit reload
+
+ +

Now, the monit summary command should have the following output :

+ +
The Monit daemon 5.4 uptime: 3d 0h 41m
+
+Process 'postfix'                   Running
+Process 'dovecot'                   Running
+Process 'spamassassin'              Running
+Process 'opendkim'                  Running
+Process 'opendmarc'                 Running
+
+ +

Configuring Rainloop

+ +

Rainloop is a web-based email client. I won’t go into details on how to configure it in this tutorial ; here’s a link to the official documentation.

+ +

You’ll need a web server with PHP 5.3+ to run Rainloop. You do not have to run Rainloop on the same host as your mail server. No database is required.

+ +

Conclusion

+ +

We now have a mail server that should be running pretty smoothly. It could still be improved by setting up things such as greylisting or virus detection.

+ +

If you have found this tutorial useful, if you’ve found an error in it or if you have any question, please feel free to leave a comment below or to contact me on Twitter.

+ +

References

+ +

Here are the tutorials I used to set up my own mail server :

+ + + +
+ + + +
+ +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + diff --git a/public/2015/05/25/flexget-init-script/index.html b/public/2015/05/25/flexget-init-script/index.html new file mode 100644 index 0000000..eb5d05a --- /dev/null +++ b/public/2015/05/25/flexget-init-script/index.html @@ -0,0 +1,625 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Flexget init script · Sysadmining. All day. Every day. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ +
+ + + +
+ + + + +
+ +
+

Flexget init script

+ + + +
+ +
+ + +

I’ve been using Flexget for the past two years or so as a download automator.

+ +

Since I wrote an init script for it a while back, and it is compatible with Debian Jessie / systemd, I figured I’d share it here.

+ +

The script

+ +

All of the following should be done as the root user.

+ +

First, create a /etc/default/flexget file with the following content :

+ +
# Configuration file for /etc/init.d/flexget
+
+# User to run flexget as.
+# Daemon will not start if left empty.
+FGUSER=""
+
+# Full path to the flexget config.yml file to use.
+# Defaults to FGUSER $HOME/.flexget/config.yml
+CONFIG=""
+
+# Path to the directory where flexget should log. Do not add trailing slash.
+# Defaults to the FGUSER $HOME/.flexget directory
+LOG=""
+
+# Log verbosity
+# Available options : none critical error warning info verbose debug trace
+# Defaults to info
+LEVEL=""
+
+ +

Please note that the FGUSER variable needs to be defined for the daemon to start. It can be set to your current user, or you can run flexget as its own user.

+ +

You can create a flexget user with the following command :

+ +
useradd -m -d /var/lib/flexget -r -s /bin/false flexget
+
+ +

Then, create the /etc/init.d/flexget file :

+ +
#!/bin/bash
+
+### BEGIN INIT INFO
+# Provides:          flexget
+# Required-Start:    $network $remote_fs
+# Required-Stop:     $network $remote_fs
+# Should-Start:      
+# Should-Stop:       
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: Flexget
+# Description:       FlexGet is a multipurpose automation tool
+#                    for content like torrents, nzbs, podcasts,
+#                    comics, series, movies, etc.
+### END INIT INFO
+
+# Author: Antoine Joubert, 19/01/2014
+
+NAME="flexget"
+DAEMON="/usr/local/bin/flexget"
+SETTINGS="/etc/default/$NAME"
+
+DESC="Flexget"
+PIDFILE="/var/run/$NAME.pid"
+
+set -e
+
+. /lib/lsb/init-functions
+
+unset FGUSER CONFIG LOG LEVEL
+
+# Exit if flexget not installed
+if [ ! -x "$DAEMON" ]; then
+  log_action_msg "$DESC: Could not find flexget executable. Exiting."
+  exit 2
+fi
+
+# Read configuration variables
+if [ -r /etc/default/$NAME ]; then
+  . /etc/default/$NAME
+else
+  log_action_msg "$DESC: /etc/default/$NAME not found. Exiting."
+  exit 2
+fi
+
+# Exit if FGUSER has not been set in /etc/default/flexget
+if [ -z $FGUSER ]; then
+  log_action_msg "$DESC: FGUSER not set in /etc/default/$NAME. Exiting."
+  exit 2
+fi
+
+# Function to verify if flexget is already running
+run_check() {
+  if [ -e $PIDFILE ]; then
+    status_of_proc -p $PIDFILE $DAEMON $NAME > /dev/null && RETVAL=0 || RETVAL="$?"
+  else
+    RETVAL="2"
+  fi
+}
+
+end_log() {
+  if [ $RETVAL -eq 0 ]; then
+    log_end_msg 0
+    return 0
+  else
+    log_end_msg 1
+    exit 1
+  fi
+}
+
+# Function to define config file, log file and log level
+conf_check() {
+  if [ -z $CONFIG ]; then
+    OPTIONS="$OPTIONS"
+  else
+    OPTIONS="-c $CONFIG"
+  fi
+
+  if [ -z $LOG ]; then
+    OPTIONS="$OPTIONS"
+  else
+    OPTIONS="$OPTIONS -l $LOG/flexget.log"
+    if [ ! -d $LOG ]; then
+      mkdir -p -m 750 $LOG
+      chown $FGUSER $LOG
+    fi
+  fi
+
+  if [ -z $LEVEL ]; then
+    OPTIONS="$OPTIONS"
+  else
+    OPTIONS="$OPTIONS -L $LEVEL"
+  fi
+}
+
+start_flexget() {
+  run_check
+  if [ $RETVAL = 0 ]; then
+    log_action_msg "$DESC: Already running with PID $(cat $PIDFILE). Aborting."
+    exit 2
+  else
+    conf_check
+    log_daemon_msg "$DESC: Starting the daemon."
+    start-stop-daemon --start --background --quiet --pidfile $PIDFILE --make-pidfile \
+    --chuid $FGUSER --user $FGUSER --exec $DAEMON -- $OPTIONS daemon start
+    RETVAL=$?
+    end_log
+  fi
+}
+
+stop_flexget() {
+  run_check
+  if [ $RETVAL = 0 ]; then
+    log_daemon_msg "$DESC: Stopping the daemon."
+    start-stop-daemon --stop --quiet --chuid "$FGUSER" --pidfile "$PIDFILE" --retry 30
+    RETVAL=$?
+    [ -e "$PIDFILE" ] && rm -f "$PIDFILE"
+    end_log
+  else
+    log_action_msg "$DESC: Not currently running. Aborting."
+  exit 2
+  fi
+}
+
+status_flexget() {
+  run_check
+  if [ $RETVAL = 0 ]; then
+    log_action_msg "$DESC: Currently running with PID $(cat $PIDFILE)."
+  else
+    log_action_msg "$DESC: Not currently running."
+  fi
+  exit $RETVAL
+}
+
+case "$1" in
+  start)
+    start_flexget
+  ;;
+  stop)
+    stop_flexget
+  ;;
+  restart)
+    stop_flexget && sleep 2 && start_flexget
+  ;;
+  status)
+    status_flexget
+  ;;
+  *)
+    echo "Usage: $0 {start|stop|restart|status}"
+  ;;
+esac
+
+exit 0
+
+
+ +

Then, give execution rights to the script :

+ +
chmod +x /etc/init.d/flexget
+
+ +

And then, generate the necessary symlinks for the service to start on boot :

+ +

Debian Jessie

+ +
systemctl enable flexget
+
+ +

Debian Wheezy

+ +
insserv flexget
+
+ +

To start, stop or check if the daemon is running :

+ +

Debian Jessie

+ +
systemctl start flexget
+systemctl stop flexget
+systemctl status flexget
+
+ +

Debian Wheezy / Jessie

+ +
service flexget start
+service flexget stop
+service flexget status
+
+ +

Debian Wheezy

+ +
/etc/init.d/flexget start
+/etc/init.d/flexget stop
+/etc/init.d/flexget status
+
+ +

Conclusion

+ +

That’s all ! If you are using this script, please let me know in the comment section below !

+ +
+ + + +
+ +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + diff --git a/public/2015/11/19/installing-ghost/index.html b/public/2015/11/19/installing-ghost/index.html new file mode 100644 index 0000000..3c5c8d3 --- /dev/null +++ b/public/2015/11/19/installing-ghost/index.html @@ -0,0 +1,675 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Installing Ghost · Sysadmining. All day. Every day. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ +
+ + + +
+ + + + +
+ +
+

Installing Ghost

+ + + +
+ +
+ + +

I haven’t published an article on here for over a year and a half… While this was mostly due to a lack of motivation, another reason was that I didn’t enjoy the blogging system I was using.

+ +

As lightweight as Pelican is, I found it cumbersome to use on a regular basis. Every time I wanted to publish or update an article, I had to : +- edit local markdown files ; +- regenerate the website files ; +- start a webserver locally to proofread the article ; +- commit and push the files to my git repo ; +- pull the files on the webserver.

+ +

I hadn’t had a look at the CMS landscape for a while, and I started searching for one with a web editor that supports markdown. I also wanted to avoid anything that runs on PHP if possible.

+ +

I quickly discovered Ghost, and decided to give it a shot. I was convinced by it within a few hours and I decided to migrate this blog.

+ +

So, to celebrate my move to Ghost, I figured I’d write an article on how I’ve installed it on my server.

+ +

All commands in this article have to be run as the root user on a Debian server.

+ +

Installing nodejs

+ +

Unlike most CMS (Wordpress, for example), Ghost is not files that you have to upload to a webserver, but a daemon that runs on nodejs.

+ +

Here’s the official recommended way of installing the current LTS version of nodejs on Debian :

+ +
curl -sL https://deb.nodesource.com/setup_8.x | bash -
+apt-get install -y nodejs
+
+ +

If, like me, you don’t want to run a bash script downloaded from the internet on your server, here are the commands you have to run to install it manually.

+ +

Since the nodejs repo uses https, we’ll first need to install the required package to use those :

+ +
apt install apt-transport-https
+
+ +

We’ll then have to add the nodejs repository public key to the system :

+ +
curl -s https://deb.nodesource.com/gpgkey/nodesource.gpg.key | apt-key add -
+
+ +

Now we have to add the nodejs repository to our sourcelist :

+ +
echo 'deb https://deb.nodesource.com/node_8.x stretch main' > /etc/apt/sources.list.d/nodesource.list
+
+ +

We can now install nodejs

+ +
apt update
+apt install nodejs
+
+ +

System configuration

+ +

Before installing Ghost, some system configuration is required.

+ +

First, let’s create a new ghost system user that’ll be used to run the Ghost daemon :

+ +
useradd -s /bin/false -r -d /opt/ghost -m ghost
+
+ +

Ghost needs an empty folder for the automated installation script to work. For that purpose, let’s create a subfolder in the ghost user home folder :

+ +
sudo -Hu ghost mkdir /opt/ghost/app
+
+ +

Database

+ +

Ghost requires a MySQL/MariaDB database to store its data (technically, you could use a SQLite database, but please don’t).

+ +

I personnally have all my databases stored on a single LXC container running MariaDB. However, if you need to, you can install MariaDB locally this way :

+ +
apt install mariadb-server mariadb-common
+
+ +

We now have to declare a ghost user and database in the MariaDB shell :

+ +
create database ghost;
+create user `ghost`@`%` identified by 'password';
+grant all privileges on ghost.* to 'ghost'@`%`;
+
+ +

You can change the % to localhost in the create user command if you’ve installed MariaDB locally. Please also remember to change 'password' by an actual password.

+ +

Once that’s done, we’re ready to install Ghost !

+ +

Installing Ghost

+ +

Ghost CLI

+ +

To install Ghost, we first have to install the Ghost CLI :

+ +
npm i -g ghost-cli
+
+ +

The Ghost CLI is a tool that lets you install, upgrade and manage your Ghost installation easily. Its usage is thoroughly documented on the official website here.

+ +

Installing Ghost

+ +

Let’s install Ghost :

+ +
cd /opt/ghost/app
+sudo -Hu ghost ghost install --no-setup-nginx --no-setup-systemd --no-setup-linux-user --no-setup-mysql
+
+ +

The command will ask you for the following information : +- the URL of your website ; +- the hostname or IP of the server that’s hosting your MariaDB installation ; +- the username to use to connect to the database (ghost) ; +- the password you’ve configured for the database user ; +- the database name (ghost).

+ +

Once the script has finished running, you’ve successfully installed Ghost ! However, the daemon won’t start since we haven’t configured systemd yet.

+ +

Since it contains a password, let’s fix the permissions on our installation’s configuration file to make sure it’s not world-readable :

+ +
chmod 600 /opt/ghost/app/config.production.json
+
+ +

As you can see from the ghost install command, it can install and configure pretty much all of its dependencies on its own. However, since I’m a sysadmin, that’s not how I roll.

+ +

Systemd configuration

+ +

As I wrote earlier, Ghost runs as a daemon. For us to be able to start it, we now need to declare a systemd unit file :

+ +

Let’s create the file :

+ +
vim /etc/systemd/system/ghost.service
+
+ +

And add the following content to it :

+ +
[Unit]
+Description=Ghost systemd service
+Documentation=https://docs.ghost.org
+
+[Service]
+Type=simple
+WorkingDirectory=/opt/ghost/app
+User=ghost
+Group=ghost
+Environment="NODE_ENV=production"
+ExecStart=/usr/bin/ghost run
+
+[Install]
+WantedBy=multi-user.target
+
+ +

We can now reload systemd an start Ghost :

+ +
systemctl daemon-reload
+systemctl start ghost.service
+
+ +

The daemon should now be running :

+ +
pgrep -alf ghost
+14184 ghost run
+
+ +

Nginx

+ +

With its default configuration, Ghost runs as a webserver on localhost, on a non-standard HTTP port (TCP 2368). For your website to be publicly browseable, you’ll need to configure a webserver as a reverse-proxy in front of your Ghost installation. We’ll use nginx for that purpose.

+ +

If you already have nginx running on a different server from your Ghost installation, you can use it for that purpose. For it to work, you’ll need to edit the server host IP in Ghost’s config.production.json configuration file with your Ghost server public IP and to restart Ghost. If you do so, make sure to limit direct access to your Ghost installation to the IP of your reverse-proxy by using iptables.

+ +

If you need to, you can install nginx locally this way :

+ +
apt install nginx
+
+ +

I won’t go into details on how to configure and secure a nginx installation here as it is beyond the scope of this article.

+ +

Here is my nginx configuration for this website :

+ +
  location / {
+    proxy_pass http://127.0.0.1:2368;
+    include proxy.conf;
+    add_header Front-End-Https on;
+    proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
+    proxy_set_header Authorization "";
+    proxy_set_header Accept-Encoding "";
+    proxy_redirect off;
+  }
+
+  location /ghost/ {
+    proxy_pass http://127.0.0.1:2368/ghost/;
+    allow 192.0.2.100;
+    deny all;
+    include proxy.conf;
+    add_header Front-End-Https on;
+    proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
+    proxy_set_header Accept-Encoding "";
+    proxy_redirect off;
+  }
+
+ +

As you can see, I’ve declared two location blocks : +- / is publicly visible by anyone ; +- /ghost (Ghost’s administation interface) is only accessible from 192.0.2.100 (my public IP address).

+ +

I’d rather have left Ghost’s administation interface accessible from anywhere. However, since there is currently no way to replace /ghost by another subfolder and two-factor authentification is not available, I’ve decided against it.

+ +

Monit

+ +

As I mentionned in previous articles, I have monit running on all of my servers to make sure that my services are running and to restart them should they crash.

+ +

I’ve created a configuration file for Ghost :

+ +
vim /etc/monit/conf.d/ghost
+
+ +

With the following content :

+ +
check process ghost
+  matching "ghost run"
+  start program = "/bin/systemctl start ghost"
+  stop program = "/bin/systemctl stop ghost"
+  if changed pid then alert
+  if changed ppid then alert
+
+ +

Let’s reload monit :

+ +
monit reload
+
+ +

Ghost should now appear in your monit summary.

+ +

Logging

+ +

Ghost writes its logs through syslog. If you don’t want those messages to end up in /var/log/syslog, you’ll have to configure your syslog daemon. For me, that’s syslog-ng.

+ +

Syslog-ng

+ +

Let’s create a dedicated folder for the Ghost daemon’s log files :

+ +
mkdir /var/log/ghost
+chown root:adm /var/log/ghost
+
+ +

Then, we need to create a configuration file :

+ +
vim /etc/syslog-ng/conf.d/ghost.conf
+
+ +

And add the following content to it :

+ +
filter f_ghost { program ("ghost"); };
+destination d_ghost { file (/var/log/ghost/ghost.log); };
+log { source(s_src); filter (f_ghost); destination (d_ghost); flags(final); };
+
+ +

We can now reload syslog-ng :

+ +
service syslog-ng reload
+
+ +

Once that’s done, Ghost should start logging in /var/log/ghost/ghost.log. Accessing a page on your site will create a new log entry, so that’ll be enough to make sure it’s working properly.

+ +

Logrotate

+ +

As always with logs, let’s configure logrotate to make sure we don’t end up with huge files.

+ +

Let’s create a new logrotate configuration file :

+ +
vim /etc/logrotate.d/ghost
+
+ +

And add the following content to it :

+ +
/var/log/ghost/ghost.log {
+  rotate 8
+  weekly
+  notifempty
+  missingok
+  create 640 root adm
+  compress
+  copytruncate
+}
+
+ +

There’s no need to reload anything here. This new configuration file will be read by logrotate automatically next time its cron runs.

+ +

Conclusion

+ +

This blog uses a previous version of Ghost’s default theme, Casper.

+ +

I’ve modified it a bit, and I really enjoy how it looks now ! You can get the theme with my modifications from my GitHub ! Credits to this article for some of the changes, and thanks @Aguay for the help !

+ +

You’ve also probably noticed that I now use a private installation of NodeBB for the comments section. I’ll probably write an article on how I’ve installed and implemented it in my Ghost installation in the near future. In the meantime, please feel free to make use of it !

+ +
+ + + +
+ +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + diff --git a/public/2016/01/31/debian-updates-with-ansible/index.html b/public/2016/01/31/debian-updates-with-ansible/index.html new file mode 100644 index 0000000..3224822 --- /dev/null +++ b/public/2016/01/31/debian-updates-with-ansible/index.html @@ -0,0 +1,462 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Debian updates with Ansible · Sysadmining. All day. Every day. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ +
+ + + +
+ + + + +
+ +
+

Debian updates with Ansible

+ + + +
+ +
+ + +

I’ve recently bought a HP Proliant Microserver Gen8 to play around with LXC and try new stuff.

+ +

From the 4 Debian machines I had to keep up-to-date, I now have 7, so it became quite time-consumming to manually SSH to each of them whenever an update became available.

+ +

I ended up looking at Ansible to speed up the process and, within an hour, I had a working playbook that updates the debian packages, pip packages and git repos installed on all of my servers with a single command.

+ +

I figured I’d share the playbook I use to update the Debian packages !

+ +

The playbook

+ +

I modified this gist to only use apt-get instead of both apt-get and aptitude.

+ +
- hosts: all
+  tasks:
+
+    - name: update cache
+      apt: update_cache=yes
+
+    - name: list packages to upgrade (1/2)
+      shell: apt-get upgrade -s -V | awk '/=>/{print $1}'
+      register: updates
+      changed_when: False
+
+    - name: list packages to upgrade (2/2)
+      debug: msg="{{ updates.stdout_lines | count }} packages to upgrade ({{ updates.stdout_lines | join(', ') }})"
+      when: (updates.stdout_lines)
+
+    - name: upgrade packages
+      apt: upgrade=dist
+      when: (updates.stdout_lines)
+
+    - name: check what the new version is
+      shell: lsb_release -r | awk '{print $2}'
+      changed_when: False
+      register: new_release
+
+    - name: notify distribution version upgrade
+      debug: msg="Debian has been upgraded from {{ ansible_lsb.release }} to {{ new_release.stdout }}"
+      when: ansible_lsb.release != new_release.stdout
+
+    - name: /wheezy/ install the debian-goodies package if it is missing
+      apt: name=debian-goodies state=present
+      when: ansible_distribution_release == 'wheezy'
+
+    - name: /jessie/ install the needrestart package if it is missing
+      apt: name=needrestart state=present default_release=jessie-backports
+      when: ansible_distribution_release == 'jessie'
+
+    - name: /wheezy/ list services to restart (1/3)
+      shell: checkrestart | awk '/^service/{print $2}'
+      register: wheezy_services
+      changed_when: False
+      when: ansible_distribution_release == 'wheezy'
+
+    - name: /jessie/ list services to restart (1/3)
+      shell: needrestart -blrl | awk '/^NEEDRESTART-SVC/{print $2}'
+      register: jessie_services
+      changed_when: False
+      when: ansible_distribution_release != 'wheezy'
+
+    - name: merge services list (2/3)
+      set_fact:
+        services: "{{ wheezy_services if ansible_distribution_release == 'wheezy' else jessie_services }}"
+
+    - name: list services to restart (3/3)
+      debug: msg="{{ services.stdout_lines | count }} services to restart ({{ services.stdout_lines | join (', ') }})"
+      when: (services.stdout_lines)
+
+    - name: cache cleanup
+      shell: apt-get autoclean
+
+ +

Conclusion

+ +

That’s all ! Please leave a comment if you’ve found this playbook helpful !

+ +
+ + + +
+ +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + diff --git a/public/2016/01/31/private-git-repo/index.html b/public/2016/01/31/private-git-repo/index.html new file mode 100644 index 0000000..2d230af --- /dev/null +++ b/public/2016/01/31/private-git-repo/index.html @@ -0,0 +1,550 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Private Git repo · Sysadmining. All day. Every day. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ +
+ + + +
+ + + + +
+ +
+

Private Git repo

+ + + +
+ +
+ + +

I’ve decided to migrate this blog to Pelican. I’ve been playing around with it over the week-end, and it turns out to be way easier to manage than Jekyll. Themes are much easier to install and configure, so it ends up looking better as well !

+ +

Since I’m basically recreating this blog from scratch, I’ve decided to delete the old git repo that was hosting it and to create a new one.

+ +

Setting up your own private git repo is pretty easy to achieve and is already well-documented on the Git website.

+ +

Every time I want to create a new repo, I’ve had time to forget how to do it and I end up looking for that page, so I figured I’d write a few lines on the subject.

+ +

In this tutorial, I’ll configure a git repo on a distant server running Debian 8 (Jessie). This repo will be remotely accessible using SSH. Two users will be able to connect to it : me and the www-data user on my webserver.

+ +

SSH Keys

+ +

If you don’t have one already, you’ll need a ssh-key to connect to the git repo.

+ +

On your computer, in a shell, as your usual user :

+ +
ssh-keygen -t rsa -b 3072
+Generating public/private rsa key pair.
+Enter file in which to save the key (/home/user/.ssh/id_rsa):
+Enter passphrase (empty for no passphrase):
+Enter same passphrase again:
+Your identification has been saved in /home/user/.ssh/id_rsa.
+Your public key has been saved in /home/user/id_rsa.pub.
+The key fingerprint is:
+[Redacted]
+
+ +

For security reasons, configuring a passphrase is recommended. On Mac OS X and most desktop environnements on Linux, you can store this passphrase for the duration of your session using the ssh-add command, so you won’t have to type it every time you want to connect to a host.

+ +

On the server, we also have to create a ssh-key for the user that is running our webserver (you’ll need to have sudo installed) :

+ +
sudo -H -u www-data ssh-keygen -t rsa -b 3072
+Generating public/private rsa key pair.
+Enter file in which to save the key (/var/www/.ssh/id_rsa):
+Enter passphrase (empty for no passphrase):
+Enter same passphrase again:
+Your identification has been saved in /var/www/.ssh/id_rsa.
+Your public key has been saved in /var/www/.ssh/id_rsa.pub.
+The key fingerprint is:
+[Redacted]
+
+ +

If you decide to configure a passphrase for that ssh-key, you’ll have to type it every time you’ll want to pull from your repo.

+ +

Server management

+ +

All of the commands in this section have to be run as root.

+ +

First thing first, we have to install the git package on the server that will be hosting our git repos :

+ +
apt update && apt install git -y
+
+ +

Then, we have to create a user named git :

+ +
useradd -s /usr/bin/git-shell -m -r git
+
+ +

This will create a system user (UID < 1000) with a /home/git home directory. If you want to host your git repos somewhere else on your filesystem, you should add a -d /home/directory/for/git in the previous command.

+ +

This user will use the git-shell shell. This limits remote connection to that user to git commands (like the rssh shell can limit remote connection to a user to scp or rsync commands).

+ +

We have to configure our system to allow the use of this shell :

+ +
echo '/usr/bin/git-shell' >> /etc/shells
+
+ +

From this point, you should have to following output if you try to SSH to your server with that user :

+ +
ssh git@git.captainark.net
+fatal: Interactive git shell is not enabled.
+hint: ~/git-shell-commands should exist and have read and execute access.
+Connection to git@git.captainark.net closed.
+
+ +

We now need to create the .ssh/authorized_keys file for the git user with the correct permissions :

+ +
sudo -H -u git mkdir /home/git/.ssh && chmod 700 /home/git/.ssh
+sudo -H -u git touch /home/git/.ssh/authorized_keys && chmod 600 /home/git/.ssh/authorized_keys
+
+ +

You can now copy/paste the content of the two $HOME/.ssh/id_rsa.pub files we’ve created earlier using the ssh-keygen command in /home/git/.ssh/authorized_keys.

+ +

The last thing we have to do is to create our first git repo. In this example, my project will be called ‘captainarkdotnet’ as it will be hosting this blog :

+ +
sudo -H -u git mkdir /home/git/captainarkdotnet.git
+cd /home/git/captainarkdotnet.git
+sudo -H -u git git init --bare
+
+ +

The last command should give you the following output :

+ +
Initialized empty Git repository in /home/git/captainarkdotnet.git/.git/
+
+ +

We’re done with the server configuration. Let’s now actually push stuff to our repo !

+ +

Initial push

+ +

The files for my blog are store in the ~/Documents/projects/captainarkdotnet on my computer. Before doing anything else, we first have to make sure that we currently are in that folder :

+ +
cd ~/Documents/projects/captainarkdotnet
+
+ +

Let’s now push the content of that folder to our repo :

+ +
git init
+git add .
+git commit -m 'initial commit'
+git remote add origin git@git.captainark.net:captainarkdotnet.git
+git push origin master
+
+ +

Please note that you’ll need to edit git.captainark.net to the FQDN or IP of your git server, and captainarkdotnet.git to the name of the git project on your server.

+ +

If everything went well, the last command should give you the following output :

+ +
Counting objects: 69, done.
+Delta compression using up to 4 threads.
+Compressing objects: 100% (64/64), done.
+Writing objects: 100% (69/69), 1.01 MiB | 0 bytes/s, done.
+Total 69 (delta 15), reused 0 (delta 0)
+To git@git.captainark.net:captainarkdotnet.git
+ * [new branch]      master -> master
+
+ +

That’s it, we’ve now pushed our first commit to our server !

+ +

First pull

+ +

Alright, time to pull the files we’ve just pushed on our webserver. I personally store my web content in /var/www ; if you don’t, you’ll have to adjust the path accordingly :

+ +
cd /var/www
+sudo -H -u www-data git clone git@git.captainark.net:captainarkdotnet.git
+
+ +

SSH will ask you to type ‘yes’ since it’s the first time the www-data user connects to the server. If everything goes well, you should have the following output :

+ +
Cloning into 'captainarkdotnet'...
+remote: Counting objects: 70, done.
+remote: Compressing objects: 100% (65/65), done.
+remote: Total 70 (delta 16), reused 0 (delta 0)
+Receiving objects: 100% (70/70), 1.01 MiB | 0 bytes/s, done.
+Resolving deltas: 100% (16/16), done.
+Checking connectivity... done.
+
+ +

Conclusion

+ +

That’s it ! We now have a working private git repo ! I won’t go into details into the git commands in this tutorial, but here’s a quick overwiew of the ones I use the most :

+ +
    +
  • git add . recursively adds all files from the directory to the repo ;
  • +
  • git commit -a -m 'This is a comment' commits the current state of your local repo with the ‘This is a comment’ comment ;
  • +
  • git push pushes your commits to the distant repo ;
  • +
  • git pull pulls the latest version of the distant repo locally ;
  • +
  • git branch -av shows all available branches for the repo ;
  • +
  • git checkout -b testing remotes/origin/testing create a local ‘testing’ branch based on the remote ‘remotes/origin/testing’ branch ;
  • +
  • once a branch has been copied locally, you can switch to it with the git checkout {branch} command.
  • +
+ +

For more information on git a command, use man git-{command} !

+ +

If you’ve found this tutorial in any way helpful, please feel free to leave a comment !

+ +
+ + + +
+ +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + diff --git a/public/2016/02/02/my-tmux-configuration/index.html b/public/2016/02/02/my-tmux-configuration/index.html new file mode 100644 index 0000000..2e49dac --- /dev/null +++ b/public/2016/02/02/my-tmux-configuration/index.html @@ -0,0 +1,568 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + My tmux configuration · Sysadmining. All day. Every day. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ +
+ + + +
+ + + + +
+ +
+

My tmux configuration

+ + + +
+ +
+ + +

tmux is a terminal mutiplexer. It lets you have multiples shells running in a single terminal emulator window and it keeps those shells running in the background should you need to close your terminal emulator.

+ +

I’ve played around with the configuration quite a bit to find settings that suit my needs. Here’s what it ended up looking like :

+ +

tmux_fullsize

+ +

This screenshot was done on Mac OS X, using the Terminal app and this Solarized theme.

+ +

I figured I’d share my tmux configuration here !

+ +

Installing tmux

+ +

tmux is available on Debian. I suggest using the jessie backports version :

+ +

apt -t jessie-backports install tmux

+ +

tmux is also available on Mac OS X using brew :

+ +

brew install tmux

+ +

tmux.conf

+ +

I used screen before tmux, so I configured the prefix key on C-a instead of C-b. tmux has the advantage of being much simpler to configure than screen.

+ +

If you want to use this configuration, simply copy the following in ~/.tmux.conf. This file is read by default when tmux starts.

+ +

If you simply want to try it out, copy it in a file somewhere else and have tmux load with the -f parameter (tmux -f ~/tmux-test.conf).

+ +
# use utf8
+set -g utf8
+set-option -g status-utf8 on
+set-window-option -g utf8 on
+
+# do not wait on esc key
+set-option -g escape-time 0
+
+# completely disable automatic rename
+set-window-option -g automatic-rename off
+
+# basic settings
+set -g default-terminal "screen-256color"
+set -g aggressive-resize off
+set-window-option -g xterm-keys on
+#set-window-option -g mode-mouse off
+
+# command history
+set -g history-limit 10000
+
+# messages
+set -g message-bg default
+set -g message-fg red
+
+# no visual activity
+set -g visual-activity off
+set -g visual-bell off
+
+# status bar
+set-option -g status-justify centre
+set-option -g status-bg default
+set-option -g status-fg blue
+set-option -g status-interval 5
+set-option -g status-left-length 30
+set-option -g status-left '#[fg=red][ #[fg=white]#H #[fg=red]]#[default]'
+set-option -g status-right '#[fg=red][ #[fg=white]%R %d/%m #[fg=red]]#[default]'
+
+# modes
+set-option -g mode-bg default
+set-option -g mode-fg blue
+
+# inactive window format
+set-window-option -g window-status-format '#I:#W#F'
+set-window-option -g monitor-activity on
+#set-window-option -g monitor-content on # not available in tmux 2.0
+
+# activity in a window
+set-window-option -g window-status-activity-attr dim
+set-window-option -g window-status-activity-bg default
+set-window-option -g window-status-activity-fg yellow
+
+# content in a window # not available in tmux 2.0
+#set-window-option -g window-status-content-attr dim
+#set-window-option -g window-status-content-bg default
+#set-window-option -g window-status-content-fg red
+
+# active window format
+set-window-option -g window-status-current-fg white
+set-window-option -g window-status-current-bg default
+set-window-option -g window-status-current-format '#[fg=red](#[default]#I:#W#F#[fg=red])#[default]'
+
+# reload tmux configuration
+unbind r
+bind r source-file ~/.tmux.conf \; display "Configuration reloaded!"
+
+# Screen-like keybinds
+unbind C-b
+set -g prefix ^A
+set -g prefix2 ^Q
+bind a send-prefix
+bind q send-prefix
+
+unbind c
+bind c new-window
+unbind ^C
+bind ^C new-window
+
+unbind n
+bind n next-window
+unbind ^N
+bind ^N next-window
+
+unbind A
+bind A command-prompt "rename-window %%"
+
+unbind p
+bind p previous-window
+unbind ^P
+bind ^P previous-window
+
+unbind a
+bind a last-window
+unbind ^A
+bind ^A last-window
+
+unbind [
+bind Escape copy-mode
+
+unbind w
+bind w list-windows
+
+unbind k
+bind k confirm-before "kill-window"
+
+unbind l
+bind l refresh-client
+
+unbind '"'
+bind '"' choose-window
+
+ +

Aliases

+ +

I also use two functions with tmux (in ~/.bash_aliases).

+ +

The first one creates a new “mytmux” tmux session if one doesn’t exist yet, opens 10 shells and selects the first one.

+ +
mytmux() {
+  tmux has-session -t mytmux
+  if [ $? != 0 ]; then
+    tmux new-session -s mytmux -n $(hostname) -d
+    tmux new-window -t mytmux:1 -n $(hostname)
+    tmux new-window -t mytmux:2 -n $(hostname)
+    tmux new-window -t mytmux:3 -n $(hostname)
+    tmux new-window -t mytmux:4 -n $(hostname)
+    tmux new-window -t mytmux:5 -n $(hostname)
+    tmux new-window -t mytmux:6 -n $(hostname)
+    tmux new-window -t mytmux:7 -n $(hostname)
+    tmux new-window -t mytmux:8 -n $(hostname)
+    tmux new-window -t mytmux:9 -n $(hostname)
+    tmux select-window -t mytmux:0
+  fi
+  tmux attach -t mytmux
+}
+
+ +

The second one changes the tmux window name whenever I ssh to a remote host, and switches the window name back to the name of my computer when I logout from the host.

+ +
if [ -n "$TMUX" ]; then
+  ssh() {
+    if [ $# -le 2 ]; then
+      tmux rename-window "${@: -1}"
+      command ssh "$@"
+      tmux rename-window "$(hostname)"
+    else
+      command ssh "$@"
+    fi
+  }
+fi
+
+ +

Conclusion

+ +

That’s all ! As always, please do leave a comment if you’ve found something useful in this article !

+ +
+ + + +
+ +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + diff --git a/public/2016/03/13/mysql-backup-script/index.html b/public/2016/03/13/mysql-backup-script/index.html new file mode 100644 index 0000000..e12d960 --- /dev/null +++ b/public/2016/03/13/mysql-backup-script/index.html @@ -0,0 +1,431 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MySQL backup script · Sysadmining. All day. Every day. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ +
+ + + +
+ + + + +
+ +
+

MySQL backup script

+ + + +
+ +
+ + +

I wrote a MySQL database backup script a while back. I known they are more than enough of them already floating around the internet, but hey, I figured I’d share it here anyway.

+ +

The script

+ +

For the script to work, you’ll need to edit a few variable to match your configuration.

+ +
    +
  • BACKUPDIR is the path of the directory where you want your backups to be stored.
  • +
  • BACKUPUSR is the user that will connect to MySQL to dump the databases. It should have access to all you databases without needing a password.
  • +
  • EXCLUDELIST is a list of databases that should not be backed-up. Leaving it as is is probably fine.
  • +
+ +
#!/bin/bash
+
+BACKUPDIR="/home/user/backup"
+BACKUPUSR="user"
+EXCLUDELIST="^Databases$|^information_schema$|^mysql$|^performance_schema$"
+
+sqlbk() {
+  for each in $(mysqlshow | awk '/[[:alnum:]]/{print $2}'); do
+    if [[ $each =~ $EXCLUDELIST ]]; then
+      true
+    else
+      mysqldump $each | bzip2 > ${BACKUPDIR}/${each}.sql.bz2
+      chown ${BACKUPUSR}: ${BACKUPDIR}/${each}.sql.bz2 && chmod 600 ${BACKUPDIR}/${each}.sql.bz2
+    fi
+  done
+}
+
+[[ -e /etc/init.d/mysql ]] && sqlbk
+
+ +

I personnaly have this script running once a week, in my user’s personnal crontab (editable using the crontab -e command) :

+ +
## WEEKLY DATABASE BACKUP
+@weekly /home/user/bin/backupdb
+
+ +

Conclusion

+ +

You’ve probably noticed that the script erases the previous backup when a new one is made.

+ +

I don’t need to keep multiple versions of the same database backup on my servers because they are all saved remotely on a daily basis using Rsnapshot. I’ll probably write an article on the subject in the future.

+ +

As usual, feedback is always appreciated !

+ +
+ + + +
+ +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + diff --git a/public/2016/03/26/webdav-with-nginx/index.html b/public/2016/03/26/webdav-with-nginx/index.html new file mode 100644 index 0000000..ad3fd34 --- /dev/null +++ b/public/2016/03/26/webdav-with-nginx/index.html @@ -0,0 +1,713 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WebDAV with nginx · Sysadmining. All day. Every day. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ +
+ + + +
+ + + + +
+ +
+

WebDAV with nginx

+ + + +
+ +
+ + +

This website has been hosted on an Online.net dedicated server since its creation. I’ve been one of their customers for the past 3 years now, and I still don’t have anything bad to say about them.

+ +

They recently upgraded their personnal range, and I took the opportunity to upgrade from a single server running all of my services to 2 servers running LXC containers that are hosting my services.

+ +

It took me 2 days to migrate everything, but it was worth it. If I decide to switch servers again, I’ll have to migrate the containers instead of the services themselves. Considering they are stored on a separate BTRFS volume, it shouldn’t take me more than a few hours at most.

+ +

During the migration, I realized that I needed to make files that were hosted on one server accessible to the other. I could have gone with CIFS or NFS, but I wanted to have encryption built-in instead of having to rely on a VPN for that. Since I figured it was a good opportunity to learn something new, I ended up going with WebDAV.

+ +

In this tutorial, I’ll explain how I’ve configured a read-only WebDAV share using nginx and Let’sEncrypt SSL certificates between two Debian Jessie containers.

+ +

Server configuration

+ +

Installing the required packages

+ +

First thing first, we need to install the packages we’ll need for this configuration :

+ +
apt update
+
+apt -t jessie-backports install nginx letsencrypt
+apt install apache2-utils
+
+ +

Getting our first certificate from letsencrypt

+ +

letsencrypt configuration

+ +

Let’s create a configuration file for letsencrypt :

+ +
mkdir /etc/letsencrypt
+
+echo 'rsa-key-size = 3072
+renew-by-default
+text = True
+agree-tos = True
+renew-by-default = True
+authenticator = webroot
+email = admin@example.com
+webroot-path = /var/www/letsencrypt/' > /etc/letsencrypt/cli.ini
+
+ +

Please do modify admin@example.com by your actual e-mail address.

+ +

We also need to create the directory structure where letsencrypt ACME challenge temporary files will be stored :

+ +
mkdir -p /var/www/letsencrypt/.well-known
+
+ +

nginx configuration

+ +

We now need to configure nginx by adding the following in the /etc/nginx/sites-available/default file, anywhere in the server{} block that is configured to listen on port 80.

+ +
location /.well-known/acme-challenge {
+  root /var/www/letsencrypt;
+}
+
+ +

Let’s make sure that we haven’t done anything wrong :

+ +
nginx -t
+
+ +

The command should give you the following output :

+ +
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
+nginx: configuration file /etc/nginx/nginx.conf test is successful
+
+ +

If that’s the case, you can safely reload the nginx daemon :

+ +
nginx -s reload
+
+ +

Certificate request

+ +

Now that letsencrypt and nginx are properly configured, we can request our certificate from letsencrypt :

+ +
letsencrypt --config /etc/letsencrypt/cli.ini certonly -w /var/www/letsencrypt -d www.example.com
+
+ +

Please do modify www.example.com by your server’s FQDN, and please note that the letsencrypt servers need to be able to resolve that name to your server’s IP.

+ +

If everything goes well, your certificates will be generated and stored in the /etc/letsencrypt folder.

+ +

WebDAV configuration

+ +

Now that we’ve obtained our certificate from letsencrypt, we can begin configuring nginx.

+ +

First, we need to comment two SSL directives from the default nginx configuration :

+ +
sed -i '/ssl_/ s/^/#/' /etc/nginx/nginx.conf
+
+ +

Let’s now create a /etc/nginx/conf.d/ssl.conf with the following content :

+ +
ssl_session_timeout 1d;
+ssl_session_cache shared:SSL:50m;
+ssl_session_tickets off;
+
+ssl_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem;
+ssl_certificate_key /etc/letsencrypt/live/www.example.com/privkey.pem;
+ssl_trusted_certificate /etc/letsencrypt/live/www.example.com/fullchain.pem;
+
+ssl_dhparam /etc/nginx/ssl/dhparam.pem;
+
+ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
+ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS';
+ssl_prefer_server_ciphers on;
+
+add_header Strict-Transport-Security max-age=15768000;
+add_header X-Frame-Options DENY;
+add_header X-Content-Type-Options nosniff;
+
+ssl_stapling on;
+ssl_stapling_verify on;
+resolver 127.0.0.1 valid=300s;
+resolver_timeout 5s;
+
+ +

This configuration will work if you’re using a single certificate on your server. If not, you’ll have to remove the ssl_certificate, ssl_certificate_key and ssl_trusted_certificate directives from this file and move them to the correct server{} block.

+ +

We now need to generate a dhparam.pem file :

+ +
mkdir /etc/nginx/ssl && chmod 700 /etc/nginx/ssl
+openssl dhparam -out /etc/nginx/ssl/dhparam.pem 3072
+chmod 600 /etc/nginx/ssl/dhparam.pem
+
+ +

Let’s now generate a HTTP basic authentication file. This example creates a user named example :

+ +
mkdir /etc/nginx/auth
+
+htpasswd -c /etc/nginx/auth/webdav example
+New password:
+Re-type new password:
+Adding password for user user
+
+ +

This file has to be readable by the user running your webserver. For security reasons, we’ll make it readable only by him :

+ +
chown -R www-data:nogroup /etc/nginx/auth
+chmod 700 /etc/nginx/auth
+chmod 400 /etc/nginx/auth/webdav
+
+ +

Let’s now modify our /etc/nginx/sites-available/default file with the following content :

+ +
server {
+  listen 80 default_server;
+  listen [::]:80 default_server ipv6only=on;
+  server_name "";
+
+  return 444;
+}
+
+server {
+  listen 443 default_server ssl http2;
+  listen [::]:443 default_server ipv6only=on ssl http2;
+  server_name "";
+
+  return 444;
+}
+
+ +

We now have to create a /etc/nginx/sites-available/example file that will contain our actual webdav configuration. This example makes a data folder stored in /var/www/ accessible.

+ +
server {
+  listen 80;
+  listen [::]:80;
+  server_name www.example.com;
+  return 301 https://$server_name$request_uri;
+}
+
+server {
+  listen 443 ssl http2;
+  listen [::]:443 ssl http2;
+  server_name www.example.com;
+
+  root /var/www;
+
+  location / {
+    index index.html;
+  }
+
+  location /.well-known/acme-challenge {
+    root /var/www/letsencrypt;
+  }
+
+  location /data {
+    client_body_temp_path /tmp;
+    dav_methods PUT DELETE MKCOL COPY MOVE;
+    dav_ext_methods PROPFIND OPTIONS;
+    create_full_put_path on;
+    dav_access user:r group:r;
+
+    auth_basic "Restricted access";
+    auth_basic_user_file auth/webdav;
+
+    limit_except GET {
+      allow <YOUR IP HERE>;
+      deny all;
+    }
+  }
+
+}
+
+ +

The last thing we have to do is to create a symlink so that nginx will load our configuration :

+ +
ln -s /etc/nginx/sites-available/example /etc/nginx/sites-enabled/example
+
+ +

Like before, let’s make sure our configuration is correct and then reload the daemon :

+ +
nginx -t
+nginx -s reload
+
+ +

That’s it for the WebDAV configuration server-side !

+ +

nginx monitoring

+ +

If you’re using monit, you can easily monitor the nginx daemon by copying the following in /etc/monit/conf.d/nginx :

+ +
check process nginx
+  with pidfile "/run/nginx.pid"
+  start program = "/bin/systemctl start nginx"
+  stop program = "/bin/systemctl stop nginx"
+  alert monit@example.com
+
+ +

Certificates auto-renewal

+ +

This goes beyond the scope of the article, but since letsencrypt certficates are only valid for 3 months, you’ll need to renew them regularily. You can do so manually or you can setup a cron that does it for you.

+ +

I personnaly use the following script :

+ +
#!/bin/bash
+
+PRG="/usr/bin/letsencrypt"
+CONFIG="/etc/letsencrypt/cli.ini"
+MAILDEST="admin@example.com"
+GLOBAL=0
+
+# www.example.com
+$PRG --config $CONFIG certonly -w /var/www/letsencrypt -d www.example.com
+[[ $? != 0 ]] && GLOBAL=$(( $GLOBAL + 1 ))
+
+if [[ $GLOBAL == 0 ]]; then
+  /usr/sbin/nginx -s reload
+else
+  echo "Something went wrong while renewing the certificates on $(hostname -f)
+  Manual action needed." | mail -s "Letsencrypt error on $(hostname -f)" $MAILDEST
+fi
+
+ +

You can add multiple domains in the script. As long as you add all 3 lines for each domain, it will not automatically reload nginx if one or more certificate could not be renewed and will send an e-mail to the address configured in the MAILDEST variable.

+ +

You can configure this script in the root user crontab using the crontab -e command :

+ +
## LETSENCRYPT CERTIFICATE AUTORENEWAL
+30 03 01 */2 * /root/bin/tlsrenew
+
+ +

This will run the script every two months, on the first day of the month, at 3:30 AM.

+ +

Client configuration

+ +

Installing the required packages

+ +

A single package is required to mount a webdav volume on Debian :

+ +
apt update && apt install davfs2
+
+ +

Mounting the share manually

+ +

If like me, you want to mount your webdav share in a LXC container, you’ll first need to make sure that the following line is present in its configuration file :

+ +
lxc.cgroup.devices.allow = c 10:229 rwm
+
+ +

You’ll also need to create the /dev/fuse node in the container :

+ +
mknod /dev/fuse c 10 229
+
+ +

In any case, we have to edit the /etc/davfs2/secrets file to add the mount point, username and password that will be used to mount the share :

+ +
echo '/data webdav notanactualpassword' >> /etc/davfs2/secrets
+
+ +

Once that’s done, we can mount our share with the following command :

+ +
mount -t davfs https://www.example.com/data /data -o ro,dir_mode=750,file_mode=640,uid=root,gid=root
+
+ +

You might need to edit the parameters depending on which users you want to make the share available to.

+ +

Mouting the share on boot

+ +

A davfs volume can be mounted via the /etc/fstab file, but I decided to use monit instead so that the volume would be mounted again automatically should my WebDAV server reboot.

+ +

In order to do so, I first created a davfs.txt file in the /var/www/data folder on my WebDAV server :

+ +
touch /var/www/data/davfs.txt
+
+ +

I then created the following /root/bin/mount_davfs script :

+ +
#!/bin/bash
+
+mknod /dev/fuse c 10 229
+mount -t davfs https://www.example.com/data /data -o ro,dir_mode=750,file_mode=640,uid=root,gid=root
+
+ +

The last thing I did was create a /etc/monit/conf.d/davfs file with the following content :

+ +
check file davfs with path /data/davfs.txt
+  alert monit@example.com
+  if does not exist then exec "/root/bin/mount_davfs"
+
+ +

That way, if monit notices that the /data/davfs.txt file becomes inaccessible for some reason, it will try remouting the share.

+ +

Conclusion

+ +

That’s all ! Hopefully this has been useful to someone. Please do comment below if you have any question or if this has been helpful !

+ +
+ + + +
+ +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + diff --git a/public/2018/04/14/dns-zone-versioning/index.html b/public/2018/04/14/dns-zone-versioning/index.html new file mode 100644 index 0000000..4c56f9a --- /dev/null +++ b/public/2018/04/14/dns-zone-versioning/index.html @@ -0,0 +1,519 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DNS zone versioning · Sysadmining. All day. Every day. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ +
+ + + +
+ + + + +
+ +
+

DNS zone versioning

+ + + +
+ +
+ + +

I’ve been using PowerDNS with a SQL backend as a hidden master DNS server for a few years now.

+ +

I’ve been wanting to write a quick shell script to version my DNS zones for a while, and since I’ve finally taken the time to do so today, I figured I’d share it here.

+ +

The script uses PowerDNS API to list the configured zones. It then exports them to a file in an AXFR-like format, commits and finally pushes them on a git repository

+ +

Configuration

+ +

PowerDNS

+ +

For the script to work, we have to activate PowerDNS’ API.

+ +

To do so, let’s create a /etc/powerdns/pdns.d/api.conf file with the following content :

+ +
api=yes
+api-key=mysupersecretapikey
+webserver=yes
+webserver-address=10.0.0.10
+webserver-allow-from=10.0.0.0/8
+webserver-port=8081
+
+ +

You should change mysupersecretapikey to an actual secret.

+ +

You should also adapt the webserver-address and webserver-allow-from to reflect your network configuration.

+ +

Once the file is created, we have to restart pdnsd :

+ +
systemctl restart pdns.service
+
+ +

N.B. : As with all my other articles, I’m assuming here you’re running Debian. The path of the configuration file you have to create or edit might not be the same if you’re running another distribution or if you’ve installed PowerDNS from source.

+ +

jq

+ +

jq is required for the script to work, so let’s install it !

+ +
apt install jq
+
+ +

Git

+ +

We now have to create a git repository to host our zone files.

+ +

To do so, you can follow my previous tutorial on the subject if you want.

+ +

I’ve personnaly migrated my git repos to a self-hosted Gogs installation a while back.

+ +

If you don’t care about your zones content being public (it already is, technically), you could create a GitHub repo for that use (or on any other available git hosting).

+ +

Once you’ve created your repo, you should clone it on the machine that will run the script. For me, the path to the repo will be /home/captainark/backup/dnsexport.

+ +
apt install git
+mkdir ~/backup && cd ~/backup
+git clone ssh://git@git.captainark.net/captainark/dnsexport.git
+
+ +

You should also create a ~/.gitconfig for the user that will run the script with the following parameters configured :

+ +
[user]
+        email = captainark@captainark.net
+        name = CaptainArk
+[push]
+        default = simple
+
+ +

Also, make sure your user can push to the remote server before running the script. The following should work :

+ +
cd ~/backup/dnsexport
+echo '# DNSEXPORT' > README.md
+git add README.md
+git commit README.md -m 'adding README'
+git push
+
+ +

Script

+ +

Once we’ve finished configuring PowerDNS and Git, we can run the script.

+ +

You can copy the following to ~/bin/dnsexport :

+ +
#!/bin/bash
+
+ApiKey="mysupersecretapikey"
+PdnsUrl="10.0.0.10:8081"
+PdnsServerName="localhost"
+PdnsZoneUrl="http://${PdnsUrl}/api/v1/servers/${PdnsServerName}/zones"
+ZoneList=$(/usr/bin/curl -sH "X-API-Key: ${ApiKey}" ${PdnsZoneUrl} | jq -r '.[].id')
+ExportFolder="/home/captainark/backup/dnsexport"
+
+updateremote() {
+  cd $ExportFolder
+  git add db.${Zone%.}
+  git commit -m "Automated commit due to modification on ${Zone%.} at $(date -Iseconds)"
+  git push
+  cd -
+}
+
+for Zone in ${ZoneList}; do
+  ZoneFile="${ExportFolder}/db.${Zone%.}"
+  CurrentShaSum=$(/usr/bin/sha256sum ${ZoneFile})
+  /usr/bin/curl -o ${ZoneFile} -sH "X-API-Key: ${ApiKey}" ${PdnsZoneUrl}/${Zone}/export
+  NewShaSum=$(/usr/bin/sha256sum ${ZoneFile})
+  [[ ${NewShaSum%  *} != ${CurrentShaSum%  *} ]] && updateremote
+done
+
+ +

It’s nothing fancy, but it does the job.

+ +

You’ll have to adapt the ApiKey, PdnsUrl and ExportFolder variables to your configuration.

+ +

Once that’s done, let’s fix the permissions on the script :

+ +
chmod 700 ~/bin/dnsexport
+
+ +

You should run the script manually once to make sure everything is working OK. If it is, you should see a new commit on the repo for each zone you have configured in PowerDNS.

+ +

Once the script has executed once without issue, you can schedule it regularly. I have it running every 10 minutes in my user’s crontab :

+ +
crontab -e
+# DNSEXPORT
+*/10 * * * * /home/captainark/bin/dnsexport
+
+ +

Conclusion

+ +

That’s all !

+ +

As always, if you’ve found this article useful, please feel free to make use of the comments section below !

+ +

Hopefully it won’t take as long before I write another article here next time !

+ +
+ + + +
+ +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + diff --git a/public/2018/11/27/self-hosted-report-uri/index.html b/public/2018/11/27/self-hosted-report-uri/index.html new file mode 100644 index 0000000..a335fb7 --- /dev/null +++ b/public/2018/11/27/self-hosted-report-uri/index.html @@ -0,0 +1,527 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Self-hosted report-uri · Sysadmining. All day. Every day. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ +
+ + + +
+ + + + +
+ +
+

Self-hosted report-uri

+ + + +
+ +
+ + +

I’ve been playing with the security headers for this website for the past few days, most notably with the Content-Security-Policy as well as the Expect-CT headers.

+ +

After having spent a few hours on this, I’m pretty happy with the results !

+ +

Screenshot-2018-11-27-at-21.52.58 +Source : Observatory by Mozilla

+ +

This website runs on a Ghost installation that I keep up-to-date. Since an update might mean that the site will try to load new external resources, the Content-Security-Policy header might need updating as well.

+ +

This header has a report-uri directive that makes web browsers send json-formatted messages of policy violations they encounter.

+ +

There’s a great website (Report-URI) that you can use to handle these reports. It allows up to 10.000 reports per month with a free account, which should be enough for a low to mid trafic website once you’ve setup your initial policy.

+ +

However, since I’m all about self-hosting all of the things, I figured I would configure my own report-uri using a php script.

+ +

The script

+ +

This script is heavily inspired from the ones available here and here.

+ +

The script checks that the content that was sent by the web browser is correctly formatted json message. It then removes the backslashes from the message, opens a connection to the local syslog daemon and sends the message.

+ +
<?php
+  // Send `204 No Content` status code.
+  http_response_code(204);
+  // collect data from post request
+  $data = file_get_contents('php://input');
+  if ($data = json_decode($data)) {
+    // Remove slashes from the JSON-formatted data.
+    $data = json_encode(
+      $data, JSON_UNESCAPED_SLASHES
+    );
+    # set options for syslog daemon
+    openlog('report-uri', LOG_NDELAY, LOG_USER);
+
+    # send warning about csp report
+    syslog(LOG_WARNING, $data);
+  }
+?>
+
+ +

Nginx

+ +

I won’t go into too much details regarding the nginx configuration here as I’ve written on this subject before.

+ +

Since I now have a wildcard Let’s Encrypt certificate on captainark.net, I’ve decided to use a dedicated vhost for my report-uri. However, a subfolder would work just as well. Just make sure the script is stored in a folder that nginx can access.

+ +

I’ve also decided to call the script index.php. You can call it whatever you want, but your report-uri directive will have to match the full URL of the script (if I had named the script report.php, my report-uri would have been https://report-uri.captainark.net/report.php instead of https://report-uri.captainark.net).

+ +

A nginx location configured as follows should do the trick :

+ +
  location / {
+    index index.php;
+
+    location ~ \.php$ {
+      try_files $uri =404;
+      fastcgi_split_path_info ^(.+?\.php)(/.*)$;
+      fastcgi_pass unix:/run/php/php7.0-fpm.sock;
+      fastcgi_index index.php;
+      include fastcgi.conf;
+      fastcgi_hide_header X-Powered-By;
+    }
+  }
+
+ +

I’ve omitted the security headers I usually configure in all locations here because they are outside of the scope of this article (HSTS, X-Frame-Options, etc.)

+ +

Once you’ve configured nginx, you can nginx -t to check that the syntax is correct, and nginx -s reload to reload the configuration.

+ +

Syslog-ng

+ +

Now that our reports are being sent to syslog-ng, we need to log them as proprely formatted json messages, in a dedicated file.

+ +

I’ve created a /etc/syslog-ng/conf.d/report-uri.conf configuration file for that :

+ +
filter f_report-uri { program ("report-uri"); };
+destination d_report-uri { file ("/var/log/report-uri/report-uri.json" template("{\"@timestamp\": \"${ISODATE}\", \"host\": \"${HOST}\", \"message\": ${MSG} }\n")); };
+log { source(s_src); filter (f_report-uri); destination (d_report-uri); flags(final); };
+
+ +

We’ll also need to create the folder for the logs :

+ +
mkdir -m 0750 /var/log/report-uri
+chown root:adm /var/log/report-uri
+
+ +

You can then reload syslog-ng with a systemctl reload syslog-ng.service

+ +

Policy violation messages should now start to appear in the /var/log/report-uri/report-uri.json

+ +

If you want to test that it’s working, you can create a csp.json file with the following content :

+ +
{"csp-report":{"document-uri":"https://www.captainark.net/foo/bar","referrer":"https://www.google.com/","violated-directive":"default-src self","original-policy":"default-src self; report-uri https://report-uri.captainark.net","blocked-uri":"http://jscryptocurrency.cx"}}
+
+ +

You can now POST it to your report-uri :

+ +
curl -XPOST https://report-uri.captainark.net -d @csp.json
+
+ +

The message should be added to your report-uri.json log file, and you should be able to prettify it with jq :

+ +
tail -n1 /var/log/report-uri/report-uri.json | jq                                                                                                   
+{
+  "@timestamp": "2018-11-27T22:57:06+01:00",
+  "host": "webserver",
+  "message": {
+    "csp-report": {
+      "document-uri": "https://www.captainark.net/foo/bar",
+      "referrer": "https://www.google.com/",
+      "violated-directive": "default-src self",
+      "original-policy": "default-src self; report-uri https://report-uri.captainark.net",
+      "blocked-uri": "http://jscryptocurrency.cx"
+    }
+  }
+}
+
+ +

Logrotate

+ +

It’s always a good idea to configure a log rotation when you add a new log file. To do so, let’s create the /etc/logrotate.d/report-uri file with the following content :

+ +
/var/log/report-uri/report-uri.json {
+  rotate 8
+  weekly
+  notifempty
+  missingok
+  create 640 root adm
+  compress
+  copytruncate
+}
+
+ +

Conclusion

+ +

This configuration works as a report-uri for the Content-Security header as well as the newer Expect-CT header, and any future header that uses a report-uri directive (as long as the generated messages are json formatted).

+ +

Having a log file instead of the clean web interface of Report URI is not for everybody, but it is more than enough for my use case (this site gets like 10 clicks a day when I’m not playing with it so… yeah.)

+ +

Since the log messages are formatted in json, they should be pretty easy to integrate in Elasticsearch or Graylog. If I ever decide to configure one of those solutions, I should then be able to configure cool looking dashboards in Grafana as well.

+ +

As always, if you’ve found this article useful in any way, please let me know in the comments here, on Twitter or on the Fediverse if you’re a real cool kid !

+ +
+ + + +
+ +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + diff --git a/public/2018/12/03/debian-repos-over-https/index.html b/public/2018/12/03/debian-repos-over-https/index.html new file mode 100644 index 0000000..911ed62 --- /dev/null +++ b/public/2018/12/03/debian-repos-over-https/index.html @@ -0,0 +1,416 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Debian repos over HTTPS · Sysadmining. All day. Every day. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ +
+ + + +
+ + + + +
+ +
+

Debian repos over HTTPS

+ + + +
+ +
+ + +

I’ve been using deb.debian.org as the main debian repo on my servers pretty much since it’s become available.

+ +

I’ve recently realized that the service is available over HTTPS, and since I’m all about encrypting all of the things, I figured I’d configure it on my servers.

+ +

This is going to be a very short post on how to do the same.

+ +

Required package

+ +

apt can’t use repositories available over https without installing a required package first.

+ +
apt install apt-transport-https
+
+ +

The source.list file

+ +

Once the package has been installed, you can edit your /etc/apt/sources.list file with the following content :

+ +
deb https://deb.debian.org/debian stable main contrib non-free
+deb https://deb.debian.org/debian-security stable/updates main contrib non-free
+deb https://deb.debian.org/debian stable-updates main contrib non-free
+deb https://deb.debian.org/debian stretch-backports main contrib non-free
+
+ +

Or, if you’d rather follow the stretch releases instead of stable (to avoid upgrading to buster until you’re ready once it will become the new stable) :

+ +
deb https://deb.debian.org/debian stretch main contrib non-free
+deb https://deb.debian.org/debian-security stretch/updates main contrib non-free
+deb https://deb.debian.org/debian stretch-updates main contrib non-free
+deb https://deb.debian.org/debian stretch-backports main contrib non-free
+
+ +

Conclusion

+ +

That’s all! As I said, this was going to be a short post.

+ +

As always, feel free to contact me here, on Twitter or on the Fediverse!

+ +
+ + + +
+ +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + diff --git a/public/404.html b/public/404.html new file mode 100644 index 0000000..4d441aa --- /dev/null +++ b/public/404.html @@ -0,0 +1,313 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 404 Page not found · Sysadmining. All day. Every day. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

Sysadmining. All day. Every day.

+

Yet Another Blog about Linux and Networking

+
+
+ +
+ + +
+ +
+

404 Not Found

+

Sorry, but we couldn't find the page you're looking for.

+

Please head back home.

+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + diff --git a/public/about/index.html b/public/about/index.html new file mode 100644 index 0000000..01ed3fe --- /dev/null +++ b/public/about/index.html @@ -0,0 +1,419 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + About · Sysadmining. All day. Every day. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + + + + +
+ + + +
+ + + + + + +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + diff --git a/public/categories/index.html b/public/categories/index.html new file mode 100644 index 0000000..588ada5 --- /dev/null +++ b/public/categories/index.html @@ -0,0 +1,304 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Categories · Sysadmining. All day. Every day. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
+ + + +
+
+

Categories

+

+ +

+
+
+
+ +
+ + +
+ + +
+ + + + + +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + diff --git a/public/categories/index.xml b/public/categories/index.xml new file mode 100644 index 0000000..1a231e3 --- /dev/null +++ b/public/categories/index.xml @@ -0,0 +1,15 @@ + + + + Categories on Sysadmining. All day. Every day. + https://www.captainark.net/categories/ + Recent content in Categories on Sysadmining. All day. Every day. + Hugo -- gohugo.io + en-us + © 2015 - 2019 + + + + + + \ No newline at end of file diff --git a/public/categories/page/1/index.html b/public/categories/page/1/index.html new file mode 100644 index 0000000..444a05d --- /dev/null +++ b/public/categories/page/1/index.html @@ -0,0 +1 @@ +https://www.captainark.net/categories/ \ No newline at end of file diff --git a/public/css/nav.css b/public/css/nav.css new file mode 100644 index 0000000..724c3c9 --- /dev/null +++ b/public/css/nav.css @@ -0,0 +1,108 @@ +nav.breadcrumb{ + margin-bottom:0px; +} +nav.breadcrumb li { + display: inline ; + font-size: 14px; + font-family: helvetica; +} +nav.breadcrumb li a { + text-decoration: none; +} +nav.breadcrumb li a:after { + content: " >"; +} + + nav.navpage { + font-size: 1em; + font-family: helvetica; + width: 100%; + + } + +nav.navpage > ul { + background: #efefef; + background: linear-gradient(top, #efefef 0%, #bbbbbb 100%); + background: -moz-linear-gradient(top, #efefef 0%, #bbbbbb 100%); + background: -webkit-linear-gradient(top, #efefef 0%,#bbbbbb 100%); + box-shadow: 0px 0px 90px rgba(0,0,0,0.15); + padding: 0 20px; + border-radius: 10px; + list-style: none; + position: relative; + display: inline-table; + width: 100%; +} +nav.navpage li { + list-style: none; +} + + nav.navpage:after { + content: ""; clear: both; display: block; + } + +nav.navpage > ul > li > a { + font-weight: bold; + text-decoration: none; + font-size: 1.2em; + font-variant: small-caps; +} + + +nav.navpage ul { + padding-left: 0px; + margin-left: 0px; +} +nav.navpage ul ul li { + display: block; + margin-left: 30px; + line-height: 1.2em; +} +nav.navpage ul ul li a { + text-decoration: none +} +nav.navpage ul ul li a:hover { + text-decoration: underline; +} +nav.navpage ul ul li:after { + content: ""; +} + +nav.navpage ul ul li ul li{ + display: block; + font-size: smaller; + margin-left: 30px; + line-height: 1.1em; +} + +nav.navchildren{ + margin-top: 0px; + background: #efefef; + background: linear-gradient(top, #efefef 0%, #bbbbbb 100%); + background: -moz-linear-gradient(top, #efefef 0%, #bbbbbb 100%); + background: -webkit-linear-gradient(top, #efefef 0%,#bbbbbb 100%); + box-shadow: 0px 0px 90px rgba(0,0,0,0.15); + padding: 0 20px; + border-radius: 10px; + list-style: none; + position: relative; + display: inline-table; + width: 100%; +} + +nav.navchildren li { + font-size: smaller; + line-height: 1.1em; + font-family: helvetica; + /*display: block;*/ + +} +nav.navchildren a { + + line-height: 1.1em; + + text-decoration: none +} +nav.navchildren a:hover { + text-decoration: underline; +} \ No newline at end of file diff --git a/public/css/screen.css b/public/css/screen.css new file mode 100644 index 0000000..cbae3b5 --- /dev/null +++ b/public/css/screen.css @@ -0,0 +1,2152 @@ +/* ========================================================================== + Table of Contents + ========================================================================== */ + +/* + + 0. Normalize + 1. Icons + 2. General + 3. Utilities + 4. General + 5. Single Post + 6. Tag Archive + 7. Third Party Elements + 8. Pagination + 9. Footer + 10. Media Queries (Tablet) + 11. Media Queries (Mobile) + 12. Animations + 13. Suggestions + +*/ + +/* ========================================================================== + 0. Normalize.css v2.1.3 | MIT License | git.io/normalize | (minified) + ========================================================================== */ + +article, aside, details, +figcaption, figure, +footer, header, hgroup, +main, nav, section, +summary { display: block; } +audio, canvas, video { display: inline-block; } +audio:not([controls]) { display: none; height: 0; } +[hidden], template { display: none; } +html { + font-family: sans-serif; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; +} +body { margin: 0; } +a { background: transparent; } +a:focus { outline: thin dotted; } +a:active, a:hover { outline: 0; } +h1 { font-size: 2em; margin: 0.67em 0; } +abbr[title] { border-bottom: 1px dotted; } +b, strong { font-weight: 700; } +dfn { font-style: italic; } +hr { + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} +mark { background: #FF0; color: #000; } +code, kbd, pre, +samp { font-family: monospace, serif; font-size: 1em; } +pre { white-space: pre-wrap; } +q { quotes: "\201C" "\201D" "\2018" "\2019"; } +small { font-size: 80%; } +sub, sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} +sup { top: -0.5em; } +sub { bottom: -0.25em; } +img { border: 0; } +svg:not(:root) { overflow: hidden; } +figure { margin: 0; } +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} +legend { border: 0; padding: 0; } +button, input, select, +textarea { font-family: inherit; font-size: 100%; margin: 0; } +button, input { line-height: normal; } +button, select { text-transform: none; } +button, html input[type="button"], +input[type="reset"], input[type="submit"] { + -webkit-appearance: button; + cursor: pointer; +} +button[disabled], html input[disabled] { cursor: default; } +input[type="checkbox"], +input[type="radio"] { box-sizing: border-box; padding: 0; } +input[type="search"] { + -webkit-appearance: textfield; + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; + box-sizing: content-box; +} +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } +button::-moz-focus-inner, +input::-moz-focus-inner { border: 0; padding: 0; } +textarea { overflow: auto; vertical-align: top; } +table { border-collapse: collapse; border-spacing: 0; } + + +/* ========================================================================== + 1. Icons - Sets up the icon font and respective classes + ========================================================================== */ + +/* Import the font files */ +@font-face { + font-family: 'Open Sans'; + src: local('Open Sans'), local('OpenSans'), url('../fonts/Open_Sans/OpenSans-Regular.ttf') format('truetype'); + font-weight: normal; font-style: normal; +} + +@font-face { + font-family: 'Open Sans'; + src: local('Open Sans Bold'), local('OpenSans-Bold'), url('../fonts/Open_Sans/OpenSans-Bold.ttf') format('truetype'); + font-weight: bold; font-style: normal; +} + +@font-face { + font-family: 'Merriweather'; + src: local('Merriweather Light'), local('Merriweather-Light'), url('../fonts/Merriweather/Merriweather-Light.ttf') format('truetype'); + font-weight: light; font-style: normal; +} + +@font-face { + font-family: 'Merriweather'; + src: local('Merriweather Light Italic'), local('Merriweather-LightItalic'), url('../fonts/Merriweather/Merriweather-LightItalic.ttf') format('truetype'); + font-weight: light; font-style: italic; +} + +@font-face { + font-family: 'Merriweather'; + src: local('Merriweather Bold'), local('Merriweather-Bold'), url('../fonts/Merriweather/Merriweather-Bold.ttf') format('truetype'); + font-weight: bold; font-style: normal; +} + +@font-face { + font-family: 'Merriweather'; + src: local('Merriweather Bold Italic'), local('Merriweather-BoldItalic'), url('../fonts/Merriweather/Merriweather-BoldItalic.ttf') format('truetype'); + font-weight: bold; font-style: italic; +} + +@font-face { + font-family: 'Inconsolata'; + src: local('Inconsolata Regular'), local('Inconsolata-Regular'), url('../fonts/Inconsolata/Inconsolata-Regular.ttf') format('truetype'); + font-weight: normal; font-style: normal; +} + +/* Import the font file with the icons in it */ +@font-face { + font-family: 'genericons'; + src: url('../fonts/Genericons/Genericons.eot'); + src: url('../fonts/Genericons/Genericons.eot?#iefix') format('embedded-opentype'), + url('../fonts/Genericons/Genericons.woff') format('woff'), + url('../fonts/Genericons/Genericons.ttf') format('truetype'), + url('../fonts/Genericons/Genericons.svg#entypo') format('svg'); + font-weight: normal; font-style: normal; +} + +/* Apply these base styles to all icons */ +[class^="icon-"]:before, [class*=" icon-"]:before { + font-family: "genericons", "Open Sans", sans-serif; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + text-decoration: none !important; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.fa-icons { + text-decoration: none; +} + +/* In order to have correct background when using the monokai pygments style*/ +.highlight pre { + background-color: inherit; + color: #f8f8f2; +} + +/* Each icon is created by inserting the correct character into the + content of the :before pseudo element. Like a boss. */ +.icon-github:before { + content: "\f200"; +} + +.icon-codepen:before { + content: "\f216"; +} + +.icon-hugo:before{ + content:"\f216"; +} + +.icon-ghost:before { + content: "\f100"; +} +.icon-feed:before { + content: "\f413"; +} +.icon-twitter:before { + content: "\f202"; +} +.icon-flickr:before { + content: "\f211"; +} +.icon-mail:before { + content: "\f410"; +} +.icon-google-plus:before { + content: "\f206"; +} +.icon-facebook:before { + content: "\f203"; +} +.icon-instagram:before { + content: "\f215"; +} +.icon-pinterest:before { + content: "\f210"; +} +.icon-linkedin:before { + content: "\f208"; +} +.icon-arrow-left:before { + content: "\f431"; +} +.icon-stats:before { + content: "\f508"; +} +.icon-location:before { + content: "\f417"; +} +.icon-link:before { + content: "\f442"; +} + + +/* ========================================================================== + 2. General - Setting up some base styles + ========================================================================== */ + +html { + height: 100%; + max-height: 100%; + font-size: 62.5%; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +body { + height: 100%; + max-height: 100%; + font-family: "Merriweather", serif; + letter-spacing: 0.01rem; + font-size: 1.8rem; + line-height: 1.75em; + color: #3A4145; + -webkit-font-feature-settings: 'kern' 1; + -moz-font-feature-settings: 'kern' 1; + -o-font-feature-settings: 'kern' 1; + text-rendering: geometricPrecision; +} + +::-moz-selection { + background: #D6EDFF; +} + +::selection { + background: #D6EDFF; +} + +h1, h2, h3, +h4, h5, h6 { + -webkit-font-feature-settings: 'dlig' 1, 'liga' 1, 'lnum' 1, 'kern' 1; + -moz-font-feature-settings: 'dlig' 1, 'liga' 1, 'lnum' 1, 'kern' 1; + -o-font-feature-settings: 'dlig' 1, 'liga' 1, 'lnum' 1, 'kern' 1; + color: #2E2E2E; + line-height: 1.15em; + margin: 0 0 0.4em 0; + font-family: "Open Sans", sans-serif; + text-rendering: geometricPrecision; +} + +h1 { + font-size: 5rem; + letter-spacing: -2px; + text-indent: -3px; +} + +h2 { + font-size: 3.6rem; + letter-spacing: -1px; +} + +h3 { + font-size: 3rem; +} + +h4 { + font-size: 2.5rem; +} + +h5 { + font-size: 2rem; +} + +h6 { + font-size: 2rem; +} + +a { + color: #4A4A4A; + transition: color 0.3s ease; +} + +a:hover { + color: #111; +} + +p, ul, ol, dl { + -webkit-font-feature-settings: 'liga' 1, 'onum' 1, 'kern' 1; + -moz-font-feature-settings: 'liga' 1, 'onum' 1, 'kern' 1; + -o-font-feature-settings: 'liga' 1, 'onum' 1, 'kern' 1; + margin: 0 0 1.75em 0; + text-rendering: geometricPrecision; +} + +ol, ul { + padding-left: 3rem; +} + +ol ol, ul ul, +ul ol, ol ul { + margin: 0 0 0.4em 0; + padding-left: 2em; +} + +dl dt { + float: left; + width: 180px; + overflow: hidden; + clear: left; + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; + font-weight: 700; + margin-bottom: 1em; +} + +dl dd { + margin-left: 200px; + margin-bottom: 1em +} + +li { + margin: 0.4em 0; +} + +li li { + margin: 0; +} + +hr { + display: block; + height: 1px; + border: 0; + border-top: #EFEFEF 1px solid; + margin: 3.2em 0; + padding: 0; +} + +blockquote { + -moz-box-sizing: border-box; + box-sizing: border-box; + margin: 1.75em 0 1.75em -2.2em; + padding: 0 0 0 1.75em; + border-left: #4A4A4A 0.4em solid; +} + +blockquote p { + margin: 0.8em 0; + font-style: italic; +} + +blockquote small { + display: inline-block; + margin: 0.8em 0 0.8em 1.5em; + font-size: 0.9em; + color: #CCC; +} + +blockquote small:before { content: "\2014 \00A0"; } + +blockquote cite { + font-weight: 700; +} + +blockquote cite a { font-weight: normal; } + +mark { + background-color: #FFC336; +} + +code, tt { + padding: 1px 3px; + font-family: Inconsolata, monospace, sans-serif; + font-size: 0.85em; + white-space: pre-wrap; + border: #E3EDF3 1px solid; + background: #FDF6E3; + border-radius: 2px; +} + +pre { + -moz-box-sizing: border-box; + box-sizing: border-box; + margin: 0 0 1.75em 0; + border: #E3EDF3 1px solid; + width: 100%; + padding: 10px; + font-family: Inconsolata, monospace, sans-serif; + font-size: 0.9em; + white-space: pre; + overflow: auto; + background: #FDF6E3; + border-radius: 3px; + word-wrap: normal; + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + color: #333; +} + +pre code, pre tt { + font-size: inherit; + white-space: pre; + background: transparent; + border: none; + padding: 0; +} + +kbd { + display: inline-block; + margin-bottom: 0.4em; + padding: 1px 8px; + border: #CCC 1px solid; + color: #666; + text-shadow: #FFF 0 1px 0; + font-size: 0.9em; + font-weight: 700; + background: #F4F4F4; + border-radius: 4px; + box-shadow: + 0 1px 0 rgba(0, 0, 0, 0.2), + 0 1px 0 0 #fff inset; +} + +table { + -moz-box-sizing: border-box; + box-sizing: border-box; + margin: 1.75em 0; + width: 100%; + max-width: 100%; + background-color: transparent; +} + +table th, +table td { + padding: 8px; + line-height: 20px; + text-align: left; + vertical-align: top; + border-top: #EFEFEF 1px solid; +} + +table th { color: #000; } + +table caption + thead tr:first-child th, +table caption + thead tr:first-child td, +table colgroup + thead tr:first-child th, +table colgroup + thead tr:first-child td, +table thead:first-child tr:first-child th, +table thead:first-child tr:first-child td { + border-top: 0; +} + +table tbody + tbody { border-top: #EFEFEF 2px solid; } + +table table table { background-color: #FFF; } + +table tbody > tr:nth-child(odd) > td, +table tbody > tr:nth-child(odd) > th { + background-color: #F6F6F6; +} + +table.plain tbody > tr:nth-child(odd) > td, +table.plain tbody > tr:nth-child(odd) > th { + background: transparent; +} + +iframe, .fluid-width-video-wrapper { + display: block; + margin: 1.75em 0; +} + +/* When a video is inside the fitvids wrapper, drop the +margin on the iframe, cause it breaks stuff. */ +.fluid-width-video-wrapper iframe { + margin: 0; +} + + +/* ========================================================================== + 3. Utilities - These things get used a lot + ========================================================================== */ + +/* Clears shit */ +.clearfix:before, +.clearfix:after { + content: " "; + display: table; +} +.clearfix:after { clear: both; } +.clearfix { zoom: 1; } + +/* Hides shit */ +.hidden { + text-indent: -9999px; + visibility: hidden; + display: none; +} + +/* Creates a responsive wrapper that makes our content scale nicely */ +.inner { + position: relative; + width: 80%; + max-width: 960px; + margin: 0 auto; +} + +/* Centres vertically yo. (IE8+) */ +.vertical { + display: table-cell; + vertical-align: middle; +} + +/* Wraps the main content & footer */ +.site-wrapper { + position: relative; + z-index: 10; + min-height: 100%; + background: #fff; + -webkit-transition: -webkit-transform 0.5s ease; + transition: transform 0.5s ease; +} + +body.nav-opened .site-wrapper { + overflow-x: hidden; + -webkit-transform: translate3D(-240px, 0, 0); + -ms-transform: translate3D(-240px, 0, 0); + transform: translate3D(-240px, 0, 0); + -webkit-transition: -webkit-transform 0.3s ease; + transition: transform 0.3s ease; +} + + +/* ========================================================================== + 4. General - The main styles for the the theme + ========================================================================== */ + +/* Big cover image on the home page */ +.main-header { + position: relative; + display: table; + width: 100%; + height: 50vh; + margin-bottom: 5rem; + text-align: center; + background: #222 no-repeat center center; + background-size: cover; + overflow: hidden; +} + +.main-header .inner { + width: 80%; +} + +.main-header a.bloglogo { + text-decoration: none; +} + +.main-header a.bloglogo .icon-linkedin, +.main-header a.bloglogo .icon-codepen, +.main-header a.bloglogo .icon-github, +.main-header a.bloglogo .icon-twitter, +.main-header a.bloglogo .icon-facebook, +.main-header a.bloglogo .icon-instagram, +.main-header a.bloglogo .icon-pinterest, +.main-header a.bloglogo .icon-google-plus { + color: white; + font-size: 2em; +} + +.main-nav { + position: relative; + padding: 35px 40px; + margin: 0 0 30px 0; +} + +.main-nav a { + text-decoration: none; + font-family: 'Open Sans', sans-serif; +} + +/* Navigation */ +body.nav-opened .nav-cover { + position: fixed; + top: 0; + left: 0; + right: 240px; + bottom: 0; + z-index: 200; +} + +.nav { + position: fixed; + top: 0; + right: 0; + bottom: 0; + z-index: 5; + width: 240px; + opacity: 0; + background: #111; + margin-bottom: 0; + text-align: left; + overflow-y: auto; + -webkit-transition: -webkit-transform 0.5s ease, + opacity 0.3s ease 0.7s; + transition: transform 0.5s ease, + opacity 0.3s ease 0.7s; +font-family: "Open Sans","Myriad Pro","Lucida Grande","Lucida Sans Unicode","Lucida Sans",Geneva,Verdana,sans-serif; +font-weight: 700; +font-size: 11px; +color: #909090; +} + +body.nav-closed .nav { + -webkit-transform: translate3D(97px, 0, 0); + -ms-transform: translate3D(97px, 0, 0); + transform: translate3D(97px, 0, 0); +} + +body.nav-opened .nav { + opacity: 1; + -webkit-transition: -webkit-transform 0.3s ease, + opacity 0s ease 0s; + transition: transform 0.3s ease, + opacity 0s ease 0s; + -webkit-transform: translate3D(0, 0, 0); + -ms-transform: translate3D(0, 0, 0); + transform: translate3D(0, 0, 0); +} + +.nav-title { + position: absolute; + top: 45px; + left: 30px; + font-size: 16px; + font-weight: 100; + text-transform: uppercase; + color: #fff; +} + +.nav-close { + position: absolute; + top: 38px; + right: 25px; + width: 20px; + height: 20px; + padding: 0; + font-size: 10px; +} + +.nav-close:focus { + outline: 0; +} + +.nav-close:before, +.nav-close:after { + content: ''; + position: absolute; + top: 0; + width: 20px; + height: 1px; + background: rgb(150,150,150); + top: 15px; + -webkit-transition: background 0.15s ease; + transition: background 0.15s ease; +} + +.nav-close:before { + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); +} + +.nav-close:after { + -webkit-transform: rotate(-45deg); + -ms-transform: rotate(-45deg); + transform: rotate(-45deg); +} + +.nav-close:hover:before, +.nav-close:hover:after { + background: rgb(255,255,255); +} + +.nav ul { + padding: 90px 9% 5%; + list-style: none; + counter-reset: item; +} + +.nav li:before { + display: block; + float: right; + padding-right: 4%; + padding-left: 5px; + text-align: right; + font-size: 1.2rem; + vertical-align: bottom; + color: #B8B8B8; + content: counter(item, lower-roman); + counter-increment: item; +} +.nav li { + margin: 0; +} +.nav li a { + text-decoration: none; + line-height: 1.4; + font-size: 1.4rem; + display: block; + padding: 0.6rem 4%; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.nav li a:after { + display: inline-block; + content: " ......................................................."; + color: rgba(255,255,255,0.2); + margin-left: 5px; +} +.nav .nav-current:before { + color: #fff; +} +.nav .nav-current a:after { + content: " "; + border-bottom: rgba(255,255,255,0.5) 1px solid; + width: 100%; + height: 1px; +} + +.nav a:link, +.nav a:visited { + color: #B8B8B8; +} + +.nav li.nav-current a, +.nav a:hover, +.nav a:active, +.nav a:focus { + color: #fff; +} + +.subscribe-button { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + display: block; + position: absolute; + bottom: 30px; + left: 30px; + right: 30px; + height: 38px; + padding: 0 20px; + color: #111 !important; + text-align: center; + font-size: 12px; + font-family: "Open Sans", sans-serif; + text-transform: uppercase; + text-decoration: none; + line-height: 35px; + border-radius: 3px; + background: #fff; + transition: all ease 0.3s; +} +.subscribe-button:before { + font-size: 9px; + margin-right: 6px; +} + + +/* Create a bouncing scroll-down arrow on homepage with cover image */ +.scroll-down { + display: none; + position: absolute; + z-index: 100; + bottom: 45px; + left: 50%; + margin-left: -16px; + width: 34px; + height: 34px; + font-size: 44px; + text-align: center; + text-decoration: none; + color: rgba(255,255,255,0.7); + -webkit-animation: bounce 4s 2s infinite; + animation: bounce 4s 2s infinite; +} + +/* Stop it bouncing and increase contrast when hovered */ +.scroll-down:hover { + color: #fff; + -webkit-animation: none; + animation: none; +} + +/* Put a semi-opaque radial gradient behind the icon to make it more visible + on photos which happen to have a light background. */ +.home-template .main-header:after { + display: block; + content: " "; + width: 150px; + height: 130px; + border-radius: 100%; + position: absolute; + bottom: 0; + left: 50%; + margin-left: -75px; + background: radial-gradient(ellipse at center, rgba(0,0,0,0.15) 0%,rgba(0,0,0,0) 70%,rgba(0,0,0,0) 100%); +} + +/* Hide when there's no cover image or on page2+ */ +.no-cover .scroll-down, +.no-cover.main-header:after, +.archive-template .scroll-down, +.archive-template .main-header:after { + display: none +} + +/* Appears in the top right corner of your home page */ +.blog-logo { + display: block; + float: left; + background: none !important; + border: none !important; +} + +.blog-logo img { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + display: block; + height: 38px; + padding: 1px 0 5px 0; + width: auto; +} + +.menu-button { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + display: inline-block; + float: right; + height: 38px; + padding: 0 15px; + border: #bfc8cd 1px solid; + opacity: 1; + color: #9EABB3; + text-align: center; + font-size: 12px; + text-transform: uppercase; + line-height: 35px; + white-space: nowrap; + border-radius: 3px; + background: rgba(0,0,0,0.1); + transition: all 0.5s ease; +} +.menu-button:focus { + outline: 0; +} +.menu-button .burger { + font-size: 12px; + margin-right: 6px; +} + +body.nav-opened .menu-button { + padding: 0 12px; + background: #111 !important; + border-color: #111 !important; + color: #fff !important; + -webkit-transform: translate3D(94px, 0, 0); + -ms-transform: translate3D(94px, 0, 0); + transform: translate3D(94px, 0, 0); + transition: all 0.3s ease; +} +body.nav-opened .menu-button .word { + opacity: 0; + transition: all 0.3s ease; +} + +/* Special styles when overlaid on an image*/ +.main-nav.overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + height: 70px; + border: none; + background: linear-gradient(to bottom, rgba(0,0,0,0.2) 0%,rgba(0,0,0,0) 100%); +} +.no-cover .main-nav.overlay, +.no-cover .menu-button { + background: none; +} + +.main-nav.overlay a { + color: #fff; +} + +.main-nav.overlay .menu-button { + border-color: rgba(255,255,255,0.6); +} + +.main-nav.overlay a:hover { + color: #222; + border-color: #fff; + background: #fff; +} + +/* Add a border to the buttons on hover */ +.menu-button:hover { + border-color: #555; + color: #555; +} + +/* The details of your blog. Defined in ghost/settings/ */ +.page-title { + margin: 10px 0 10px 0; + font-size: 5rem; + letter-spacing: -1px; + font-weight: 700; + font-family: "Open Sans", sans-serif; + color: #fff; +} + +.page-description { + margin: 0; + font-size: 2rem; + line-height: 1.5em; + font-weight: 400; + font-family: "Merriweather", serif; + letter-spacing: 0.01rem; + color: rgba(255,255,255,0.8); +} + +.no-cover.main-header { + min-height: 160px; + max-height: 40vh; + background: #f5f8fa; +} + +.no-cover .page-title { + color: rgba(0,0,0,0.8); +} + +.no-cover .page-description { + color: rgba(0,0,0,0.5); +} + +.no-cover .main-nav.overlay .menu-button { + color: rgba(0,0,0,0.4); + border-color: rgba(0,0,0,0.3); +} + +/* Add subtle load-in animation for content on the home page */ +.home-template .page-title { + -webkit-animation: fade-in-down 0.6s; + animation: fade-in-down 0.6s; + -webkit-animation-delay: 0.2s; + animation-delay: 0.2s; +} +.home-template .page-description { + -webkit-animation: fade-in-down 0.9s; + animation: fade-in-down 0.9s; + -webkit-animation-delay: 0.1s; + animation-delay: 0.1s; +} + +/* Every post, on every page, gets this style on its
tag */ +.post { + position: relative; + width: 80%; + max-width: 960px; + margin: 4rem auto; + padding-bottom: 4rem; + border-bottom: #EBF2F6 1px solid; + word-wrap: break-word; + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +/* Add a little circle in the middle of the border-bottom on our .post + just for the lolz and stylepoints. */ +.post:after { + display: block; + content: ""; + width: 7px; + height: 7px; + border: #E7EEF2 1px solid; + position: absolute; + bottom: -5px; + left: 50%; + margin-left: -5px; + background: #FFF; + border-radius: 100%; + box-shadow: #FFF 0 0 0 5px; +} + +body:not(.post-template) .post-title { + font-size: 3.6rem; +} + +.post-title a { + text-decoration: none; +} + +.post-excerpt p { + margin: 0; + font-size: 0.9em; + line-height: 1.7em; +} + +.read-more { + text-decoration: none; +} + +.post-meta { + display: block; + margin: 1.75rem 0 0 0; + font-family: "Open Sans", sans-serif; + font-size: 1.5rem; + line-height: 2.2rem; + color: #9EABB3; +} + +.author-thumb { + width: 24px; + height: 24px; + float: left; + margin-right: 9px; + border-radius: 100%; +} + +.post-meta a { + color: #9EABB3; + text-decoration: none; +} + +.post-meta a:hover { + text-decoration: underline; +} + +.user-meta { + position: relative; + padding: 0.3rem 40px 0 100px; + min-height: 77px; +} + +.post-date { + display: inline-block; + margin-left: 8px; + padding-left: 12px; + margin-bottom: 6px; + border-left: #d5dbde 1px solid; + text-transform: uppercase; + font-size: 1.3rem; + white-space: nowrap; +} + +.user-image { + position: absolute; + top: 0; + left: 0; +} + +.user-name { + display: block; + font-weight: 700; +} + +.user-bio { + display: block; + max-width: 440px; + font-size: 1.4rem; + line-height: 1.5em; +} + +.publish-meta { + position: absolute; + top: 0; + right: 0; + padding: 4.3rem 0 4rem 0; + text-align: right; +} + +.publish-heading { + display: block; + font-weight: 700; +} + +.publish-date { + display: block; + font-size: 1.4rem; + line-height: 1.5em; +} + + +/* ========================================================================== + 5. Single Post - When you click on an individual post + ========================================================================== */ + +.post-template .post-header { + margin-bottom: 3.4rem; +} + +.post-template .post-title { + margin-bottom: 0; +} + +.post-template .post-meta { + margin: 0; +} + +.post-template .post-date { + padding: 0; + margin: 0; + border: none; +} + +/* Stop elements, such as img wider than the post content, from + creating horizontal scroll - slight hack due to imperfections + with browser width % calculations and rounding */ +.post-template .content { + overflow: hidden; +} + +/* Tweak the .post wrapper style */ +.post-template .post { + margin-top: 0; + border-bottom: none; + padding-bottom: 0; +} + +/* Kill that stylish little circle that was on the border, too */ +.post-template .post:after { + display: none; +} + +/* Keep images centered, and allow images wider than the main + text column to break out. */ +.post-content img { + display: block; + max-width: 126%; + height: auto; + padding: 0.6em 0; + /* Centers an image by (1) pushing its left edge to the + center of its container and (2) shifting the entire image + in the opposite direction by half its own width. + Works for images that are larger than their containers. */ + position: relative; + left: 50%; + -webkit-transform: translateX(-50%); /* for Safari and iOS */ + -ms-transform: translateX(-50%); /* for IE9 */ + transform: translateX(-50%); +} + +/* The author credit area after the post */ +.post-footer { + position: relative; + margin: 6rem 0 0 0; + padding: 6rem 0 0 0; + border-top: #EBF2F6 1px solid; +} + +.post-footer h4 { + font-size: 1.8rem; + margin: 0; +} + +.post-footer p { + margin: 1rem 0; + font-size: 1.4rem; + line-height: 1.75em; +} + +/* list of author links - location / url */ +.author-meta { + padding: 0; + margin: 0; + list-style: none; + font-size: 1.4rem; + line-height: 1; + font-style: italic; + color: #9EABB3; +} + +.author-meta a { + color: #9EABB3; +} +.author-meta a:hover { + color: #111; +} + +/* Create some space to the right for the share links */ +.post-footer .author { + margin-right: 180px; +} + +.post-footer h4 a { + color: #2e2e2e; + text-decoration: none; +} + +.post-footer h4 a:hover { + text-decoration: underline; +} + +/* Drop the share links in the space to the right. + Doing it like this means it's easier for the author bio + to be flexible at smaller screen sizes while the share + links remain at a fixed width the whole time */ +.post-footer .share { + position: absolute; + top: 6rem; + right: 0; + width: 180px; +} + +.post-footer .share a { + font-size: 1.8rem; + display: inline-block; + margin: 1rem 1.6rem 1.6rem 0; + color: #BBC7CC; + text-decoration: none; +} + +.post-footer .share a:hover { + color: #50585D; +} + + +/* ========================================================================== + 6. Author profile + ========================================================================== */ + +.post-head.main-header { + height: 65vh; + min-height: 180px; +} + +.no-cover.post-head.main-header { + height: 85px; + min-height: 0; + margin-bottom: 0; + background: transparent; +} + +.tag-head.main-header { + height: 40vh; + min-height: 180px; +} + +.author-head.main-header { + height: 40vh; + min-height: 180px; +} + +.no-cover.author-head.main-header { + height: 10vh; + min-height: 100px; + background: transparent; +} + +.author-profile { + padding: 0 15px 5rem 15px; + border-bottom: #EBF2F6 1px solid; + text-align: center; +} + +/* Add a little circle in the middle of the border-bottom */ +.author-profile:after { + display: block; + content: ""; + width: 7px; + height: 7px; + border: #E7EEF2 1px solid; + position: absolute; + bottom: -5px; + left: 50%; + margin-left: -5px; + background: #FFF; + border-radius: 100%; + box-shadow: #FFF 0 0 0 5px; +} + +.author-image { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + display: block; + position: absolute; + top: -40px; + left: 50%; + margin-left: -40px; + width: 80px; + height: 80px; + border-radius: 100%; + overflow: hidden; + padding: 6px; + background: #fff; + z-index: 2; + box-shadow: #E7EEF2 0 0 0 1px; +} + +.author-image .img { + position: relative; + display: block; + width: 100%; + height: 100%; + background-size: cover; + background-position: center center; + border-radius: 100%; +} + +.author-profile .author-image { + position: relative; + left: auto; + top: auto; + width: 120px; + height: 120px; + padding: 3px; + margin: -100px auto 0 auto; + box-shadow: none; +} + +.author-title { + margin: 1.5rem 0 1rem; +} + +.author-bio { + font-size: 1.8rem; + line-height: 1.5em; + font-weight: 200; + color: #50585D; + letter-spacing: 0; + text-indent: 0; +} + +.author-meta { + margin: 1.6rem 0; +} +/* Location, website, and link */ +.author-profile .author-meta { + margin: 2rem 0; + font-family: "Merriweather", serif; + letter-spacing: 0.01rem; + font-size: 1.7rem; +} +.author-meta span { + display: inline-block; + margin: 0 2rem 1rem 0; + word-wrap: break-word; +} +.author-meta a { + text-decoration: none; +} + +/* Turn off meta for page2+ to make room for extra + pagination prev/next links */ +.archive-template .author-profile .author-meta { + display: none; +} + +/* ========================================================================== + 7. Third Party Elements - Embeds from other services + ========================================================================== */ + +/* Github */ +.gist table { + margin: 0; + font-size: 1.4rem; +} +.gist td { + line-height: 1.4; +} +.gist .line-number { + min-width: 25px; +} + +/* Pastebin */ +.content .embedPastebin { + margin-bottom: 1.75em; +} + +/* ========================================================================== + 8. Pagination - Tools to let you flick between pages + ========================================================================== */ + +/* The main wrapper for our pagination links */ +.pagination { + position: relative; + width: 80%; + max-width: 710px; + margin: 4rem auto; + font-family: "Open Sans", sans-serif; + font-size: 1.3rem; + color: #9EABB3; + text-align: center; +} + +.pagination a { + color: #9EABB3; + transition: all 0.2s ease; +} + +/* Push the previous/next links out to the left/right */ +.older-posts, +.newer-posts { + position: absolute; + display: inline-block; + padding: 0 15px; + border: #bfc8cd 1px solid; + text-decoration: none; + border-radius: 4px; + transition: border 0.3s ease; +} + +.older-posts { + right: 0; +} + +.page-number { + display: inline-block; + padding: 2px 0; + min-width: 100px; +} + +.newer-posts { + left: 0; +} + +.older-posts:hover, +.newer-posts:hover { + color: #889093; + border-color: #98a0a4; +} + +.extra-pagination { + display: none; + border-bottom: #EBF2F6 1px solid; +} +.extra-pagination:after { + display: block; + content: ""; + width: 7px; + height: 7px; + border: #E7EEF2 1px solid; + position: absolute; + bottom: -5px; + left: 50%; + margin-left: -5px; + background: #FFF; + border-radius: 100%; + box-shadow: #FFF 0 0 0 5px; +} +.extra-pagination .pagination { + width: auto; +} + +/* On page2+ make all the headers smaller */ +.archive-template .main-header { + max-height: 30vh; +} + +/* On page2+ show extra pagination controls at the top of post list */ +.archive-template .extra-pagination { + display: block; +} + + +/* ========================================================================== + 9. Footer - The bottom of every page + ========================================================================== */ + +.site-footer { + position: relative; + margin: 8rem 0 0 0; + padding: 0.5rem 15px; + /*border-top: #EBF2F6 1px solid;*/ + font-family: "Open Sans", sans-serif; + font-size: 1rem; + line-height: 1.75em; + color: #BBC7CC; +} + +.site-footer a { + color: #BBC7CC; + text-decoration: none; + font-weight: bold; +} + +.site-footer a:hover { + color: #50585D; +} + +.poweredby { + display: block; + width: 45%; + float: right; + text-align: right; +} + +.copyright { + display: block; + width: 45%; + float: left; +} + + +/* ========================================================================== + 10. Media Queries - Smaller than 900px + ========================================================================== */ + +@media only screen and (max-width: 900px) { + + blockquote { + margin-left: 0; + } + + .main-header { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + height: auto; + min-height: 240px; + height: 50vh; + padding: 15% 0; + } + + .scroll-down, + .home-template .main-header:after { display: none; } + + .archive-template .main-header { + min-height: 180px; + padding: 10% 0; + } + + .blog-logo img { + padding: 4px 0; + } + + .page-title { + font-size: 4rem; + letter-spacing: -1px; + } + + .page-description { + font-size: 1.8rem; + line-height: 1.5em; + } + + .post { + font-size: 0.95em + } + + body:not(.post-template) .post-title { + font-size: 3.2rem; + } + + hr { + margin: 2.4em 0; + } + + ol, ul { + padding-left: 2em; + } + + h1 { + font-size: 4.5rem; + text-indent: -2px; + } + + h2 { + font-size: 3.6rem; + } + + h3 { + font-size: 3.1rem; + } + + h4 { + font-size: 2.5rem; + } + + h5 { + font-size: 2.2rem; + } + + h6 { + font-size: 1.8rem; + } + + .author-profile { + padding-bottom: 4rem; + } + + .author-profile .author-bio { + font-size: 1.6rem; + } + + .author-meta span { + display: block; + margin: 1.5rem 0; + } + .author-profile .author-meta span { + font-size: 1.6rem; + } + + .post-head.main-header { + height:45vh; + } + + .tag-head.main-header, + .author-head.main-header { + height: 30vh; + } + + .no-cover.post-head.main-header { + height: 55px; + padding: 0; + } + + .no-cover.author-head.main-header { + padding: 0; + } + +} + + +/* ========================================================================== + 11. Media Queries - Smaller than 500px + ========================================================================== */ + +@media only screen and (max-width: 500px) { + + .main-header { + margin-bottom: 15px; + height: 40vh; + } + + .no-cover.main-header { + height: 30vh; + } + + .archive-template .main-header { + max-height: 20vh; + min-height: 160px; + padding: 10% 0; + } + + .main-nav { + padding: 5px; + margin-bottom: 2rem; + } + + .blog-logo { + padding: 5px; + } + + .blog-logo img { + height: 30px; + } + + .menu-button { + padding: 0 5px; + border-radius: 0; + border-color: transparent; + color: #2e2e2e; + background: transparent; + } + .menu-button:hover { + color: #2e2e2e; + border-color: transparent; + background: none; + } + body.nav-opened .menu-button { + background: none !important; + border: transparent !important; + } + + .main-nav.overlay a:hover { + color: #fff; + border-color: transparent; + background: transparent; + } + + .no-cover .main-nav.overlay { + background: none; + } + .no-cover .main-nav.overlay .menu-button { + border: none; + } + + .main-nav.overlay .menu-button { + border-color: transparent; + } + + .nav-title { + top: 25px; + + } + + .nav-close { + position: absolute; + top: 18px; + } + + .nav ul { + padding: 60px 9% 5%; + } + + .inner, + .pagination { + width: auto; + margin: 2rem auto; + } + + .post { + width: auto; + margin-top: 2rem; + margin-bottom: 2rem; + margin-left: 16px; + margin-right: 16px; + padding-bottom: 2rem; + line-height: 1.65em; + } + + .post-date { + display: none; + } + + .post-template .post-header { + margin-bottom: 2rem; + } + + .post-template .post-date { + display: inline-block; + } + + hr { + margin: 1.75em 0; + } + + p, ul, ol, dl { + font-size: 0.95em; + margin: 0 0 2.5rem 0; + } + + .page-title { + font-size: 3rem; + } + + .post-excerpt p { + font-size: 0.85em; + } + + .page-description { + font-size: 1.6rem; + } + + h1, h2, h3, + h4, h5, h6 { + margin: 0 0 0.3em 0; + } + + h1 { + font-size: 2.8rem; + letter-spacing: -1px; + } + + h2 { + font-size: 2.4rem; + letter-spacing: 0; + } + + h3 { + font-size: 2.1rem; + } + + h4 { + font-size: 1.9rem; + } + + h5 { + font-size: 1.8rem; + } + + h6 { + font-size: 1.8rem; + } + + body:not(.post-template) .post-title { + font-size: 2.5rem; + } + + .post-template .post { + padding-bottom: 0; + margin-bottom: 0; + } + + .post-template .site-footer { + margin-top: 0; + } + + .post-content img { + padding: 0; + width: calc(100% + 32px); /* expand with to image + margins */ + min-width: 0; + max-width: 112%; /* fallback when calc doesn't work */ + } + + .post-meta { + font-size: 1.3rem; + margin-top: 1rem; + } + + .post-footer { + padding: 5rem 0 3rem 0; + text-align: center; + } + + .post-footer .author { + margin: 0 0 2rem 0; + padding: 0 0 1.6rem 0; + border-bottom: #EBF2F6 1px dashed; + } + + .post-footer .share { + position: static; + width: auto; + } + + .post-footer .share a { + margin: 1.4rem 0.8rem 0 0.8rem; + } + + .author-meta li { + float: none; + margin: 0; + line-height: 1.75em; + } + + .author-meta li:before { + display: none; + } + + .older-posts, + .newer-posts { + position: static; + margin: 10px 0; + } + + .page-number { + display: block; + } + + .site-footer { + margin-top: 3rem; + } + + .author-profile { + padding-bottom: 2rem; + } + + .post-head.main-header { + height: 30vh; + } + + .tag-head.main-header, + .author-head.main-header { + height: 20vh; + } + + .author-profile .author-image { + margin-top: -70px; + } + + .author-profile .author-meta span { + font-size: 1.4rem; + } + + .archive-template .main-header .page-description { + display: none; + } + +} + + +/* ========================================================================== + 12. Animations + ========================================================================== */ + +/* Used to fade in title/desc on the home page */ +@-webkit-keyframes fade-in-down { + 0% { + opacity: 0; + -webkit-transform: translateY(-10px); + transform: translateY(-10px); + } + 100% { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); + } +} +@keyframes fade-in-down { + 0% { + opacity: 0; + -webkit-transform: translateY(-10px); + transform: translateY(-10px); + } + 100% { + opacity: 1; + -webkit-transform: translateY(0); + transform: translateY(0); + } +} + +/* Used to bounce .scroll-down on home page */ +@-webkit-keyframes bounce { + 0%, 10%, 25%, 40%, 50% { + -webkit-transform: translateY(0) ; + transform: translateY(0) ; + } + 20% { + -webkit-transform: translateY(-10px) ; + transform: translateY(-10px) ; + } + 30% { + -webkit-transform: translateY(-5px) ; + transform: translateY(-5px) ; + } +} +@keyframes bounce { + 0%, 20%, 50%, 80%, 100% { + -webkit-transform: translateY(0) ; + transform: translateY(0) ; + } + 40% { + -webkit-transform: translateY(-10px) ; + transform: translateY(-10px) ; + } + 60% { + -webkit-transform: translateY(-5px) ; + transform: translateY(-5px) ; + } +} + +/* ========================================================================== + 13. Suggestions - Further Reading Suggestions + ========================================================================== */ +.read-next { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-align: stretch; + -webkit-align-items: stretch; + -ms-flex-align: stretch; + align-items: stretch; + /*margin-top: 10rem;*/ +} + +.read-next-story { + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-flex: 1; + -webkit-flex-grow: 1; + -ms-flex-positive: 1; + flex-grow: 1; + min-width: 50%; + text-decoration: none; + position: relative; + text-align: center; + color: #fff; + background: #222 no-repeat center center; + background-size: cover; + overflow: hidden; +} +.read-next-story:hover:before { + background: rgba(0,0,0,0.8); + transition: all 0.2s ease; +} +.read-next-story:hover .post:before { + color: #222; + background: #fff; + transition: all 0.2s ease; +} + +.read-next-story:before { + content: ""; + display: block; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: rgba(0,0,0,0.7); + transition: all 0.5s ease; +} + +.read-next-story .post { + padding-top: 6rem; + padding-bottom: 6rem; + border-bottom: none; +} + +.read-next-story .post:before { + content: "Read This Next"; + padding: 4px 10px 5px; + text-transform: uppercase; + font-size: 1.1rem; + font-family: "Open Sans", sans-serif; + color: rgba(255,255,255,0.8); + border: rgba(255,255,255,0.5) 1px solid; + border-radius: 4px; + transition: all 0.5s ease; +} +.read-next-story.prev .post:before { + content: "You Might Enjoy"; +} + +.read-next-story h2 { + margin-top: 1rem; + color: #fff; +} + +.read-next-story p { + margin: 0; + color: rgba(255,255,255,0.8); +} + +/* Special styles for posts with no cover images */ +.read-next-story.no-cover { + background: #f5f8fa; +} + +.read-next-story.no-cover:before { + display: none; +} + +.read-next-story.no-cover .post:before { + color: rgba(0,0,0,0.5); + border-color: rgba(0,0,0,0.2); +} + +.read-next-story.no-cover h2 { + color: rgba(0,0,0,0.8); +} + +.read-next-story.no-cover p { + color: rgba(0,0,0,0.5); +} + +/* if there are two posts without covers, put a border between them */ +.read-next-story.no-cover + .read-next-story.no-cover { + border-left: rgba(0,0,100,0.04) 1px solid; + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +/* Correctly position site-footer when next to the .read-next container */ +.read-next + .site-footer { + position: absolute; + bottom: 0; + left: 0; + right: 0; + margin: 0; +} + +@media only screen and (max-width: 900px) { + .read-next { + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + /*margin-top: 4rem;*/ + margin-top: 0; + + } + + .read-next p { + display: none; + } + + .read-next-story.no-cover + .read-next-story.no-cover { + border-top: rgba(0,0,100,0.06) 1px solid; + border-left: none; + } +} + + +@media only screen and (max-width: 500px) { + .read-next { + margin-top: 0; + margin-bottom: -37px; + } + + .read-next .post { + width: 100%; + } +} + + +/* ========================================================================== + End of file. Animations should be the last thing here. Do not add stuff + below this point, or it will probably fuck everything up. + ========================================================================== */ diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..817e7a0 --- /dev/null +++ b/public/favicon.ico @@ -0,0 +1 @@ +Found. Redirecting to /favicon.png \ No newline at end of file diff --git a/public/favicon.png b/public/favicon.png new file mode 100644 index 0000000..f8a9589 Binary files /dev/null and b/public/favicon.png differ diff --git a/public/fonts/Genericons/Genericons.eot b/public/fonts/Genericons/Genericons.eot new file mode 100644 index 0000000..b5f8647 Binary files /dev/null and b/public/fonts/Genericons/Genericons.eot differ diff --git a/public/fonts/Genericons/Genericons.svg b/public/fonts/Genericons/Genericons.svg new file mode 100644 index 0000000..f813110 --- /dev/null +++ b/public/fonts/Genericons/Genericons.svg @@ -0,0 +1,543 @@ + + + + + +Created by FontForge 20120731 at Fri Oct 3 09:39:07 2014 + By Joen +Created by Joen with FontForge 2.0 (http://fontforge.sf.net) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/fonts/Genericons/Genericons.ttf b/public/fonts/Genericons/Genericons.ttf new file mode 100644 index 0000000..1f160dd Binary files /dev/null and b/public/fonts/Genericons/Genericons.ttf differ diff --git a/public/fonts/Genericons/Genericons.woff b/public/fonts/Genericons/Genericons.woff new file mode 100644 index 0000000..973e033 Binary files /dev/null and b/public/fonts/Genericons/Genericons.woff differ diff --git a/public/fonts/Genericons/example.html b/public/fonts/Genericons/example.html new file mode 100644 index 0000000..fdb7cf1 --- /dev/null +++ b/public/fonts/Genericons/example.html @@ -0,0 +1,719 @@ + + + +Genericons + + + + + + + + +
+ +
+
+ +

Genericons — A free, GPL, flexible icon font for blogs!

+ + + +
+
+ +
+

Genericons are vector icons embedded in a webfont designed to be clean and simple keeping with a generic aesthetic. Use for instant HiDPI or to easily change colors on the fly.

+
+ +
+
+ +
+
+ + + +
404
+ +
activity
+ +
anchor
+ +
aside
+ +
attachment
+ +
audio
+ +
bold
+ +
book
+ +
bug
+ +
cart
+ +
category
+ +
chat
+ +
checkmark
+ +
close
+ +
close-alt
+ +
cloud
+ +
cloud-download
+ +
cloud-upload
+ +
code
+ +
codepen
+ +
cog
+ +
collapse
+ +
comment
+ +
day
+ +
digg
+ +
document
+ +
dot
+ +
downarrow
+ +
download
+ +
draggable
+ +
dribbble
+ +
dropbox
+ +
dropdown
+ +
dropdown-left
+ +
edit
+ +
ellipsis
+ +
expand
+ +
external
+ +
facebook
+ +
facebook-alt
+ +
fastforward
+ +
feed
+ +
flag
+ +
flickr
+ +
foursquare
+ +
fullscreen
+ + + +
github
+ +
googleplus
+ +
googleplus-alt
+ +
handset
+ +
heart
+ +
help
+ +
hide
+ +
hierarchy
+ +
home
+ +
image
+ +
info
+ +
instagram
+ +
italic
+ +
key
+ +
leftarrow
+ + + +
linkedin
+ +
linkedin-alt
+ +
location
+ +
lock
+ +
mail
+ +
maximize
+ +
menu
+ +
microphone
+ +
minimize
+ +
minus
+ +
month
+ +
move
+ +
next
+ +
notice
+ +
paintbrush
+ +
path
+ +
pause
+ +
phone
+ +
picture
+ +
pinned
+ +
pinterest
+ +
pinterest-alt
+ +
play
+ +
plugin
+ +
plus
+ +
pocket
+ +
polldaddy
+ +
portfolio
+ +
previous
+ +
print
+ +
quote
+ +
rating-empty
+ +
rating-full
+ +
rating-half
+ +
reddit
+ +
refresh
+ +
reply
+ +
reply-alt
+ +
reply-single
+ +
rewind
+ +
rightarrow
+ + + +
send-to-phone
+ +
send-to-tablet
+ +
share
+ +
show
+ +
shuffle
+ +
sitemap
+ +
skip-ahead
+ +
skip-back
+ +
skype
+ +
spam
+ +
spotify
+ +
standard
+ +
star
+ +
status
+ +
stop
+ +
stumbleupon
+ +
subscribe
+ +
subscribed
+ +
summary
+ +
tablet
+ +
tag
+ +
time
+ +
top
+ +
trash
+ +
tumblr
+ +
twitch
+ +
twitter
+ +
unapprove
+ +
unsubscribe
+ +
unzoom
+ +
uparrow
+ +
user
+ +
video
+ +
videocamera
+ +
vimeo
+ +
warning
+ +
website
+ +
week
+ +
wordpress
+ +
xpost
+ +
youtube
+ +
zoom
+ + +
+ + + +
+ + + +
+ + + \ No newline at end of file diff --git a/public/fonts/Genericons/genericons.css b/public/fonts/Genericons/genericons.css new file mode 100644 index 0000000..f58f42e --- /dev/null +++ b/public/fonts/Genericons/genericons.css @@ -0,0 +1,213 @@ +/** + + Genericons + +*/ + + +/* IE8 and below use EOT and allow cross-site embedding. + IE9 uses WOFF which is base64 encoded to allow cross-site embedding. + So unfortunately, IE9 will throw a console error, but it'll still work. + When the font is base64 encoded, cross-site embedding works in Firefox */ + +@font-face { + font-family: 'Genericons'; + src: url('Genericons.eot'); +} + +@font-face { + font-family: 'Genericons'; + src: url(data:application/font-woff;charset=utf-8;base64,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) format('woff'), + url('Genericons.ttf') format('truetype'), + url('Genericons.svg#genericonsregular') format('svg'); + font-weight: normal; + font-style: normal; +} + +@media screen and (-webkit-min-device-pixel-ratio:0) { + @font-face { + font-family: "Genericons"; + src: url("./Genericons.svg#Genericons") format("svg"); + } +} + + +/** + * All Genericons + */ + +.genericon { + font-size: 16px; + vertical-align: top; + text-align: center; + -moz-transition: color .1s ease-in 0; + -webkit-transition: color .1s ease-in 0; + display: inline-block; + font-family: "Genericons"; + font-style: normal; + font-weight: normal; + font-variant: normal; + line-height: 1; + text-decoration: inherit; + text-transform: none; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + speak: none; +} + + +/** + * Individual icons + */ + +.genericon-404:before { content: "\f423"; } +.genericon-activity:before { content: "\f508"; } +.genericon-anchor:before { content: "\f509"; } +.genericon-aside:before { content: "\f101"; } +.genericon-attachment:before { content: "\f416"; } +.genericon-audio:before { content: "\f109"; } +.genericon-bold:before { content: "\f471"; } +.genericon-book:before { content: "\f444"; } +.genericon-bug:before { content: "\f50a"; } +.genericon-cart:before { content: "\f447"; } +.genericon-category:before { content: "\f301"; } +.genericon-chat:before { content: "\f108"; } +.genericon-checkmark:before { content: "\f418"; } +.genericon-close:before { content: "\f405"; } +.genericon-close-alt:before { content: "\f406"; } +.genericon-cloud:before { content: "\f426"; } +.genericon-cloud-download:before { content: "\f440"; } +.genericon-cloud-upload:before { content: "\f441"; } +.genericon-code:before { content: "\f462"; } +.genericon-codepen:before { content: "\f216"; } +.genericon-cog:before { content: "\f445"; } +.genericon-collapse:before { content: "\f432"; } +.genericon-comment:before { content: "\f300"; } +.genericon-day:before { content: "\f305"; } +.genericon-digg:before { content: "\f221"; } +.genericon-document:before { content: "\f443"; } +.genericon-dot:before { content: "\f428"; } +.genericon-downarrow:before { content: "\f502"; } +.genericon-download:before { content: "\f50b"; } +.genericon-draggable:before { content: "\f436"; } +.genericon-dribbble:before { content: "\f201"; } +.genericon-dropbox:before { content: "\f225"; } +.genericon-dropdown:before { content: "\f433"; } +.genericon-dropdown-left:before { content: "\f434"; } +.genericon-edit:before { content: "\f411"; } +.genericon-ellipsis:before { content: "\f476"; } +.genericon-expand:before { content: "\f431"; } +.genericon-external:before { content: "\f442"; } +.genericon-facebook:before { content: "\f203"; } +.genericon-facebook-alt:before { content: "\f204"; } +.genericon-fastforward:before { content: "\f458"; } +.genericon-feed:before { content: "\f413"; } +.genericon-flag:before { content: "\f468"; } +.genericon-flickr:before { content: "\f211"; } +.genericon-foursquare:before { content: "\f226"; } +.genericon-fullscreen:before { content: "\f474"; } +.genericon-gallery:before { content: "\f103"; } +.genericon-github:before { content: "\f200"; } +.genericon-googleplus:before { content: "\f206"; } +.genericon-googleplus-alt:before { content: "\f218"; } +.genericon-handset:before { content: "\f50c"; } +.genericon-heart:before { content: "\f461"; } +.genericon-help:before { content: "\f457"; } +.genericon-hide:before { content: "\f404"; } +.genericon-hierarchy:before { content: "\f505"; } +.genericon-home:before { content: "\f409"; } +.genericon-image:before { content: "\f102"; } +.genericon-info:before { content: "\f455"; } +.genericon-instagram:before { content: "\f215"; } +.genericon-italic:before { content: "\f472"; } +.genericon-key:before { content: "\f427"; } +.genericon-leftarrow:before { content: "\f503"; } +.genericon-link:before { content: "\f107"; } +.genericon-linkedin:before { content: "\f207"; } +.genericon-linkedin-alt:before { content: "\f208"; } +.genericon-location:before { content: "\f417"; } +.genericon-lock:before { content: "\f470"; } +.genericon-mail:before { content: "\f410"; } +.genericon-maximize:before { content: "\f422"; } +.genericon-menu:before { content: "\f419"; } +.genericon-microphone:before { content: "\f50d"; } +.genericon-minimize:before { content: "\f421"; } +.genericon-minus:before { content: "\f50e"; } +.genericon-month:before { content: "\f307"; } +.genericon-move:before { content: "\f50f"; } +.genericon-next:before { content: "\f429"; } +.genericon-notice:before { content: "\f456"; } +.genericon-paintbrush:before { content: "\f506"; } +.genericon-path:before { content: "\f219"; } +.genericon-pause:before { content: "\f448"; } +.genericon-phone:before { content: "\f437"; } +.genericon-picture:before { content: "\f473"; } +.genericon-pinned:before { content: "\f308"; } +.genericon-pinterest:before { content: "\f209"; } +.genericon-pinterest-alt:before { content: "\f210"; } +.genericon-play:before { content: "\f452"; } +.genericon-plugin:before { content: "\f439"; } +.genericon-plus:before { content: "\f510"; } +.genericon-pocket:before { content: "\f224"; } +.genericon-polldaddy:before { content: "\f217"; } +.genericon-portfolio:before { content: "\f460"; } +.genericon-previous:before { content: "\f430"; } +.genericon-print:before { content: "\f469"; } +.genericon-quote:before { content: "\f106"; } +.genericon-rating-empty:before { content: "\f511"; } +.genericon-rating-full:before { content: "\f512"; } +.genericon-rating-half:before { content: "\f513"; } +.genericon-reddit:before { content: "\f222"; } +.genericon-refresh:before { content: "\f420"; } +.genericon-reply:before { content: "\f412"; } +.genericon-reply-alt:before { content: "\f466"; } +.genericon-reply-single:before { content: "\f467"; } +.genericon-rewind:before { content: "\f459"; } +.genericon-rightarrow:before { content: "\f501"; } +.genericon-search:before { content: "\f400"; } +.genericon-send-to-phone:before { content: "\f438"; } +.genericon-send-to-tablet:before { content: "\f454"; } +.genericon-share:before { content: "\f415"; } +.genericon-show:before { content: "\f403"; } +.genericon-shuffle:before { content: "\f514"; } +.genericon-sitemap:before { content: "\f507"; } +.genericon-skip-ahead:before { content: "\f451"; } +.genericon-skip-back:before { content: "\f450"; } +.genericon-skype:before { content: "\f220"; } +.genericon-spam:before { content: "\f424"; } +.genericon-spotify:before { content: "\f515"; } +.genericon-standard:before { content: "\f100"; } +.genericon-star:before { content: "\f408"; } +.genericon-status:before { content: "\f105"; } +.genericon-stop:before { content: "\f449"; } +.genericon-stumbleupon:before { content: "\f223"; } +.genericon-subscribe:before { content: "\f463"; } +.genericon-subscribed:before { content: "\f465"; } +.genericon-summary:before { content: "\f425"; } +.genericon-tablet:before { content: "\f453"; } +.genericon-tag:before { content: "\f302"; } +.genericon-time:before { content: "\f303"; } +.genericon-top:before { content: "\f435"; } +.genericon-trash:before { content: "\f407"; } +.genericon-tumblr:before { content: "\f214"; } +.genericon-twitch:before { content: "\f516"; } +.genericon-twitter:before { content: "\f202"; } +.genericon-unapprove:before { content: "\f446"; } +.genericon-unsubscribe:before { content: "\f464"; } +.genericon-unzoom:before { content: "\f401"; } +.genericon-uparrow:before { content: "\f500"; } +.genericon-user:before { content: "\f304"; } +.genericon-video:before { content: "\f104"; } +.genericon-videocamera:before { content: "\f517"; } +.genericon-vimeo:before { content: "\f212"; } +.genericon-warning:before { content: "\f414"; } +.genericon-website:before { content: "\f475"; } +.genericon-week:before { content: "\f306"; } +.genericon-wordpress:before { content: "\f205"; } +.genericon-xpost:before { content: "\f504"; } +.genericon-youtube:before { content: "\f213"; } +.genericon-zoom:before { content: "\f402"; } + + + + diff --git a/public/fonts/Inconsolata/Inconsolata-Regular.ttf b/public/fonts/Inconsolata/Inconsolata-Regular.ttf new file mode 100644 index 0000000..fc981ce Binary files /dev/null and b/public/fonts/Inconsolata/Inconsolata-Regular.ttf differ diff --git a/public/fonts/Inconsolata/OFL.txt b/public/fonts/Inconsolata/OFL.txt new file mode 100644 index 0000000..e5c8ffe --- /dev/null +++ b/public/fonts/Inconsolata/OFL.txt @@ -0,0 +1,92 @@ +Copyright 2006 The Inconsolata Project Authors +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/public/fonts/Merriweather/Merriweather-Bold.ttf b/public/fonts/Merriweather/Merriweather-Bold.ttf new file mode 100644 index 0000000..9418489 Binary files /dev/null and b/public/fonts/Merriweather/Merriweather-Bold.ttf differ diff --git a/public/fonts/Merriweather/Merriweather-BoldItalic.ttf b/public/fonts/Merriweather/Merriweather-BoldItalic.ttf new file mode 100644 index 0000000..a710de2 Binary files /dev/null and b/public/fonts/Merriweather/Merriweather-BoldItalic.ttf differ diff --git a/public/fonts/Merriweather/Merriweather-Light.ttf b/public/fonts/Merriweather/Merriweather-Light.ttf new file mode 100644 index 0000000..68c00b4 Binary files /dev/null and b/public/fonts/Merriweather/Merriweather-Light.ttf differ diff --git a/public/fonts/Merriweather/Merriweather-LightItalic.ttf b/public/fonts/Merriweather/Merriweather-LightItalic.ttf new file mode 100644 index 0000000..dfa087c Binary files /dev/null and b/public/fonts/Merriweather/Merriweather-LightItalic.ttf differ diff --git a/public/fonts/Merriweather/OFL.txt b/public/fonts/Merriweather/OFL.txt new file mode 100644 index 0000000..141ec2d --- /dev/null +++ b/public/fonts/Merriweather/OFL.txt @@ -0,0 +1,92 @@ +Copyright (c) 2010-2016, Sorkin Type Co (www.sorkintype.com) with Reserved Font Name 'Merriweather'. Merriweather is a trademark of Sorkin Type Co. +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/public/fonts/Open_Sans/LICENSE.txt b/public/fonts/Open_Sans/LICENSE.txt new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/public/fonts/Open_Sans/LICENSE.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/public/fonts/Open_Sans/OpenSans-Bold.ttf b/public/fonts/Open_Sans/OpenSans-Bold.ttf new file mode 100644 index 0000000..fd79d43 Binary files /dev/null and b/public/fonts/Open_Sans/OpenSans-Bold.ttf differ diff --git a/public/fonts/Open_Sans/OpenSans-Regular.ttf b/public/fonts/Open_Sans/OpenSans-Regular.ttf new file mode 100644 index 0000000..db43334 Binary files /dev/null and b/public/fonts/Open_Sans/OpenSans-Regular.ttf differ diff --git a/public/images/author.jpg b/public/images/author.jpg new file mode 100644 index 0000000..af12e01 Binary files /dev/null and b/public/images/author.jpg differ diff --git a/public/images/cover.jpg b/public/images/cover.jpg new file mode 100644 index 0000000..f1af10b Binary files /dev/null and b/public/images/cover.jpg differ diff --git a/public/images/logo.png b/public/images/logo.png new file mode 100644 index 0000000..112b3a1 Binary files /dev/null and b/public/images/logo.png differ diff --git a/public/images/mozilla_observatory.png b/public/images/mozilla_observatory.png new file mode 100644 index 0000000..f735a89 Binary files /dev/null and b/public/images/mozilla_observatory.png differ diff --git a/public/images/tmux.png b/public/images/tmux.png new file mode 100644 index 0000000..9899041 Binary files /dev/null and b/public/images/tmux.png differ diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..30977aa --- /dev/null +++ b/public/index.html @@ -0,0 +1,537 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Sysadmining. All day. Every day. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +

Sysadmining. All day. Every day.

+

Yet Another Blog about Linux and Networking

+
+
+ +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + +
+
+

Debian repos over HTTPS

+
+
+ +

I’ve been using deb.debian.org as the main debian repo on my servers pretty much since it’s become available. +I’ve recently realized that the service is available over HTTPS, and since I’m all about encrypting all of the things, I figured I’d configure it on my servers. »

+ +
+
+ + + + + + + + Author image + + + + Antoine Joubert + + + + + +
+
+ + +
+
+

Self-hosted report-uri

+
+
+ +

I’ve been playing with the security headers for this website for the past few days, most notably with the Content-Security-Policy as well as the Expect-CT headers. +After having spent a few hours on this, I’m pretty happy with the results ! »

+ +
+
+ + + + + + + + Author image + + + + Antoine Joubert + + + + + +
+
+ + +
+
+

DNS zone versioning

+
+
+ +

I’ve been using PowerDNS with a SQL backend as a hidden master DNS server for a few years now. +I’ve been wanting to write a quick shell script to version my DNS zones for a while, and since I’ve finally taken the time to do so today, I figured I’d share it here. »

+ +
+
+ + + + + + + + Author image + + + + Antoine Joubert + + + + + +
+
+ + +
+
+

WebDAV with nginx

+
+
+ +

This website has been hosted on an Online.net dedicated server since its creation. I’ve been one of their customers for the past 3 years now, and I still don’t have anything bad to say about them. »

+ +
+
+ + + + + + + + Author image + + + + Antoine Joubert + + + + + +
+
+ + +
+
+

MySQL backup script

+
+
+ +

I wrote a MySQL database backup script a while back. I known they are more than enough of them already floating around the internet, but hey, I figured I’d share it here anyway. +The script For the script to work, you’ll need to edit a few variable to match your configuration. »

+ +
+
+ + + + + + + + Author image + + + + Antoine Joubert + + + + + +
+
+ + + + + + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + diff --git a/public/index.xml b/public/index.xml new file mode 100644 index 0000000..02782a7 --- /dev/null +++ b/public/index.xml @@ -0,0 +1,141 @@ + + + + Sysadmining. All day. Every day. + https://www.captainark.net/ + Recent content on Sysadmining. All day. Every day. + Hugo -- gohugo.io + en-us + © 2015 - 2019 + Sun, 06 Jan 2019 12:20:53 +0100 + + + + + + Resume + https://www.captainark.net/resume/ + Sun, 06 Jan 2019 12:20:53 +0100 + + https://www.captainark.net/resume/ + Profile Hi ! I&rsquo;m Antoine. I&rsquo;m a 31 years old Systems and Network administrator, specialized in Linux and network management. I am not currently looking for a new opportunity. +If you find my profile interesting or if you have any questions, please send me an email ! + + + + About + https://www.captainark.net/about/ + Sun, 06 Jan 2019 12:20:50 +0100 + + https://www.captainark.net/about/ + My blog. + + + + Debian repos over HTTPS + https://www.captainark.net/2018/12/03/debian-repos-over-https/ + Mon, 03 Dec 2018 00:00:00 +0100 + + https://www.captainark.net/2018/12/03/debian-repos-over-https/ + I&rsquo;ve been using deb.debian.org as the main debian repo on my servers pretty much since it&rsquo;s become available. +I&rsquo;ve recently realized that the service is available over HTTPS, and since I&rsquo;m all about encrypting all of the things, I figured I&rsquo;d configure it on my servers. + + + + Self-hosted report-uri + https://www.captainark.net/2018/11/27/self-hosted-report-uri/ + Tue, 27 Nov 2018 00:00:00 +0100 + + https://www.captainark.net/2018/11/27/self-hosted-report-uri/ + I&rsquo;ve been playing with the security headers for this website for the past few days, most notably with the Content-Security-Policy as well as the Expect-CT headers. +After having spent a few hours on this, I&rsquo;m pretty happy with the results ! + + + + DNS zone versioning + https://www.captainark.net/2018/04/14/dns-zone-versioning/ + Sat, 14 Apr 2018 00:00:00 +0100 + + https://www.captainark.net/2018/04/14/dns-zone-versioning/ + I&rsquo;ve been using PowerDNS with a SQL backend as a hidden master DNS server for a few years now. +I&rsquo;ve been wanting to write a quick shell script to version my DNS zones for a while, and since I&rsquo;ve finally taken the time to do so today, I figured I&rsquo;d share it here. + + + + WebDAV with nginx + https://www.captainark.net/2016/03/26/webdav-with-nginx/ + Sat, 26 Mar 2016 00:00:00 +0100 + + https://www.captainark.net/2016/03/26/webdav-with-nginx/ + This website has been hosted on an Online.net dedicated server since its creation. I&rsquo;ve been one of their customers for the past 3 years now, and I still don&rsquo;t have anything bad to say about them. + + + + MySQL backup script + https://www.captainark.net/2016/03/13/mysql-backup-script/ + Sun, 13 Mar 2016 00:00:00 +0100 + + https://www.captainark.net/2016/03/13/mysql-backup-script/ + I wrote a MySQL database backup script a while back. I known they are more than enough of them already floating around the internet, but hey, I figured I&rsquo;d share it here anyway. +The script For the script to work, you&rsquo;ll need to edit a few variable to match your configuration. + + + + My tmux configuration + https://www.captainark.net/2016/02/02/my-tmux-configuration/ + Tue, 02 Feb 2016 00:00:00 +0100 + + https://www.captainark.net/2016/02/02/my-tmux-configuration/ + tmux is a terminal mutiplexer. It lets you have multiples shells running in a single terminal emulator window and it keeps those shells running in the background should you need to close your terminal emulator. + + + + Debian updates with Ansible + https://www.captainark.net/2016/01/31/debian-updates-with-ansible/ + Sun, 31 Jan 2016 01:00:00 +0100 + + https://www.captainark.net/2016/01/31/debian-updates-with-ansible/ + I&rsquo;ve recently bought a HP Proliant Microserver Gen8 to play around with LXC and try new stuff. +From the 4 Debian machines I had to keep up-to-date, I now have 7, so it became quite time-consumming to manually SSH to each of them whenever an update became available. + + + + Private Git repo + https://www.captainark.net/2016/01/31/private-git-repo/ + Sun, 31 Jan 2016 00:00:00 +0100 + + https://www.captainark.net/2016/01/31/private-git-repo/ + I&rsquo;ve decided to migrate this blog to Pelican. I&rsquo;ve been playing around with it over the week-end, and it turns out to be way easier to manage than Jekyll. Themes are much easier to install and configure, so it ends up looking better as well ! + + + + Installing Ghost + https://www.captainark.net/2015/11/19/installing-ghost/ + Thu, 19 Nov 2015 00:00:00 +0100 + + https://www.captainark.net/2015/11/19/installing-ghost/ + I haven&rsquo;t published an article on here for over a year and a half&hellip; While this was mostly due to a lack of motivation, another reason was that I didn&rsquo;t enjoy the blogging system I was using. + + + + Flexget init script + https://www.captainark.net/2015/05/25/flexget-init-script/ + Mon, 25 May 2015 00:00:00 +0100 + + https://www.captainark.net/2015/05/25/flexget-init-script/ + I&rsquo;ve been using Flexget for the past two years or so as a download automator. +Since I wrote an init script for it a while back, and it is compatible with Debian Jessie / systemd, I figured I&rsquo;d share it here. + + + + Setting up a mail server + https://www.captainark.net/2015/04/24/setting-up-a-mail-server/ + Fri, 24 Apr 2015 00:00:00 +0100 + + https://www.captainark.net/2015/04/24/setting-up-a-mail-server/ + In this first tutorial, I&rsquo;ll explain how I&rsquo;ve configured my mail server using the following : + A server running Linux Debian (jessie) ; Postfix ; Postfix-policyd-spf-python ; Dovecot ; Spamassassin ; OpenDKIM ; OpenDMARC ; Monit ; Rainloop. + + + + \ No newline at end of file diff --git a/public/js/index.js b/public/js/index.js new file mode 100644 index 0000000..aacb397 --- /dev/null +++ b/public/js/index.js @@ -0,0 +1,56 @@ +/** + * Main JS file for Casper behaviours + */ + +/* globals jQuery, document */ +(function ($, undefined) { + "use strict"; + + var $document = $(document); + + $document.ready(function () { + + var $postContent = $(".post-content"); + $postContent.fitVids(); + + $(".scroll-down").arctic_scroll(); + + $(".menu-button[href='#'], .nav-cover, .nav-close").on("click", function(e){ + e.preventDefault(); + $("body").toggleClass("nav-opened nav-closed"); + }); + + }); + + // Arctic Scroll by Paul Adam Davis + // https://github.com/PaulAdamDavis/Arctic-Scroll + $.fn.arctic_scroll = function (options) { + + var defaults = { + elem: $(this), + speed: 500 + }, + + allOptions = $.extend(defaults, options); + + allOptions.elem.click(function (event) { + event.preventDefault(); + var $this = $(this), + $htmlBody = $('html, body'), + offset = ($this.attr('data-offset')) ? $this.attr('data-offset') : false, + position = ($this.attr('data-position')) ? $this.attr('data-position') : false, + toMove; + + if (offset) { + toMove = parseInt(offset); + $htmlBody.stop(true, false).animate({scrollTop: ($(this.hash).offset().top + toMove) }, allOptions.speed); + } else if (position) { + toMove = parseInt(position); + $htmlBody.stop(true, false).animate({scrollTop: toMove }, allOptions.speed); + } else { + $htmlBody.stop(true, false).animate({scrollTop: ($(this.hash).offset().top) }, allOptions.speed); + } + }); + + }; +})(jQuery); diff --git a/public/js/jquery.fitvids.js b/public/js/jquery.fitvids.js new file mode 100644 index 0000000..b9b8d5d --- /dev/null +++ b/public/js/jquery.fitvids.js @@ -0,0 +1,67 @@ +/*global jQuery */ +/*jshint browser:true */ +/*! +* FitVids 1.1 +* +* Copyright 2013, Chris Coyier - http://css-tricks.com + Dave Rupert - http://daverupert.com +* Credit to Thierry Koblentz - http://www.alistapart.com/articles/creating-intrinsic-ratios-for-video/ +* Released under the WTFPL license - http://sam.zoy.org/wtfpl/ +* +*/ + +(function( $ ){ + + "use strict"; + + $.fn.fitVids = function( options ) { + var settings = { + customSelector: null + }; + + if(!document.getElementById('fit-vids-style')) { + // appendStyles: https://github.com/toddmotto/fluidvids/blob/master/dist/fluidvids.js + var head = document.head || document.getElementsByTagName('head')[0]; + var css = '.fluid-width-video-wrapper{width:100%;position:relative;padding:0;}.fluid-width-video-wrapper iframe,.fluid-width-video-wrapper object,.fluid-width-video-wrapper embed {position:absolute;top:0;left:0;width:100%;height:100%;}'; + var div = document.createElement('div'); + div.innerHTML = '

x

'; + head.appendChild(div.childNodes[1]); + } + + if ( options ) { + $.extend( settings, options ); + } + + return this.each(function(){ + var selectors = [ + "iframe[src*='player.vimeo.com']", + "iframe[src*='youtube.com']", + "iframe[src*='youtube-nocookie.com']", + "iframe[src*='kickstarter.com'][src*='video.html']", + "object", + "embed" + ]; + + if (settings.customSelector) { + selectors.push(settings.customSelector); + } + + var $allVideos = $(this).find(selectors.join(',')); + $allVideos = $allVideos.not("object object"); // SwfObj conflict patch + + $allVideos.each(function(){ + var $this = $(this); + if (this.tagName.toLowerCase() === 'embed' && $this.parent('object').length || $this.parent('.fluid-width-video-wrapper').length) { return; } + var height = ( this.tagName.toLowerCase() === 'object' || ($this.attr('height') && !isNaN(parseInt($this.attr('height'), 10))) ) ? parseInt($this.attr('height'), 10) : $this.height(), + width = !isNaN(parseInt($this.attr('width'), 10)) ? parseInt($this.attr('width'), 10) : $this.width(), + aspectRatio = height / width; + if(!$this.attr('id')){ + var videoID = 'fitvid' + Math.floor(Math.random()*999999); + $this.attr('id', videoID); + } + $this.wrap('
').parent('.fluid-width-video-wrapper').css('padding-top', (aspectRatio * 100)+"%"); + $this.removeAttr('height').removeAttr('width'); + }); + }); + }; +// Works with either jQuery or Zepto +})( window.jQuery || window.Zepto ); diff --git a/public/js/jquery.js b/public/js/jquery.js new file mode 100644 index 0000000..25714ed --- /dev/null +++ b/public/js/jquery.js @@ -0,0 +1,4 @@ +/*! jQuery v2.1.3 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.3",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)+1>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b=a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=hb(),z=hb(),A=hb(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ab=/[+~]/,bb=/'|\\/g,cb=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),db=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},eb=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fb){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function gb(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(bb,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+rb(o[l]);w=ab.test(a)&&pb(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function hb(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ib(a){return a[u]=!0,a}function jb(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function kb(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function lb(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function mb(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function nb(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function ob(a){return ib(function(b){return b=+b,ib(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pb(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=gb.support={},f=gb.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=gb.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",eb,!1):e.attachEvent&&e.attachEvent("onunload",eb)),p=!f(g),c.attributes=jb(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=jb(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=jb(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(cb,db);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(jb(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),jb(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&jb(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return lb(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?lb(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},gb.matches=function(a,b){return gb(a,null,null,b)},gb.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return gb(b,n,null,[a]).length>0},gb.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},gb.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},gb.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},gb.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=gb.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=gb.selectors={cacheLength:50,createPseudo:ib,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(cb,db),a[3]=(a[3]||a[4]||a[5]||"").replace(cb,db),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||gb.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&gb.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(cb,db).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=gb.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||gb.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ib(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ib(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ib(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ib(function(a){return function(b){return gb(a,b).length>0}}),contains:ib(function(a){return a=a.replace(cb,db),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ib(function(a){return W.test(a||"")||gb.error("unsupported lang: "+a),a=a.replace(cb,db).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:ob(function(){return[0]}),last:ob(function(a,b){return[b-1]}),eq:ob(function(a,b,c){return[0>c?c+b:c]}),even:ob(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:ob(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:ob(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:ob(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function sb(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function tb(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ub(a,b,c){for(var d=0,e=b.length;e>d;d++)gb(a,b[d],c);return c}function vb(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wb(a,b,c,d,e,f){return d&&!d[u]&&(d=wb(d)),e&&!e[u]&&(e=wb(e,f)),ib(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ub(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:vb(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=vb(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=vb(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xb(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sb(function(a){return a===b},h,!0),l=sb(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sb(tb(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wb(i>1&&tb(m),i>1&&rb(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xb(a.slice(i,e)),f>e&&xb(a=a.slice(e)),f>e&&rb(a))}m.push(c)}return tb(m)}function yb(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=vb(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&gb.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ib(f):f}return h=gb.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xb(b[c]),f[u]?d.push(f):e.push(f);f=A(a,yb(e,d)),f.selector=a}return f},i=gb.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(cb,db),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(cb,db),ab.test(j[0].type)&&pb(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&rb(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,ab.test(a)&&pb(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=jb(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),jb(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||kb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&jb(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||kb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),jb(function(a){return null==a.getAttribute("disabled")})||kb(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),gb}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+K.uid++}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){return M.access(a,b,c) +},removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthx",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,bb=/<([\w:]+)/,cb=/<|&#?\w+;/,db=/<(?:script|style|link)/i,eb=/checked\s*(?:[^=]|=\s*.checked.)/i,fb=/^$|\/(?:java|ecma)script/i,gb=/^true\/(.*)/,hb=/^\s*\s*$/g,ib={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ib.optgroup=ib.option,ib.tbody=ib.tfoot=ib.colgroup=ib.caption=ib.thead,ib.th=ib.td;function jb(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function kb(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function lb(a){var b=gb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function mb(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function nb(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function ob(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pb(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=ob(h),f=ob(a),d=0,e=f.length;e>d;d++)pb(f[d],g[d]);if(b)if(c)for(f=f||ob(a),g=g||ob(h),d=0,e=f.length;e>d;d++)nb(f[d],g[d]);else nb(a,h);return g=ob(h,"script"),g.length>0&&mb(g,!i&&ob(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(cb.test(e)){f=f||k.appendChild(b.createElement("div")),g=(bb.exec(e)||["",""])[1].toLowerCase(),h=ib[g]||ib._default,f.innerHTML=h[1]+e.replace(ab,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=ob(k.appendChild(e),"script"),i&&mb(f),c)){j=0;while(e=f[j++])fb.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=jb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(ob(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&mb(ob(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(ob(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!db.test(a)&&!ib[(bb.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(ab,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(ob(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(ob(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&eb.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(ob(c,"script"),kb),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,ob(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,lb),j=0;g>j;j++)h=f[j],fb.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(hb,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qb,rb={};function sb(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function tb(a){var b=l,c=rb[a];return c||(c=sb(a,b),"none"!==c&&c||(qb=(qb||n("