Rails Memos

說明

原本是寫 Python 比較多,因為工作上需要用到 Rails,所以這邊紀錄一些常用的指令。

TIPS

  1. Run specific version of bundler

    1
    2
    3
    $ gem install bundler -v 1.17.3
    $ bundle _1.17.3_ install
    # 使用特定版本的 bundler 安裝 gem
  2. bundler-audit

    1
    2
    3
    $ bundle-audit update
    $ bundle-audit check
    # 檢查 gem 是否有安全漏洞

    有時候呢,因為版本問題實在無法解決,可以使用 bundle-audit check --ignore CVE-2023-XXXX 忽略特定的漏洞。或是在專案內新增 .bundler-audit.yml 檔案,內容如下:

    1
    2
    3
    4
    ---
    ignore:
    - CVE-YYYY-XXXX
    - ...
  3. Update/Add gem

    1
    2
    3
    4
    $ bundle update <gem_name>
    # 更新 Gemfile.lock 內的 gem
    $ bundle add <gem_name>
    # 新增 gem 到 Gemfile
  4. About gem version

    1
    2
    ~> 1.0.0 # 1.0.0 <= x < 2.0.0
    ~> 1.0 # 1.0.0 <= x < 1.1.0
  5. Print out contents of object

    1
    2
    3
    4
    5
    6
    puts user.inspect
    #=> #<User:0x423270c @name="John Due", @age=45>
    puts user.to_yaml
    #=> --- !ruby/object:User
    #=> age: 45
    #=> name: John Due
  6. Time format 與 Timezone 轉換

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    Time.zone.now
    # => Tue, 13 Apr 2021 01:32:04 UTC +00:00
    Time.now
    # => 2021-04-13 01:32:04 +0000
    Time.zone.now.to_s(:db)
    # => "2021-04-13 01:32:04"
    Time.zone.now.to_s(:short)
    # => "13 Apr 01:32"
    Time.zone.now.to_s(:long)
    # => "13 April 2021 01:32"
    Time.zone.now.to_s(:rfc822)
    # => "Tue, 13 Apr 2021 01:32:04 +0000"
    Time.zone.now.to_s(:iso8601)
    # => "2021-04-13T01:32:04Z"
    Time.zone.now.to_s(:number)
    # => "20210413013204"
    # 轉換成特定時區
    t = created_at = 2023-07-16 03:00:00 UTC
    t.zone # 查看時區 "UTC"
    t.in_time_zone("Taipei") # 轉換成台北時區與 t.zone = "Taipei" 相同
    => Sat, 16 Jul 2023 11:00:00 CST +08:00
    Time.zone.local_to_utc(t) # 轉換成 UTC 時區
    => 2023-07-16 03:00:00 UTC
    Time.use_zone("Taipei") { Time.zone.local_to_utc(t) }.localtime # 轉換成台北時區
    => 2023-07-16 11:00:00 +0800
    t.asctime.in_time_zone("Taipei") # 轉換成台北時區字串
    => "Sat Jul 16 11:00:00 2023"
    Time.parse(t.asctime.in_time_zone("Taipei")) # 轉換成台北時區時間
    => 2023-07-16 11:00:00 +0800
  7. rbenv Ruby 管理工具

    1
    2
    3
    4
    5
    6
    7
    8
    9
    $ brew install rbenv # 安裝 rbenv on macOS
    $ rbenv versions # 列出所有已安裝的 Ruby 版本
    $ rbenv install -l # 列出所有可安裝的 Ruby 版本
    $ rbenv install 2.7.3 # 安裝 Ruby 2.7.3
    $ rbenv global 2.7.3 # 設定全域 Ruby 版本為 2.7.3
    $ rbenv local 2.7.3 # 設定專案 Ruby 版本為 2.7.3
    $ rbenv shell 2.7.3 # 設定 shell Ruby 版本為 2.7.3
    $ rbenv rehash # 重新建立 Ruby 版本的 shims
    $ rbenv uninstall 2.7.3 # 移除 Ruby 2.7.3
  8. 在 console 裡測試 ActionMailer

    1
    2
    3
    4
    5
    6
    7
    mailer = ActionMailer::Base.new
    # check settings:
    mailer.delivery_method
    mailer.smtp_settings
    # send mail:
    mailer.mail(
    from: 'sneder@example.com', to: 'user@example.com', subject: 'test', body: 'test').deliver

