Jex’s Note

Git - Submodule 基本語法 & 實例操作

基本語法

git submodule foreach git pull origin master : 將每個submodule重新pull git submodule update --init : update submodule

git subomdule init :

根據 .gitmodules 的名稱和 URL,將這些資訊註冊到 .git/config 內,可是把 .gitmodules 內不用的 submodule 移除,使用這個指令並沒辦法自動刪除 .git/config 的相關內容,必須手動刪除;

git submodule update :

根據已註冊(也就是 .git/config )的 submodule 進行更新,例如 clone 遺失的 submodule,也就是上一段講的方法,所以執行這個指令前最好加上 --init

git submodule sync :

如果 submodule 的 remote URL 有變動,可以在 .gitmodules 修正 URL,然後執行這個指令,便會將 submodule 的 remote URL 更正

加入submodule :

git submodule add https://github.com/msanders/snipmate.vim.git bundle/snipmate.vim

clone時順便把submodule抓下來 :

git clone --recursive https://github.com/jex-lin/jex-vim.git clone

remove Git submodule (example : remove bundle/snipmate.vim)

$ oldPath="bundle/snipmate.vim"
$ git config -f .git/config --remove-section "submodule.${oldPath}"
$ git config -f .gitmodules --remove-section "submodule.${oldPath}"
$ git rm --cached "${oldPath}"
rm 'bundle/snipmate.vim'
$ rm -rf "${oldPath}"
$ rm -rf ".git/modules/${oldPath}"
$ git add .gitmodules
$ git commit -m "Removed ${oldPath}"
[master f36aa2e] Removed bundle/snipmate.vim
 2 files changed, 4 deletions(-)
 delete mode 160000 bundle/snipmate.vim

ref: http://blog.chh.tw/posts/git-submodule/

實例操作

practice_submodule => 假設它是如 jQuery 之類的第三方程式,我們要把它掛進來 test1 => 我 test2 => 是你同事

  • 3個都是獨立的repo
  • practice_submodule 是 push 到 github 的 practice_submodule.git,假設裡面目前已有 commit 紀錄了
  • test1、test2 目前是空資料夾也還不是個repo,之後會將它 push 到 github 的 test.git

我要先把 practice_submodule 掛進來,並且 push 到github,你同事再去 pull 下來,但發現 practice_submodule 沒東西,所以要做兩個關鍵的動作,然候”第三方程式”更新了,你同事將它的 practice_submodule 更新到最新版後 push 到 github,我將它 pull 下來,並且發現你的 practice_submodule 裡還是舊的,你要做一個關鍵動作把它更新到最新版…

我(test1) - 將 submodule 掛進來並且 push

jex_lin@devm3 ~/public_html/test1$ git init                                                             #初始化
Initialized empty Git repository in /home/jex_lin/public_html/test1/.git/
jex_lin@devm3 ~/public_html/test1$ git remote add origin https://github.com/jex-lin/test.git            #加入遠端位置
jex_lin@devm3 ~/public_html/test1$ git submodule add https://github.com/jex-lin/practice_submodule.git  #將 submodule 掛進來
Cloning into 'practice_submodule'...
remote: Counting objects: 27, done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 27 (delta 5), reused 24 (delta 2)
 Unpacking objects: 100% (27/27), done.
jex_lin@devm3 ~/public_html/test1$ git add .                              #add
jex_lin@devm3 ~/public_html/test1$ git submodule init                     #記得 submodule 掛進來後要將 submodule 做初始化,git 才會知道
jex_lin@devm3 ~/public_html/test1$ git commit -m "add submodule"          #commit
[master (root-commit) a47ffce] add submodule
 2 files changed, 4 insertions(+)
 create mode 100644 .gitmodules
 create mode 160000 practice_submodule
jex_lin@devm3 ~/public_html/test1(master)$ git push origin master -f      #push 到 github。因為我遠端目前已有 commit,所以我將它強制 push
Username for 'https://github.com': jex-lin
Password for 'https://jex-lin@github.com':
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 329 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
To https://github.com/jex-lin/test.git
 + 67b6611...a47ffce master -> master (forced update)

你同事(test2) - pull 回來,並且做 submodule 的處理

jex_lin@devm3 ~/public_html/test2$ git init                                                   #初始化
Initialized empty Git repository in /home/jex_lin/public_html/test2/.git/
jex_lin@devm3 ~/public_html/test2$ git remote add origin https://github.com/jex-lin/test.git  #加入遠端位置
jex_lin@devm3 ~/public_html/test2$ git pull origin master                                     #pull
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 0), reused 3 (delta 0)
Unpacking objects: 100% (3/3), done.
From https://github.com/jex-lin/test
 * branch            master     -> FETCH_HEAD

jex_lin@devm3 ~/public_html/test2(master)$ git submodule            #查看 submodule 的commit id
-0fae9e91890d86e633a48ba3d6c43d6d1b783360 practice_submodule        #減號...有問題!  什麼問題?  不知道,之後知道再補充
                                                                    #這時候進去 practice_submodule/ 裡面是沒有東西的,還需要做兩個步驟

