電影流水帳(2020/07/01~2020/07/15)

132991_5420

엑시트

去年本來打算去電影院看這部電影的,但後來沒有去。今年應該是因為疫情吧,第四台提早上了片,照理來說都會滿一年才上片的。故事很直白,有不少笑點,節奏也緊湊,蠻好看的。

李永南在學校時,是攀岩、登山社的成員,但畢業後連續幾年都求職失敗,被家裡的人看不起。這天,媽媽大壽,永南跟家人一起去飯店吃飯。在飯店,他遇到他的初戀告白對象-意珠,意珠現在在這間飯店當副店長,永南自慚形穢,跟意珠打招呼後,說了謊,說自己現在在大公司上班,過的不錯。

怎知,外面突然煙霧四起,沾染到氣體的人都昏倒在地,永南的姐姐也因此而昏倒,還好被緊急拉回飯店。大家逼不得已,只能往上跑,以避免氣體的侵襲。永南姐姐情況緊急,亟需求援,可是飯店的頂樓又被鎖住,永南只好拿出以前登山社的本事,以有限的裝備往上爬。意珠見狀,也來幫忙。雖然過程千驚萬險,永南總算是爬上頂樓,並找到救援。

故事就這樣結束了嗎?並沒有。來救援的直升機有承重限制,永南跟意珠只好留下,等待後續救援。只是氣體仍然在蔓延,兩人只能往毒氣少的大樓去。兩人就這樣在高空邊攀爬、邊逃走,兩人一同攜手共渡生死難關,也慢慢培養出感情來。總之,最後兩人順利逃離。

Hitman: Agent 47

以前也看過另外一部殺手47,但我已經不太記得劇情了,所以無從跟這部比較起。單就動作、爆破來說,蠻好看的,就是一部爽片。

這次故事主角不太算是殺手47,而是女主角 Katia。Katia 一直在世界各地尋找父親,但始終找不到。某天在路上走,被一個帥哥 John 搭訕之後,開始被一個光頭大哥追捕,她很吃驚。不過更令人吃驚的事情還在後頭,被光頭大哥追捕時,她隱隱約約感覺到,自己並不是被追捕的對象。光頭大哥把 John 打趴了,帶走了 Katia。這時光頭大哥才跟 Katia 自我介紹,「我沒有名字,就叫 Agent 47,我帶你去找你父親」,於是 Agent 47 就帶 Katia 去找她的父親。

被打趴的 John 並沒有死,捲土重來,繼續追著 Agent 47 跟 Katia 跑。Agent 47 並非浪得虛名,實力堅強,每每都能躲過 John 的追擊。後來 Agent 47 把 Katia 帶到她父親那兒去,這時才明白了為什麼自己被追著跑,為什麼 Agent 47 要保護她。

Katia 的父親就是開發 Agent 計畫的人,Agent 是什麼?Agent 就是人工培育出來的超級特務,強大又聽話。這麼好的 Agent ,組織高層當然想要更多,但 Katia 父親發現了這件事情,於是用最強大的基因培育出 Katia,並且命令 Agent 47 保護 Katia。

組織當然沒這麼容易放過他們三人,傾全力追殺三人。Agent 47 告訴 Katia 父親,「你覺得組織會放過你跟 Katia 嗎?射人先射馬,擒賊先擒王,不把組織的首腦解決,我們永無寧日。」Katia 父親知道 Agent 47 的意思。但這時,三人也只能繼續逃。組織動員全部力量果然厲害,成功阻截街頭飛車,並擄走 Katia 父親。Agent 47 沒說什麼,只默默的帶走 Katia。到了安全的地方,Katia 就很氣,為什麼不救父親?Agent 47 這時才說,我在你父親身上裝了 GPS,利用你父親去找到組織首腦。

