2019年3月12日 星期二

DNS Challenge for HTTPs

簡而言之 你要在DNS加一筆record
每次更新他會要求你放不同的值


https://community.letsencrypt.org/t/i-am-confused-about-dns-challenge/18754




2018年12月8日 星期六

Heoku pipeline stateless builds


Heroku pipeline  從 staging promot 到 production 的時候會把 compile 過的 file 一起 copy 過去,所以如果用到 Node + Webpack 讀取環境變數再 compile 成 js 的 file 就會吃到 staging 的環境變數而不是 production 的環境變數,這又稱為 stateful builds,可以看 https://github.com/heroku/heroku-buildpack-nodejs/issues/545 有更多的討論

解決方式可以參考 https://github.com/nholden/crawl_vote/commit/cd1b89dff4812da1270d6a5d54b594aecb1a5ca7 把一些變數放到 window裡面,這樣就會吃到正確的變數

https://devcenter.heroku.com/articles/pipelines#design-considerations

rspec test protocal (interface) - part 2 - define our own DSL for rspec


接續上一篇 https://everyday1percent.blogspot.com/2018/05/rspec-test-protocal-interface.html


現在我們要做一個客製化的 rspec matcher,達到的效果如下:


```ruby
RSpec.describe FooService do

  describe "interfaces" do
     specify do
        expect(FooService).to has_implemented_interfaces :call, :call!
     end
  end

end
```

可以先參考 rspec 的 custom matcher 應該就可以做到了

https://relishapp.com/rspec/rspec-expectations/docs/custom-matchers/define-a-matcher-supporting-block-expectations

如果要做成 gem 的話可以參考 rspec-expectations 開放的 protocal

https://github.com/rspec/rspec-expectations/blob/master/lib/rspec/matchers/matcher_protocol.rb

https://makandracards.com/makandra/662-write-custom-rspec-matchers

https://github.com/kucaahbe/rspec-html-matchers

在 Rails 使用 custom font


參考 https://coderwall.com/p/v5c8kq/web-fonts-and-rails-asset-pipeline 把檔案放到 `app/assets/fonts` 裡面

然後在 `application.scss` 裡加上

```scss

@font-face {
  font-family: 'Calibre';
  src: font-url('calibre/regular/Calibre-Regular.otf')  format('opentype'),
       font-url('calibre/regular/Calibre-Regular.woff') format('woff'),
       font-url('calibre/regular/Calibre-Regular.ttf')  format('truetype'),
       font-url('calibre/regular/Calibre-Regular.svg#Calibre-Regular') format('svg');
  font-weight: normal;
  font-style: normal;
}

@font-face {
  font-family: 'Calibre';
  src: font-url('calibre/medium/Calibre-Semibold.woff2') format('woff');\
  font-weight: bold;
  font-style: bold;
}

b, strong {
  font-weight:bold
}

h1, h2, h3, h4, h5, h6 {
  font-weight:bold
}

```

這樣就行

(font-face 可以放在別的 file 再 import 也可以)




https://coderwall.com/p/v5c8kq/web-fonts-and-rails-asset-pipeline
https://github.com/nicotaing/chloepark/tree/master/themes/park/source/fonts/calibre/regular

2018年12月7日 星期五

Ruby instance_eval & block



```rb
class Test
  def initialize(&block)
    @name = "Hello World"

    instance_eval(&block) if block_given?
  end

  def name
    @name
  end
end
```

這樣是完全可以的,因為在 block 裡面的 `self` 變成了 `Test` instance

```rb
Test.new do
  puts name
end

# => Hello World
```

2018年12月2日 星期日

Ansible + rails + letsencrypt


First, install nginx

roles/nginx/tasks/main.yml

```yml
- name: Install nginx
  apt:
    name: nginx
    state: latest
- name: Disable default nginx config
  file:
    path: /etc/nginx/sites-enabled/default
    state: absent
  notify:
    - Restart nginx
```

roles/nginx/handlers/main.yml


```yml
- name: Restart nginx
  service: name=nginx state=restarted
```

Then, install LetsEncrypt

roles/letsencrypt/tasks/main.yml

```yml
- name: Generate dhparams file
  shell: openssl dhparam -out {{ dhparam_pem_path }} 2048
  args:
    creates: "{{ dhparam_pem_path }}"
- name: Update apt cache
  apt: update_cache=yes cache_valid_time=86400
- name: Install depends
  apt: name={{ item }} state=latest
  with_items:
    - python
    - python-dev
    - gcc
    - dialog
    - libaugeas0
    - augeas-lenses
    - libssl-dev
    - libffi-dev
    - ca-certificates
    - python-pip
    - python-virtualenv
    - git
    - libpython-dev
    - zlib1g-dev
- name: Lets Encrypt client
  git: dest=/opt/certbot clone=yes repo=https://github.com/certbot/certbot force=yes
# Auto-renew certificates and reload nginx
- name: Add crontab to renew certificates
  cron: minute="30" hour="2" weekday="1" job="/opt/certbot/certbot-auto renew >> /var/log/le-renew.log"
- name: Add crontab to reload server
  cron: minute="35" hour="2" weekday="1" job="/etc/init.d/nginx reload"
```


Last, configure service

roles/service/tasks/main.yml

