在本指南中,我們將瞭解如何自行發佈套件儲存庫中的 Hex 套件。首先,提供一些關於 Hex 儲存庫實際為何的背景知識。
Hex 部署必須遵循 Hex 規格。https://hex.dev.org.tw 由 https://github.com/hexpm/hexpm 和相關服務(例如 https://github.com/hexpm/hexdocs)提供支援。雖然您可以使用這些專案來執行自己的 Hex 架構,但通常不建議這麼做,因為它們包含許多一般部署不需要的功能和複雜度。Hex 團隊也維護一個較低層級的函式庫 https://github.com/hexpm/hex_core,您可以使用它來建構和互動 Hex 服務。
這些規格說明了兩個 端點。
如果您只想提供套件,您只需要實作儲存庫端點。
Hex v0.21 引入了 mix hex.registry build
工作,它提供建構本機儲存庫的簡易方法。
mix hex.registry build
需要三樣東西
讓我們建立一個“acme”儲存庫、產生一組隨機的私鑰、一個public
目錄,最後建置儲存庫
$ mkdir acme
$ cd acme
$ openssl genrsa -out private_key.pem
$ mkdir public
$ mix hex.registry build public --name=acme --private-key=private_key.pem
* creating public/public_key
* creating public/tarballs
* creating public/names
* creating public/versions
這樣就完成了!現在,我們只需要啟動一個公開public
目錄的 HTTP。伺服器,就能讓 Hex 客戶端指向它。不過,我們先把我們的存放庫新增一組套件。
要發布套件,你需要將 tarball 複製到public/tarballs
並重新建置儲存庫。你可以建置自己的套件(使用mix hex.build
)或直接使用現成的套件。我們選擇後者
$ mix hex.package fetch decimal 2.0.0
decimal v2.0.0 downloaded to decimal-2.0.0.tar
$ cp decimal-2.0.0.tar public/tarballs/
$ mix hex.registry build public --name=acme --private-key=private_key.pem
* creating public/packages/decimal
* updating public/names
* updating public/versions
現在讓我們來測試我們的存放庫。我們可以使用隨 Erlang/OTP 一併提供的內建伺服器,來提供public
目錄的服務
$ erl -s inets -eval 'inets:start(httpd,[{port,8000},{server_name,"localhost"},{server_root,"."},{document_root,"public"}]).'
現在讓我們來新增存放庫,並嘗試擷取我們剛發佈的套件
$ mix hex.repo add acme https://127.0.0.1:8000 --public-key=public/public_key
$ mix hex.package fetch decimal 2.0.0 --repo=acme
decimal v2.0.0 downloaded to decimal-2.0.0.tar
如果一切順利,你會看到你的套件從你的本機伺服器下載下來!
要在你的 Mix 專案中使用套件,將它新增為依賴項,並將:repo
選項設定為你的存放庫名稱
defp deps() do
{:decimal, "~> 2.0", repo: "acme"}
end
在下一個章節,我們將說明我們如何將我們的存放庫部署到正式環境中。
部署到 Amazon S3(或類似的雲端服務)可能是建置可靠的 Hex 儲存庫的最簡單方法。
如果你已經有一個 S3 儲存貯體,請使用例如AWS CLI來同步public/
目錄的內容,如下所示
$ aws s3 sync public s3://my-bucket
警告:記得只同步公開目錄,而不是private_key.pem
!如果你真的想同步你的私鑰,請記得設定適當的儲存貯體政策,這樣才不會意外外洩。
你的儲存庫現在應該可以在類似這樣的網址下使用:https://<bucket>.s3.<region>.amazonaws.com
或在其他你設定好的儲存貯體中。
如果你還沒有儲存貯體,現在就建立一個!預設情況下,儲存在 S3 上的檔案不會公開。你可以在儲存貯體的特性中設定以下儲存貯體政策來啟用公開存取
{
"Version": "2008-10-17",
"Statement": [
{
"Sid": "AllowPublicRead",
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-bucket/*"
}
]
}
你也可以考慮新增一個 IAM 政策給存取儲存空間的使用者。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:ListBucket",
"s3:DeleteObject"
],
"Resource": [
"arn:aws:s3:::my-bucket",
"arn:aws:s3:::my-bucket/*"
]
}
]
}
請參閱 Amazon S3 文件 以取得更多資訊,並記得以對部署有意義的方式自訂儲存空間/IAM 政策。
如果你需要自訂 Hex 伺服器,你可以考慮建立一個適當的 Elixir 專案。我們就直接動手做吧:我們將提供靜態檔案、加入基本驗證、透過環境變數設定它,並準備使用 Docker 進行部署。
讓我們開始一個新專案
$ mix new my_app --sup
$ cd my_app
然後加入相依關係
# mix.exs
defp deps do
[
{:plug, "~> 1.11"},
{:plug_cowboy, "~> 2.4"}
]
end
並更新我們的監督樹以啟動 Cowboy
# lib/my_app/application.ex
@impl true
def start(_type, _args) do
port = Application.fetch_env!(:my_app, :port)
children = [
{Plug.Cowboy, scheme: :http, plug: MyApp.Plug, options: [port: port]}
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
最後,讓我們實作 MyApp.Plug
# lib/my_app/plug.ex
defmodule MyApp.Plug do
use Plug.Builder
plug(Plug.Logger)
plug(:auth)
plug(:static)
plug(:not_found)
defp auth(conn, _opts) do
auth = Application.fetch_env!(:my_app, :auth)
Plug.BasicAuth.basic_auth(conn, auth)
end
defp static(conn, _opts) do
public_dir = Application.fetch_env!(:my_app, :public_dir)
opts = Plug.Static.init(at: "/", from: public_dir)
Plug.Static.call(conn, opts)
end
defp not_found(conn, _opts) do
send_resp(conn, 404, "not found")
end
end
我們已準備好準備我們的應用程式進行發佈!讓我們從定義執行時期設定開始
# config/runtime.exs
import Config
config :my_app,
port: String.to_integer(System.get_env("PORT", "8000")),
auth: [
username: System.get_env("AUTH_USERNAME", "hello"),
password: System.get_env("AUTH_PASSWORD", "secret")
],
public_dir: System.get_env("PUBLIC_DIR", "tmp/public")
我們允許我們的應用程式透過環境變數來設定,但為了方便,我們也提供預設值。我們已準備好組裝我們的發佈!
$ MIX_ENV=prod mix release
讓我們執行它!我們將提供在本指南第一部分建立的本機儲存庫的 public
目錄
$ PORT=8000 PUBLIC_DIR=$HOME/acme/public _build/prod/rel/my_app/bin/my_app start
由於我們已加入基本驗證,讓我們更新儲存庫網址
$ mix hex.repo set acme --url http://hello:secret@localhost:8000
並讓我們透過再次嘗試擷取套件來確保一切正常運作
$ mix hex.package fetch decimal 2.0.0 --repo=acme
我們已準備好將我們的應用程式放入 Docker 容器,讓我們定義 Dockerfile
FROM hexpm/elixir:1.11.2-erlang-23.1.2-alpine-3.12.1 as build
RUN apk add --no-cache git
WORKDIR /app
RUN mix local.hex --force && mix local.rebar --force
ENV MIX_ENV=prod
COPY mix.exs mix.lock ./
RUN mix deps.get --only $MIX_ENV
RUN mkdir config
RUN mix deps.compile
COPY lib lib
RUN mix compile
COPY config/runtime.exs config/
RUN mix release
# Start a new build stage so that the final image will only contain
# the compiled release and other runtime necessities
FROM alpine:3.12.1 AS app
RUN apk add --no-cache openssl ncurses-libs
WORKDIR /app
RUN chown nobody:nobody /app
USER nobody:nobody
COPY --from=build --chown=nobody:nobody /app/_build/prod/rel/my_app ./
ENV HOME=/app
ENTRYPOINT ["bin/my_app"]
CMD ["start"]
讓我們建立我們的容器並執行它
$ docker build . -t my_app
$ docker run --env PUBLIC_DIR=/public --env PORT=8000 -v $HOME/acme/public:/public -p 8000:8000 my_app
請注意我們如何使用適當的環境變數、共用磁碟區和已發佈的埠來設定容器。
讓我們再次從我們的本機儲存庫擷取套件來測試一切是否正常運作
$ mix hex.package fetch decimal 2.0.0 --repo=acme
我們跳過了許多詳細資料,如果你想進一步瞭解,請務必查看