diff --git a/README.md b/README.md index 20c8b99..53be7cd 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,70 @@ -# install-ssh -Quickly install SSH into the server. +# Quickly Configuring SSH Server +快速地将 SSH 密钥部署到服务器中,并定期更新。 + +这个脚本可以帮你做到这些事情: +- 快速配置服务器的 SSH Server; +- 定期自动更新 SSH 公钥到服务器中; +- 集中管理注册到服务器的 SSH 公钥(例如使用 Github 管理 SSH 公钥); + +## Usage +部署密钥并设置定期更新: +```bash +curl -s https://ssh.lamgc.me | bash -s -- -c +``` +如果不希望自动更新密钥,可以移除 `-c` 参数: +```bash +curl -s https://ssh.lamgc.me | bash -s +``` +添加 `-p yes` 参数可以设置允许 Root 用户使用密码登录 SSH: +```bash +curl -s https://ssh.lamgc.me | bash -s -- -p yes +``` +要查看脚本的帮助信息,请使用 `-h` 参数: +```bash +curl -s https://ssh.lamgc.me | bash -s -- -h +``` + +## Install +### Configuration +先 Fork 本仓库,Fork 后在仓库设置中启用 Github Pages,分支指向 main 分支,目录为仓库根目录。 +配置好后将仓库克隆到本地,然后修改 `cf-worker/src/index.js` 中的前五个变量: +```javascript +// 改成你自己的 Github 用户名,注意是登录 Github 的那个用户名. +const githubUserName = "LamGC"; +// 改成你 Fork 后的仓库名,记得要开启 Github Pages 功能. +const githubInstSshProjectName = "quickly-conf-sshd"; +// 如果可以,建议在此设置备用的 SSH 公钥, 以防 Github 无法使用. +const backupSshKeys = ``; +// Worker 的访问地址, 如果不填的话默认为请求的地址, 填了就会用这里的地址(要去 Worker 的触发器那绑定, 否则无效). +const defaultBaseUrl = ""; +// Cron 表达式, 默认 1 天执行一次更新. +const cronExpression = "0 0 * * *"; +``` + +### Use Wrangler +如果你安装了 Cloudflare 的 Wrangler,可以直接在 `cf-worker` 目录中执行: +```bash +wrangler publish +``` +执行后将会自动创建 Worker 并上传 worker js 代码,Worker 的名字是 `quickly-conf-ssh-worker`。 + +### Manual Install +如果你没有安装 Wrangler,那就手动在 Cloudflare Workers 中新建一个 Worker,名字你喜欢就好(也可以使用 `quickly-conf-ssh-worker`),选择 HTTP 处理程序,然后下一步; + +![Create a new worker](docs/Create-a-new-worker.png) + +创建完成后点击 Worker 名称右边的**快速编辑**,将 `cf-worker/src/index.js` 中的内容复制到编辑框中,把原本的代码覆盖掉,然后保存; +![Copy Code](docs/copy-js-code-to-worker.png) + +保存后需要访问一下 Worker,检查是否有误(如果不检查仔细,把其他人的 SSH 密钥设置到服务器里了,那就寄咯): +- 直接访问 Worker 地址,看看是否能跳转到你 Fork 的仓库中(地址栏显示了 `https://{你的 Github 用户名}.github.io/{Fork的仓库名}/`); +- 访问 `{Worker地址}/ssh.keys`,应该显示你的在 Github 设置了的所有 SSH 公钥,如果是空的或者不是你的密钥,那就检查一下是不是 Github 用户名填错了; +- 访问 `{Worker地址}/script.sh`,应该返回一个脚本,脚本开头的配置应与你在 Worker 开头的有关配置一致。 + +检查无误后就算安装完成了。 + +### (可选)使用自定义域名 +在 Worker 详情中,点击“触发器”,然后点击“添加自定义域”,按照提示输入已添加在 Cloudflare 中的域名即可。 +![Add Custom Domain](docs/add-custom-domain.png) +![Input Custom Domain](docs/input-custom-domain.png) +![Added Custom Domain](docs/added-custom-domain.png) diff --git a/cf-worker/src/index.js b/cf-worker/src/index.js new file mode 100644 index 0000000..fb21f6b --- /dev/null +++ b/cf-worker/src/index.js @@ -0,0 +1,105 @@ +// 改成你自己的 Github 用户名,注意是登录 Github 的那个用户名. +const githubUserName = "LamGC"; +// 改成你 Fork 后的仓库名,记得要开启 Github Pages 功能. +const githubInstSshProjectName = "quickly-conf-sshd"; +// 如果可以,建议在此设置备用的 SSH 公钥, 以防 Github 无法使用. +const backupSshKeys = ``; +// Worker 的访问地址, 如果不填的话默认为请求的地址, 填了就会用这里的地址(要去 Worker 的触发器那绑定, 否则无效). +const defaultBaseUrl = ""; +// Cron 表达式, 默认 1 天执行一次更新. +const cronExpression = "0 0 * * *"; + +// 下面的东西一般不用改. +const baseRepoPageUrl = `https://${githubUserName.toLowerCase()}.github.io/${githubInstSshProjectName}/`; +const installScriptUrl = `${baseRepoPageUrl}/conf-sshd.sh`; +// 如果出现 Github 无法使用的情况, 可以修改 sshKeyUrl 来变更位置. +// 也可以添加额外的 SSH 公钥地址(比如 KeyBase). +const sshKeyUrls = [ + `https://github.com/${githubUserName}.keys` +]; + +// 下面是脚本的占位符. +const SCRIPT_PH_SSH_KEY_URL = "{{ SSH_KEY_URL }}" +const SCRIPT_PH_SCRIPT_URL = "{{ SCRIPT_URL }}" +const SCRIPT_PH_DEFAULT_CRON = "{{ DEFAULT_CRON }}" + + +async function sendScriptContent(baseUrl) { + let scriptResp = await fetch(new Request(installScriptUrl)); + if (scriptResp.ok) { + let scriptContent = await scriptResp.text(); + + scriptContent = scriptContent.replace(SCRIPT_PH_SSH_KEY_URL, `${baseUrl}/ssh.keys`) + .replace(SCRIPT_PH_SCRIPT_URL, `${baseUrl}/script.sh`) + .replace(SCRIPT_PH_DEFAULT_CRON, cronExpression) + + return new Response(scriptContent, { + headers: { + "content-type": "text/plain; charset=utf-8" + } + }); + } else { + return new Response("Failed to get install script.", { + status: 500, + statusText: "Failed to get install script", + headers: { + "content-type": "text/plain; charset=utf-8" + } + }); + } +} + +export default { + async fetch(request, env) { + const { pathname, host } = new URL(request.url); + const baseUrl = defaultBaseUrl || `https://${host}`; + if (pathname === "/ssh.keys") { + for (let url of sshKeyUrls) { + let response = await fetch(new Request(url)); + if (response.ok) { + let keys = await response.text() + return new Response(keys, { + headers: { + "content-type": "text/plain; charset=utf-8" + } + }); + } + } + if (backupSshKeys.length > 0) { + return new Response(backupSshKeys, { + headers: { + "content-type": "text/plain; charset=utf-8" + } + }); + } else { + return new Response("Failed to get keys.", { + status: 500, + statusText: "Failed to get keys", + headers: { + "content-type": "text/plain; charset=utf-8" + } + }); + } + } else if (pathname === "/") { + const userAgent = request.headers.get("User-Agent"); + if (userAgent != null && userAgent.match(/curl|libcurl/) !== null) { + return await sendScriptContent(baseUrl); + } else { + return new Response("", { + status: 301, + statusText: "Redirect", + headers: { + "Location": baseRepoPageUrl + } + }); + } + } else if (pathname === "/script.sh") { + return await sendScriptContent(baseUrl); + } else { + return new Response("Not found.", { + status: 404, + statusText: "Not Found" + }) + } + } +} diff --git a/cf-worker/wrangler.toml b/cf-worker/wrangler.toml new file mode 100644 index 0000000..956a932 --- /dev/null +++ b/cf-worker/wrangler.toml @@ -0,0 +1,3 @@ +name = "quickly-conf-ssh-worker" +main = "src/index.js" +compatibility_date = "2023-01-26" diff --git a/conf-sshd.sh b/conf-sshd.sh new file mode 100644 index 0000000..0825870 --- /dev/null +++ b/conf-sshd.sh @@ -0,0 +1,217 @@ +#!/bin/bash + +########## 一些配置 ########## + +# 默认获取 SSH key 的地方,一般是 Github. +sshkey_url="{{ SSH_KEY_URL }}" +# 默认的 Cron 执行计划, 每天凌晨 0 点执行 +default_cron="{{ DEFAULT_CRON }}" +# 脚本 Url +script_url="{{ SCRIPT_URL }}" + +############ 脚本区 ########## + +script_params=$* +has_param() { + for param in $script_params; do + for tParam in $@; do + if [ "$tParam" == "$param" ]; then + echo "true" + return + fi + done + done + echo "false" +} + +get_param_value() { + local find=false + for param in $script_params; do + if [ "$find" == "true" ]; then + if [[ $param == -* ]]; then + return + fi + echo $param + return + fi + for tParam in $@; do + if [ "$tParam" == "$param" ]; then + find=true + break + fi + done + done +} + +use_param_keys_url() { + local new_sshkey_url=$(get_param_value "-k" "--sshkey-url") + if [ "$new_sshkey_url" == "" ]; then + echo "Please specify the URL of the SSH public key." + exit 1 + fi + sshkey_url=$new_sshkey_url + echo "A new SSH keys URL has been specified: $sshkey_url" +} + +# 检查并更新 SSH key 地址. +if [ $(has_param "-k" "--sshkey-url") == "true" ]; then + use_param_keys_url +fi + +# 帮助信息. +if [ $(has_param "-h" "--help") == "true" ]; then + echo "Usage: $0 [options]" + echo "Options:" + echo " -h, --help Print this help message." + echo "" + echo "Available to any user: " + echo " -k, --sshkey-url The URL of the SSH public key." + echo " -c, --cron [cron | false] Configure Crontab to automatically update ssh keys," + echo " Cron expression can be specified, If false is specified, " + echo " Crontab settings will be deleted automatically." + echo "" + echo " -o, --only-update-keys Only update SSH keys, do not configure ssh server." + echo " -u, --update-self Update this script to the latest version." + echo "" + echo "only available when the script is executed as root:" + echo " -n, --no-install-sshd Do not install SSH Server." + echo " -p, --allow-root-passwd Allow Root to log in with a password." + echo "" + exit 0 +fi + +update_sshkeys() { + if [ "$sshkey_url" == "" ]; then + echo "Please specify the URL of the SSH public key." + exit 1 + fi + echo "Downloading SSH public key from '$sshkey_url'" + mkdir -p ~/.ssh + local ssh_keys=$(curl -s $sshkey_url) + if [ $? -ne 0 ] || [ "$ssh_keys" == "" ]; then + echo "Failed to download SSH public key at $(date '+%Y-%m-%d %H:%M:%S')" + exit 1 + fi + echo "-------------------- SSH Keys --------------------" + echo "$ssh_keys" + echo "--------------------------------------------------" + echo $ssh_keys > ~/.ssh/authorized_keys + chmod 600 ~/.ssh/authorized_keys + # 输出更新成功,需要附带时间日期 + echo "SSH public key updated successfully at $(date '+%Y-%m-%d %H:%M:%S')" +} + +# 检查是否只更新密钥. +if [ $(has_param "-o" "--only-update-keys") == "true" ]; then + update_sshkeys + exit 0 +fi + +# 检查是否指定了 --update-self +if [ $(has_param "-u" "--update-self") == "true" ]; then + echo "Updating conf-sshd script..." + cp $0 ~/.conf-sshd/conf-sshd.sh.bak + curl -s $script_url > $0 || cp ~/.conf-sshd/conf-sshd.sh.bak $0 && echo "Script update failed at $(date '+%Y-%m-%d %H:%M:%S')" && exit 1 + chmod +x ~/.conf-sshd/conf-sshd.sh + echo "Script updated successfully at $(date '+%Y-%m-%d %H:%M:%S')" + exit 0 +fi + +# 检查 /usr/sbin/sshd 是否存在,且 /usr/sbin/sshd 执行后退出代码为 0 +/usr/sbin/sshd -T > /dev/null +if [ $? -ne 0 ] && [ $(has_param "-n" "--no-install-sshd") == "false" ]; then + if [ $(id -u) -eq 0 ]; then + echo "The ssh server is not installed, and the script is executed as root, so it will be installed." + if [ -f /etc/redhat-release ]; then + yum install -y openssh-server + elif [ -f /etc/debian_version ]; then + apt-get update + apt-get install -y openssh-server + fi + echo "The ssh server has been installed." + else + echo "The ssh server is not installed, but the script is executed as a non-root user and cannot be installed." + exit 1 + fi +else + echo "The ssh server is already installed." +fi + +# 检查是否指定了 --allow-root-passwd +if [ $(has_param "-p" "--allow-root-passwd") == "true" ]; then + # 检查当前用户是否为 root + if [ $(id -u) -eq 0 ]; then + # 获取参数值 + allow_root_passwd=$(get_param_value "-p" "--allow-root-passwd" | tr '[:upper:]' '[:lower:]') + if [ "$allow_root_passwd" == "yes" ]; then + # 设置允许 root 使用密码登录 + sed -i 's/^#?PermitRootLogin.*/PermitRootLogin yes/g' /etc/ssh/sshd_config + echo "Root user is allowed to log in with password." + elif [ "$allow_root_passwd" == "no" ]; then + # 设置禁止 root 使用密码登录 + sed -i 's/^#?PermitRootLogin.*/PermitRootLogin prohibit-password/g' /etc/ssh/sshd_config + echo "Root user is prohibited from logging in with password." + else + echo "Please specify whether to allow root to log in with a password." + exit 1 + fi + else + echo "The script is executed as a non-root user and cannot set whether to allow root to log in with a password." + exit 1 + fi +fi + +# 更新密钥. +update_sshkeys + +# 检查是否指定了 --cron +if [ $(has_param "-c" "--cron") == "true" ]; then + # 检查 Crontab 是否已安装 + if [ "$(command -v crontab)" == "" ]; then + if [ $(id -u) -eq 0 ]; then + echo "The crontab is not installed, and the script is executed as a root user, so it will be installed." + if [ -f /etc/redhat-release ]; then + yum install -y crontabs + elif [ -f /etc/debian_version ]; then + apt-get update + apt-get install -y cron + fi + echo "The crontab has been installed." + else + echo "The crontab is not installed, but the script is executed as a non-root user and cannot be installed." + exit 1 + fi + else + echo "The crontab is already installed." + fi + cron=$(get_param_value "-c" "--cron" | tr '[:upper:]' '[:lower:]') + if [ "$cron" == "false" ]; then + # 检查 Crontab 是否已经设置 + if [ "$(crontab -l | grep "conf-sshd.sh")" == "" ]; then + echo "Crontab will not be configured." + exit 0 + else + crontab -l | grep -v "conf-sshd.sh" | crontab - + echo "Crontab has been removed." + exit 0 + fi + else + if [ "$cron" == "" ]; then + cron=$default_cron + fi + # 将当前脚本移动到 ~/.conf-sshd/conf-sshd.sh 中. + mkdir -p ~/.conf-sshd + # 检查当前脚本是否为文件 + if [ ! -f $0 ]; then + echo "Downloading conf-sshd script..." + curl -o ~/.conf-sshd/conf-sshd.sh $script_url + else + echo "Copying conf-sshd script..." + cp $0 ~/.conf-sshd/conf-sshd.sh + fi + chmod +x ~/.conf-sshd/conf-sshd.sh + echo "Install conf-sshd script successfully." + # 将当前脚本添加到 Crontab 中 + echo "$cron /bin/bash ~/.conf-sshd/conf-sshd.sh -o -k $sshkey_url >> ~/.conf-sshd/run.log" | crontab - + fi +fi diff --git a/docs/Create-a-new-worker.png b/docs/Create-a-new-worker.png new file mode 100644 index 0000000..b9aed41 Binary files /dev/null and b/docs/Create-a-new-worker.png differ diff --git a/docs/add-custom-domain.png b/docs/add-custom-domain.png new file mode 100644 index 0000000..bb5efac Binary files /dev/null and b/docs/add-custom-domain.png differ diff --git a/docs/added-custom-domain.png b/docs/added-custom-domain.png new file mode 100644 index 0000000..2a64cd6 Binary files /dev/null and b/docs/added-custom-domain.png differ diff --git a/docs/copy-js-code-to-worker.png b/docs/copy-js-code-to-worker.png new file mode 100644 index 0000000..94c0b02 Binary files /dev/null and b/docs/copy-js-code-to-worker.png differ diff --git a/docs/input-custom-domain.png b/docs/input-custom-domain.png new file mode 100644 index 0000000..33e4751 Binary files /dev/null and b/docs/input-custom-domain.png differ