jex_lin@devm3 ~/public_html/test2(master)$ git submodule init       #將 submodule 初始化
Submodule 'practice_submodule' (https://github.com/jex-lin/practice_submodule.git) registered for path 'practice_submodule'
jex_lin@devm3 ~/public_html/test2(master)$ git submodule update     #更新 submodule
Cloning into 'practice_submodule'...
remote: Counting objects: 27, done.
remote: Compressing objects: 100% (13/13), done.
remote: Total 27 (delta 5), reused 24 (delta 2)
Unpacking objects: 100% (27/27), done.
Submodule path 'practice_submodule': checked out '0fae9e91890d86e633a48ba3d6c43d6d1b783360'

jex_lin@devm3 ~/public_html/test2(master)$ git submodule                      #這時候再查看 submodule 的 commit id
 0fae9e91890d86e633a48ba3d6c43d6d1b783360 practice_submodule (heads/master)   #最前面沒符號了,舒服多了,再進去 practice_submodule/ 裡面就有東西了

第三方程式(practice_submodule) - 發行新版本了! (這部份指令省略,隨便改個檔案,並且 add, commit, push )

你同事(test2) - 接下來”你同事”發現新版本釋出了,趕緊來用用 :b

jex_lin@devm3 ~/public_html/test2(master)$ cd practice_submodule/                         #進入 submodule 的資料夾
jex_lin@devm3 ~/public_html/test2/practice_submodule((no branch))$ git checkout master    #發現是(no branch),這時 pull 是沒用的,要先切到 master
Switched to branch 'master'
jex_lin@devm3 ~/public_html/test2/practice_submodule(master)$ git pull                    #然候將 practice_submodule 最新版下載下來
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 3 (delta 1), reused 3 (delta 1)
Unpacking objects: 100% (3/3), done.
From https://github.com/jex-lin/practice_submodule
   0fae9e9..7ca8d97  master     -> origin/master
Updating 0fae9e9..7ca8d97
Fast-forward
 test | 1 +
 1 file changed, 1 insertion(+)

jex_lin@devm3 ~/public_html/test2/practice_submodule(master)$ cd ..             #回上一層
jex_lin@devm3 ~/public_html/test2(master)$ git submodule                        #查看 submodule 的 commit id
+7ca8d979e730727ccfbaed3c0dbfe5c8ff3b209e practice_submodule (heads/master)     #是加號

                  #(略)... 做 add、commit

jex_lin@devm3 ~/public_html/test2(master)$ git submodule                        #再查看 submodule 的 commit id
 7ca8d979e730727ccfbaed3c0dbfe5c8ff3b209e practice_submodule (heads/master)     #正常了
jex_lin@devm3 ~/public_html/test2(master)$ git push origin master               #push 到 github
Username for 'https://github.com': jex-lin
Password for 'https://jex-lin@github.com':
Counting objects: 3, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 275 bytes, done.
Total 2 (delta 0), reused 0 (delta 0)
To https://github.com/jex-lin/test.git
   a47ffce..d920edf  master -> master

你(test1) - 現在”你”要 pull 更新”你同事”的變更

jex_lin@devm3 ~/public_html/test1(master)$ git pull origin master               #pull 下來
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 2 (delta 0), reused 2 (delta 0)
Unpacking objects: 100% (2/2), done.
From https://github.com/jex-lin/test
 * branch            master     -> FETCH_HEAD
Updating a47ffce..d920edf
Fast-forward
 practice_submodule | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
jex_lin@devm3 ~/public_html/test1(master)$ git submodule                        #查看 submodule 的 commit id
+0fae9e91890d86e633a48ba3d6c43d6d1b783360 practice_submodule (heads/master)     #是加號,代表 submodule 是舊的,現在要將 submodule 更新
jex_lin@devm3 ~/public_html/test1(master)$ git submodule update                 #更新 submodule ,在做這個動作之前你可以進去 practice_submodule 的資料夾裡面看看,資料還是上一版的資料,但應該要是最新版的才對阿!所以才需要更新 submodule
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (1/1), done.
remote: Total 3 (delta 1), reused 3 (delta 1)
Unpacking objects: 100% (3/3), done.
From https://github.com/jex-lin/practice_submodule
   0fae9e9..7ca8d97  master     -> origin/master
Submodule path 'practice_submodule': checked out '7ca8d979e730727ccfbaed3c0dbfe5c8ff3b209e'

jex_lin@devm3 ~/public_html/test1(master)$ git submodule                            #查看 submodule 的 commit id
 7ca8d979e730727ccfbaed3c0dbfe5c8ff3b209e practice_submodule (remotes/origin/HEAD)  #沒問題了,再去 submodule 裡面看,檔案都是最新版的

將 submodule 掛進來的套件停用在某個 commit id

有時候我們在專案裡使用某些套件並不會想一直隨著套件更新而也跟著更新, 因為可能會導致原本功能是正常的, 更新完就不正常了, 所以希望將掛進來的套件就一直停在固定的版本就好了

  1. 將套件 fork 到自己的 github 裡
  2. 到該 submodule 下 checkout 到要的 tag 或 commit id (會變成 no branch, 但不用理會)
  3. 回到專案根目錄下 add 全部及 commit, git submodule 就會記住該套件的 commit id, 之後拉下來都會是那個 commit id 的 code 了

Sed, Awk 指令

sed

  • -i : is used to affect the file.
  • ^ : is a beginning of line
  • $ : is a end of line
  • d : delete if there is a empty line
  • ((.|\n)*) : multiple line

刪除含有jex.conf及jex_test這兩個字串的行

sed "s/^.*jex\.conf.*jex_test.*$//g" fstab

這作法該行被刪除,但是會是空的一行

改成 :

sed "/^.*jex\.conf.*jex_test.*$/d" file.txt

該行被刪除,不會變成空行

去除空行

sed -i "/^\s*$/d" file.txt

取代空白

\s*

取代 windows 換行

sed -i 's/\r$//' wrap-trim.log

修改檔案內容

假設有一個test檔:

$a = 'ccc';
$b = 'ddd';
$c = 'ccc';
$d = 'fff';

將取代結果印出,但還不會做取代的動作

$ sed "s/'ccc'/'QQQ'/g" test
$a = 'QQQ';
$b = 'ddd';
$c = 'QQQ';
$d = 'fff';

取代檔案內容

sed -i "s/'ccc'/'QQQ'/g" test

取代檔案內容, 還會建一個原始檔案的輩份檔(.bak, 可自訂SUFFIX)

sed -i.bak "s/'ccc'/'QQQ'/g" test

test.bak 為原始檔案的輩份 ; test 為取代後的檔案

執行多個取代

sed -e "s/line1/line3/g" -e"s/line2/line4/g" /tmp/q.txt

使用-e串在後面 -e script, –expression=script : add the script to the commands to be executed -i[SUFFIX], –in-place[=SUFFIX] : edit files in place (makes backup if extension supplied)

sed: -e expression #1, char 26: extra characters after command :

注意脫逸字元

-e script, –expression=script : add the script to the commands to be executed

取代資料夾下所有檔案的取代的字串(recursive)

find . -type f -print0 | xargs -0 sed -i 's/from/to/g'

取得指定行數的區間, 並輸出成一個新檔案

sed -n 5870,5900p handler.log > /tmp/tmp.log

awk

預設以空白分隔

df | grep "/dev/loop" | awk '{print $2}'

% 分隔

df | grep "/dev/loop" | awk -F "%" '{print $2}'

print要用單引號括起來`, 不能用雙引號"

找出 lengh > 200 的行

awk 'length>200' common.log

Samba 網路芳鄰

安裝

sudo apt-get install samba

關閉

$ sudo service smbd stop
smbd stop/waiting

開啟

$ sudo service smbd start
smbd start/running, process 15892

重新啟動

$ sudo service smbd restart
smbd stop/waiting
smbd start/running, process 7782

查看目前狀態

$ sudo service smbd status
smbd start/running, process 9395

設定 /etc/samba/smb.conf : (append到最下面)

[share]                     <= 這個samba位置的名字, 變更時URL也會變
   path = /home/jex/share   <= 自已指定放置網芳的位置
   public = yes
   writable = yes
   printable = no
   #only guest = yes
   #guest ok = yes

連線

windows連進samba輸入\\{IP/Domain name}\share :

ex: \\192.168.74.65\share)

注意!! 資料夾權限

因為public = yes,所以每個人都可以丟東西進來,傳上來會是nobody:nogroup

所以samba的資料夾權限一定要設定都可寫

chmod a+w /samba資料夾

檔案誤刪處置

第一件事最重要 - 不要執行任何會覆寫硬碟的事情

  • 也就是事情發生當下,停止一切複製,移動,安裝等等會讀寫硬碟的事情
  • 如果硬碟在自架的主機,最保險的作法是馬上拆下來,接到其他電腦
  • 如果是誤刪了遠端租用的主機資料,那也能靠遠端連線還原去救

使用R-Studio還原,他的特色:

  1. 要錢 (免費版只能還原64K以下的資料,而且不能還原RAID)
  2. 可以選擇要用本機還原或是遠端還原
  3. 線上付費後授權碼馬上就寄到你的E-Mail了
  4. 評價不錯,有興趣可以google看看

必看:

  1. 要還原硬碟要先掃描, 可能因為共有400~500G, 所以掃描很容易crash, 我掃了幾次才成功, 建議本機還原掃描時不要做其他事, 掃描後要進入硬碟前會有個讀取結構的動作, 我的會固定到55﹪就會當掉, 這軟體似乎並不是那麼靠譜的..
  2. 所以要確定能還原成功再買授權,反正它的授權付完錢就寄給你了

註 :

  • 如果是還原 ext 2, 3, 4 版可以使用R-Linux這個方案,它免費最重要的是還原可以不受64K限制
  • 我是使用win8去還原外接的ext4硬碟所以我買的是R-Studio NTFS的方案
  • 它的設計是你可以先用免費的版本,開啟時都會問你要不要輸入授權,如果選擇試用,那版號就是試用,如果輸入授權碼(例如我輸入R-Studio NTFS授權碼),那啟動後就是R-Studio NTFS版

Ubuntu

查 ubuntu 版本

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 13.10
Release:        13.10
Codename:       saucy

升級 Linux kernel

查看目前版本

確切版號
$ uname -r
4.8.3-x86_64-linode76

$ uname -a
Linux 139-162-12-42 4.8.3-x86_64-linode76 #1 SMP Thu Oct 20 19:05:39 EDT 2016 x86_64 x86_64 x86_64 GNU/Linux

尋找要的 Kernel image

sudo apt-get update
sudo apt-cache search linux-image

    linux-image-3.19.0-15-generic - Linux kernel image for version 3.19.0 on 64 bit x86 SMP
    linux-image-3.19.0-15-lowlatency - Linux kernel image for version 3.19.0 on 64 bit x86 SMP
    (...略)

apt-get install linux-image-xxx

重開後移除原本的版本

reboot
sudo apt-get remove linux-image-ooo

升級 Linux 小版

sudo apt-get update
sudo apt-get dist-upgrade

更換mirror, 官方認可Mirror名單

[1] 修改Mirror源檔

sudo vim /etc/apt/sources.list

[2] 取代成 NCHC, Taiwan (National Center for High-performance Computing, Taiwan)

%s/http:\/\/tw.archive.ubuntu.com\/ubuntu\//http:\/\/free.nchc.org.tw\/ubuntu\//g

apt 指令

  • apt-cache search 'openjdk' : 尋找套件
  • apt-get update : 更新套件列表
  • apt-get upgrade : 更新已安裝套件列表
  • apt-get dist-upgrade : 更新已安裝套件(含相依)列表
  • apt-get install phpmyadmin : 安裝套件 , 例如 : phpmyadmin
  • apt-get remove phpmyadmin : 移除套件 , 例如 : phpmyadmin
  • apt-get autoremove phpmyadmin : 移除(含相依)套件 , 例如 phpmyadmin
  • apt-get -purge phpmyadmin : 移除套件及設定檔 , 例如 phpmyadmin
  • apt-get clean : 清除暫存檔

install 時找不到某個套件

apt-get install software-properties-common
apt-key adv --recv-keys --keyserver keyserver.ubuntu.com 0xcbcb082a1bb943db
sudo add-apt-repository "deb http://ftp.osuosl.org/pub/mariadb/repo/5.5/ubuntu $(lsb_release -c -s) main"
sudo apt-get update
sudo apt-get install mariadb-server mariadb-client

dpkg 指令

指令安裝.deb

sudo dpkg -i 檔案名稱.deb   //apt-get 是安裝套件(包含相依套件) , dpkg是個別檔案

移除套件.deb

sudo dpkg -r 檔案名稱.deb

查詢是否安裝某個套件 (例如git)

dpkg --get-selections | grep git

查詢已安裝套件的版本(例如php)

dpkg -l | grep php

查看套件版本等資訊

dpkg -s xfsprogs

檢查套件是否安裝

dpkg -s libapache2-mod-php5 | grep installed
Status: install ok installed
  • -s|–status … Display package status details.
  • -S|–search … Find package(s) owning file(s).

更新單一套件

sudo apt-get install packageName

This will upgrade the package even if is already installed

or

sudo apt-get --only-upgrade install packageName

重新安裝套件

user@hostname:~$ sudo apt-get purge mysql-server-5.5 mysql-client-5.5
user@hostname:~$ sudo apt-get autoremove
user@hostname:~$ sudo apt-get install mysql-server-5.6 mysql-client-5.6

sudo not found

Ubuntu 18.04

apt-get update
apt-get upgrade
apt install sudo

sudo add-apt-repository : command not found

execute :

sudo apt-get update
sudo apt-get install python-software-properties

or

sudo apt-get install software-properties-common

apt-get purge 與 remote 的差別

remove : remove - Packages installed are removed (Does NOT include configuration files)

purge : purge - Purge is identical to remove except that packages are removed and purged. Purge meaning that any configuration files are deleted too.

修改 hostname

[1] 修改 /etc/hostname, ex: ec2

[2] 修改 /etc/hosts, 加上 127.0.1.1 ec2

[3] sudo hostname -F /etc/hostname

-F, –file : read host name or NIS domain name from given file

Set Time zone

查看目前的 timezone

$ cat /etc/timezone
Etc/UTC

設定 time zone

sudo tzselect

執行 command, 或加在 .bashrc 裡

TZ='Asia/Taipei'; export TZ

重啟 time zone

sudo dpkg-reconfigure tzdata

設定完成後立即生效

cat /etc/timezone
Asia/Taipei

(Optional) 重啟 crontab

sudo service cron restart

Set Locale

查看目前 default

$ cat /etc/default/locale
LANG="en_US.UTF-8"

查看目前有的

$ locale
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_ALL to default locale: No such file or directory
LANG=en_US.UTF-8
LANGUAGE=
LC_CTYPE=UTF-8
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_PAPER="en_US.UTF-8"
LC_NAME="en_US.UTF-8"
LC_ADDRESS="en_US.UTF-8"
LC_TELEPHONE="en_US.UTF-8"
LC_MEASUREMENT="en_US.UTF-8"
LC_IDENTIFICATION="en_US.UTF-8"
LC_ALL=

perl warning

perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
    LANGUAGE = (unset),
    LC_ALL = (unset),
    LC_CTYPE = "UTF-8",
    LANG = "en_US.UTF-8"
    are supported and installed on your system.
perl: warning: Falling back to a fallback locale ("en_US.UTF-8").

你用 SSH 連到主機而主機的 SSH 設定

/etc/ssh/sshd_config :

# Allow client to pass locale environment variables
AcceptEnv LANG LC_*

你的 OS 不認識 en_US.UTF-8

解決方法1 : 使用以下方式連到 server

LC_ALL=C ssh your_server

解決方法2 : 加在 .bashrc

export LANGUAGE=en_US.UTF-8
export LC_ALL=en_US.UTF-8
export LANG=en_US.UTF-8
export LC_TYPE=en_US.UTF-8

內建自動啟動的套件

取消自動啟動

sudo update-rc.d nginx disable

移除

sudo update-rc.d -f apache2 remove

PHP

Install php package

sudo apt-get install php5 php5-curl php5-gd php5-imagick imagemagick php5-intl php5-mcrypt php5-memcached php5-mysql php5-json

顯示PHP版本資訊

$ php -v
PHP 5.3.10-1ubuntu3.5 with Suhosin-Patch (cli) (built: Jan 18 2013 23:40:19)
Copyright (c) 1997-2012 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2012 Zend Technologies

檢查語法錯誤

$ php -l test.php
No syntax errors detected in test.php

啟動php shell模式

$ php -a
Interactive shell
php > echo "Hello World!!";
Hello World!!
php > exit                      #離開shell模式

開一個SERVER

php -S test.google.com:8002

直接執行php指令

$ php -r "echo 'hello';"
hello

顯示已載入的 extension

echo "<pre>";
print_r(get_loaded_extensions());

判斷某個 extension 是否載入,未載入時載入

if ( ! extension_loaded('Memcached'))
{
    dl('memcached.so');
}

include vs require & require vs require_once

  • The require() function is identical to include(), except that it handles errors differently. If an error occurs, the include() function generates a warning, but the script will continue execution. The require() generates a fatal error, and the script will stop.
  • The require_once() statement is identical to require() except PHP will check if the file has already been included, and if so, not include (require) it again.

json_encode 中文亂碼

預設轉為json時, 如果裡面包含中文, 會被轉成編碼, 如:\u53f0\u6e7e \u53f0\u5317\u5e02

如果加上第二個參數 JSON_UNESCAPED_UNICODE:

json_encode($data, JSON_UNESCAPED_UNICODE)

出來json就會是中文了 : 台湾 台北市

將字串切割成陣列

$row_arr = preg_split('/\s+/', $row, $limit = 5);

第三個參數指定要切幾次 array preg_split ( string $pattern , string $subject [, int $limit = -1 [, int $flags = 0 ]] )

字串也能用陣列來取字元

$a = "238dsfjkh2378afhkj234d";
php > echo $a[0];
2
php > echo $a[1];
3

php multiple replace

$patterns = array("this", "that", "other");
$regex = '/(' .implode('|', $patterns) .')/i';
if (preg_replace($regex, "me")) { ...

rawurlencode 與 urlencode 的差異

編碼後都一樣的符號:

` ! @ # $(錢號) % ^ & * ( ) _ - + = < > , . / ? | \ ' " : ; { } [ ] 數字 英文 中文

不同的:

~

urlencode('~') : %7E
rawurlencode('~') : ~

(空格)

urlencode(' ') : +
rawurlencode(' ') : %20

PHP 設定檔路徑

  • php.ini : /etc/php5/apache2/php.ini
  • session 路徑 : /var/lib/php5
  • pecl (php 5.4) 預設安裝路徑 /usr/lib/php5/20100525/

server variable

.htaccess :

setEnv STATIC_DEBUG 1

php :

$_SERVER['STATIC_DEBUG']

php 傳json物件給javascript

php :

<input type="hidden" id="json-uids" value='<?php if (isset($uids)) echo json_encode($uids); ?>'/>

javascript :

var j = JSON.parse($('#json-uids').val());

不顯示錯誤訊息 : @

@ rename($old_name, $new_name)

加上@後,發生錯誤時頁面上不顯示錯誤訊息,但還是會記log (codeigniter測試)

脫逸

只有單引號裡的\需要特別脫逸 : '\\',其他符號都不需脫逸

php debug

echo "<pre>" . print_r($user_list) . "</pre>";

browser 顯示結果 :

Array
(
    [0] => Array
        (
            [id] => 1
                            [name] => jex_lin
        )
 )

隱藏錯誤

error_reporting(E_ALL ^ E_STRICT);

PHP 內部常數

DIRECTORY_SEPARATOR 是 php 內部變數, 在 windows 為 \, linux 為 /

php script 宣告

#!/usr/bin/php
<?php
// code..
?>

php script 接收指令參數

執行 php script 並接收入進來的參數, 指令 : php t.php Hello-World

t.php :

echo $argv[1];              // Hello-World

簡寫

if else :

php > ($a > 0) ? $b = 2 : $b = 3; echo $b;
2

另種寫法 :

php > $a = 1;

// 成立
php > ($a > 0) && $b = 2; echo $b;
2

// 不成立
php > ($a < 0) && $b = 3; echo $b;
2

檢查及引入某個 module (ex: mcrypt)

php -m | grep mcrypt : 檢查 mcrypt 有沒有引入

沒有則執行以下

sudo apt-get install php5-mcrypt
sudo php5enmod mcrypt

使用 nginx 記得重啟 fpm : sudo service php5-fpm restart

Session 存活時間

php.ini :

session.cookie_lifetime = 0             // The value 0 means "until the browser is closed."
session.gc_maxlifetime = 1440           // session 最多存活時間 (seconds)

Out of memory 錯誤

發生原因

Out of memory (allocated 2097152) (tried to allocate 1052672 bytes) ...略

不知道為什麼明明流量很小但 php-fpm 記憶體卻吃這麼多,後來發現 API 在結束之後,記憶體都不會自動釋放,有可能程式中有 memory leak 的問題, 造成越積越多在 php-fpm 的 process,最後沒記憶體用就 crash 了

當時總記憶體吃到 900 M+,過很長的時間 memory 都沒下降的趨勢

             total       used       free     shared    buffers     cached
Mem:           995        921         73          0        103        123

Restart php-fpm 強迫釋放總記憶體後剩 426M

$ sudo /etc/init.d/php-fpm restart
Stopping php-fpm-7.0:                                      [  OK  ]
Starting php-fpm-7.0:                                      [  OK  ]

             total       used       free     shared    buffers     cached
Mem:           995        426        568          0        103        123

可以看的出來 php-fpm 吃掉很多記憶體,雖然 restart php-fpm 可以暫時解決問題,但最好的辦法還是讓 php-fpm 自已管理好記憶體

如何解決

方法相當簡單,只需修改幾個 php-fpm 的設定 (/etc/php-fpm-7.0.d/www.conf) 就可以解決了,詳細可以參考這篇教學

改成 dynamic (固定數量+彈性增減)

pm = dynamic
pm.max_children = 15
pm.start_servers = 5            // php-fpm 一啟動就會開5個 child process 起來
pm.min_spare_servers = 5        // 最少 5 個
pm.max_spare_servers = 15       // 最多到 15 個
pm.max_requests = 500

或改成 ondemand (有需要才服務)

pm = ondemand                   // ondemand 是最少 0 個,最多到 max_children 設定的數量
pm.max_children = 15
pm.process_idle_timeout = 10s   // 當 child process 閒置 10 秒自動 kill 掉
pm.max_requests = 500

補充說明 :

max_children 要設定幾個取決於你的 php-fpm child process 吃多少記憶體,假如一個 process 吃 20MB,總記憶體有 1G, 假設扣掉系統等等雜7雜8的程序記憶體剩 300,那你的 max_children 就是 300MB/20MB = 15 個,可以設定保守點避免 php-fpm 把記憶體吃光光

max_requests 建議一定要設定這個,它意思是說當 php-fpm 的 child process 處理一個特定數量的 request 後就會把它 kill 再重新產生新的 php-fpm process, 它可以避免你因為 memory leak 問題造成整個 server crash,尤其你引入很多第三方的話你根本不知道什麼時候會出現 memory leak,即使知道了可能當下也沒辦法解, 最保險的方法就是設定這個值,即時有 leak 但到一定 request 數量就會立即釋放讓 process 及記憶體重新來過,而且它只會影響單個 process 不用擔心會有 downtime 問題

後記

原本以為 memory leak 發生在使用 aws sqs.sendMessage,因為除了更新 sdk,也另外單獨把 sqs 拉成一個獨立且單純的 api,並且一直戳它, memory 如預期一直緩慢增加且不會釋放,以為可以確認是 sqs 導致 memory leak,但是連續做 執行 sqs ->GC->看記億體使用量,卻又很正常, 判斷應該不是 sqs 造成的 leak,但確實是執行 sqs 會發生 leak 的狀況,或許是 php-fpm 底層有問題?最後實在找不出原因,也沒辦法解決,所以只好繞過這個問題, 得知一個 request 大概會有 20k 的 memory leak,根據前面設定的 pm.max_requests = 500 以及 pm.max_children = 15, 所以至少需要保留 500 * 20k * 15 = 150M 以上的記憶體做緩衝以確保 php-fpm 不會沒記憶體可用

如何確認並找出 memory leak 問題

用另一個程式一直戳你覺得有問題的 api,邊用 ps 去查看 php-fpm process memory 成長的狀況,

  PID %CPU %MEM   RSS COMMAND
19269  0.0  1.1 11872 php-fpm-7.0
19271  2.2  2.8 28696 php-fpm-7.0

當 php-fpm 執行時,mem 有增長是很正常的,但執行同一個 api 幾次到一個量後就不太會變動了,如果它一直保持穩定增長, 就表示程式有可能存在 memory leak 問題,這時候我們就要細看問題出在哪裡了。

可以把出問題的 api 分成幾個部份,在不同地方結束,邊戳邊看 memory 的變化,看哪一段會讓 memory 突然的增加, 如果找到在有問題的地方,可以在外層包一個迴圈印出記憶體使用量,如果 GC 後記憶體用量一直增加就能確定有 memory leak 的問題了

while (true) {
    // do

    gc_collect_cycles();
    echo memory_get_usage() . "\n";
    usleep(2000000);                    // wait for 2 seconds
}

MySQL 回覆 Too many connections 錯誤

發生原因是 request 量太大,並且操作 MySQL 造成 MySQL server 無法處理他同時最大能處理的連線數所導致的

可以查看目前與 MySQL 建立的 connections 數量

SHOW STATUS WHERE `variable_name` = 'Threads_connected';

最多支援的數量

SHOW VARIABLES LIKE 'max_connections';

或查看哪個 process 有建立連線但沒有用到 (Sleep)

SHOW FULL PROCESSLIST;

升級/安裝 PHP (Ubuntu)

刪除舊版本 5.0

sudo apt-get autoremove --purge php5-*

安裝 7.0

sudo add-apt-repository ppa:ondrej/php
sudo apt-get -y update
sudo apt-get install php7.0 php7.0-fpm php7.0-mysql php7.0-mcrypt php7.0-curl -y

安裝完 php 的 extension (e.g. mcrypt) 會自動幫你建立 /etc/php/7.0/mods-available/mcrypt.ini 並 link 到 /etc/php/7.0/fpm/conf.d/20-mcrypt.ini

Optional:

  • php7.0-bz2 (壓縮)
  • php7.0-dom (xml)
  • php7.0-mbstring (處理編碼)

PHP.ini/PHP-FPM 設定

(updated at 2017-08-07)

/etc/php/7.0/fpm/php.ini: cgi.fix_pathinfo 安全性設定

預設為1, 可能會導致安全性問題: www.example.com/1.jpg, 改用www.example.com/1.jpg/xxx.php 會被執行 (此段未試過, 從此篇抄過來)

解法有兩種

1) 將 cgi.fix_pathinfo = 0

