#env-var #deployment #secret #applications #script #environment #run

bin+lib tidploy

简单部署工具,用于部署小型应用程序和加载机密信息

20 个版本 (破坏性更新)

0.16.0 2024年5月16日
0.14.0 2024年5月5日
0.13.1 2024年3月16日
0.11.2 2023年12月15日

#36 in #deployment

自定义许可协议

155KB
3.5K SLoC

此命令行工具可以轻松部署小型应用程序,特别针对个人、组织和小型企业,他们将在单个机器上托管应用程序。

它旨在解决一个简单的问题

我有一个要运行的版本化脚本,并且我想给它提供一些环境变量。

你该如何解决这个问题?嗯,最简单的解决方案是每次你想运行脚本时,你下载一个新的版本,你手动将你需要的环境变量加载到你的 shell 中,然后调用脚本。

这种解决方案存在一些问题

  • 从正确的地方获取正确版本的脚本到正确的地方可能会很烦人。
  • 环境变量可能难以加载,或者你不想只是在你的 shell 中写下它们并保存在你的历史记录中。或者,它们可能不太经常更改,你不想每次都输入它们。或者,你可能希望有一个自动操作来调用你的脚本,而不需要提供环境变量/机密信息。

tidploy 旨在解决这些 exactly 问题。它对您想使用环境变量做什么以及想运行什么类型的脚本完全中立。然而,它当然是为特定系统设计的,因此理解这个系统可以帮助您理解 tidploy,这在 本节 中解释。

快速入门

保存机密信息

保存全局范围的机密信息

tidploy secret<机密名称> --上下文 none

您可以通过在命令中添加 -r <repository url> 来将其范围限定为存储库 URL。或者,您可以在 git 仓库中直接调用该命令。

tidploy secret<机密名称>

然后它将自动将其范围限定为您所在的 git 仓库。

运行脚本

如果您有一个名为 abc.sh 的文件

#!/bin/bash

echo $ABCD