```yml
- name: Add http nginx configuration
  template:
    src: templates/http.conf.j2
    dest: /etc/nginx/sites-available/{{ service_name }}.http.conf
  notify:
    - Restart nginx
- name: Enable nginx config
  file:
    src: /etc/nginx/sites-available/{{ service_name }}.http.conf
    dest: /etc/nginx/sites-enabled/{{ service_name }}.http.conf
    state: link
  notify:
    - Restart nginx
- name: Create letsencrypt certificate
  shell: ./certbot-auto certonly --webroot --email {{ service_admin_email }} --agree-tos --webroot-path=/usr/share/nginx/html -d {{ service_host }};
  args:
    chdir: /opt/certbot
- name: Add https nginx configuration
  template:
    src: templates/https.conf.j2
    dest: /etc/nginx/sites-available/{{ service_name }}.https.conf
  notify:
    - Restart nginx
- name: Add external https nginx symlink
  file:
    src: /etc/nginx/sites-available/{{ service_name }}.https.conf
    dest: /etc/nginx/sites-enabled/{{ service_name }}.https
    state: link
  notify:
    - Restart nginx
```

with nginx http template, this will redirect all http request into https


roles/service/templates/http.conf.j2

```
server {
  listen 80;
  server_name {{ service_host }};

  root /usr/share/nginx/html;

  index index.html index.htm index.nginx-debian.html;

  location ~ /.well-known {
    allow all;
  }

  location / {
    return 301 https://$host$request_uri;
  }
}
```

also add a https template for rails


roles/service/templates/https.conf.j2

```
upstream app {
  server unix:///home/deploy/apps/waynechu_blog/shared/tmp/sockets/puma.sock fail_timeout=0;
}

server {
  listen 443 ssl;
  server_name {{ service_host }};

  root /home/deploy/apps/{{ service_name }}/current/public;

  try_files $uri/index.html $uri @app;

  ssl_certificate {{ letsencrypt_ssl_dir }}/{{ service_host }}/fullchain.pem;
  ssl_certificate_key {{ letsencrypt_ssl_dir }}/{{ service_host }}/privkey.pem;

  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_prefer_server_ciphers on;
  ssl_dhparam {{ dhparam_pem_path }};
  ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
  ssl_session_timeout 1d;
  ssl_session_cache shared:SSL:50m;
  ssl_stapling on;
  ssl_stapling_verify on;

  location @app {
    proxy_pass http://app;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Host $http_host;
    proxy_redirect off;
  }

  error_page 500 502 503 504 /500.html;
  client_max_body_size 10M;
  keepalive_timeout 10;
}
```

roles/service/handlers/main.yml


```yml
- name: Restart nginx
  service: name=nginx state=restarted
```


At the last, set your own vars

vars/main.yml

```yml
service_host: "example.cc"
service_name: "example"
service_admin_email: "example@gmail.com"
letsencrypt_ssl_dir: "/etc/letsencrypt/live"
dhparam_pem_path: "/home/deploy/dhparams.pem"
```


That's it, now you have a https rails web



References:

https://medium.com/@gmaliar/generating-lets-encrypt-certificates-for-nginx-using-ansible-9fd27b90993a
https://gist.github.com/mattiaslundberg/ba214a35060d3c8603e9b1ec8627d349
https://docs.ansible.com/ansible/2.5/modules/letsencrypt_module.html
https://gorails.com/guides/free-ssl-with-rails-and-nginx-using-let-s-encrypt
https://blog.frost.tw/posts/2018/05/28/Getting-started-deploy-your-Ruby-on-Rails-Part-8/

2018年11月18日 星期日

Implement algolia autocomplete with rails


```js
// app/javascripts/application.js
const algoliasearch = require('algoliasearch/lite')
const autocomplete = require('autocomplete.js')

const client = algoliasearch(process.env.ALGOLIA_APPLICATION_ID, process.env.ALGOLIA_SEARCH_ONLY_API_KEY)

const pack = {
  algoliaClient: client,
  autocomplete: autocomplete
}

window.Pack = pack
```


```erb
<!-- Algolia search autocomplete HTML markup -->
<div class="aa-input-container" id="aa-input-container">
  <input type="search" id="aa-search-input" class="aa-input-search" placeholder="Search multiple fields in one place..." name="search" autocomplete="off" />
  <svg class="aa-input-icon" viewBox="654 -372 1664 1664">
    <path d="M1806,332c0-123.3-43.8-228.8-131.5-316.5C1586.8-72.2,1481.3-116,1358-116s-228.8,43.8-316.5,131.5  C953.8,103.2,910,208.7,910,332s43.8,228.8,131.5,316.5C1129.2,736.2,1234.7,780,1358,780s228.8-43.8,316.5-131.5  C1762.2,560.8,1806,455.3,1806,332z M2318,1164c0,34.7-12.7,64.7-38,90s-55.3,38-90,38c-36,0-66-12.7-90-38l-343-342  c-119.3,82.7-252.3,124-399,124c-95.3,0-186.5-18.5-273.5-55.5s-162-87-225-150s-113-138-150-225S654,427.3,654,332  s18.5-186.5,55.5-273.5s87-162,150-225s138-113,225-150S1262.7-372,1358-372s186.5,18.5,273.5,55.5s162,87,225,150s113,138,150,225  S2062,236.7,2062,332c0,146.7-41.3,279.7-124,399l343,343C2305.7,1098.7,2318,1128.7,2318,1164z" />
  </svg>
</div>

<!-- Initialize autocomplete menu -->
<script>
  var index = Pack.algoliaClient.initIndex('<%= index_name %>');
  Pack.autocomplete('#aa-search-input',
  { hint: false }, {
    source: Pack.autocomplete.sources.hits(index, {hitsPerPage: 5, filters: 'discarded_at_i < 1'}),
    displayKey: 'name',
    templates: {
      suggestion: function(suggestion) {
        return `<a href='<%= path_prefix %>/${suggestion.objectID}'>${suggestion._highlightResult.name.value}</a>`
      }
    }
  }).on("autocomplete:selected", function (event, suggestion, dataset) {
    window.location.href = `<%= path_prefix %>/${suggestion.objectID}`;
  })
</script>

```