參考資料

  1. Gemfiles
  2. Ruby Gossip
  3. rbenv

Running BAT Script & Execute CMD At Startup in Windows

問題描述

公司有一個專案需要在展會上展示,為了讓展場人員可以減少操作,所以需要在 WINDOWS 電腦開機時自動執行一些指令,這邊紀錄一下。

解決方法

  1. 首先需要先建立一個 BAT 檔,這邊以 run.bat 為例(副檔名必須是.bat),內容如下:

    1
    2
    @echo off
    start cmd /k "cd C:\Users\user\Desktop\test && python test.py"
  2. run.bat 放到 C:\Users\user\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup 資料夾中,這樣電腦開機時就會自動執行 run.bat 了。

參考資料

@ 是禁止顯示指令(禁止 echo off 本身顯示)
echo off 是關閉指令回應
start 是執行指令
cmd 是執行命令提示字元
/k 是執行完指令後不關閉命令提示字元

Port 5000 Listening by AirPlay

問題描述

在我的 MacBook 上執行 Docker 跑一個 Django 專案,我的一個服務是使用 Port 5000,但是我發現在啟動這個服務的時候,Port 5000 已經被佔用了,我查了一下,發現是 AirPlay 的服務在使用 Port 5000。

解決方法

打開 Mac 的 System Preferences,選擇 Sharing,然後取消 AirPlay 的勾選,就可以解決這個問題了。

參考資料

Port 7000 is the same as AirPlay.
參照網頁

SSH Tunneling Remote Port Forwarding

問題描述

在研究ESL電子標籤時,了解到這個應用的架構是由基站與標籤兩種部件所組成,因為兩者間的通訊是透過2.4GZH來進行,通常是一部基站管理一個區域的多個標籤,然後應用程式會透基站提供的 API 來進行標籤的管理。
這次專案中使用的基站被要求使用 Private IP,但是公司的應用程式是 SaaS 服務,也就是說我們必須從外部(Cloud 的 Public IP)來呼叫基站的 API(Private IP),這時候就需要透過 SSH Tunneling Remote Port Forwarding 來達成,當然也還有別的方式,下面會再舉一個例子。

解決方法

方法一:DDNS 服務

  1. 將基站安裝在一部分享器下,並將分享器的 Public IP 與 DDNS 綁定,並設定 Port Forwarding 將基站的 Private IP 與 Port 轉發到 Public IP 與 Port,這樣就可以從外部透過 DDNS 來呼叫基站的 API。
  2. 若有多個基站,則可以將基站的 Private IP 與 Port 轉發到不同的 Port。
  3. 缺點是,你必須要有一台可以安裝基站的分享器,且分享器必須要有 Public IP,以用來綁定 DDNS 的 Domain。

方法二:透過 SSH Port Fortwarding

  1. 在基站同一個 Private IP 網段內設置一部電腦,而這部 Client 電腦必須要能夠連接到 Internet。
  2. 在 Cloud 上設置一部主機來運行 Nginx,我們會將這部主機稱為 Proxy,並新增設定檔 /etc/nginx/site-enabled/proxy,內容如下:
    1
    2
    3
    4
    5
    6
    7
    server {
    listen 80;
    server_name <your_domain>;
    location / {
    proxy_pass http://localhost:<service_port>/;
    }
    }
    存檔後,重啟 Nginx 服務。
  3. 而 your_domain 的 DNS 設定,要做一個 A record 指向 Proxy 的 Public IP。
  4. 在 Client 電腦上設置 SSH Tunneling Remote Port Forwarding,指令如下:
    1
    $ ssh -R <service_port>:基站_ip:<service_port> <user>@<proxy_ip>
    這樣就可以從外部透過 your_domain 連接到 Proxy 的 Public IP 來呼叫基站的 API,這樣的好處是 Client 的 Public IP 可以是固定的也可以是浮動的,ssh tunnel 建立後, your_domain 就會對應到 Client 電腦來進行 port forwarding,這就像是 ngrok 的服務原理一樣。
  5. 缺點就是有點複雜,準備工作比較多,但是一但設置好後,就可以很方便做這一類的應用,在 local 端測試的時候經常會用到。