Katia 父親被帶回組織總部後,被組織首腦審問,兩人談不出結果,一個不交出研究成果,一個不願意放手征服世界。接著,Agent 47 跟 Katia 就來了,兩人殺進組織總部,大開殺戒。一場大戰以後,組織總部被毀,組織首腦帶著 Katia 父親搭直升機逃掉。就這樣,Agent 47 跟 Katia 在屋頂上眼錚錚看著直升機飛走…直升機爆炸!為什麼會爆炸?Agent 47 這時才說,是我給你父親炸彈的,因為我預期到組織首腦會帶著你父親逃走,而你父親也知道只有首腦消滅,妳才會安全。Katia 流著眼淚,邊撂下狠話給 Agent 47,之後再找你算帳。兩人要下樓時,看到長的跟 Agent 48 一樣的 Agent 47 出來,電影就這樣結束了。

Django site framework

之前專案都是使用 cookiecutter + cookiecutter-django 來產生的,前一陣子在它產生出來的設定裡看到 SITE_ID 以及註解,就發現了 Django site framework

Django site framework 裡的 site ,就有點像是 virtual host 裡的 host,簡單的說,可以做到以下事情:

  • Virtual host:Django 可以有能力去判斷網址裡的網域,來決定如何處理。
  • 不同網站入口,但共用資料庫

啟用方法

基本上 site framework 已經內建在 Django 裡了,所以只要啟用就可以

  1. 在 settings 的 INSTALLED_APPS 加入 “django.contrib.sites”
  2. 增加 SITE_ID  (選填,可以加這個設定,也可以不加)

執行 migrate python manage.py migrate

接著利用 admin 後台或者是 shell 去新增 site。

功能

有提供以下功能

  • 物件 (model) 跟指定的 site 關連
  • View 可以依據 site 來做出不同的處理跟回應
  • 多個 site,但只使用指定的 site
  • 自動依照網址內的網域來判斷,並將 site 放到 request 裡
物件 (model) 跟指定的 site 關連

site framework 本身有建立 model ,也就是說有建立資料表格,所以其實是可以讓物件模型跟 site 做關連,那麼之後在處理時,就可以依據這個關係,只顯示出跟指定 site 相關連的物件。

# 引用自 django 文件
from django.contrib.sites.models import Site
from django.db import models

class Article(models.Model):
    headline = models.CharField(max_length=200)
    # ...
    sites = models.ManyToManyField(Site)

上面的程式碼,就是表示 Article 跟 Site 做多對多的關聯。之後在 View 裡就可以用下面的程式碼撈出跟指定站點相關連的 Article

# 引用自 Django 文件
from django.contrib.sites.shortcuts import get_current_site

def article_detail(request, article_id):
    try:
        a = Article.objects.get(id=article_id, sites__id=get_current_site(request).id)
    except Article.DoesNotExist:
        raise Http404("站點裡沒有文章")
    # ...
View 可以依據 site 來做出不同的處理跟回應

其實上面的程式碼已經就是了,這邊再舉個例子

# 引用自 Django 文件
from django.contrib.sites.shortcuts import get_current_site

def my_view(request):
    current_site = get_current_site(request)
    if current_site.domain == 'foo.com':
        # Do something
        pass
    else:
        # Do something else.
        pass

這邊使用 site framework 提供的 shortcut – get_current_site 來取得目前的 site,然後用 if – else 來做判斷,執行不同的邏輯

多個 site,但只使用指定的 site

那程式裡已經有多個 site,但是想拆分出來,只服務單一個 site 時,可以在 settings 裡指定 SITE_ID,程式裡可以直接引用,例如

from django.conf import settings

def my_view(request):
    if settings.SITE_ID == 3:
        # Do something.
        pass
    else:
        # Do something else.
        pass

這個蠻適合應用在 docker 上,我只要打包好 docker image,之後就可以再利用指定環境變數的方式去讓這個 container 能處理指定的 site

自動依照網址內的網域來判斷,並將 site 放到 request 裡

要完成這個功能,需要在 settings 裡的 MIDDLEWARE 裡,加入 “django.contrib.sites.middleware.CurrentSiteMiddleware”

