Skip to content

Commit

Permalink
Finish the document for deployment
Browse files Browse the repository at this point in the history
We finish the document for deployment, and we add two options to
configure the static resources.

This solved #58. And this solved #62 partly.
  • Loading branch information
Kaiser-Yang committed Oct 9, 2024
1 parent 71b7382 commit 78e824a
Show file tree
Hide file tree
Showing 9 changed files with 222 additions and 31 deletions.
166 changes: 164 additions & 2 deletions README-zh.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,65 @@
# gcs-back-end
`git` 中央仓库服务的后端实现。

# 部署设置说明
# 部署介绍
## 使用 `deploy_ubuntu.sh` 脚本进行部署
如果使用 `deploy_ubuntu.sh` 脚本进行自动部署,那么你只需要将仓库克隆到一个 `ubuntu` 的主机上,然后
使用 `root` 用户执行脚本即可实现部署。在部署的过程中会使用 `apt` 安装以下的包,你需要保证 `apt`
源能够获取到这些包:
* `postgresql`
* `postgresql-client`
* `openjdk-17-jdk-headless`
* `maven`
* `sudo`
* `git`
* `openssh-server`
* `systemd` (可选)

其中 `systemd` 是可选项,是否需要取决于 `serviceType` 的值是否是 `"systemd"`,当值为 `"systemd"`
时候需要保证 `systemd` 可以被 `apt` 安装。

自动部署是通过 `bash deploy_ubuntu.sh [config_file]` 来进行部署,当不指定 `config_file` 选项时将会
使用 `config.json` 作为默认值,所以推荐将配置直接写入到 `config.json` 中,然后通过
`bash deploy_ubuntu.sh` 进行部署。

在进行配置的时候,如果用户没有指定配置项,那么将会使用 `config_default.json` 中的值作为默认值,所以
请不要修改 `config_default.json` 文件中的内容。

通常情况下,用户配置的时候需要特别注意以下的配置选项:
* `gitUserpassword``git` 用户的密码,`git` 用户用于执行与 `git` 命令相关的操作的以及用户通过 `ssh`
协议获取仓库时将会使用 `git` 用户进行登录。
* `gitServerDomain``git` 服务器的域名,生成 `ssh` 链接时将会使用该域名。
* `gitServerPort``git` 服务器的端口,生成 `ssh` 链接时将会使用该端口。
* `deployWithDocker`:是否使用 `Docker` 进行部署,如果为 `true`,那么将会通过
`3rdparty/docker-script` 中的脚本自动创建 `docker` 并将程序部署在 `docker` 中。
* `dockerImage``Docker` 镜像,如果 `deployWithDocker``true`,那么将会使用该镜像。目前支持
`ubuntu`
* `dockerPortMapping``Docker` 端口映射,如果 `deployWithDocker``true`,那么将会使用该端口映射。
* `serviceType`:部署的服务类型,如果 `deployWithDocker` 为真,那么该值只有为 `sys-init-v` 时脚本才
能正确执行,当然如果直接在 `docker` 内部执行自动部署脚本,那么该值可以为 `systemd` (`docker` 必须
使用 `--privileged` 选项进行创建)。
* `serviceUserPassword`:服务的用户密码,部署的服务 (`Java` 程序) 会以一个新的用户身份执行,该用户
的密码将会被设置为该值。
* `postgresUserPassword`:操作系统中的 `postgres` 用户的密码,`postgres` 用户会在安装 `postgresql`
的时候自动创建,部署脚本会将 `postgres` 用户的密码更改为该值。
* `postgresqlUserPassword``Postgres` 数据库用户的密码,部署脚本默认会创建一个名为 `gcs` 数据库
用户,该用户的密码将会被设置为该值。
* `postgresqlHost``Postgres` 数据库的主机地址,部署脚本会使用该地址进行数据库的连接。
* `postgresqlPort``Postgres` 数据库的端口,部署脚本会使用该端口进行数据库的连接。
* `druidLoginPassword``Druid` 登录密码。
* `md5Salt``MD5` 加密盐值,用于对用户密码进行加密。当完成部署后请不要修改此值,否则用户密码将无法
正确验证。推荐使用随机数生成的 `sha1``hash` 值。
* `frontEndUrl`: 前端地址,用于进行跨域配置。
* `staticPathPattern`:静态资源的匹配模式。
* `staticLocations`:静态资源的路径,需要使用绝对路径。

**注意**:需要注意的是,所有的后端接口均是以 `gcs` 开头,所以在静态资源路径下面不应该有名为 `gcs`
的文件或者文件夹