參考資料

方法二是基本的概念說明,實際上如果需要使用到 https 的話,可以在 Proxy 上透過 Certbot 來產生 SSL 憑證,一樣可以打到 local 端,像是在做 FB Login 的時候,有限制 webhook 只能是 https 的網址,這時候就可以透過這個方法來達成。
另外如果是多個基站的話,可以利用 uri 導向不同的 port,例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
server {
listen 80;
server_name <your_domain>;
location / {
proxy_pass http://localhost:<service_port>/;
}
location /api1 {
proxy_pass http://localhost:<service_port1>/;
}
location /api2 {
proxy_pass http://localhost:<service_port2>/;
}
}

不過這都是在開發過程中會遭遇到的問題,實際上在正式產品環境中,應該會讓基站維持在 Private 網段,而應用程式應該會透過 VPN 或是 middleware server 來進行連線,這樣就不會有這種問題了。

SSH Tunneling Remote Port Forwarding

Port Forwarding

Service with Systemd

問題描述

最近在研究如何使用 Systemd 來管理服務,這邊紀錄一下。公司的 CI/CD 環境是由 GitHub Actions + Drone CI 腳本來管理,因此需要在 Drone CI 腳本中執行 Systemd 指令來啟動服務。其中有兩個服務出了問題, Faye 啟動了但無法使用,而 Sidekiq 則是無法啟動。
在經過一些測試以後發現,Faye 啟動後無法使用的問題是因為沒有設定 --port 參數,而 Sidekiq 啟動失敗的問題是因為 rvm 版本的問題。而這些都與設定 Systemd 的 service file 有關。

解決方法

  1. Faye 啟動後無法使用的問題是因為沒有設定 --port 參數,因此需要在 service file 中設定 ExecStart 的參數。

    1
    $ sudo vim /etc/systemd/system/faye.service
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    [Unit]
    Description=Faye
    After=network.target

    [Service]
    Type=simple
    User=deploy
    WorkingDirectory=/home/deploy/apps/faye/current
    ExecStart=/home/deploy/.rvm/bin/rvm default do bundle exec faye --port 9292
    Restart=always
    RestartSec=10

    [Install]
    WantedBy=multi-user.target

    另一種做法,也就是我們公司的做法,是透過 puma 來執行 Faye ,因此是透過 faye.rb 來啟動 Faye。

    1
    ExecStart=/home/deploy/.rvm/bin/rvm default do bundle exec puma -C /home/deploy/<app_path>/faye.rb

    所以我們可以透過 faye.rb 來設定 faye 的 port ,在檔案中加入以下內容:

    1
    port 9292

    設定好以後重新載入 Systemd 並啟動服務:

    1
    2
    $ sudo systemctl daemon-reload
    $ sudo systemctl start faye
  2. Sidekiq 啟動失敗的問題是因為 rvm 版本的問題,因此需要在 service file 中檢查'ExecStart , WorkingDirectory 與設定 Environment 的參數。

    1
    $ sudo vim /etc/systemd/system/sidekiq.service
    1
    2
    3
    4
    5
    WorkingDirectory=/home/deploy/app/current

    ExecStart=/home/deploy/.rvm/gems/ruby-2.6.10/wrappers/bundle exec sidekiq -e production

    Environment=PATH=/home/deploy/.rvm/gems/ruby-2.6.10/bin:/home/deploy/.rvm/gems/ruby-2.6.10@global/bin:/home/deploy/.rvm/rubies/ruby-2.6.10/bin:/home/deploy/.rvm/bin:/home/deploy/.nvm/versions/node/v16.14.2/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/deploy/.local/bin:/home/deploy/bin

    設定好以後重新載入 Systemd 並啟動服務:

    1
    2
    $ sudo systemctl daemon-reload
    $ sudo systemctl start sidekiq
  3. 透過 Systemd 來管理服務,可以透過 systemctl 指令來查看服務的狀態,例如:

    1
    2
    $ sudo systemctl status faye
    $ sudo systemctl status sidekiq

    也可以透過 journalctl 指令來查看服務的 log ,例如:

    1
    2
    $ sudo journalctl -u faye
    $ sudo journalctl -u sidekiq

    也可以透過 systemctl 指令來啟動、停止、重啟服務,這邊就不贅述了。