加入以後,View 裡的 request 就會多出一個 site 的屬性,那在 View 裡,就可以直接使用 request.site

Site framework 內部的運作

依據上面的說明,除了資料庫以外,get_current_site 好像…挺重要的,下面就來繼續挖掘。

我們先從 middleware 開始,CurrentSiteMiddleware 裡面蠻簡單的,只有 override process_request(),裡面只有一行:request.site = get_current_site(request)

!! 又是 get_current_site()

那 get_current_site() 又做了什麼事情?get_current_site() 只判斷 django.contrib.sites 有沒有在 settings INSTALLED_APPS 裡,有的話,就使用 Site.objects.get_current() 來取得目前的 site;沒有的話,改用 RequestSite 類別來判斷,RequestSite 只是一個封裝,封裝出類似 Site 的物件,讓你後續存取可以跟原來的 site 物件一樣。

    if apps.is_installed('django.contrib.sites'):
        from .models import Site
        return Site.objects.get_current(request)
    else:
        from .requests import RequestSite
        return RequestSite(request)

先看 Site.objecs.get_current() ,這函式的實作是在 django/contrib/sites/models.py 裡。裡面也很簡單,先去看 settings 裡有沒有 SITE_ID,有的話,就用 SITE_ID 去查詢資料表格,取出對應的 site;沒有 SITE_ID 或者是 SITE_ID 被判定為 False (‘’, 0 都算是 False),就改使用 _get_site_by_request() 從 request 去做判讀。

_get_site_by_request() 使用 request.get_host() 取出 host,然後解析出 domain/port,再使用 domain 去查詢資料表格,找到對應的 site。

總結

以上,就是 site framework ,為了驗證我對 site framework 的認知,製作了一個小的展示專案,放在 github 上:https://github.com/elleryq/site_framework_demo

檢查 python 套件是否過期

用 pip 就可以檢查了

pip list ——outdated ——format=columns

也可以改用 json 格式輸出

pip list ——outdated ——format=json

但有時會想知道目前裝的版本跟最新版本差多少,這時可以使用 pip-check 這個工具,先安裝

pip install pip-check

進行檢查

pip-check ——cmd=pip ——hide-unchanged

加上 -u 的話,後面會補上升級指令

pip-check ——cmd=pip ——hide-unchanged -u

Visual Studio code snippets

一直都沒認真研究 vscode snippets 怎麼弄
今天去看了以後,發現蠻簡單的:

  • 啟用:選到 Preferences > Settings ,在上方的搜索框輸入 “editor.tabcompletion”,把 Tab Completion 選為 on 或 onlySnippets 就可以。在輸入 prefix (前置字串)後,按下 tab ,就會出現 snippets 讓你選擇。
  • 建立 snippets:
    • 手動建立:選到 Preferences > User snippets ,此時會要問你要編輯哪種類型檔案的 snippets,選好以後,會開啟一個 json 檔案,檔案裡面有註解說明,依照說明去編輯就可以了。編輯好,會立即生效。
    • 快速建立:安裝 Snippet Creator 這個 Extension (尋找 ryanolsonx.snippet-creator),安裝好以後,先選取你要建立 snippet 的文字,然後按下 `ctrl + shift + p` ,依照提示輸入 prefix, description 以後,就可以了。
  • 現成的 snippets:事實上,也有很多人把自己建立的 snippet 分享為 extension,供大家安裝。像 Python 就可以安裝 “Python snippets” (尋找 frhtylcn.pythonsnippets),或是 “Python extended” (尋找 tushortz.python-extended-snippets),或是 “Django” (尋找 bigonesystems.django),安裝好以後,就可以使用了。至於該 extension 有提供哪些 snippets,就得自己去挖 extension 的說明檔或是原始碼了,對,這個意思就是要稍微記一下有哪些 prefix (前置字串),否則就都沒用到,等於是白裝。