2) 調整 nginx 設定

if ( $fastcgi_script_name ~ \..*\/.*php ) {
    return 403;
}

/etc/php/7.0/fpm/pool.d/www.conf: process manager 相關設定

pm = dynamic
pm.max_children = 15
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 15
pm.max_requests = 500


access.log = /var/log/php-fpm/$pool.access.log
slowlog = /var/log/php-fpm/$pool.log.slow


php_admin_value[error_log] = /var/log/php-fpm/www-error.log
php_admin_flag[log_errors] = on

/etc/php/7.0/fpm/php-fpm.conf:

# 修改 log path
error_log = /var/log/php-fpm/php7.0-fpm.log`

php-fpm 執行失敗

/var/log/nginx/error.log 顯示 /run/php/php7.0-fpm.sock 不存在 :

2017/08/07 09:03:09 [crit] 4711#0: *1 connect() to unix:/run/php/php7.0-fpm.sock failed (2: No such file or directory) while connecting to upstream, client: 127.0.0.1, server: astra.cloud, request: "GET /api/ HTTP/1.1", upstream: "fastcgi://unix:/run/php/php7.0-fpm.sock:", host: "127.0.0.1:8080"

表示可能 php-fpm 沒執行成功, 發現重新執行 php-fpm 雖然成功, 但是 process 啟動後就被 kill 了

$ sudo /etc/init.d/php7.0-fpm start
php7.0-fpm start/running, process 7801

檢查 /var/log/php7.0-fpm.log 有沒有 error message

如果上面的 log 沒有顯示 error message, 看看 /var/log/syslog 有沒有相關線索

Aug  7 09:47:45 ip-10-0-13-120 kernel: [23129.971713] init: php7.0-fpm main process ended, respawning
Aug  7 09:47:45 ip-10-0-13-120 kernel: [23129.997879] init: php7.0-fpm main process (14519) terminated with status 78`

