diff --git a/.gitignore b/.gitignore index bf0824e..16834ea 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -*.log \ No newline at end of file +*.log +.envrc* +postgres_password.enc* \ No newline at end of file diff --git a/README.md b/README.md index 92469c8..d7e8fd5 100644 --- a/README.md +++ b/README.md @@ -1,56 +1,249 @@ -# docker启动的postgres服务 +# PostgreSQL数据库服务 -## 使用说明 +本目录提供了基于Docker的PostgreSQL数据库服务,使用direnv和OpenSSL实现安全的密码管理,避免在配置文件中硬编码敏感信息。系统采用交互式初始化脚本和统一的服务管理脚本,简化了部署和维护流程。 -```sh -git clone https://git.wandoubaba.com/wandoubaba/docker-postgres.git -cd docker-postgress -# chmod +x start -./start +## 目录结构及文件说明 + +``` +├── .envrc # 环境变量配置文件,使用direnv管理 +├── .gitignore # Git忽略文件配置 +├── README.md # 本文档 +├── clear # 清理数据库数据的脚本 +├── conf/ # PostgreSQL配置文件目录 +├── data/ # PostgreSQL数据存储目录 +├── docker-compose.yml # Docker Compose配置文件 +├── fulldump # 数据库完全备份脚本(支持多数据库备份) +├── init # 交互式初始化配置脚本 +├── postgres_password.enc # 加密的数据库密码文件 +├── restore # 数据库恢复脚本(支持智能备份选择) +└── service # 统一的服务管理脚本(替代原start/stop脚本) ``` -执行结果为启动一个名为postgres的容器,其中运行了postgres服务,默认端口号为5432,默认用户postgres,默认密码123456,默认数据库postgres +## 环境要求 -## 脚本工具 +### 开发环境 +- macOS +- Docker 和 Docker Compose +- direnv (`brew install direnv`) +- OpenSSL -> 所有脚本在执行之前都需要确保具有可执行权限。 +### 生产环境 +- 支持主流Linux发行版,包括但不限于银河麒麟服务器系统v10、Debian 12等 +- Docker 和 Docker Compose(自动处理架构兼容性) +- direnv (`apt-get install direnv` 或发行版对应包管理器) +- OpenSSL + +> **注意**:Docker会自动处理ARM和x64架构的兼容性,本方案不区分硬件架构 + +## 使用方法 + +### 首次使用配置 + +1. **安装依赖** + + macOS: + ```bash + # 使用Homebrew + brew install direnv openssl + ``` + + Linux: + ```bash + apt-get update + apt-get install direnv openssl + ``` + +2. **执行交互式初始化** + + ```bash + cd /path/to/server/database + chmod +x init service + ./init + ``` + + 初始化过程会引导您设置: + - 数据库密码(PostgreSQL root密码) + - 加密主密钥(用于加密/解密密码文件) + - 服务端口(默认为25001) + - 容器名称(默认为guarddoc-server-postgres) + +3. **设置文件权限** + + ```bash + chmod 600 .envrc postgres_password.enc + chmod +x clear fulldump + ``` + +> **注意**:初始化脚本会自动执行`direnv allow`,无需手动操作。如果初始化过程中密码验证失败,脚本会立即停止执行。 + +### 服务管理命令 + +使用统一的`service`脚本来管理PostgreSQL服务: + +```bash +cd /path/to/server/database + +# 启动服务 +./service start + +# 停止服务 +./service stop + +# 检查服务状态 +./service status + +# 重启服务 +./service restart + +# 查看帮助信息 +./service help +``` + +> **注意**:所有命令执行前都需要输入加密主密钥进行验证。如果密码验证失败,命令会立即停止执行。 + +### 连接数据库 + +数据库服务使用在初始化过程中指定的端口(默认为5432): + +```bash +# 使用psql客户端连接 +psql -h localhost -p 5432 -U postgres +# 输入初始化过程中设置的数据库密码 +``` + +## 安全注意事项 + +1. **密码管理** + - 数据库密码使用AES-256-CBC加密存储,采用PBKDF2密钥派生函数增强安全性 + - 主密钥仅保存在用户记忆中,不存储在任何文件中 + - 环境变量在离开工作目录时自动清除(direnv特性) + - 所有服务管理命令都需要密码验证才能执行 + +2. **文件权限** + - 敏感文件(.envrc, postgres_password.enc)设置严格的600权限 + - 容器内数据目录使用PostgreSQL用户权限(700) + +3. **注意事项** + - 请妥善保管主密钥,丢失后无法恢复数据库密码 + - 定期备份数据库,避免数据丢失 + - 不要将.envrc和postgres_password.enc提交到版本控制系统(已在.gitignore中配置) + +## 自定义配置 + +### 修改现有配置 + +如果需要修改已初始化的配置: + +1. 停止服务: + ```bash + ./service stop + ``` + +2. 删除现有的配置文件: + ```bash + rm -f .envrc postgres_password.enc + ``` + +3. 重新运行初始化脚本: + ```bash + ./init + ``` + +4. 重新启动服务: + ```bash + ./service start + ``` + +### 添加自定义PostgreSQL配置 + +在`conf/`目录下添加配置文件,服务启动时会自动复制到容器内并应用: + +```bash +# 示例:修改最大连接数 +cat > conf/postgresql.conf << EOF +max_connections = 200 +EOF + +# 重启服务以应用配置 +./service restart +``` + +## 备份与恢复 + +### 执行数据库备份 + +使用`fulldump`脚本进行数据库备份: + +```bash +# 备份所有用户数据库(排除系统数据库),默认清除15天前的备份 +./fulldump + +# 备份指定数据库,默认清除15天前的备份 +./fulldump + +# 备份指定数据库,并清除指定天数前的备份 +./fulldump +``` + +备份文件将保存在`data/backup/`目录中,命名格式为`_full_`。 + +### 数据库恢复 + +使用`restore`脚本从备份恢复数据库: + +```bash +# 恢复最新备份到原始数据库(根据备份文件名自动确定目标数据库) +./restore + +# 恢复指定数据库的最新备份(优先查找与数据库名匹配的备份) +./restore +``` + +脚本会选择与目标数据库匹配的最新备份文件(如果指定了数据库名),并在恢复前提供确认提示,避免误操作。 + +### 定时备份 + +将`fulldump`脚本加入到系统crontab定时任务中实现定时备份: + +```bash +# 编辑crontab +crontab -e + +# 添加以下行(每天1点备份所有用户数据库并清除15天前的备份) +0 1 * * * bash /path/to/server/database/fulldump 15 + +# 或者只备份特定数据库 +0 1 * * * bash /path/to/server/database/fulldump 15 +``` + +## 故障排除 + +### 常见问题 + +1. **密码验证失败** + - 错误提示:"错误: POSTGRES_PASSWORD环境变量未设置" + - 解决方案:确保输入正确的加密主密钥 + +2. **direnv未安装** + - 错误提示:"警告: direnv未安装" + - 解决方案:按照环境要求部分安装direnv + - macOS: `brew install direnv` + - Linux: `apt-get install direnv` + +3. **容器名称冲突** + - 错误提示:"Error response from daemon: Conflict. The container name ... is already in use" + - 解决方案:使用不同的容器名称重新初始化 + +4. **端口冲突** + - 错误提示:"Error starting userland proxy: listen tcp4 0.0.0.0:25001: bind: address already in use" + - 解决方案:使用不同的端口重新初始化 + +## 脚本工具详情 |脚本|作用|用法| |---|---|---| -|start|启动服务|./start| -|stop|停止服务|./stop| -|fulldump|对指定数据库执行全量备份(通过pg_dump实现),其中参数的作用是清除多少天之前的备份,默认值是15天|./fulldump | -|clear|危险!!!清空所有数据、备份、日志等文件|./clear| - -## 修改默认值 - -- 如何修改端口号? - -在`conf/postgresql.conf`中找到`port = 5432`一行,修改(需要使用`start`脚本重启后才会生效)。 - -- 如何修改密码? - -在`docker-compose.yml`中修改环境变量`POSTGRES_PASSWORD`的值。 - -- 如何修改默认用户? - -在`docker-compose.yml`中设置环境变量`POSTGRES_USER`(fulldump脚本中的脚本可能需要修改)。 - -- 如何修改默认数据库? - -在`docker-compose.yml`中设置环境变量`POSTGRES_DB`(fulldump脚本中的脚本可能需要修改)。 - -### 修改配置文件 - -通过start脚本启动服务时会自动将conf目录下的配置文件应用到容器内,而配置文件中的大部分配置项都需要重启服务才会生效,因些建议每次都直接修改conf目录中的配置文件,然后再执行`./stop && ./start`重启服务。 - -## 定时备份 - -手动调用`fulldump`脚本可以实时备份数据库,将`fulldump`脚本加入到系统crontab定时任务中即可实现定时备份。 - -下面是一个示例: - -```conf -# 每天1点备份postgres数据库并清除15天前的备份 -0 1 * * * bash /app/dev/postgres/fulldump postgres 15 -``` +|init|交互式初始化数据库配置|./init| +|service|统一服务管理(支持start/stop/status/restart)|./service [command]| +|fulldump|数据库全量备份|./fulldump [] []| +|restore|数据库恢复|./restore []| +|clear|清空所有数据(保留备份目录)|./clear| diff --git a/clear b/clear index d172c2f..6f1abf4 100755 --- a/clear +++ b/clear @@ -6,34 +6,58 @@ SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) # 切换工作目录到脚本所在目录 cd $SCRIPT_DIR +# 加载环境变量 +load_env_variables() { + if [ -f ".envrc" ]; then + # 使用direnv加载环境变量 + if command -v direnv &> /dev/null; then + eval "$(direnv export bash)" + # 检查POSTGRES_PASSWORD是否已设置 + if [ -z "$POSTGRES_PASSWORD" ]; then + echo "错误: 密码验证失败,无法继续操作" + return 1 + fi + else + echo "错误: 未安装direnv,请先安装direnv" + return 1 + fi + else + echo "错误: 找不到.envrc文件" + return 1 + fi + return 0 +} + +# 调用函数加载环境变量 +if ! load_env_variables; then + echo "无法加载环境变量,脚本退出" + exit 1 +fi + # 确认提示 -read -p "将终止postgres服务并删除data目录下的所有的数据库文件、日志、备份、归档,删除操作不可逆,是否继续?(YES/no): " confirm +read -p "将终止postgres服务并删除数据库文件和归档文件,但保留所有备份。删除操作不可逆,是否继续?(YES/no): " confirm if [ "$confirm" != "YES" ]; then echo "退出脚本" exit 1 fi -container_name="postgres" +container_name=${POSTGRES_CONTAINER_NAME:-postgres} -# 判断名为postgres的容器是否存在 +# 判断容器是否存在 if [ "$(docker ps -q -f name=${container_name})" ]; then # 如果容器存在,则执行docker compose down docker compose down fi +echo "清除数据库文件..." echo "clear ./data/pgdata/ ..." rm -rf ./data/pgdata/* + +echo "清除归档文件..." echo "clear ./data/archived/ ..." rm -rf ./data/archived/* -echo "clear ./data/backup/first/ ..." -rm -rf ./data/backup/first/* + echo "clear ./data/wal_backup/ ..." rm -rf ./data/wal_backup/* -# 获取data/backup目录下除了first目录以外的所有目录和文件 -backup_files=$(ls -d ./data/backup/* | grep -v "first") -echo "clear ./data/backup/ ..." -# 删除data/backup目录下除了first目录以外的其他所有目录和文件 -for file in $backup_files; do - rm -rf "$file" -done \ No newline at end of file +echo "备份目录保留,未做任何修改。" \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 3b9783d..049519c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,13 +1,13 @@ services: postgres: - image: quay.io/wandoubaba517/postgres:17 - container_name: postgres + image: postgres:17 + container_name: ${POSTGRES_CONTAINER_NAME:-postgres} restart: always environment: - POSTGRES_PASSWORD: 123456 + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-123456} PGDATA: /data/pgdata volumes: - ./data:/data ports: - - 5432:5432 + - ${POSTGRES_PORT:-5432}:5432 diff --git a/fulldump b/fulldump index a6165c7..215f5a3 100755 --- a/fulldump +++ b/fulldump @@ -6,18 +6,128 @@ SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) # 切换工作目录到脚本所在目录 cd $SCRIPT_DIR +# 加载环境变量 +load_env_variables() { + if [ -f ".envrc" ]; then + # 使用direnv加载环境变量 + if command -v direnv &> /dev/null; then + eval "$(direnv export bash)" + # 检查POSTGRES_PASSWORD是否已设置 + if [ -z "$POSTGRES_PASSWORD" ]; then + echo "错误: 密码验证失败,无法继续操作" + return 1 + fi + else + echo "错误: 未安装direnv,请先安装direnv" + return 1 + fi + else + echo "错误: 找不到.envrc文件" + return 1 + fi + return 0 +} + +# 调用函数加载环境变量 +if ! load_env_variables; then + echo "无法加载环境变量,脚本退出" + exit 1 +fi + # 声明变量 -container_name="postgres" -pg_database=$1 # 设置pg_database为第一个传参,默认为postgres -pg_user="postgres" +container_name=${POSTGRES_CONTAINER_NAME:-postgres} +pg_user=${POSTGRES_USER:-postgres} backup_dir="./data/backup/" -current_datetime=$(date +"%Y%m%d_%H%M%S") -filename="${pg_database}_full_${current_datetime}" +# 获取宿主机上的绝对备份路径 +HOST_BACKUP_DIR="$(pwd)/${backup_dir}" # 清除几天前的备份 days=${2:-15} -# 删除符合条件的文件并记录到cleardump.log -find $backup_dir -maxdepth 1 -name "${pg_database}*" -mtime +$days -exec sh -c 'echo "$(date): $1" >> cleardump.log; rm -rf $1' sh {} \; -# 创建新备份 -docker exec -i "$container_name" pg_dump -U postgres -Fd "$pg_database" -f "${backup_dir}${filename}" -j 4 + +# 备份单个数据库的函数 +backup_database() { + local db_name=$1 + local datetime=$(date +"%Y%m%d_%H%M%S") + local filename="${db_name}_full_${datetime}" + local backup_path="${backup_dir}${filename}" + local host_backup_path="${HOST_BACKUP_DIR}${filename}" + + echo "开始备份数据库 $db_name..." + # 创建新备份 + docker exec -i "$container_name" pg_dump -U postgres -Fd "$db_name" -f "$backup_path" -j 4 + + # 检查备份是否成功 + if [ $? -eq 0 ]; then + echo "备份完成!" + echo "宿主机上的备份文件路径: ${host_backup_path}" + echo "备份目录名: ${filename}" + echo "注意:PostgreSQL目录格式备份包含多个文件,其中toc.dat是目录文件,包含备份内容的表目录信息" + # 显示宿主机上备份文件大小信息 + du -sh "${host_backup_path}" 2>/dev/null || echo "无法获取备份大小信息" + # 显示备份目录中的文件数量 + find "${host_backup_path}" -type f | wc -l | xargs echo "备份目录中的文件数量:" + echo "" + return 0 + else + echo "备份失败!" + return 1 + fi +} + +# 主逻辑 +if [ -n "$1" ]; then + # 如果指定了数据库名称,则只备份该数据库 + pg_database=$1 + echo "将备份指定的数据库: $pg_database" + + # 清除该数据库几天前的备份 + echo "清理 $pg_database 数据库 $days 天前的备份..." + find $backup_dir -maxdepth 1 -name "${pg_database}*" -mtime +$days -exec sh -c 'echo "$(date): $1" >> cleardump.log; rm -rf $1' sh {} \; + + # 备份指定数据库 + backup_database "$pg_database" +else + # 如果未指定数据库名称,则备份所有用户数据库(排除系统数据库) + echo "未指定数据库名称,将备份所有用户数据库" + + # 获取所有用户数据库(排除postgres, template0, template1系统数据库) + echo "正在获取数据库列表..." + databases=$(docker exec -i "$container_name" psql -U postgres -t -c "SELECT datname FROM pg_database WHERE datistemplate = false AND datname NOT IN ('postgres', 'template0', 'template1');") + + # 检查是否成功获取数据库列表 + if [ -z "$databases" ]; then + echo "警告: 未找到用户数据库,将备份默认的postgres数据库" + # 清理postgres数据库几天前的备份 + echo "清理 postgres 数据库 $days 天前的备份..." + find $backup_dir -maxdepth 1 -name "postgres*" -mtime +$days -exec sh -c 'echo "$(date): $1" >> cleardump.log; rm -rf $1' sh {} \; + # 备份postgres数据库 + backup_database "postgres" + else + echo "找到以下用户数据库:" + echo "$databases" + echo "" + + # 清理所有数据库几天前的备份 + echo "清理所有数据库 $days 天前的备份..." + find $backup_dir -maxdepth 1 -name "*_full_*" -mtime +$days -exec sh -c 'echo "$(date): $1" >> cleardump.log; rm -rf $1' sh {} \; + + # 备份每个用户数据库 + for db in $databases; do + # 去除可能的空格和换行符 + db=$(echo "$db" | tr -d ' \n\r') + if [ -n "$db" ]; then + backup_database "$db" + fi + done + fi +fi + +# 检查是否有备份成功完成 +if [ $? -eq 0 ]; then + echo "所有指定的数据库备份完成!" + exit 0 +else + echo "备份过程中出现错误,请检查日志" + exit 1 +fi diff --git a/init b/init new file mode 100755 index 0000000..e4a57aa --- /dev/null +++ b/init @@ -0,0 +1,171 @@ +#!/bin/bash + +# PostgreSQL初始化脚本 +# 用于初始化或重新初始化PostgreSQL配置环境 + +# 获取脚本所在目录的绝对路径 +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) + +# 切换工作目录到脚本所在目录 +cd $SCRIPT_DIR + +# 检查必要的命令 +check_commands() { + if ! command -v openssl &> /dev/null; then + echo "错误: 未找到openssl命令,请先安装" + exit 1 + fi + + if ! command -v direnv &> /dev/null; then + echo "警告: 未找到direnv命令,建议安装以获得最佳体验" + fi +} + +# 创建必要的目录结构 +create_directories() { + echo "创建必要的目录结构..." + mkdir -p ./data/pgdata + mkdir -p ./data/archived + mkdir -p ./data/backup/first + mkdir -p ./conf + echo "目录结构创建完成" +} + +# 初始化配置文件 +initialize_files() { + # 提示用户输入数据库密码(步骤1) + read -s -p "请输入PostgreSQL数据库密码: " postgres_password + echo + read -s -p "请再次输入密码确认: " postgres_password_confirm + echo + + # 验证密码一致性 + if [ "$postgres_password" != "$postgres_password_confirm" ]; then + echo "错误: 两次输入的密码不一致" + return 1 + fi + + # 验证密码强度(可选) + if [ ${#postgres_password} -lt 8 ]; then + echo "警告: 密码长度少于8个字符,建议使用强密码" + read -p "是否继续使用此密码?(y/n): " continue + if [ "$continue" != "y" ]; then + return 1 + fi + fi + + # 创建加密文件(步骤2) + echo "创建加密的密码文件..." + echo "请为加密文件设置一个密码(主密钥):" + echo -n "$postgres_password" | openssl enc -aes-256-cbc -salt -pbkdf2 -iter 10000 -out postgres_password.enc + + # 提示用户输入映射端口号(步骤3) + default_port="5432" + read -p "请输入PostgreSQL映射端口号 [$default_port]: " postgres_port + + # 如果用户直接回车,使用默认值 + if [ -z "$postgres_port" ]; then + postgres_port="$default_port" + fi + echo "映射端口号设置为: $postgres_port" + + # 提示用户输入容器名称(步骤4) + default_container_name="postgres" + read -p "请输入PostgreSQL容器名称 [$default_container_name]: " postgres_container_name + + # 如果用户直接回车,使用默认值 + if [ -z "$postgres_container_name" ]; then + postgres_container_name="$default_container_name" + fi + echo "容器名称设置为: $postgres_container_name" + + # 检查加密是否成功 + if [ $? -ne 0 ]; then + echo "错误: 创建加密文件失败" + return 1 + fi + + # 设置加密文件权限 + chmod 600 postgres_password.enc + echo "加密文件创建成功,权限设置为600" + + # 创建.envrc文件 + echo "创建.envrc配置文件..." + cat > .envrc << EOF +# PostgreSQL配置环境变量 +export POSTGRES_PASSWORD=\$(openssl enc -aes-256-cbc -d -pbkdf2 -iter 10000 -in postgres_password.enc) +export POSTGRES_PORT=$postgres_port +export POSTGRES_CONTAINER_NAME=$postgres_container_name +EOF + + # 设置.envrc文件权限 + chmod 600 .envrc + echo ".envrc文件创建成功,权限设置为600" + + # 自动执行direnv allow并提供状态反馈 + if command -v direnv &> /dev/null; then + echo "" + echo "📝 初始化完成!自动配置环境变量..." + echo "正在执行 direnv allow..." + if direnv allow > /dev/null 2>&1; then + echo "✅ direnv allow 执行成功!环境变量已启用" + else + echo "❌ direnv allow 执行失败,请手动运行 'direnv allow' 来启用环境变量" + fi + else + echo "" + echo "初始化完成!建议安装direnv以获得更好的使用体验:" + echo " macOS: brew install direnv" + echo " Linux: apt-get install direnv 或 yum install direnv" + fi + + return 0 +} + +# 主函数 +main() { + echo "PostgreSQL环境初始化脚本" + echo "===================================" + + # 检查必要的命令 + check_commands + + # 检查文件是否存在 + if [ -f "postgres_password.enc" ] && [ -f ".envrc" ]; then + echo "" + echo "检测到postgres_password.enc和.envrc文件已存在" + read -p "是否重新初始化?这将覆盖现有配置!(y/n): " reinitialize + + if [ "$reinitialize" != "y" ]; then + echo "初始化取消" + exit 0 + fi + + # 备份现有文件(可选) + backup_suffix="_bak_$(date +%Y%m%d%H%M%S)" + echo "备份现有文件..." + cp postgres_password.enc "postgres_password.enc$backup_suffix" 2>/dev/null + cp .envrc ".envrc$backup_suffix" 2>/dev/null + echo "备份完成" + fi + + # 创建目录结构 + create_directories + + # 初始化配置文件 + while ! initialize_files; do + echo "请重新输入密码..." + done + + echo "" + echo "===================================" + echo "初始化成功!" + echo "使用说明:" + echo "1. 使用 './service start' 启动服务" + echo "2. 使用 './service stop' 停止服务" + echo "3. 使用 './service status' 查看服务状态" + echo "4. 使用 './service restart' 重启服务" +} + +# 执行主函数 +main \ No newline at end of file diff --git a/restore b/restore new file mode 100755 index 0000000..e6fa062 --- /dev/null +++ b/restore @@ -0,0 +1,158 @@ +#!/bin/bash + +# 获取脚本所在目录的绝对路径 +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) + +# 切换工作目录到脚本所在目录 +cd $SCRIPT_DIR + +# 加载环境变量 +load_env_variables() { + if [ -f ".envrc" ]; then + # 使用direnv加载环境变量 + if command -v direnv &> /dev/null; then + eval "$(direnv export bash)" + # 检查POSTGRES_PASSWORD是否已设置 + if [ -z "$POSTGRES_PASSWORD" ]; then + echo "错误: 密码验证失败,无法继续操作" + return 1 + fi + else + echo "错误: 未安装direnv,请先安装direnv" + return 1 + fi + else + echo "错误: 找不到.envrc文件" + return 1 + fi + return 0 +} + +# 调用函数加载环境变量 +if ! load_env_variables; then + echo "无法加载环境变量,脚本退出" + exit 1 +fi + +# 设置变量 +container_name=${POSTGRES_CONTAINER_NAME:-postgres} +pg_user=${POSTGRES_USER:-postgres} +backup_dir="./data/backup/" + +# 获取宿主机上的绝对备份路径 +HOST_BACKUP_DIR="$(pwd)/${backup_dir}" + +# 查找最新的备份文件 +if [ -n "$1" ]; then + # 如果指定了数据库名称,优先查找该数据库的最新备份 + target_db=$1 + echo "正在查找数据库 $target_db 的最新备份..." + latest_backup=$(find "$backup_dir" -maxdepth 1 -type d -name "${target_db}_full_*" | sort -r | head -n 1) + + # 如果没有找到该数据库的备份,才考虑使用从备份文件名中提取的数据库名 + if [ -z "$latest_backup" ]; then + echo "警告: 未找到数据库 $target_db 的备份,尝试使用从备份文件名中提取的数据库名查找" + extracted_db_name=$(echo "$1" | sed -E 's/^([^_]+)_full_.+$|^([^_]+)$/\1\2/') + latest_backup=$(find "$backup_dir" -maxdepth 1 -type d -name "${extracted_db_name}_full_*" | sort -r | head -n 1) + fi +else + # 如果未指定数据库名称,查找所有备份中的最新一个 + echo "未指定数据库名称,查找所有备份中的最新一个..." + latest_backup=$(find "$backup_dir" -maxdepth 1 -type d -name "*_full_*" | sort -r | head -n 1) +fi + +# 检查是否找到备份文件 +if [ -z "$latest_backup" ]; then + echo "错误: 在 $backup_dir 目录下未找到备份文件" + exit 1 +fi + +# 提取备份文件名(不含路径) +backup_name=$(basename "$latest_backup") +HOST_BACKUP_PATH="${HOST_BACKUP_DIR}${backup_name}" + +# 从备份文件名中提取数据库名(假设格式为 databaseName_full_timestamp) +extracted_db_name=$(echo "$backup_name" | sed -E 's/^([^_]+)_full_.+$/\1/') + +# 目标数据库:优先使用命令行参数,否则使用从备份文件名中提取的数据库名 +pg_database=${1:-$extracted_db_name} + +# 验证找到的备份是否与目标数据库匹配 +if [ -n "$1" ] && [ "$1" != "$extracted_db_name" ]; then + echo "注意:找到的备份文件是 $backup_name,其中包含数据库 $extracted_db_name 的数据" + echo "您指定的目标数据库是 $1,将把 $extracted_db_name 的数据恢复到 $1 数据库中" + read -p "是否继续?(YES/no): " confirm_match + if [ "$confirm_match" != "YES" ]; then + echo "用户取消操作,脚本退出" + exit 1 + fi +fi + +# 显示找到的备份信息 +echo "找到最新备份文件:" +echo "备份目录名: $backup_name" +echo "宿主机上的备份路径: $HOST_BACKUP_PATH" + +# 检查备份目录中是否包含toc.dat文件(确认是有效的PostgreSQL目录格式备份) +if [ ! -f "$latest_backup/toc.dat" ]; then + echo "错误: 备份目录不包含toc.dat文件,可能不是有效的PostgreSQL目录格式备份" + exit 1 +fi + +# 显示恢复目标信息 +echo "\n恢复目标:" +echo "目标数据库: $pg_database" +echo "目标容器: $container_name" + +# 确认提示 +echo "\n警告:此操作将恢复数据到数据库 $pg_database,可能会覆盖现有数据!" +read -p "是否继续执行恢复操作?(YES/no): " confirm +if [ "$confirm" != "YES" ]; then + echo "用户取消恢复操作,脚本退出" + exit 1 +fi + +# 检查数据库容器是否正在运行 +if ! docker ps | grep -q "$container_name"; then + echo "错误: 数据库容器 $container_name 未运行,请先启动服务" + echo "您可以使用 ./service start 命令启动服务" + exit 1 +fi + +# 检查目标数据库是否存在 +if ! docker exec -i "$container_name" psql -U "$pg_user" -lqt | cut -d \| -f 1 | grep -qw "$pg_database"; then + echo "警告: 目标数据库 $pg_database 不存在,将创建该数据库" + if ! docker exec -i "$container_name" createdb -U "$pg_user" "$pg_database"; then + echo "错误: 创建数据库 $pg_database 失败" + exit 1 + fi +fi + +# 执行恢复操作 +echo "\n开始恢复数据库 $pg_database 从备份 $backup_name..." +echo "恢复过程可能需要一些时间,请耐心等待..." + +# 使用pg_restore恢复数据库 +if docker exec -i "$container_name" pg_restore \ + -U "$pg_user" \ + -d "$pg_database" \ + -Fd \ + -j 4 \ + "${backup_dir}${backup_name}"; then + + echo "\n数据库恢复成功!" + echo "恢复详情:" + echo "- 备份源: $HOST_BACKUP_PATH" + echo "- 目标数据库: $pg_database" + echo "- 目标容器: $container_name" + + # 可选:显示数据库中的表数量,验证恢复结果 + table_count=$(docker exec -i "$container_name" psql -U "$pg_user" -d "$pg_database" -c "SELECT COUNT(*) FROM pg_tables WHERE schemaname NOT IN ('pg_catalog', 'information_schema');" -t -A) + echo "- 恢复的表数量: $table_count" + + exit 0 +else + echo "\n数据库恢复失败!" + echo "请检查错误信息并重试" + exit 1 +fi \ No newline at end of file diff --git a/service b/service new file mode 100755 index 0000000..e0dd6c5 --- /dev/null +++ b/service @@ -0,0 +1,178 @@ +#!/bin/bash + +# PostgreSQL服务管理脚本 +# 用法: ./service start|stop|status|restart + +# 获取脚本所在目录的绝对路径 +SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) + +# 切换工作目录到脚本所在目录 +cd $SCRIPT_DIR + +# 容器名称(从环境变量读取,默认为postgres) +container_name=${POSTGRES_CONTAINER_NAME:-postgres} + +# 检查并加载环境变量 +load_env_variables() { + if command -v direnv &> /dev/null; then + echo "使用direnv加载环境变量..." + # 尝试使用direnv加载环境变量 + eval "$(direnv export bash)" + else + echo "警告: direnv未安装,使用当前环境变量" + fi + + # 检查POSTGRES_PASSWORD环境变量是否设置 + if [ -z "$POSTGRES_PASSWORD" ]; then + echo "错误: POSTGRES_PASSWORD环境变量未设置" + echo "请确保已安装direnv并运行 'direnv allow'" + echo "提示: 可能是加密密码输入错误,请重新输入正确的密码" + return 1 + fi + return 0 +} + +# 启动服务 +start_service() { + echo "启动PostgreSQL服务..." + + # 加载环境变量 + if ! load_env_variables; then + exit 1 + fi + + # 声明变量 + conf_dir="./conf" + conf_files=($(ls $conf_dir 2>/dev/null || echo "")) + target_dir="./data/pgdata" + + # 创建目录结构 + mkdir -p ./data/pgdata + mkdir -p ./data/archived + mkdir -p ./data/backup/first + + # 启动docker容器 + docker compose up -d + + # 等待容器启动并复制配置文件 + if [ ${#conf_files[@]} -gt 0 ]; then + echo "等待容器初始化并复制配置文件..." + for file in "${conf_files[@]}" + do + while [ ! -f "$target_dir/$file" ]; do + echo "等待容器 '$container_name' 初始化..." + sleep 5 + done + cp "$conf_dir/$file" "$target_dir/$file" + echo "已复制 $file 从 $conf_dir 到 $target_dir" + done + fi + + echo "PostgreSQL服务启动完成!" +} + +# 停止服务 +stop_service() { + echo "停止PostgreSQL服务..." + + # 必须成功加载环境变量(包括密码验证)才能继续 + if ! load_env_variables; then + echo "错误: 密码验证失败,无法继续操作" + exit 1 + fi + + # 使用docker compose down停止服务 + docker compose down + + # 如果docker compose命令失败,尝试直接通过容器名称停止 + if [ $? -ne 0 ]; then + echo "尝试直接停止容器..." + docker stop $container_name > /dev/null 2>&1 + docker rm $container_name > /dev/null 2>&1 + fi + + echo "PostgreSQL服务已停止." +} + +# 检查服务状态 +status_service() { + echo "检查PostgreSQL服务状态..." + + # 必须成功加载环境变量(包括密码验证)才能继续 + if ! load_env_variables; then + echo "错误: 密码验证失败,无法继续操作" + exit 1 + fi + + # 重新获取容器名称,确保使用最新的环境变量值 + updated_container_name=${POSTGRES_CONTAINER_NAME:-postgres} + + # 检查容器是否正在运行 + if docker ps | grep -q "$updated_container_name"; then + echo "PostgreSQL服务正在运行." + echo "访问地址: localhost:${POSTGRES_PORT:-25001}" + echo "容器名称: $updated_container_name" + return 0 + else + echo "PostgreSQL服务未运行." + return 1 + fi +} + +# 重启服务 +restart_service() { + echo "重启PostgreSQL服务..." + # 先停止服务(会验证密码) + stop_service + + # 停止成功后,重新验证密码并启动服务 + if [ $? -eq 0 ]; then + echo "正在启动PostgreSQL服务..." + start_service + else + echo "错误: 服务停止失败,无法重启" + exit 1 + fi +} + +# 显示帮助信息 +show_help() { + echo "用法: ./service [command]" + echo "命令:" + echo " start 启动PostgreSQL服务" + echo " stop 停止PostgreSQL服务" + echo " status 检查PostgreSQL服务状态" + echo " restart 重启PostgreSQL服务" + echo " help 显示此帮助信息" +} + +# 检查命令参数 +if [ $# -eq 0 ]; then + echo "错误: 请指定命令" + show_help + exit 1 +fi + +# 执行对应的命令 +case "$1" in + start) + start_service + ;; + stop) + stop_service + ;; + status) + status_service + ;; + restart) + restart_service + ;; + help) + show_help + ;; + *) + echo "错误: 未知命令 '$1'" + show_help + exit 1 + ;; +esac \ No newline at end of file diff --git a/start b/start deleted file mode 100755 index f172eee..0000000 --- a/start +++ /dev/null @@ -1,41 +0,0 @@ -#!/bin/bash - -# 获取脚本所在目录的绝对路径 -SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) - -# 切换工作目录到脚本所在目录 -cd $SCRIPT_DIR - -# 声明变量 -container_name="postgres" -conf_dir="./conf" -conf_files=($(ls $conf_dir)) -target_dir="./data/pgdata" - -# 创建目录结构 -mkdir -p ./data/pgdata -mkdir -p ./data/archived -mkdir -p ./data/backup/first - -# 启动docker容器 -docker compose up -d - -# 等待5秒,确保容器已经启动 -# sleep 5 - -# 遍历conf目录下的文件 -for file in "${conf_files[@]}" -do - while [ ! -f "$target_dir/$file" ]; do - echo "Waiting for the container \"$container_name\" to be initialized..." - sleep 5 - done - cp "$conf_dir/$file" "$target_dir/$file" - echo "Copied $file from $conf_dir to $target_dir" -done - -docker exec -it "$container_name" chown postgres:postgres -R $target_dir -docker exec -it "$container_name" chmod 700 -R $target_dir - -# 重启容器 -docker restart "$container_name" \ No newline at end of file diff --git a/stop b/stop deleted file mode 100755 index 268acc6..0000000 --- a/stop +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -# 获取脚本所在目录的绝对路径 -SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd) - -# 切换工作目录到脚本所在目录 -cd $SCRIPT_DIR - -# 关闭并删除docker容器 -docker compose down \ No newline at end of file