參考資料

由於 CI/CD 有時候會需要使用 sudo 指令,因此需要在 /etc/sudoers 中設定 deploy 使用者可以使用 sudo 指令,例如:

1
deploy ALL=(ALL) NOPASSWD:ALL

或是透過 visudo 指令來設定:

1
$ sudo visudo
1
2
3
deploy ALL=(ALL) NOPASSWD:ALL
# 嚴謹一點的話可以這樣設定,只列出需要使用 sudo 指令的指令
deploy ALL=NOPASSWD: /usr/bin/systemctl start faye

About gem whenever & crontab

問題描述

在 rails 專案裡面使用 whenever gem 來設定排程,整合到 capistrano(v3)參考 之後,發現排程沒有被設定成功。

解決方法

  1. 檢查 Capfile 裡面有無加入 require 'whenever/capistrano' 在 capistrano v3 版本裡面,不需要加上 set :whenever_command, "bundle exec whenever --update-crontab" 指令,v3 版本已有整合 程式碼

  2. 檢查在 config/deploy/production.rb 裡面有無加入 Rake::Task['whenever:update_crontab'].clear_actions,這樣的指令? 這個指令會把該 task 移除,這樣部署的時候就不會更新 crontab 排程內容,所以要把這個指令移除。

  3. 修改好以後重新部署,檢查 crontab 內容是否有更新。

    1
    $ crontab -l

    你應該會看到類似這樣的內容:

    1
    2
    3
    # Begin Whenever generated tasks for: yourapp at: 2023-02-16 17:09:27 +0000
    ......
    # End Whenever generated tasks for: yourapp at: 2023-02-16 17:09:27 +0000
  4. 如果還是沒有更新或沒有執行,可以檢查 config/schedule.rb 裡面的排程內容是否有誤,或是config/deploy/production.rb 裡面的 roles 相關設定是否有誤。

  5. 另外可以檢視 /var/log/cron 裡面的內容,看看是否有錯誤訊息。

參考資料

有時候你可能想要手動測試排程是否有正常執行,可以使用 whenever --update-crontab 指令來更新 crontab 內容,然後使用 crontab -l 指令來檢查 crontab 內容是否有更新,如果有更新,就可以使用 crontab -e 指令來編輯 crontab 內容,然後把 # 移除,這樣就可以手動測試排程是否有正常執行。
P.S. cron 是不需要重新啟動的,只要更新 crontab 內容就可以了。

O.C.E.A.N Personality Model

讀書筆記 (TBC)

劍橋分析公司透過心理測驗小遊戲收集個資,並結合第三方資料公司資料,對其進行比對,對用戶數以萬計的資料節點進行統計貼標與OCEAN計分。

Open 開放
Conscientious 嚴謹
Extroverted 外向
Agreeable 親和
Neurotic 神經質

藉此,劍橋分析公司能夠對用戶進行細緻(複雜)分類,作為精準行銷的策略依據,總共可以細分為32個主要類別,2^5。

Nginx error.log (13: Permission denied)