參考資料:

電影流水帳(2020/06/21~2020/06/30)

August 18th, 2010 | Scott Pilgrim Vs. The World European Premiere
  • Gemini Man (IMDB, Wikipedia),台譯:雙子殺手。
  • Jumanji: the next level (IMDB, Wikipedia),台譯:野蠻遊戲-全面晉級。

Gemini Man

雙子殺手,記得是去年上映的片,蠻快就在第四台上片了,我忘了是哪一台,猜想可能是疫情的關係。劇情還不錯,不過說要有什麼印象深刻的,就說不出來。

Henry 是個殺手,嗯,應該算吧,是幫國安局出不可告人的任務的。Henry 在一次任務以後,決定退下來,想要好好過生活。但是呢,國安局不放過他,派了人去監視他,並且打算將他滅口。Henry 當然沒想那麼多,也不知情。他去港口租船釣魚,在售票口遇到 Danny ,一個漂亮又風趣的女孩子,誰不動心呢?聊了幾句,確認彼此好感之後,就約了晚上吃飯。

Henry 出海釣魚,是要跟以前的好友聯絡,聊了好一陣子,好友透漏了國安局可能會派人來對付他,Henry 記住了。Henry 離開之後沒多久,他的好友就被國安局滅口了。Henry 晚上跟 Danny 吃飯,聊了幾句,Henry 就問了 Danny ,你是誰派來的?Danny 有點吃驚,看 Henry 其實沒惡意,就乾脆地說出了自己的出身跟來意,兩人反而聊開了。Henry 送 Danny 回家,Danny 發現家裡怪怪的,於是拿出了槍,準備禦敵。就在鬼影幢幢之際,Henry 闖了進來,表明這些人是來追殺他的。兩方立刻知道,除了監視Henry的人 (Danny)之外,還有另外一股勢力,兩人就開始邊打邊逃了。

然後我去處理了一些事情,隔了一陣子才回來接著看。兩人已經遇到那個以 Henry 基因製造出來的年輕人 Junior 了,他們也搞清楚了大致的事情經過,並且設計了 Junior ,告訴了他真相。最終是三人聯手去對付大魔王 Clay,並且解決了他。末了,Henry 跟 Danny 過著退休生活,Junior 到學校念書,去過他以前沒有經歷過的生活。

Jumanji: The next level

我個人覺得,不一樣的角色扮演,衍生出不一樣的趣事,我覺得編劇這一手相當不錯。新秀演員 Awkwafina ,加上老牌演員:Danny DeVito, Danny Glover ,的確也激盪出不少有趣的事情。但我仍然覺得這集續差了,沒有前集來的好看。

在上次的經歷之後,四人在現實生活成了好朋友,定期聯絡,但 Spencer 陷入了情感與社群的焦慮,他覺得自己不夠好,也不適合跟 Martha 在一起,就疏遠了 Martha。這次聖誕節從紐約回到家裡,Spencer 得跟爺爺住在一起,隔天本來是要跟其他三人碰面的,但 Spencer 左思右想,覺得應該回到遊戲裡去當 Dr. Xander,想拾回那時的感覺。於是他組裝了之前留下的遊戲機,於是就進到遊戲裡了。

隔天,其他三人,Martha, Fridge, Bethany 碰面聊了幾句以後,想說怎麼 Spencer 還沒來,打了電話也沒人接,就決定到他家裡看看。到 Spencer 家,正巧 Spencer 爺爺多年沒見、已經翻臉的朋友(Milo)來找 Spencer 爺爺(Eddie),Eddie 跟 Milo 小吵了一下,三人打過照面後,分別去找 Spencer ,找了好一會,才在地下室找到遊戲機。三人心想,事情大條了,看來得再進去野蠻遊戲裡救他出來。Fridge 不太樂意,他覺得很不爽,但在 Martha 跟 Bethany 感召下決定還是進去。不過這次有些不一樣了,Bethany 沒進去,Martha、Bethany、Eddie 跟 Milo 進去了。進去以後的角色除了Martha仍然是Ruby之外,其他人的角色都不一樣了。

