2023/01/10

Powershell Note

Windows 的 cmd 不是很好用,我選擇使用 PoweShell 在 Windows 執行 command line,他還是沒有 Linux 的 cli 好用,但為了快速在 Window s 達到某些動作,還是來學一下基本指令並且做個筆記。

搜尋內文,類似 grep

ls -Path ./ -r | sls "WORD" | select Path -u

上面的效果是用 ls -r fetch 該目錄的所有檔案,再透過 sls 的 pipline 在每個撈出來的檔案中找尋內文符合的內容,最後利用 select Path -u 將重複的檔名過濾,在 PowerShell 裡面沒有大小寫之分。

搜尋檔案,類似 find

ls -Path ./ -r -Filter "WORD" -Name

在該目錄下搜尋檔案名稱。

2023/01/04

Vagrant SSH Issue

我的開發環境都是 base on Vagrant,某一天 Windows 更新後使用 vagrant ssh 時無法登入虛機內,本以為是更新到 2.3.4 後的 bug,因此發了這篇 issue,經過幾個回合的討論後,發現問題出在 Windows OpenSSH 更新後帶來的影響,因此要正常使用的話有幾個方法。

  1. 將 Vagrantfile 相關的資料目錄搬到你的個人目錄下,如 C:\Users\username\vagrant
  2. 使用 vagrant 自身的 embedded ssh,一般 command prompt 的話設定 SET VAGRANT_PREFER_SYSTEM_BIN=0,PowerShell 的話 key 入 $Env:VAGRANT_PREFER_SYSTEM_BIN=0,經測試都可以順利進入虛機。

2022/12/05

From Docker Virtual Network Connect To Solid MySQL Server

假設我們的 server 只有一台,PHP 環境為 docker container,而資料庫為實體機安裝,資料庫 3306 防火牆防堵不對外,但要允許從 Docker 環境內的服務存取,最簡單的方式就是防火牆讓 Docker 產生的 network interface 通過,下面我們來實做這件事。

今天示範環境為使用 Vagrant 起一台 Ubuntu jammy64,然後在裡面進行防火牆的測試。

sudo service ufw start
sudo ufw enable
sudo ufw allow ssh

記得開啟防火牆要先把 ssh 打開,不然就悲劇了,接著安裝 MySQL,docker 以及 docker compose 部分就麻煩自行安裝。

sudo apt install -y mariadb-server mariadb-client

安裝完畢後,我們來建立測試用資料庫以及帳號。

sudo mysql
create database laravel;
GRANT ALL PRIVILEGES ON `laravel`.* TO 'homestead'@'%' IDENTIFIED BY 'secret' WITH GRANT OPTION;

這邊權限開 % 沒關係,因為我們只會允許 docker network 指定的 network interface 進入而已。

docker-compose.yml
version: "3.9"

services:
  fpm:
    container_name: fpm
    build: .
    restart: always
    working_dir: /www
    extra_hosts:
      - "host.docker.internal:host-gateway"
    networks:
      - front_end
    volumes:
      - ./www/:/www

networks:
  front_end:
    driver_opts:
      com.docker.network.bridge.name: br-fpm
Dockerfile
FROM php:8.1-fpm

RUN apt-get update && apt-get install -y \
    git \
    curl \
    libpng-dev \
    libonig-dev \
    libxml2-dev \
    zip \
    unzip

ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/

RUN chmod +x /usr/local/bin/install-php-extensions && \
    install-php-extensions @composer pdo_mysql mbstring exif pcntl bcmath gd

WORKDIR /www

這是要安裝 Laravel 的最小環境,我們今天只是要測試連線所以其他的設定就先不理會了。

sudo docker compose up -d

等 image build 完以及 service 起來以後,我們來利用指令安裝一個 Laravel 的環境。

sudo docker compose exec fpm composer create-project laravel/laravel .

接著我們打開 .env 檔案設定資料庫連線內容。

DB_CONNECTION=mysql
DB_HOST=host.docker.internal
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=homestead
DB_PASSWORD=secret

這時來測試一下連線。

sudo docker compose exec fpm php artisan migrate

因為防火牆擋住了,所以 connection 會 hang 住,最終失敗,接著來開始防火牆設定。

sudo ufw allow in on br-fpm to any port 3306

接著再執行一次 migrate 的指令。

sudo docker compose exec fpm php artisan migrate

   INFO  Preparing database.

  Creating migration table ............................................................................................................... 10ms DONE

   INFO  Running migrations.

  2014_10_12_000000_create_users_table .................................................................................................... 9ms DONE
  2014_10_12_100000_create_password_resets_table .......................................................................................... 2ms DONE
  2019_08_19_000000_create_failed_jobs_table .............................................................................................. 3ms DONE
  2019_12_14_000001_create_personal_access_tokens_table ................................................................................... 4ms DONE