問題描述

日前公司產品的網頁上傳檔案時出現 return code 500 的錯誤,查看 Nginx 的 /var/log/nginx/error.log 時發現有以下錯誤訊息:

1
2021/02/15 23:28:38 [error] 1#1: *1 open() "/var/lib/nginx/tmp/client_body/0000000001" failed (13: Permission denied), request: ......

還有一種情況是發生在瀏覽器 get javascript 檔案時 200 OK ,但出現 net::ERR_QUIC_PROTOCOL_ERROR 的錯誤訊息,查看 Nginx 的 /var/log/nginx/error.log 時發現有以下錯誤訊息:

1
2021/02/15 23:28:38 [error] 1#1: *1 open() "/var/lib/nginx/tmp/proxy/0/0000000010" failed (13: Permission denied), request: ......

解決方法

  1. 確認 Nginx service 的 process user 是否為你預期的使用者,這裡範例的使用者為 www-data:

    1
    2
    3
    4
    5
    6
    $ ps -aux | grep nginx
    www-data 6252 0.0 0.0 56216 6264 ? S 2020 0:00 nginx: worker process
    www-data 6253 0.0 0.0 56480 6292 ? S 2020 0:01 nginx: worker process
    www-data 6254 0.0 0.0 56216 6300 ? S 2020 0:01 nginx: worker process
    www-data 6255 0.0 0.0 56480 6500 ? S 2020 0:03 nginx: worker process
    root 15636 0.0 0.0 55008 3840 ? Ss 2020 0:00 nginx: master process /usr/sbin/nginx
  2. 接著查看 /var/lib/nginx/tmp/ 的權限:

    1
    2
    3
    4
    5
    6
    7
    $ ls -l /var/lib/nginx/tmp/
    total 8
    drwxr-xr-x 2 root root 4096 Feb 15 23:28 client_body
    drwxr-xr-x 2 www-data www-data 4096 Feb 15 23:28 fastcgi
    drwxr-xr-x 2 www-data www-data 4096 Feb 15 23:28 proxy
    drwxr-xr-x 2 www-data www-data 4096 Feb 15 23:28 scgi
    drwxr-xr-x 2 www-data www-data 4096 Feb 15 23:28 uwsgi
  3. 發現 client_body 的 owner 為 root,而非 www-data 這個使用者,所以才會出現 Permission denied 的錯誤訊息,因此我們需要將 client_body 的 owner 改為 www-data:

    1
    $ sudo chown -R www-data:www-data /var/lib/nginx/
  4. 重新啟動 Nginx service:

    1
    $ sudo systemctl restart nginx
  5. 重新上傳檔案,發現已經可以正常上傳了。

  6. 記得回頭檢查 nginx.conf 中的 user 設定,以釐清問題。

Useful Linux Command

問題描述

這邊紀錄一些自己常用的 Linux 指令,方便日後查詢。指令都還有很多細節,這邊只列出常用的部分。

參考資料

[C]

chmod: 更改檔案權限 (此為目錄擁有者與群組)

1
2
3
4
5
6
$ chmod -R group:user <folder_path>
-R: 子目錄也一併更改
# 權限分數 r: 4, w: 2, x: 1
# 例如: rwxr-xr-x = 755
# rwx: 擁有者, r-x: 群組, r-x: 其他
# r: 讀取, w: 寫入, x: 執行

chronyc: 查看系統時間源

1
2
3
$ chronyc sources -v
sources: 狀態
-v: 顯示詳細資訊

crontab: 設定排程

1
2
3
4
$ crontab -e  # 編輯
$ crontab -l # 列出
$ crontab -r # 移除
$ crontab -u <user> -e # 編輯其他使用者的排程

[D]

du: 查看目錄所佔用大小並排序

1
$ du -chd 1 | sort -h

date: 查看系統時間

1
$ date -R # 顯示 RFC-2822 時間格式

[F]

file: 查看檔案 mime type

1
$ file --mime-type <file name>