**注意**:如果将前端直接部署在同一个域上面,那么可以设置 `frontEndUrl` 为空字符串,然后配置
`staticPathPattern``staticLocations` 为前端的静态资源路径。

下面列出了完整的配置选项:
| 变量 | 类型 | 默认值 | 说明 |
| - | - | - | - |
| `deploy` | `bool` | `true` | 是否进行部署,当为 `false` 只进行打包操作。 |
Expand Down Expand Up @@ -49,7 +107,111 @@
| `postgresqlPort` | `int` | `5432` | `Postgres` 端口。 |
| `druidLoginUsername` | `string` | `"druid"` | `Druid` 登录用户名。 |
| `druidLoginPassword` | `string` | `"druid"` | `Druid` 登录密码。 |
| `frontEndUrl` | `string` | `"http://localhost:3000"` | 前端地址。 |
| `deleteGitUser` | `bool` | `true` | 清理时是否删除 `git` 用户。 |
| `deleteServiceUser` | `bool` | `true` | 清理时是否删除 `service` 用户。 |
| `md5Salt` | `string` | `""` | `MD5` 加密盐值。 |
| `frontEndUrl` | `string` | `"http://localhost:3000"` | 前端地址。 |
| `staticPathPattern` | `string` | `null` | 静态资源的匹配模式。 |
| `staticLocations` | `list` | `null` | 静态资源的路径,使用绝对路径,例如 `['/home/gcs/static']`|

## 手动部署
手动部署可以在任意的 `UNIX-like` 系统上面进行,下面依次介绍你需要手动完成的操作。

### 前置环境安装
以下的软件包你必须进行安装:
* `postgresql`
* `postgresql-client`
* `openjdk-17-jdk-headless`
* `maven`
* `sudo`
* `git`
* `openssh-server`

### 初始化数据库
当你完成了数据库的配置后 (通常包括创建一个新的数据库和用户以及相关的授权工作),你可以直接执行
`bash database/database_deploy.sh <db_user> <db_name> <db_host> <db_port> <db_password>` 来完成
数据库的自动初始化 (请确保工作目录为仓库的根目录)。

### `git` 用户创建
你需要创建一个用户用于执行仓库创建、克隆等操作,通常取名为 `git`,并且在其家目录下创建 `.ssh` 文件夹
并将权限修改为 `700`