一行人就跟上集一樣,邊解任務,邊尋找 Spencer 。在尋找的過程裡,Eddie 跟 Milo 化解了多年來的心結,當初 Milo 拆夥餐館,是因為覺得錢賺夠了,要好好享受生活,就把餐館轉手賣掉了。但他沒想到 Eddie 的樂趣就是在餐館工作,一拆夥,反倒惹怒了 Eddie,這一氣就氣了好幾年。Spencer 也化解了新裡的對愛情與工作的焦慮,他明白了 Martha 的想法,也讓自己恢復了跟 Dr. Xander 一樣的勇氣。

在外面的 Bethany 沒進遊戲裡,她心急如焚,趕緊去找了之前的機師 Jefferson,請他幫忙修復遊戲機,一起進去遊戲裡。一群人總算是會合了,他們也透過奇妙顏色的湖水把角色換了回來,接著打敗大魔王,結束了這個遊戲。

故事的最後,揭露了 Milo 去找 Eddie 的原因,大夥要離開遊戲,但 Milo 想要留下來,因為 Milo 得了絕症快死了,所以想留在遊戲裡。大夥尊重 Milo 想法,就離開了遊戲,大概就這樣子。
嗯,寫到這邊,我想我大概知道為什麼我覺得沒有前集來的好看了,因為導演想講的事情太多了,角色也有點太多,每個人都要有戲,導致劇情有點冗長,節奏也鬆散,沒有那種緊湊感,真的是蠻可惜的。

git config 的 includeIf

在推特上看到 @WanCW 的推文,才知道 git config 有 includeIf ,所以來研究一下怎麼用。
先看 git config 的說明:https://git-scm.com/docs/git-config ,用法蠻簡單的。

這邊先做假設情境

  • 使用者家目錄是 /home/user ,也就是 HOME=/home/user
  • $HOME/CompanyProjects 是公司專案目錄,而公司的 git server 是 gitlab.com
  • $HOME/SideProjects 是自己的專案目錄,自己的 git server 也是 gitlab.com
  • 公司用的 ssh key 在 $HOME/.ssh/company.pem
  • 自己用的 ssh key 在 $HOME/.ssh/id_rsa

為了避免使用者名稱、email 跟 SSH key 混用,這時候 git config 該怎麼設定呢?

首先在 $HOME 下建立 .gitconfig.d 目錄,在裡面放入 company.inc 與 side.inc,這兩個檔案的內容,在後面會提到。

先編輯 $HOME/.gitconfig 

