在本指南中,我们将学习如何在自己的软件包存储库上提供 Hex 软件包。 首先,我们先了解一下 Hex 存储库到底是什么。
Hex 部署必须遵循 Hex 规范。 https://hex.org.cn 由 https://github.com/hexpm/hexpm 和相关服务(例如 https://github.com/hexpm/hexdocs)提供支持。 虽然您可以使用这些项目来运行自己的 Hex 基础设施,但通常不建议这样做,因为它们包含许多平均部署不需要的特性和复杂性。 Hex 团队还维护着一个更低级的库 https://github.com/hexpm/hex_core,您可以使用它来构建和交互 Hex 服务。
规范描述了两个 端点
如果您只想提供软件包,则只需要实现 Repository 端点。
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 http://localhost: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
! 而且,如果您确实要同步私钥,请记住设置适当的存储桶策略,以防止意外公开。
您的存储库现在应该可以在以下 URL 下使用: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
由于我们添加了基本身份验证,让我们更新存储库 URL
$ 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
我们跳过了很多细节,因此如果您想了解更多信息,请务必查看