[G]

grep: 搜尋檔案內容 (用來搜尋 nginx 設定值很好用)

1
2
$ grep -r <keyword> <folder path>
-r: 遞迴搜尋

[I]

id: 查看使用者資訊

1
2
3
4
$ id <username>
$ id -u <username> # 只顯示 UID
$ id -g <username> # 只顯示 GID
$ id -G <username> # 顯示所屬群組

iptables: 設定防火牆規則

1
2
3
4
5
6
$ sudo iptables -F # 清空所有規則
$ sudo iptables -L # 列出所有規則
$ sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT # 新增規則 -A: append
$ sudo iptables -I INPUT -p tcp -s 0.0.0.0/0 --dport 27017 -j ACCEPT # 新增規則 -I: insert
# -p: 指定協定, -s: 指定來源, -dport: 指定目的, -j: 指定動作
$ sudo netfilter-persistent save # 最後記得儲存規則,不然重開機就會失效

[J]

journalctl: 查看系統日誌

1
2
3
$ journalctl -u <service name> -f
-u: 指定服務
-f: 跟著日誌輸出

[L]

lsof: 查看目前佔用中的 port

1
$ lsof -i -P

[N]

netstat: 查看目前佔用中的 port

1
2
3
4
5
6
$ netstat -tulpn
-t 代表找走 TCP 協定的
-u 代表找走 UDP 協定的
-l 代表找 LISTEN 的 socket
-n 代表顯示硬體名稱
-p 代表顯示 PID

[P]

ps: 查看目前執行中的 process

1
$ ps aux | grep <process name: keyword>

[S]

scp: 透過堡壘主機將檔案A從遠端主機複製到本地主機

1
2
3
$ scp -oProxyCommand = "ssh -W %h:%p bastion" user@host:/some/path/on/remote/A some/path/on/local/A

也可以使用 -oProxyJump = "bastion" 來代替 -oProxyCommand (OpenSSH 7.3+)

split: 將檔案分割成多個小檔案

1
$ split -b 100m <file name> <output file name>

sudo visudo: 編輯 sudoers 檔案

1
2
3
$ sudo visudo
經常用來設定不需要輸入密碼的指令
username ALL = NOPASSWD: /fullpath/to/command

[T]

truncate: 清空檔案內容

1
2
$ truncate -s 0 <file name>
想當於 `echo "" > <file name>` 的效果

timedatectl: 查看/設定系統時間

1
2
$ sudo timedatectl list-timezones # 列出所有時區 (搭配 grep 搜尋: | grep <keyword>)
$ sudo timedatectl set-timezone <timezone> # 設定時區

[Y]

yum: 系統套件管理工具

1
2
3
4
5
$ yum install <package name> # 安裝套件
$ yum remove <package name> # 移除套件
$ yum update <package name> # 更新套件
$ yum list installed # 列出已安裝的套件
$ yum search <keyword> # 搜尋套件

Redis Cannot allocate memory

問題描述

公司的產品架構在不同的區域有不同的主機配置,有些區域的 Redis 服務會出現 Can‘t save in background: fork: Cannot allocate memory 的錯誤,但是在其他區域的 Redis 服務卻沒有這個問題,這是因為 Redis 的記憶體配置不夠,所以會出現這個錯誤。

解決方法

在 /etc/sysctl.conf 中加入以下內容:

1
vm.overcommit_memory=1

然後執行,讓設定值生效:

1
$ sudo sysctl -p

參考資料

vm.overcommit_memory = 1
允許 overcommit 直接放行。系統完全不限制 process 分配虛擬記憶體空間,避免了 fork 可能產生的失敗,但由於 malloc 是先分配虛擬記憶體空間,而後通過中斷異常分配真正的物理記憶體,在記憶體不足的情況下,相當於完全屏蔽了 process 對系統記憶體狀態的感知,即 malloc 總是能成功,一旦記憶體不足會引起系統 OOM kill process,而 process 對於這種行為是無法預測的。