或直接使用 test 參數看 php-fpm 問題出在哪

$ sudo php-fpm7.0 -t
[07-Aug-2017 09:56:26] ERROR: failed to open access log (/var/log/php-fpm/www.access.log): No such file or directory (2)
[07-Aug-2017 09:56:26] ERROR: failed to post process the configuration
[07-Aug-2017 09:56:26] ERROR: FPM initialization failed

php-fpm 沒有權限建立我指定的 log path, 當我建立好之後就正常了
$ sudo php-fpm7.0 -t
[07-Aug-2017 09:58:19] NOTICE: configuration file /etc/php/7.0/fpm/php-fpm.conf test is successful

安裝其他 extension

redis

1) sudo vim /etc/php/7.0/mods-available/redis.ini
```
extension=redis.so
```

2)
sudo ln -s /etc/php/7.0/mods-available/redis.ini /etc/php/7.0/cli/conf.d/20-redis.ini
sudo ln -s /etc/php/7.0/mods-available/redis.ini /etc/php/7.0/fpm/conf.d/20-redis.ini

3) restart php-fpm

4) 如果依然顯示 `Exception: Class 'Redis' not found` 就執行 `sudo apt-get install php-redis`, 再 restart php-fpm

將 session 改使用 redis, /etc/php/7.0/fpm/pool.d/www.conf