### 修改 `sudo` 配置
你需要保证你运行 `Java` 程序的用户能够在执行 `sudo -u <git_user> rm``sudo -u <git_user> tee` 以及
`sudo -u <git_user> git` 时不需要输入密码,其中 `<git_user>`[git 用户创建](#git-用户创建)
创建的用户名。

通常你需要追加 `<java_user> ALL=(<git_user>) NOPASSWD:/usr/bin/git, /usr/bin/tee, /usr/bin/rm`
`/etc/sudoers` 文件中,其中 `<java_user>` 为你运行 `Java` 程序的用户。

### 配置 `application.yml` 或者 `application.properties`
你需要完成以下配置,这里以 `application.properties` 为例:

```properties
# 数据库用户名
spring.datasource.druid.username=
# 数据库用户密码
spring.datasource.druid.password=
# 数据库的主机地址
spring.datasource.druid.url=
# druid 的登录用户名
spring.datasource.druid.stat-view-servlet.login-username=
# druid 的登录密码
spring.datasource.druid.stat-view-servlet.login-password=
# 启动的配置文件,通常设置为 prod
spring.profiles.active=
# git 服务器的域名,通常为部署机器的公网 IP
git.server.domain=
# git 服务器的端口,设置为 ssh 连接时使用的端口
git.server.port=
# `git` 用户创建部分创建的用户名称
git.user.name=
# `git` 用户创建部分创建的家目录
git.home.directory=
# 仓库保存位置,需要确保 git.user.name 用户拥有 rwx 的权限
git.repository.directory=
# 仓库后缀,通常设置为 .git
git.repository.suffix=
# md5 的盐值,用于对用户密码进行加密
md5.salt=
# 前端地址,用于进行跨域配置
front-end.url=
# 静态资源的映射规则,/** 表示所有的静态资源都会被映射
spring.mvc.static-path-pattern=
# 静态资源的路径,需要使用绝对路径,例如 file:/static
spring.resources.static-locations=
```

**注意**:需要注意的是,所有的后端接口均是以 `gcs` 开头,所以在静态资源路径下面不应该有名为 `gcs`
的文件或者文件夹

**注意**:如果将前端直接部署在同一个域上面,那么可以设置 `front-end.url` 为空字符串,然后配置
`spring.mvc.static-path-pattern``spring.resources.static-locations` 为前端的静态资源路径。

### 选择喜欢的方式进行部署
完成了上述操作后,此时已经可以通过 `mvn spring-boot:run` 直接启动了。之后你只需要选择自己喜欢的方式
进行部署即可,例如可以通过 `mvn package` 将项目打包成一个 `jar` 包然后部署成一个服务。

## 自动清理功能
等你使用 `deploy_ubuntu.sh` 进行自动部署时,你可以使用配套的 `clean_ubuntu.sh` 进行清理工作。

清理脚本的使用与部署脚本类似,你也需要指定配置文件位置,如果不指定则使用 `config.json` 作为默认值。
请确保清理脚本指定的配置文件和部署脚本指定的配置文件内容是一致的。

清理脚本的主要工作逻辑如下:

```bash
if deployWithDocker then
stop docker
else
stop service
delete service files except for the log file
delete /etc/sudoers.d/{serviceUser}
if deleteGitUser then
delete git user but keep the home directory
fi
if deleteServiceUser then
delete service user but keep the home directory
fi
fi
```
6 changes: 4 additions & 2 deletions config_debug.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@
"profiles": [
"dev"
],
"serviceType": "systemd",
"postgresqlUserName": "gcs_debug",
"postgresqlUserPassword": "gcs_debug",
"postgresqlDatabaseName": "gcs_debug",
"deleteGitUser": false,
"deleteServiceUser": false,
"md5Salt": "Is that the best you can do?"
"frontEndUrl": null,
"md5Salt": "Is that the best you can do?",
"staticPathPattern": "/**",
"staticLocations": ["/home/kaiser/test1", "/home/kaiser/test2"]
}
6 changes: 4 additions & 2 deletions config_default.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@
"postgresqlPort": 5432,
"druidLoginUsername": "druid",
"druidLoginPassword": "druid",
"frontEndUrl": "http://localhost:3000",
"deleteGitUser": true,
"deleteServiceUser": true,
"md5Salt": ""
"md5Salt": "",
"frontEndUrl": "http://localhost:3000",
"staticPathPattern": null,
"staticLocations": null
}
1 change: 0 additions & 1 deletion config_user.json

This file was deleted.

2 changes: 1 addition & 1 deletion deploy_ubuntu.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env bash

config_file=${1:-"config_user.json"}
config_file=${1:-"config.json"}