并且您在文件所在的目录中运行(

tidploy run --context none -x abc.sh -v <secret name> ABCD

该文件将被运行并打印您提供的机密信息的值。请注意,如果机密信息被范围限定到仓库,则在这种情况下将找不到它。

但是,如果您运行

tidploy run --context none -r https://github.com/tiptenbrink/tidploy.git -x abc.sh -v <secret name> ABCD

如果没有设置仓库级别的密钥,它将使用我们通过第一个命令设置的全球作用域密钥,即使这次我们提供了仓库URL。

简单的部署单元

假设我们有以下结构

.
├── entrypoint.sh
└── tidploy.toml

我们的 tidploy.toml 看起来是这样的

exe_name = "entrypoint.sh"

[[vars]]
env_name = "BWS_ACCESS_TOKEN"
key = "bws"

如果我们现在运行

tidploy run

它将运行 entrypoint.sh 并尝试使用密钥 bws 加载密钥,并将其作为名为 BWS_ACCESS_TOKEN 的环境变量加载。

故障排除

运行

你的可执行文件必须是一个实际的可执行文件,或者它必须有一个表示如何运行的 shebang。所以如果你遇到 Exec format error,请尝试在文件顶部添加 #!/bin/sh 或其他相关的 shebang。

已知问题

  • git-local 上下文功能不完全。如果没有 CLI 设置的仓库,它将更改仓库名称为缓存仓库名称目录
  • 不稳定,尚未测试

注意

历史/原始目标

我设计 tidploy 是因为我想要简化部署依赖于单个主密钥的简单 Docker Compose 应用程序的过程(最初用于解密 gpg 文件,后来用于 Bitwarden Secrets Manager 的访问令牌)。我不想每次都输入这个密钥,也不想设置一个与我团队成员安全分享它的方法。我尝试使用 Python 的 keyring 将它安全地存储在类似 macOS Keychain 或 Windows Credential Locker 的东西上。然而,我无法让它与无头 Linux 系统上的 Secret Service API 一起工作(例如 WSL 或我们实际部署的服务器...)。

然后出现了 keyring-rs,它允许你使用 Linux 内核的 keyutils 作为后端,这对于很少重启的长久运行的服务器来说非常完美。它在重启时可能会丢失状态,但对于我们的目的来说完全可以接受。它在无头系统上也能完美工作!由于它是用 Rust 编写的,我想在 Rust 中开发完整的 CLI。因此,tidploy 诞生了。最后,我遇到的另一些问题,比如想要轻松使用多个版本(允许轻松回滚),自然地适应了同一工具。

便携性

它主要设计用于类 Unix 系统。 tidploy 假定存在 targit(以确切的名称存在于你的路径中)。它还依赖于 keyring-rs,这意味着它仅支持 Linux、macOS 和 Windows。此外,在某些情况下,Windows 文件路径可能存在问题。

未来

  • tar 可能会被移除作为命令行依赖项,而将使用平台无关的 crate 代替
  • 如果 libgit2 或任何其他 git 绑定(如 gitoxide,见 此处此处)支持现代 git 功能,如部分克隆、稀疏克隆和稀疏检出,我们就能移除调用 git 作为外部进程的依赖项

帮助

Simple deployment tool for deploying small applications and loading secrets

Usage: tidploy [OPTIONS] <COMMAND>

Commands:
  secret    Save secret with key until reboot
  download  Download tag or version with specific env, run automatically if using deploy
  deploy    Deploy tag or version with specific env
  run       Run an entrypoint using the password set for a specific repo and stage 'deploy', can be used after download
  help      Print this message or the help of the given subcommand(s)

Options:
      --context <CONTEXT>        Contexts other than git-remote (default) are not fully supported [possible values: none, git-remote, git-local]
  -r, --repo <REPO>              Set the repository URL, defaults to 'default_infer', in which case it is inferred from the current repository. Set to 'default' to not set it. Falls back to environment variable using TIDPLOY_REPO and then to config with key 'repo_url' For infering, it looks at the URL set to the 'origin' remote
  -t, --tag <TAG>                The git reference (commit or tag) to use
  -d, --deploy-pth <DEPLOY_PTH>  The path inside the repository that should be used as the primary config source
  -h, --help                     Print help
  -V, --version                  Print version

保存密钥

Save secret with key until reboot

Usage: tidploy secret [OPTIONS] <KEY>

Arguments:
  <KEY>  

Options:
      --context <CONTEXT>        Contexts other than git-remote (default) are not fully supported [possible values: none, git-remote, git-local]
  -r, --repo <REPO>              Set the repository URL, defaults to 'default_infer', in which case it is inferred from the current repository. Set to 'default' to not set it. Falls back to environment variable using TIDPLOY_REPO and then to config with key 'repo_url' For infering, it looks at the URL set to the 'origin' remote
  -t, --tag <TAG>                The git reference (commit or tag) to use
  -d, --deploy-pth <DEPLOY_PTH>  The path inside the repository that should be used as the primary config source
  -h, --help                     Print help

下载

注意:此命令尚不完全功能正常。

Download tag or version with specific env, run automatically if using deploy

Usage: tidploy download [OPTIONS]

Options:
      --repo-only                
      --context <CONTEXT>        Contexts other than git-remote (default) are not fully supported [possible values: none, git-remote, git-local]
  -r, --repo <REPO>              Set the repository URL, defaults to 'default_infer', in which case it is inferred from the current repository. Set to 'default' to not set it. Falls back to environment variable using TIDPLOY_REPO and then to config with key 'repo_url' For infering, it looks at the URL set to the 'origin' remote
  -t, --tag <TAG>                The git reference (commit or tag) to use
  -d, --deploy-pth <DEPLOY_PTH>  The path inside the repository that should be used as the primary config source
  -h, --help                     Print help

部署

Deploy tag or version with specific env

Usage: tidploy deploy [OPTIONS]

Options:
  -x, --exe <EXECUTABLE>          
      --no-create                 Don't clone a fresh repository. Will fail if it does not exist. WARNING: The repository might not be up-to-date
  -v <VARIABLES> <VARIABLES>      Variables to load. Supply as many pairs of <key> <env var name> as needed
      --context <CONTEXT>         Contexts other than git-remote (default) are not fully supported [possible values: none, git-remote, git-local]
  -r, --repo <REPO>               Set the repository URL, defaults to 'default_infer', in which case it is inferred from the current repository. Set to 'default' to not set it. Falls back to environment variable using TIDPLOY_REPO and then to config with key 'repo_url' For infering, it looks at the URL set to the 'origin' remote
  -t, --tag <TAG>                 The git reference (commit or tag) to use
  -d, --deploy-pth <DEPLOY_PTH>   The path inside the repository that should be used as the primary config source
  -h, --help                      Print help

运行

Run an entrypoint or archive created by download/deploy and load secrets

Usage: tidploy run [OPTIONS]

Options:
  -x, --exe <EXECUTABLE>          
  -v <VARIABLES> <VARIABLES>      Variables to load. Supply as many pairs of <key> <env var name> as needed
      --archive <ARCHIVE>         Give the exact name of the archive using the format: <repo name final path element without extension>_<commit sha>_<base64url-encoded url without name>
      --context <CONTEXT>         Contexts other than git-remote (default) are not fully supported [possible values: none, git-remote, git-local]
  -r, --repo <REPO>               Set the repository URL, defaults to 'default_infer', in which case it is inferred from the current repository. Set to 'default' to not set it. Falls back to environment variable using TIDPLOY_REPO and then to config with key 'repo_url' For infering, it looks at the URL set to the 'origin' remote
  -t, --tag <TAG>                 The git reference (commit or tag) to use
  -d, --deploy-pth <DEPLOY_PTH>   The path ins

架构

tidploy 做了 3 件事

  • 解析配置
    • 在这里,它提供了使用 OS API 安全读取和加载密钥的一等支持
  • 使用 Git 下载和隔离仓库
  • 使用环境变量将配置注入可执行文件

重要的是要认识到,唯一肯定只发生一次的步骤是最后一个步骤,至少在单次运行 tidploy 的过程中(您始终可以从可执行文件中再次调用 tidploy)。在我们能够运行可执行文件之前,我们需要构建一个 '状态',这个状态包括所有我们想要以环境变量形式提供给可执行文件的所有配置。

请记住,我们不想每次重新启动应用程序时都手动提供一个环境变量列表。此外,这种配置也不只存在于一个大 .env 文件中,该文件已签入仓库。特别是,我们可能有秘密值,或者配置分布在多个文件中。我们想要支持的复杂示例用例如下:

  • 我们仓库的最新提交(1abcdef)是应用程序的版本 Y
  • 我们想要重新启动应用程序,在生产环境中它仍在运行版本 X
  • 我们仓库在提交 1abcdef 中有一些基础设施脚本的更新,我们可能希望使用,但我们使它们指向版本 X 作为最新的生产版本(也许你添加了 tidploy
  • 现在我们想在生产服务器上检出提交 1abcdef 并运行 tidploy ...,这样它就会下载对应于版本 X 的提交的仓库,加载正确的秘密(对应于版本 X,可能在最新提交中已更改)然后运行可执行文件以启动应用程序

这需要在 tidploy 的一侧进行以下工作:

  • 解析最新仓库中的配置文件,指定下载版本 X
  • 下载它并将其放置在某个位置
  • 解析该旧状态中的仓库配置,以定位可执行文件和要加载的秘密
  • 以所有正确的环境变量运行它

如您所见,我们现在必须解析配置两次!这实际上可能会发生更多次,但每次我们都在构建一个接近最终状态的状态,允许我们实际运行应用程序。

重要状态

路径和来源

context_rootstate_rootstate_pathexe_direxe_pathcontext_root 是上下文的根,通常是 Git 仓库。它也可以只是当前目录。所有其他路径都必须是 context_root 的子目录。state_roottidploy 应考虑构建其状态的最高逻辑级别。在该级别的相关配置文件将是基础,更深层次的级别将随后合并,覆盖任何现有密钥。state_pathtidploy 将查找配置的最后逻辑级别。请注意,这必须是 state_root 的子目录,因为 tidploy 只支持单个配置树。exe_dir 将是执行可执行文件的目录,而 exe_path 将是从 exe_dir 到可执行文件的相对路径。同样,exe_path 必须是 exe_dir 的子目录。

仓库将以稀疏方式检出(使用exe_pathstate_path作为两个目录),从一个无树的局部克隆(这意味着很多不必要的版本历史都不会被克隆,只有state_pathexe_path文件夹及其子代/祖先将存在)。如果上下文为空,则当然不会发生任何类型的检出,整个当前目录将被考虑。

默认情况下,exe_dir被设置为state_root,而后者默认为context_root(默认情况下为当前的Git仓库根目录)。state_path被设置为exe_dir,而entrypoint.shexe_path。如果上下文为空,则context_root始终只是当前目录。

上下文和状态名称

默认情况下,tidploy会寻找当前目录所在的Git仓库的根,并将其作为context_root。它将使用此信息来推断诸如仓库名称之类的信息。通常,state_path文件夹的名称将被用作“状态名称”,它通常代表一个广泛的类别,例如‘生产’或‘测试’。

步骤

首先,tidploy需要找到其部署的‘地址’。它将不断解析git仓库,加载状态,并查看它是否已经改变。它将一直这样做,直到它不再改变。如果上下文为空并且加载的配置中没有地址变化,它将跳过此步骤,并直接从当前目录工作。

地址

地址可以是文件系统路径,也可以是Git仓库URL与引用的组合。对于引用,我们明确支持三种不同类型

  • 一个分支(git/refs/heads):在这种情况下,你需要确保头部是最新的
  • 一个提交
  • 一个标签(git/refs/tags)

依赖

~11–21MB
~290K SLoC