可以看到 migration 順利成功了,如果發生 connection refuse 的錯誤的話,記得把 MySQL 的 bind-address 設定為 0.0.0.0

iptable 的話用下方的指令應可達到一樣的效果。

iptables -A INPUT -i br-fpm -p tcp --dport 3306 -j ACCEPT
iptables -A INPUT -p tcp --dport 3306 -j DROP

2022/11/21

Test API Server With Express.js

測試 API 結果時常常需要 fake data 做實際測試,架設 http server 有點小題大作,我們可以利用 express.js 迅速搭建測試用的 API server。

首先安裝相關套件:

npm i --save express cors

接著建立一支 server.js,程式如下:

const express = require('express');
const app = express();
const port = 8080;
const fs = require("fs");
const path = require("path");
const cors = require("cors");

app.use(cors());

function getFile(fileName) {
    return JSON.parse(fs.readFileSync(path.join(__dirname, "data", `${fileName}.json`)));
}

app.get('/api', (req, res) => {
    res.json(getFile('index'));
});

app.listen(port, () => {
    console.log(`Example app listening on port ${port}`);
})

index.json 內容如下:

{
    "status": true,
    "data": "hello world"
}

有時候 json 的內容很龐大,我們不希望負責 route 的檔案長度過長不好維護,所以我這邊 parse 了 data 目錄裡指定的 .json 檔案返回,接著只要執行 node server.js 後就可以在 http://localhost:8080/api 看到正確的內容了。

2022/11/17

CSS Practice - Learn CSS Grid

最近在大量練習 CSS 3 切版,看到有趣的版面就拿來時做練習,Learn CSS Grid 這個炸開的效果挺有趣的,複製了 html 架構後,除了一些顏色跟間距數值以外的 css 就憑想像手刻了,除了炸裂的位置只抓個大概以外,排版的部分盡量一致。

2022/10/18

Reload Nginx And Apache Config In Docker

如果在本機安裝 apache2 或 nginx 時想要 reload config 的話我們通常會使用下列的指令:

# nginx
nginx -s reload

# apache2
service httpd reload

但在 docker 的環境下就沒有那麼簡單,像 apache 的 reload 是把 script 寫在 service 的 config 裡面,不一定每個 image 裡面都有包含這個指令,CentOS 的我記得好像就沒有,為了要兼容所有的環境,我們可以利用 kill -USR1 這個方式套用到每個我們想要 reload without restart 的專案上面。

apache2

首先在根目錄建立兩個網頁檔:

  • ./www/1/index.html
  • ./www/2/index.html

各別會在網頁印出 1 以及 2 的內容識別。

docker-compose.yml

version: "3.9"

services:
  http:
    ports:
      - "80:80"
    container_name: httpd
    image: httpd
    restart: always
    volumes:
      - "./www:/www"
      - "./config/httpd:/usr/local/apache2/conf:ro"

我們建立了一個名為 http 的 container,使用 httpd 的 image,並且把 www 目錄掛進去,我事先把 httpd 的 config 複製出來以便在外面修改內容,接下來跑 docker compose up -d,打上 IP 後可以看到 httpd 傳統的首頁內容 It works!

接著我們修改 httpd.conf

DocumentRoot "/usr/local/apache2/htdocs"
<Directory "/usr/local/apache2/htdocs">

# 改為

DocumentRoot "/www/1"
<Directory "/www/1">

我們先使用 docker compose restart 測試,執行後 refresh 網頁可以看到我們要的 1,接著再把 config 改成:

DocumentRoot "/www/2"
<Directory "/www/2">

這次我們使用 docker compose kill -s USR1 http ,此時我們可以看到 container 活得好好的,但 refresh 頁面後得到我們想要的 2 了,他的原理就是呼叫 container 內的 httpd daemon 去重新整理他的 service。

nginx

docker-compose.yml

version: "3.9"

services:
  http:
    ports:
      - "80:80"
    container_name: nginx
    image: nginx
    restart: always
    volumes:
      - "./www:/www"
      - "./config/nginx/conf.d/default.conf:/etc/nginx/conf.d/default.conf:ro"

一樣,我們事前複製了 nginx 的 config 出來重新掛載回去,啟動 container 以後看見 nginx 的預設字樣 Welcome to nginx!

接著我們修改 defalut.conf

root   /usr/share/nginx/html;

# 改為

root   /www/1;

跟 apache2 一樣我們先使用 docker compose restart,可以順利看到 1 的結果,接著我們將 config 修改為:

root   /www/2;

使用 docker compose exec http nginx -s reload,我們就可以順利看到 2 了,nginx 跟 apache2 的 container 啟動方式不太一樣,所以不能用 kill 的方法,但事實上在 nginx 的 logrotate config 裡面他使用的方式也是:

kill -USR1 `cat /var/run/nginx.pid`

上面介紹了兩個主流網頁伺服器的重取 config 的目的是在當我們把 server 的 log 掛到我們本機目錄時,我們可能會需要依照情況在本機執行 logrotate 的動作,這樣就可以把 reload 腳本寫在本機的 logrotate config 的 postrotate/endscript 裡面了。

這邊附上 logrotate for docker 基本的配置:

nginx-docker

/YOUR_PATH_TO_LOG/*.log {
        daily
        missingok
        rotate 52
        compress
        delaycompress
        notifempty
        create 640 nginx adm
        sharedscripts
        postrotate
            sudo docker exec YOUR_CONTAINER_NAME nginx -s reload
        endscript
}

httpd-docker

/YOUR_PATH_TO_LOG/*.log {
        daily
        missingok
        rotate 14
        compress
        delaycompress
        notifempty
        sharedscripts
        postrotate
            sudo docker kill -s USR1 YOUR_CONTAINER_NAME 
        endscript
}

2022/10/10

Delete None Keeping Files By Command find

之前寫過 Delete None Keeping File 這篇,用來刪除固定數量的保存檔案,使用的是 ls,今天使用 find 來達成一樣的效果。

首先我們來建二十個以 0.05 秒間距不同的檔案:

for i in {1..20}; do touch "${i}.log"; sleep 0.05; done

列出檔案一下目前排序是這樣。

0 Oct 15 23:56 1.log
0 Oct 15 23:56 10.log
0 Oct 15 23:56 11.log
0 Oct 15 23:56 12.log
0 Oct 15 23:56 13.log
0 Oct 15 23:56 14.log
0 Oct 15 23:56 15.log
0 Oct 15 23:56 16.log
0 Oct 15 23:56 17.log
0 Oct 15 23:56 18.log
0 Oct 15 23:56 19.log
0 Oct 15 23:56 2.log
0 Oct 15 23:56 20.log
0 Oct 15 23:56 3.log
0 Oct 15 23:56 4.log
0 Oct 15 23:56 5.log
0 Oct 15 23:56 6.log
0 Oct 15 23:56 7.log
0 Oct 15 23:56 8.log
0 Oct 15 23:56 9.log

用預設的 find 查詢一下檔案內容。

find . -type f -name "*.log"

./10.log
./8.log
./19.log
./14.log
./13.log
./9.log
./2.log
./20.log
./1.log
./17.log
./6.log
./15.log
./12.log
./16.log
./4.log
./5.log
./11.log
./7.log
./18.log
./3.log

有人可能會說那利用 sort 不就可以自然排序正確的數字,沒錯,但那是因為我們的檔名自然有序,若遇到實際上沒有這樣結構的檔案,或者萬一臨時修改了檔名造成新排序不是我們要的結果就會出問題,所以最保險的方法還是利用日期去自然排序。

我們利用 -printf 這個指令來達到目的。

find . -type f -name "*.log" -printf "%T+ %p\n" | sort
2022-10-15+23:56:20.1777708840 ./1.log
2022-10-15+23:56:20.2297708850 ./2.log
2022-10-15+23:56:20.2817708850 ./3.log
2022-10-15+23:56:20.3337708860 ./4.log
2022-10-15+23:56:20.3857708860 ./5.log
2022-10-15+23:56:20.4457708870 ./6.log
2022-10-15+23:56:20.5057708880 ./7.log
2022-10-15+23:56:20.5697708880 ./8.log
2022-10-15+23:56:20.6337708890 ./9.log
2022-10-15+23:56:20.6817708890 ./10.log
2022-10-15+23:56:20.7377708900 ./11.log
2022-10-15+23:56:20.7977708910 ./12.log
2022-10-15+23:56:20.8457708910 ./13.log
2022-10-15+23:56:20.9057708920 ./14.log
2022-10-15+23:56:20.9577708920 ./15.log
2022-10-15+23:56:21.0217708930 ./16.log
2022-10-15+23:56:21.0977708940 ./17.log
2022-10-15+23:56:21.1617708950 ./18.log
2022-10-15+23:56:21.2137708950 ./19.log
2022-10-15+23:56:21.2617708960 ./20.log

printf 詳細用法可以參考 How to Use find -printf in Linux?,非常的強大,其他工作就可以接續 Delete None Keeping File 處理。

find . -type f -name "*.log" -printf "%T+ %p\n" | sort -r | awk '{print $NF}' | tail -n +11
./10.log
./9.log
./8.log
./7.log
./6.log
./5.log
./4.log
./3.log
./2.log
./1.log

上方指令搭配 xargs rm -f 就可以只保留最新的十份檔案。