log_error () {
echo -e "\e[31m[ERROR]: $1\e[0m"
Expand Down
32 changes: 24 additions & 8 deletions script/deploy_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@
import logging
import inspect

essential_packages = ['python-is-python3', 'postgresql postgresql-client',
'openjdk-17-jdk-headless', 'maven', 'systemd', 'sudo', 'git',
'openssh-server']
essential_packages = ['postgresql postgresql-client', 'openjdk-17-jdk-headless', 'maven',
'systemd', 'sudo', 'git', 'openssh-server']
sudo_cmd = os.popen('command -v sudo').read().strip()
apt_updated = False
message_tmp = '''\
Expand Down Expand Up @@ -312,12 +311,20 @@ def init_database(config):
config_datasource(config)


def create_or_update_user(username, password):
def create_or_update_user(username, password, homeDirectory = None):
if username == None or username == "":
return
if os.system(f"cat /etc/passwd | grep -w -E '^{username}'") != 0:
# use -m to create the home directory for user
command = f'{sudo_cmd} useradd -m {username}'
command = f'{sudo_cmd} useradd -m '
if homeDirectory is not None:
command += f'-d {homeDirectory} '
command += username
res = os.system(command)
message = message_tmp.format(command, res)
command_checker(res, message)
elif homeDirectory is not None: # update the home directory
command = f'{sudo_cmd} usermod -d {homeDirectory} {username}'
res = os.system(command)
message = message_tmp.format(command, res)
command_checker(res, message)
Expand Down Expand Up @@ -346,7 +353,18 @@ def write_other_config(config):
"gitRepositoryDirectory": "git.repository.directory",
"gitRepositorySuffix": "git.repository.suffix",
"md5Salt": "md5.salt",
"staticPathPattern": "spring.mvc.static-path-pattern",
"staticLocations": "spring.web.resources.static-locations",
}
if config.frontEndUrl is None:
config.frontEndUrl = ""
if config.staticPathPattern is None:
config.staticPathPattern = ""
if config.staticLocations is None:
config.staticLocations = ""
else:
config.staticLocations = ['file:' + location for location in config.staticLocations]
config.staticLocations = parse_iterable_into_str(config.staticLocations, ',')
try:
with open(application_config_file_path, 'a') as f:
for key, value in other_config_map.items():
Expand All @@ -362,7 +380,7 @@ def deploy_on_ubuntu(config):
essential_packages.remove('systemd')
apt_install_package(parse_iterable_into_str(essential_packages))
init_database(config)
create_or_update_user(config.gitUserName, config.gitUserPassword)
create_or_update_user(config.gitUserName, config.gitUserPassword, config.gitHomeDirectory)
if not os.path.exists(f'{config.gitHomeDirectory}/.ssh'):
os.system(f"{sudo_cmd} -u {config.gitUserName} mkdir -p {config.gitHomeDirectory}/.ssh")
os.system(f"{sudo_cmd} -u {config.gitUserName} chmod 700 {config.gitHomeDirectory}/.ssh")
Expand Down Expand Up @@ -418,8 +436,6 @@ def clean(config):
if config.deployWithDocker:
res = os.system(f"docker stop {config.dockerName}")
command_checker(res, f"Failed to stop {config.dockerName}")
res = os.system(f"docker rm {config.dockerName}")
command_checker(res, f"Failed to remove {config.dockerName}")
return
if config.serviceType == 'systemd':
command = f'{sudo_cmd} systemctl disable {config.serviceName}'
Expand Down
14 changes: 8 additions & 6 deletions src/main/java/edu/cmipt/gcs/config/WebConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

@Configuration
public class WebConfig {
@Value("${front-end.url:http://localhost:3000}")
@Value("${front-end.url}")
private String frontEndUrl;

@Bean
Expand All @@ -38,11 +38,13 @@ FilterRegistrationBean<CorsFilter> corsFilterDev() {
@Profile({ApplicationConstant.PROD_PROFILE, ApplicationConstant.TEST_PROFILE})
FilterRegistrationBean<CorsFilter> corsFilterNonDev() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin(frontEndUrl);
config.addAllowedMethod(HttpMethod.GET);
config.addAllowedMethod(HttpMethod.POST);
config.addAllowedMethod(HttpMethod.DELETE);
config.addAllowedHeader("*");
if (frontEndUrl != null && frontEndUrl.length() > 0) {
config.addAllowedOrigin(frontEndUrl);
config.addAllowedMethod(HttpMethod.GET);
config.addAllowedMethod(HttpMethod.POST);
config.addAllowedMethod(HttpMethod.DELETE);
config.addAllowedHeader("*");
}
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration(ApiPathConstant.ALL_API_PREFIX + "/**", config);
FilterRegistrationBean<CorsFilter> bean =
Expand Down
7 changes: 7 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,10 @@ mybatis-plus:

logging:
include-application-name: false

# force to add encoding UTF-8 to response header
server:
servlet:
encoding:
charset: UTF-8
force: true
19 changes: 10 additions & 9 deletions src/test/java/edu/cmipt/gcs/config/WebConfigTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,25 @@
@AutoConfigureMockMvc
@ActiveProfiles({ApplicationConstant.TEST_PROFILE})
public class WebConfigTest {
@Value("${front-end.url:http://localhost:3000}")
@Value("${front-end.url}")
private String frontEndUrl;

@Autowired private MockMvc mockMvc;

@Test
public void testCorsFilter() throws Exception {
mockMvc.perform(
options(ApiPathConstant.DEVELOPMENT_GET_API_MAP_API_PATH)
.header("Origin", frontEndUrl)
.header("Access-Control-Request-Method", "GET"))
.andExpectAll(
status().isOk(),
header().string("Access-Control-Allow-Origin", frontEndUrl));

mockMvc.perform(
options(ApiPathConstant.DEVELOPMENT_GET_API_MAP_API_PATH)
.header("Origin", frontEndUrl + "/INVALID"))
.andExpectAll(status().isForbidden());
if (frontEndUrl != null && frontEndUrl.length() > 0) {
mockMvc.perform(
options(ApiPathConstant.DEVELOPMENT_GET_API_MAP_API_PATH)
.header("Origin", frontEndUrl)
.header("Access-Control-Request-Method", "GET"))
.andExpectAll(
status().isOk(),
header().string("Access-Control-Allow-Origin", frontEndUrl));
}
}
}

0 comments on commit 78e824a

Please sign in to comment.