php_value[session.save_path] = tcp://redis.astra.ap-northeast-1.local:6379

MySQL

(最後更新: 2016-04-14)

Install

ubuntu :

sudo apt-get install mysql-server-5.6 mysql-client-5.6 mysql-client-core-5.6

mac :

brew install mysql
mysql.server restart

// client only
brew install mysql --client-only

mac : 開機自動啟動

mkdir -p ~/Library/LaunchAgents
ln -sfv /usr/local/opt/mysql/*.plist ~/Library/LaunchAgents

登入不用密碼

如果你連 mysql 都進不去,從步驟[1]開始,如果你可以進到 mysql console 從步驟[3]開始

[1] 停止 mysql deamon

sudo /etc/init.d/mysql stop

[2] 加上 --skip-grant-tables option 啟動 mysql 指令

sudo /usr/sbin/mysqld --skip-grant-tables --skip-networking &

[3] edit password

mysql -u root
FLUSH PRIVILEGES;

修改密碼

SET PASSWORD FOR root@'localhost' = PASSWORD('');
或
UPDATE mysql.user SET Password=PASSWORD('') WHERE User='root';

[4] Flush privileges:

FLUSH PRIVILEGES;

[5] Restart mysql

sudo service mysql restart

設定/修改密碼

設定 root 密碼 (安裝時沒有設定過密碼)

mysqladmin -u root password

修改 root 密碼

mysqladmin -u root -p password

Enter password:
New password:
Confirm new password:

解除只有本機 IP 能連到 MySQL 的限制

[1] 預設 MySQL 的 3306 port 只有監聽本機的連結

root@jexpoyi:/etc/apache2# netstat -an | grep 3306
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN

[2] 修改 /etc/mysql/my.cnf, 註解 bind-address = 127.0.0.1

# bind-address      = 127.0.0.1

[3] 重啟

/etc/init.d/mysql restart

如果有使用 phpmyadmin, 要修改設定

權限登錄資料主機改成任意主機(%)

[4] 現在port就已經開啟讓外部ip可以連入了

root@jexpoyi:/etc/mysql# netstat -an | grep 3306
tcp        0      0 0.0.0.0:3306            0.0.0.0:*               LISTEN

[5] Done!

[error] 如果phpmyadmin沒辦法登入, 直接command登入也出現錯誤:

ERROR 1045 (28000): Access denied for user 'test'@'localhost' (using password: YES)

可能原因是host改成 % (任意主機),但批對不到localhost造成本機沒辦法登入

解決 : 再去phpmyadmin將主機改成localhost, 這時候host資料就有兩筆了%localhost, 就可以登入了

Access denied for user 'worker'@'10.0.21.77' (using password: YES)

有可能是你正在轉移 DB, 但轉移時並不會幫你把 account 的密碼也轉移過去, 所以必須到修改 account 的密碼把密碼填上

Host '172.18.0.4' is not allowed to connect to this MySQL server

修改 root 的 host

USE mysql;
mysql> SELECT user,host FROM user WHERE User='root';
+------+-----------+
| user | host      |
+------+-----------+
| root | localhost |
+------+-----------+
mysql> UPDATE user SET host='%' WHERE user='root';
mysql> FLUSH PRIVILEGES;
mysql> SELECT user,host FROM user WHERE User='root';
+------+------+
| user | host |
+------+------+
| root | %    |
+------+------+

Slow query

目的 : 將拖慢速度的 query 找出來, 進一步改善效能

開啟 slow query

1) /etc/mysql/mysql.conf.d/mysqld.cnf :
# 將原本被註解的 slow query 參數打開
slow-query-log = 1
slow_query_log_file = /var/log/mysql/mysql-slow.log     # slow query記錄檔的路儲存路徑
long_query_time = 1                                     # query超過1秒時,則會記錄
log-queries-not-using-indexes                           # 會記錄沒有索引的記錄

注意!! The --log-slow-queries option was removed in MySQL 5.6.1

2) 重啟動後就會在 /var/log/mysql/ 下看到這個檔案了 : mysql-slow.log

分析 Slow query

安裝 mysql-server 就會有 mysqldumpslow 這個工具了

mysqldumpslow /var/log/mysql/mysql-slow.log

找出花最多時間的 5 個 queries

mysqldumpslow -a -s r -t 5 /var/log/mysql/mysql-slow.log

依最常執行的 query 做排序

mysqldumpslow -a -s c -t 5 /var/log/mysql/mysql-slow.log

參數 :

-t NUM       just show the top n queries
-a           don't abstract all numbers to N and strings to 'S'
-s ORDER     what to sort by (al, at, ar, c, l, r, t), 'at' is default
              al: average lock time
              ar: average rows sent
              at: average query time
               c: count
               l: lock time
               r: rows sent
               t: query time

其他分析工具 : pt-query-digest, mysqlsla

尋找 slow query

找出排名前 5 筆花最久時間的 query

SELECT * FROM slow_log ORDER BY query_time DESC LIMIT 5;

找出某天前 5 筆花最久時間的 query

SELECT * FROM slow_log WHERE start_time BETWEEN '2018-06-28 00:00:00' AND '2018-06-29 00:00:00' ORDER BY query_time DESC LIMIT 5;

Binlog

目的 : 將執行過的 query 紀錄下來, 當有一天操作失誤或數據遺失還可以復原回來

開啟 binlog

1) /etc/mysql/mysql.conf.d/mysqld.cnf :
# 將原本被註解的 log_bin 打開
log_bin = /var/log/mysql/mysql-bin.log

其他 :

# 只紀錄指定的 databases
binlog-do-db=db_name1
binlog-do-db=db_name2

# 忽略指定的 database
binlog-ignore-db=db_name1

# 限制 binlog 上限 size
max_binlog_size=100M

# 10 天後自動清除 binlog
expire_logs_days=10
2) 重啟動後就會在 /var/log/mysql/ 下看到這兩個檔案了 : mysql-bin.index mysql-bin.000001
3) 測試

隨意 Inster 兩筆資料

使用 mysqlbinlog 指令將 binglog 輸出成 SQL

mysqlbinlog --start-datetime="2015-05-25 00:00:00"  --stop-datetime="2015-05-26 9:00:00" /var/log/mysql/mysql-bin.000001 > test_binlog_is_working.sql

執行還原

mysql> SOURCE test_binlog_is_working.sql;

其他指令

查看狀態

SHOW MASTER STATUS

重置

RESET MASTER

刪除指定日期以前的 binlog

PURGE BINARY LOGS BEFORE '2015-05-24 07:00:00';

刪除某個流水號以前的 binlog

PURGE BINARY LOGS TO ‘mysql-bin.000113′;

將超過 n 天的 binlog

FLUSH LOGS

輸出 mysql-bin 為檔案

mysqlbinlog /var/log/mysql/mysql-bin.000001 > qq.txt

恢復數據

1) 確定 binlog 是否有開啟, 如果有找出 binlog 位置
SHOW VARIABLES LIKE 'log_bin'
  • 如果回應為 ON, 表示有開啟, 到 /etc/mysql/mysql.conf.d/mysqld.cnf 尋找 log-bin 的值
  • 回應為 OFF 則表示沒有開啟, 不用執行以下步驟, 因為無法恢復了

假設 my.cnf 記錄著 :

log_bin = /var/log/mysql/mysql-bin.log  # 檔名前綴字串

/var/log/mysql :

(..略..)
-rw-rw----  1 mysql mysql  12967 May 24 01:19 mysql-bin.000001
-rw-rw----  1 mysql mysql     32 May 24 01:01 mysql-bin.index
2) 恢復最近一次備份的數據 (Optional)

如果 Server 之前有輩份, 先還原

mysql> SOURCE my_db.bak.sql
3) 用 mysqlbinlog 輸出 binlog

[1] 輸出整個 database

mysqlbinlog 無法直接輸出 table, 只能是一整個 database, 所以先取得 database 再到裡面找 table

指令需要指定 --database, --start-date, --stop-date, 在這期間內可能跨了很多 binlog

mysqlbinlog --database=my_db --start-datetime="2015-05-25 00:00:00"  --stop-datetime="2015-05-26 9:00:00" /var/log/mysql/mysql-bin.000001 > my_db.sql

//如果跨越了幾個 binlog, 採用追加的方式加到 bbs.sql
mysqlbinlog --database=my_db --start-datetime="2015-05-25 00:00:00"  --stop-datetime="2015-05-26 9:00:00" /var/log/mysql/mysql-bin.000001 >> my_db.sql

也可以用 /var/log/mysql/mysql-bin.00012* 模糊比對的方式指定多個 binlog 文件

[2] 取得指定的 table

cat my_db.sql | grep 'comment' > comment.sql
4) 執行要恢復的 table 的 binlog

[1] 找出 comment.sql 誤操作的語法並刪除, 如果只是要恢復數據可以不用做動作

[2] 執行 mysql> SOURCE comment.sql

ref : 參考恢復數據

MariaDB

MariaDB 是 mysql 的分支,效能比 MySQL 好,吃的資源也較少,用法與 MySQL 一模一樣,可以直接拿它來替代 MySQL

Install

sudo apt-get install mariadb-server mariadb-client

test :

mysql -u root

啟動

sudo service mysql start

Q&A

某些中文字無法存入問題

unicode 編碼一般常用字都是 3bytes, 但因為它是 4 byte 而無法寫入, 例如 : 𡟛參考 wiki

Mysql2::Error: Incorrect string value: '\xF0\xA1\x9F\x9B'

(未試過) 解法方法 The utf8mb4 Character Set (4-Byte UTF-8 Unicode Encoding)

如果 MySQL Ram 吃到 45%+

在一台乾淨的 EC2 t1.micro 1G Ram,居然被吃到 45%,而執行 Rails app 後 Memory 就快被吃快了,網頁也變很頓,簡單的登入登出就會掛掉,

接著我試著增加 Swap,解決此問題,指令請到這篇增加 Swap 那段照著執行,

再執行一次 mysql start,一樣 Memory 是衝到爆,但它會自動分配一些到 swap,之後 MySQL 的 Memory 降到 26% -> 19% -> 16% ..

欄位 Type

  • Datetime 後面可以接長度,多出來的長度是小數點,可以細到毫秒以上 e.g. datetime(3) = 2017-06-28 03:14:35.280

SELECT * 及 SELECT {特定欄位} 的差別

當取出來的資料只有 1 筆可能還沒什麼差別, 但當資料有 1000, Query 的時間會微變慢,

但更慢的是當從 MySQL 取出來的資料傳輸到程式裡所花的時間, 而時間取決於 Query 出來的資料量大小, 很容易有 2s+ 的差距, 需要注意!

區分 : 全形 半形 大小寫

在某些情況下, 需要全形半形 db 的欄位 collation 就不要用 utf8_unicode_ci

要改用 utf8_general_ci

如果要區分大小寫就改用 utf8_bin

QPS & TPS

  • QPS:Queries Per Second 查詢量/秒, 是一台服務器每秒能夠相應的查詢次數, 是對一個特定的查詢服務器在規定時間內所處理查詢量多少的衡量標準
  • TPS : Transactions Per Second 是事務數/秒, 是一台數據庫服務器在單位時間內處理的事務的個數

查詢方法是在 MySQL console 輸入 SHOW STATUS;, 並指到以下對應的欄位

  • QPS = Questions
  • TPS = Com_commit/s + Com_rollback/s

SSH 連線至主機

產生 ssh key 並且設定免密碼直接連入遠端主機

本機:

cd ~
ssh-keygen -t rsa
(enter到底)
cd .ssh
scp id_rsa.pub 遠端主機網址:~/.ssh/qq  (把本機的公鑰複製到遠端主機)

登入遠端主機:

ssh 遠端主機網址
(會要你輸入密碼)
cd .ssh
cat qq >> authorized_keys   (將主機的公鑰加入到遠端的驗證key裡)
rm qq                       (加入到authorized_keys後就沒有用了, 刪除它)
exit                        (大功告成, 離開遠端主機)

回本機:

ssh 遠端主機網址
(不會要求你輸入密碼而直接進入)

安全性設定

Open /etc/ssh/sshd_config (注意!! 不要改錯檔案, 是 sshd_config 不是 ssh_config) :

允許密碼驗證

PasswordAuthentication yes

改 SSH default port

Port 2222
Protocol 2
PermitRootLogin no
  • 1025~65536 的 port 都可以使用
  • PermitRootLogin 設為 no 的話, root 登入還是會要求你輸入密碼, 只是即使輸入的是對的密碼, 也會告訴你是錯的, 即不讓你登入就對了

只允許某個 User

UseDNS no
AllowUsers jex

Reload 新設定

別急著登出, 先測試修改是否成功

sudo service ssh restart
ssh -p 2222 jex@localhost

如果有設定防火牆記得把 2222 port 打開

當我的主機重灌後, 我要 ssh 進去時發生這樣的錯誤 :

device-f33d32:~ apple$ ssh jex@jexpoyi.com
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the RSA key sent by the remote host is
e2:c8:73:6b:01:ad:29:cf:7c:1c:54:68:93:4c:a8:7e.
Please contact your system administrator.
Add correct host key in /Users/apple/.ssh/known_hosts to get rid of this message.
Offending RSA key in /Users/apple/.ssh/known_hosts:1
RSA host key for jexpoyi.com has changed and you have requested strict checking.
Host key verification failed.

意思是 .ssh/known_hosts 原本記著這樣的紀錄

jexpoyi.com,142.4.51.133 ssh-rsa AAAAB3NzaC1yc2EAAA(..略..)

但是重灌後的 rsa 不一樣了, 所以為了安全性的考量而無法登入, 解決方法很簡單

只要把 .ssh/known_hosts 裡主機那行紀錄刪除, 再 ssh 進去重新產生紀錄就行了

設定 push 或 pull github 不用再輸入帳密

將你的 repository 的 ssh URL 加到你的 origin, 不要用 https URL

然候將你的 ssh public key 加到 github 後台的 ssh 管理

設定 alias - 每一次連線不用輸入過多參數

~/.ssh/config 加上要 alias 對應的欄位

HOST test
    HostName 192.168.79.21
    User jex
    Port 2229
    ...
    IdentityFile aws.pem
    LocalForward 35729 192.168.79.21:35729      (將主機的 35729 導到本機的 35729)
    ...

之後輸入 ssh test 即可

.pem 的權限要調低, 否則會報錯, chmod 400 aws.pem

使用 .pem 連進主機,除了上述方法設定也可以用參數 -i

sudo ssh ubuntu@123.123.123.123 -i aws.pem

AWS Ubuntu 主機預設 User 叫 ubuntu

在同一台主機避免跟同事用到同一個 session 方法

可在 .bashrc 設定 :

sssh (){ ssh -t "$1" 'tmux attach -t jex || tmux new -s jex || screen -DR'; }

之後執行 sssh 就好了!

Port Forwarding

  • Mac :

本機執行 ssh -L 127.0.0.1:35729:54.199.219.49:35729 ubuntu@54.199.219.49

或者在 ~/.ssh/config 設定 :

Host ec2
    HostName 54.199.219.49
    User ubuntu
    Port 22
    LocalForward 35729 54.199.219.49:35729
  • LocalForward 是將遠端主機 54.199.219.49 的 35729 導到本機的 35729
  • 如果要連到其他 ssh port 的寫法 ssh jx@54.164.251.31 -p 4223 -L 35729:54.164.251.31:35729
  • Windows :

如果使用 putty 要多設定 Conection->SSH->Tunels

Source port : 35729
Destination : 54.199.219.49:35729
(下面選項維持預設 Local 及 Auto)

forward agent - 本機登錄到 basion 後再登入到其他主機時不需要把 private key 放到 basion

1) 將本機的私鑰加入到 keychain

ssh-add -K

ssh-add -lssh-add -L 就可以看到剛剛加入的那把私鑰了

在本機及 basion 加入以下,當從 basion 登入到其他台時, basion 那台就不需要有本機 private key 就能連到其他台了

Host *
        ForwardAgent yes
        ServerAliveInterval 300
        ServerAliveCountMax 5

一行指令直接經由 basion 登入主機 (proxycommand)

大部份我們的主機架構會長這樣,每次要到 server 都要經由 basion,所以會輸入兩次 ssh 很麻煩

laptop -> basion -> server

我們可以用一行指令就能直接登入 server

ssh -tt basion ssh -tt server

但這樣雖然縮短時間,不需要等到進去 basion 再 ssh 到主機,但 command 還是有點太長,可以用 proxycommand 做到,它的原理是用 nc 指令建立 TCP 連線連接 basion 與 server

(TODO)

PuTTY免密碼以SSH登入主機

[1] 主機要先產生完 SSH Key (ssh請選擇rsa)

[2] 下載 WinSCP,並使用它登入主機把私鑰的 SSH Key 下載下來(id_rsa)

[3] 將私鑰名稱(id_rsa)加上副檔名(.ppk) => id_rsa.ppk

[4] 到 PuTTY 官網頁下載 puttygen.exe

[5] 打開PuTTYgen

  1. 點選 “Load” 讀取 id_rsa.ppk
  2. 選擇 SSH-1 (RSA)
  3. 點選 Save private key

[6] 打開PuTTY

  • 左邊 Connection→Data 輸入 Auto-login username
  • 左邊 Connection→SSH→Auth

1) 打勾 Attempt authentication using Pageant 2) 打勾 Attempt TIS or CrptoCard auth(SSH-1) (其他不要打勾) 3) 下面瀏覽框要將 PuTTYgen 儲存的私鑰讀進來

[7] 完成,記得回到 Session 點 save 儲存這些設定,這樣下次就不用又從頭設定一次喔!

將 AmazonEC2MicroInstance.pem 轉成 ppk

開啟 puttygen.ext -> Load (選擇 AmazonEC2MicroInstance.pem) -> Save private key (存成 amazon-ec2.ppk)

左邊 SSH -> Auth 的 Private key file for authentication 選擇剛剛生出來的 amazon-ec2.ppk

Apache 減少瀏覽器向server請求次數

當瀏覽器第一次讀取圖檔

status : 200 OK

request header:

Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding:gzip,deflate,sdch
Accept-Language:zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4
Connection:keep-alive
Host:jex.com
User-Agent:Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36

response header:

Accept-Ranges:bytes
Connection:Keep-Alive
Content-Length:45860
Content-Type:image/jpeg
Date:Thu, 11 Jul 2013 08:09:03 GMT
ETag:"bc003e-b324-4e136f4c01a37"
Keep-Alive:timeout=5, max=100
Last-Modified:Thu, 11 Jul 2013 07:00:57 GMT
Server:Apache/2.2.22 (Ubuntu)

內容會有圖檔

第二次讀取讀檔會跟cache要所以

status : 304 Not Modified

request header:

Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding:gzip,deflate,sdch
Accept-Language:zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4
Cache-Control:max-age=0
Connection:keep-alive
Host:jex.com
If-Modified-Since:Thu, 11 Jul 2013 07:00:57 GMT
If-None-Match:"bc003e-b324-4e136f4c01a37"
User-Agent:Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36

response header:

Connection:Keep-Alive
Date:Thu, 11 Jul 2013 08:09:51 GMT
ETag:"bc003e-b324-4e136f4c01a37"
Keep-Alive:timeout=5, max=100
Server:Apache/2.2.22 (Ubuntu)

內容不會有圖檔,只會送出request然候apache比對ETag及Last-Modified,如果檔案有被修改才是status:200

調整apache設定,當網址一樣時讓瀏覽器連304都不送直接取cache

[1] 打開模組

  1. /etc/apache2/mods-available
  2. ls | grep expire 及 ls | grep header 確定有沒有這兩個模組,如果沒有就開啟
  3. Once you install the module, the module will be available in the /etc/apache2/mods-available directory. You can use the a2enmod command to enable a module. You can use the a2dismod command to disable a module. Once you enable the module, the module will be available in the the /etc/apache2/mods-enabled directory.

[2] 在 /etc/apache2/sites-available/default 加上

ExpiresActive On
ExpiresByType image/jpg "access plus 10 years"
ExpiresByType image/jpeg "access plus 10 years"

[3] sudo service apache2 restart

設定後的測試:

[1] 第一次load圖

status : 200 OK

request header:

Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding:gzip,deflate,sdch
Accept-Language:zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4
Cache-Control:no-cache
Connection:keep-alive
Host:jex.com
Pragma:no-cache
User-Agent:Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36

response header:

Accept-Ranges:bytes
Cache-Control:max-age=315360000
Connection:Keep-Alive
Content-Length:45860
Content-Type:image/jpeg
Date:Thu, 11 Jul 2013 08:11:46 GMT
ETag:"bc003e-b324-4e136f4c01a37"
Expires:Sun, 09 Jul 2023 08:11:46 GMT
Keep-Alive:timeout=5, max=99
Last-Modified:Thu, 11 Jul 2013 07:00:57 GMT
Server:Apache/2.2.22 (Ubuntu)

有加上expire了

[2] 第二次load圖

status : 304 Not Modified

request header:

Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding:gzip,deflate,sdch
Accept-Language:zh-TW,zh;q=0.8,en-US;q=0.6,en;q=0.4
Cache-Control:max-age=0
Connection:keep-alive
Host:jex.com
If-Modified-Since:Thu, 11 Jul 2013 07:00:57 GMT
If-None-Match:"bc003e-b324-4e136f4c01a37"
User-Agent:Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.116 Safari/537.36

response header:

Cache-Control:max-age=315360000
Connection:Keep-Alive
Date:Thu, 11 Jul 2013 08:25:53 GMT
ETag:"bc003e-b324-4e136f4c01a37"
Expires:Sun, 09 Jul 2023 08:25:53 GMT
Keep-Alive:timeout=5, max=99
Server:Apache/2.2.22 (Ubuntu)

[3] 當從外面直接連進來後會直接從cache拿了

Request URL:http://jex.com/static/a/ce/ce9600a437486fbfd9b13856f2e1c55f.jpg?1373516123
Request Method:GET
Status Code:200 OK (from cache)
Query String Parametersview sourceview URL encoded
1373516123:

並不會有request header及response header,因為根本沒送request chrome也會顯示是 (from cache)

結語:

快取機制還是要看瀏覽器自已的實作, 如果到已造訪過的網站, chrome第一次可能拿快取的圖片, 但reload後chrome還是會去問一次得到304才去拿cache的檔案, 所以快取機制沒有一定, 或許chrome 再下一版又換方法了

註:

大頭貼檔名後加上從DB取的timestamp(ex: avatar.jpg?1373530721),當更新大頭貼時要更新DB的欄位(紀錄修改時間),才能讓瀏覽器判斷是否是修改過的圖

ref : 夯哥

PHP - Codeigniter

判斷DB是否執行成功

判斷select

if ($query->result_array())
{
    // display data..
}
return FALSE;

判斷insert

if ($this->db->insert_id())
{
    return TRUE;
}

判斷update是否成功更新

if ($this->db->update('shares'))
{
    return TRUE;
}

return ($this->db->affected_rows() > 0) ? TRUE : FALSE;

判斷delete是否成功刪除

$this->db->delete('question_options');
if ($this->db->affected_rows() > 0)
{
  return TRUE;
}
return FALSE;

欄位更新 ex: +1

$this->db->set('lose', 'lose+1', FALSE);
$this->db->where('guid', $data['guid']);
$this->db->update('users');

Form Validation 的 callback

 array(
     'field' => 'current_passwd',
     'label' => 'lang:users-current_passwd',
     'rules' => 'trim|required|callback_check_password[13]'
 )

 function check_password($current_passwd, $uid)
 {
      // $current_passwd : CI 會自已捕捉 HTML 裡 name="current_passwd" post 進來的值,放入第一個參數
      // $uid = 13

      if ( // Condition )
      {
          return TRUE;
      }
      return FALSE;
 }

給 callback 的參數是從第二個參數開始給,第一個參數都是保留給自已那個欄位的值(current_passwd的值)

Load model in Library

private $CI = NULL;

public function __construct()
{
    $this->CI =& get_instance();
    $this->CI->load->model('test_model');
}

codeigniter 寄信錯誤 “無法使用 mail() 函式發送信件. 主機可能未設定此方法發送信件”

可能沒有安裝sendmail

sudo apt-get install sendmail

測試sendmail 寄信

$ sendmail jex_lin@example.com      <= enter
Subject: test                        <= enter
test                                 <= enter
.                                    <= enter

OR

echo "My test email being sent from sendmail" | /usr/sbin/sendmail jex_lin@example.com

CI input會自動過濾 %00~%08 %0b %0c %0e %0f

system/core/Input.phpCI_INPUT class__contruct() 會執行 _sanitize_globals metheod

_sanitize_globals method 會過濾一切 $_GET$_POST 變數所以即使拿原生函數 $_GET 去抓都沒有用,會先被ci的 method 給過濾掉

而在這裡面它執行的是 _clean_input_data 這個 method 而它又會去執行

remove_invisible_characters 這個 function,而它是在 system/core/Common.php

就是由它去過濾 %00-%08 %0b %0c %0e %0f,把它們轉成空字串

可藉由 remove_invisible_characters 的第二個參數指定為 false 關閉過濾

自訂 controller

/application/core/MY_Controller :

require_once 'Base_Controller.php';

class MY_Controller extends CI_Controller {

CI 的 log_message

log_message("INFO", "data: " . var_export($data, TRUE));

第二個參數預設為 FALSE : If you would like to capture the output of print_r(), use the return parameter. When this parameter is set to TRUE, print_r() will return the information rather than print it.

crop avatar

$crop_x = $this->input->post('crop_x');
$crop_y = $this->input->post('crop_y');
$crop_w = $this->input->post('crop_w');
$crop_h = $this->input->post('crop_h');

// calculate new coordinate, width and height
list($ori_width, $ori_height) = getimagesize($original_image_path);
list($pre_width, $pre_height) = getimagesize($preview_image_path);
$width_percent = round($ori_width / $pre_width, 2);
$height_percent = round($ori_height / $pre_height, 2);
$new_x = (int) round($crop_x * $width_percent);
$new_y = (int) round($crop_y * $height_percent);
$new_w = (int) round($crop_w * $width_percent);
$new_h = (int) round($crop_h * $height_percent);

//crop
$config = array(
    'image_library'     => 'gd2',
    'source_image'      => $original_image_path,
    'new_image'         => $destination_image,
    'quality'           => 100,
    'maintain_ratio'    => FALSE,
    'x_axis'            => $new_x,
    'y_axis'            => $new_y,
    'width'             => $new_w,
    'height'            => $new_h
);
$this->image_lib->initialize($config);
if ($this->image_lib->crop())
{
    // resize to 180 * 180
    $config = array(
        'image_library'     => 'ImageMagick',
        'library_path'      => '/usr/bin',
        'source_image'      => $destination_image,
        'quality'           => 100,
        'maintain_ratio'    => TRUE,
        'width'             => 180,
        'height'            => 180
    );
    $this->image_lib->initialize($config);
    if ($this->image_lib->resize())
    {
        // update last_avatar_time
        $this->load->model(array('users_model', 'userlog_model'));
        $time = time();
        if ($this->users_model->update($user['account'], array('last_avatar_time' => $time)))
        {
            // update session
            $update_session_data = array('avatar_url' => get_avatar_path($user['id'], FALSE) .'?'. $time);
            $this->login->update_session($update_session_data);

            $this->userlog_model->insert($user['id'], $user['name'], UserLog_model::USER_AVATAR_UPDATE);
        }

        $this->_outputJSON(array('status' => 'ok'));
    }
}

parseImg

$url = $this->input->get_post('url');
if ( ! $html_content = @file_get_contents($url))
{
    return $this->_outputJSON(array('status' => 'fail', 'error' => 'Wrong url'));
}
$patten = "/<img.*src=\"(((http[s]?):\/\/|www\.)([^\s\[\\\"'])+)\".*>/";
if (preg_match_all($patten, $html_content, $matches, PREG_SET_ORDER))
{
    foreach ($matches as $key => $match)
    {
        if (list($width, $height) = @getimagesize($match[1]))
        {
            if ($width >= 100 OR $height >= 100)
            {
                return $this->_outputJSON(array('status' => 'ok', 'url' => $match[1]));
            }
        }
        else
        {
            unset($matches[$key]);
        }
    }
    if ( ! empty($matches))
    {
        reset($matches);
        return $this->_outputJSON(array('status' => 'ok', 'url' => current($matches)[1]));
    }
}
return $this->_outputJSON(array('status' => 'fail', 'error' => 'Image not found'));