; include if $GIT_DIR is under /home/user/CompanyProjects/
[includeIf “gitdir:/home/user/CompanyProjects/"]
    path = /home/user/.gitconfig.d/company.inc

; include if $GIT_DIR is under /home/user/SideProjects/
[includeIf “gitdir:/home/user/SideProjects/"]
    path = /home/user/.gitconfig.d/side.inc

再來是 company.inc 與 side.inc

; $HOME/.gitconfig.d/company.inc
[user]
    email = user@company.com
    name = user(company)

[core]
    sshcommand=ssh -i /home/user/.ssh/company.pem
; $HOME/.gitconfig.d/side.inc
[user]
    email = user@example.com
    name = user


在這樣設定之後,當切換到 $HOME/CompanyProjects 目錄下任一個專案目錄時,git 會使用 company.inc 裡的設定;切換到 $HOME/SideProjects 目錄下任一個專案目錄時,git 會使用 side.inc 裡的設定。

那也可以針對個別專案去設定,假設 $HOME/CompanyProjects/ProjectA 目錄會用到不一樣的,那就在 .gitconfig 加入

[includeIf “gitdir:~/CompanyProjects/ProjectA/“]
    path = ~/.gitconfig.d/projecta.inc

然後在 $HOME/.gitconfig.d 下新增 projecta.inc 即可。

電影流水帳(2020/06/11~2020/06/20)

Sigourney Weaver

Galaxy Quest

之前有看電影神搜的介紹,再加上有喜愛的石內卜 (Alan Rickman) 跟 Sigourney Weaver 演出,再加上又剛好在 HBO 看到,就順著從中間的地方看完了。

故事就一群演影集的演員被外星人捉走,那外星人為什麼捉他們,原因很簡單,因為他們演的影集就是講太空冒險故事的。外星人看了他們演的影集,以為他們就是真的星際艦隊,有能力、有勇氣,可以幫助他們對抗壞蛋。演員們在知道這種情況之後,知道外星人誤會他們了,可是外星人又非常仰慕他們,只好不上不下的配合著演下去。只是事情這樣下去不行,早晚會被外星人知道他們只是個演員。在受到壞蛋外星人持續進逼的威脅後,這些演員們也開始轉換了自己的想法,想要好好的幫助這些老實的外星人。當你真的把自己當作是英勇的星際艦隊之後,做出來的也就跟英勇的星際艦隊一樣了。到了後來,外星人知道了他們只是演員,演員們也坦承不諱,但演員跟外星人仍然相互幫助,一起打敗了壞蛋外星人。

有時候人缺的就是相信自己,在不相信的情況下,就會被自己困住。這些演員們原本只是逢場作戲,到了後來,認為自己是可以辦的到的,於是他們就真的辦到了。我的老同學說,我是個自我否定很強的人,是的,的確算是,我的確會懷疑自己有沒有這個能力可以做到。現在也算是慢慢能淡然處之,該做些什麼就去做,該嘗試什麼就去嘗試,這樣才能真正知道能不能做到。

दंगल

在電影台看好幾次了,這次總算是完整看完,很讓人感動的故事。

爸爸過往是個摔角選手,得獎紀錄輝煌,但因為家庭因素,決定放棄摔角事業,回歸家庭。本來想生個兒子來承繼摔角,但往往事與願違,跟太太生了四個女兒。就在心灰意冷的時候,一次女兒的打架事件,讓他發現了兩個女兒吉塔跟巴比塔的天賦,於是跟太太講好,要花一年的時間來訓練看看,如果不行,就放她們回去唸書,不再摔角。吉塔跟巴比塔很不樂意接受這樣的訓練,在學校被同學笑,鄰居也笑他們家怎麼會讓女兒練摔角呢?讓吉塔跟巴比塔轉變念頭的,是一個同學的婚禮,他們的同學也才十幾歲,就被嫁出去了。她哭著跟吉塔跟巴比塔說,「我很羨慕你們,你們的父親選擇了一條可以讓你們未來可以自由選擇的路,像我,就沒得選。」吉塔跟巴比塔這下才明白父親的苦心,於是轉了個念頭,認真接受父親的訓練。

在父親有心栽柳,而柳樹 (吉塔跟巴比塔) 又爭氣的情況下,一邊訓練,一邊以賽代訓,吉塔跟巴比塔漸漸打出了名氣。甚至打進了大學,參加了學校的摔角隊。吉塔上了外地的學校後,迷失了自己的方向,跟父親起了衝突。在參加校際比賽時也因為沒有認真練習而沒拿到好成績。後來,妹妹巴比塔也跟著上了大學,吉塔才因為妹妹巴比塔跟父親和解。爸爸也離開,住到學校附近,就近指導吉塔,吉塔重新找回方向,慢慢也有了好成績。

很快就到了大英帝國運動會,吉塔跟巴比塔也代表國家參加了摔角比賽。巴比塔沒懸念的,拿到冠軍,吉塔則是在四強賽遇到了勁敵,父親預先看過對手比賽影片,指導了吉塔,順利進入決賽。吉塔的教練卻因此眼紅,在決賽當天,找人隔離了吉塔的父親。吉塔陷入苦戰,比賽快結束的時候,吉塔想起了父親的教誨,「爸爸不能時刻都能來就你,你要靠自己。」,並且想出對策,巧妙的誘敵,並使出後背摔,漂亮地拿下分數,取得優勝。

我從電影學到兩件事情,第一件事情,就是你必須要時刻充實自己,讓自己可以站到一個能做出好選擇的位置。也許,隨遇而安是比較輕鬆的選擇,但是在事情來的時候,往往會驚慌,甚至後悔自己之前沒有好好的做好準備。第二件事情就是,無論在什麼緊急的情況,保持冷靜與理智,才是致勝的關鍵所在。

typescript 的 dirname/basename

node 是有一個 path 的 package,但這個 package 會依賴 process 來取得平台環境,以決定要用 win32 或是 posix 來處理路徑。

但我其實只是要簡單的取得父路徑跟從路徑取出檔案或目錄名稱,不需要這樣麻煩。上網找到這篇:Javascript functions for basename and dirname 。它的實作是使用 regular expression 來處理,所以傳回的並不是單純的字串,而是一個陣列 RegExMatchArray 。參考 MDN 的 String.prototype.match() 來做調整,簡單的說,陣列的第一個元素就是處理的結果,所以只要先判斷傳回值是否為  null ,然後再傳回第一個元素即可。

// Typescript
/**
 * 取得父路徑
 */
export function dirname(path: string): string {
  let newPath = '';
  // 找符合 '/aaa/bbb/ccc/' 的字串
  const regExMatchArray = path.match(/.*\//);
  // 沒有找到,傳回 '/'
  if (regExMatchArray === null ) {
    newPath = '/';
  }
  else {
    // 有找到
    newPath = regExMatchArray[0];
    // 看最後字元是否為 '/',是的話,就移除。
    if (newPath.endsWith('/')) {
      newPath = newPath.substring(0, newPath.length - 1);
    }
    // 最後結果的長度為0,加上 '/'
    if (newPath.length === 0) {
      newPath = '/';
    }
  }
  return newPath;
}

/**
 * 取得檔名或目錄名
 */
export function basename(path: string): string {
  // 把 '/' (含)之前的字元都替換成空字串
  return path.replace(/.*\//, '');
}

如何在 systemd 裡啟用 rc.local

以前用 sysv 或 upstart 時,很方便,想要在開機時跑一些指令,又懶得寫啟動 sysv init script 或 upstart job 時,就寫在 rc.local 裡就好。現在幾乎各大 Linux 發行版本都改用 systemd 了,那 systemd 又該怎麼做呢?印象中之前查過兩三次了,這次再查,決定還是記錄一下好了。

主要參考這篇:How to Enable /etc/rc.local with Systemd – LinuxBabe

第一步,新增 /etc/rc.local ,然後把要執行的指令放到裡面去,並且用 chmod +x /etc/rc.local 加上執行權限。

第二步,新增 /etc/systemd/system/rc-local.service

[Unit]
Description=/etc/rc.local Compatibility
ConditionPathExists=/etc/rc.local

[Service]
Type=forking
ExecStart=/etc/rc.local start
TimeoutSec=0
StandardOutput=tty
RemainAfterExit=yes
SysVStartPriority=99

[Install]
WantedBy=multi-user.target

第三步,啟用這個服務

sudo systemctl enable rc-local

硬碟空間不足?用OverlayFS來整合新硬碟空間

想到昨天還前天朋友問的,說伺服器的硬碟空間沒了,買了新的硬碟來用,有沒有辦法用一個比較便利的方法來掛載使用。我那時回答是說,得掛載到新的資料夾,再使用 symbolic link 方式去處理,今天看到 overlayfs,覺得應該可以用,就研究了一下。

主要是看了這三篇

第一篇側重於原理,第二篇很簡單,我覺得第三篇比較清楚。

是這樣的,本來看完第一篇跟第二篇,在嘗試的時候 (我是用 dd 做磁碟映像來掛載),一直遇到 wrong fs type, bad option, bad superblock on overlay 的問題,掛載不起來。看完第三篇,才知道自己錯在哪裡,就是 upperdir 必須要跟 workdir 在同個磁碟上,這樣掛載才不會有問題。

下面就開始實作,建立 olddisk,這裡把它當作是舊的 /home

dd if=/dev/zero of=olddisk bs=1024 count=1024
mkfs.ext4 olddisk

如果是真實情況的話,那麼這原本是掛載到 /home,這裡就把它改掛載到 /mnt/home。

sudo mkdir -p /mnt/home
sudo mount olddisk /mnt/home
sudo chown -R $USER:$USER /mnt/home

建立使用者目錄,盡可能模擬真實狀況,在真實狀況下,這步驟不用做

mkdir -p /mnt/home/user{1,2,3,4,5}

然後重新掛載為唯讀,模擬為以後都不去動的狀態

sudo mount -o remount,ro /mnt/home

建立 newdisk ,也就是模擬為新的硬碟

dd if=/dev/zero of=newdisk bs=1024 count=8192
mkfs.ext4 newdisk

掛載到 /mnt/newdisk

sudo mkdir -p /mnt/newdisk
sudo mount newdisk /mnt/newdisk
sudo chown -R $USER:$USER /mnt/newdisk

在新磁碟上建立兩個目錄,一個 home,一個 workdir。home 是 upperdir,workdir 還是 workdir,至於什麼是 upperdir,什麼是 workdir ,這裡就不多做說明了。

mkdir -p /mnt/newdisk/{home,workdir}

好,開始來掛載 overlay,這裡是掛載到 /home1,實際狀況會是 /home

sudo mount -t overlay none -o lowerdir=/mnt/home,upperdir=/mnt/newdisk/home,workdir=/mnt/newdisk/workdir /home1

看看 /home1

ls /home1
# 結果:
# lost+found  user1  user2  user3  user4  user5

建立新的使用者資料夾看看

mkdir -p /home1/user{6,7,8,9,10}

再來看看 /home1 有什麼變化

ls /home1
# 結果:
# lost+found  user1  user10  user2  user3  user4  user5  user6  user7  user8  user9

這時可以看到有確實的增加了 user6, user7, user8, user9, user10 這些資料夾,然後來看看新的硬碟裡怎麼樣了

ls /mnt/newdisk/home
# 結果:
# user10  user6  user7  user8  user9

這時可以看到新的使用者資料夾在這邊了,那 workdir 呢?

ls /mnt/newdisk/workdir
# 結果:
# work 

會發現只有 work,看來這是 kernel 工作用的目錄,可以忽略。那如果去原來在舊磁碟的使用者資料夾裡新增或修改檔案,會怎麼樣呢?

cd /home1/user1
touch {x,y}.md
cd /home1/user2
touch z.md

看舊磁碟上有沒有變化

ls /mnt/home/user1
# 結果:
#

會發現一樣是空的。來看看新磁碟

ls /mnt/newdisk/home/user1
# 結果:
# x.md  y.md

至此,存取一樣都在 /home1,但新的資料都會被放在新磁碟上了。

整理一下,在實際情況時的處置

  1. 接上新硬碟,分割、格式化好之後,掛載到 /mnt/newdisk ,建立兩個資料夾:home, workdir
  2. 如果 /home 是在分割區的話,先卸載,改掛載到 /oldhome;如果是跟 / 在一起的話,改個名字,例如 /oldhome
  3. 掛載:sudo mount -t overlay none -o lowerdir=/oldhome,upperdir=/mnt/newdisk/home,workdir=/mnt/newdisk/workdir /home
  4. 修改 /etc/fstab ,這樣下次開機時,才會生效。修改重點有三,一是原來的 /home,二是掛載新的磁碟,三是加入 overlay 的處理。