windows下搭建服务器

login anonymous
force_install_dir ./palworld
app_update 2394010 validate

通过python实现自动备份、重启和启动时检测更新

引入依赖包

pip install psutil
pip install schedule

2024-10-08T07:26:02.png

代码实现

以下代码放置steamcmd.exe同级目录

import subprocess
import psutil
import shutil
import schedule
import time
import os
import sys
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
import base64

name = "幻兽帕鲁"
smtp_server = "smtp.126.com"
smtp_port = 25
smtp_email = "darktool@126.com"
smtp_password = "XXXXXXXXXX"
to_email = "1529576424@qq.com"

def send_email_smtp(receiver_email, subject, body):
    # 创建邮件对象
    message = MIMEMultipart()
    message["From"] = f'"=?utf-8?B?{base64.b64encode((name + "-监控").encode("utf-8")).decode("utf-8")}?=" <{smtp_email}>'
    message["To"] = receiver_email
    message["Subject"] = subject

    # 邮件正文
    message.attach(MIMEText(body, "plain"))
    print(f"发送邮件\nTo:{receiver_email}\n主题:{subject}\n正文:{body}")

    try:
        # 连接到 SMTP 服务器
        server = smtplib.SMTP(smtp_server, smtp_port)
        server.starttls() # 启用安全传输
        server.login(smtp_email, smtp_password)

        # 发送邮件
        server.send_message(message)
        print("发送邮件成功!")

        server.quit()
    except Exception as e:
        print(f"发送邮件失败!\nError: {e}")

program_name = "PalServer-Win64-Shipping-Cmd.exe"  # 程序名
program_directory = "C:\steamcmd\palworld" # 程序目录
program_start_command = "PalServer.exe"  # 程序启动命令
steam_app_path = "./palworld"  # 指定程序安装目录
steam_app_id = "2394010"  # 程序id
xml_list = [
    {
        "source": "C:\steamcmd\palworld\DefaultPalWorldSettings - 副本.ini",
        "target": "C:\steamcmd\palworld\DefaultPalWorldSettings.ini"
    },
    {
        "source": "C:\steamcmd\palworld\PalWorldSettings - 副本.ini",
        "target": "C:\steamcmd\palworld\Pal\Saved\Config\WindowsServer\PalWorldSettings.ini"
    }
]
backup_list = [
    # {
    #     "source": "C:\steamcmd\palworld\Pal\Saved",
    #     "target": "C:\steamcmd\palworld\Pal\\back"
    # }
]

# # 调用函数进行备份
# backup_files('/原始目录', '/目标目录')

def backup_files(source_dir, dest_dir):
    try:
        shutil.copytree(source_dir, dest_dir)
        print("文件备份成功!")
    except Exception as e:
        print("文件备份失败:", str(e))

# 备份任务
def backup_job():
    for backup in backup_list:
        source_directory = backup["source"]
        target_directory = backup["target"]
        current_time = time.strftime("%Y-%m-%d_%H-%M-%S", time.localtime())
        directory = target_directory + "\\" + current_time
        backup_files(source_directory, directory)
        print(f"{source_directory}备份时间:", current_time)

# 检测程序是否在执行
def check_process_running(process_name):
    for process in psutil.process_iter(['name', 'cmdline']):
        if process.info['name'] and process.info['name'] == process_name:
            return True
        elif process.info['cmdline'] and process_name in process.info['cmdline']:
            return True
    return False

# 执行steamcmd更新命令
def run_steamcmd_update_command_realtime():
    # 运行SteamCMD命令并实时获取输出
    process = subprocess.Popen(['steamcmd.exe', '+login', 'anonymous', '+force_install_dir', steam_app_path, '+app_update', steam_app_id, 'validate', '+quit'], 
                               stdout=subprocess.PIPE,
                               stderr=subprocess.STDOUT,
                               universal_newlines=True,
                               encoding='utf-8')

    # 实时获取控制台输出内容
    for line in process.stdout:
        print(line, end='')

    # 等待命令执行完毕
    process.wait()

    # 检查是否有错误发生
    if process.returncode != 0:
        print(f"Command execution failed with exit code {process.returncode}")

    # 拷贝配置文件,防止更新时配置文件被还原
    for xml in xml_list:
        try:
            source = xml["source"]
            target = xml["target"]
            shutil.copy2(source, target)
            print(f"拷贝{source}配置文件到{target}成功!")
        except Exception as e:
            print(f"拷贝{source}配置文件到{target}失败:", str(e))

# 关闭服务器
def stop_program():
    print("服务器关闭时间:", time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
    if sys.platform.startswith('win'):
        subprocess.Popen('taskkill /F /IM ' + program_name, shell=True)
    # elif sys.platform.startswith('linux') or sys.platform.startswith('darwin'):
    #     subprocess.Popen(['pkill', '-f', 'your_program.py'])

# 启动服务器
def start_program():
    print("服务器启动时间:", time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
    if sys.platform.startswith('win'):
        # subprocess.Popen(program_path, shell=True)
        # subprocess.run([program_start_command], input=b'\n', shell=True, cwd=program_directory)
        process = subprocess.Popen([program_start_command],
                                    shell=True,
                                    cwd=program_directory,
                                    stdin=subprocess.PIPE,
                                    stdout=subprocess.PIPE,
                                    stderr=subprocess.STDOUT,
                                    universal_newlines=True,
                                    encoding='gbk')
        # # 实时获取控制台输出内容
        # for line in process.stdout:
        #     out = process.stdout.readline()
        #     print(out, end='')
        #     if out == '' and process.poll() is not None:
        #         break
        #     elif '请按任意键继续. . .' in out:
        #         # 当输出中出现特定内容时
        #         process.stdin.write('\n')
        #         process.stdin.flush()  # 确保输入被发送
        #         # 

        time.sleep(10)  # 等待10秒
        # 强制终止子进程
        process.kill()
        # 等待进程结束
        process.wait()
        # 检查是否有错误发生
        if process.returncode != 0:
            print(f"Command execution failed with exit code {process.returncode}")
    # elif sys.platform.startswith('linux') or sys.platform.startswith('darwin'):
    #     subprocess.Popen(['python', 'your_program.py'])

# 重启任务
restarting = False
def stop_and_restart_job():
    # 在更改全局变量前先声明
    global restarting  # 声明restarting为全局变量
    restarting = True
    while check_process_running(program_name): # 等待程序结束完成
        stop_program()
        time.sleep(10)  # 等待10秒,确保程序完全关闭
    print("启动程序更新")
    run_steamcmd_update_command_realtime()
    while not check_process_running(program_name): # 查询程序是否启动,如果未启动则再次启动
        start_program()
        time.sleep(60)
    restarting = False
    send_email_smtp(to_email, "程序定时重启", time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))

# 当程序崩溃时自动启动
start_time = 0
def auto_start_job():
    # 在更改全局变量前先声明
    global start_time  # 声明start_time为全局变量
    if not restarting and not check_process_running(program_name):
        print("检测到服务器未启动,开始自启动:", time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
        run_steamcmd_update_command_realtime()
        start_program()
        d = time.time() - start_time
        print(f"距离上一封自启动邮件发送时间间隔:{d}秒")
        if d > 1800: # 确保每半个小时内该邮件只会发送一份
            send_email_smtp(to_email, "检测到程序未执行,执行自动启动", time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
            start_time = time.time()

# 定义任务调度函数
def schedule_job():
    schedule.every(1).minutes.do(auto_start_job)   # 每分钟检测一次程序是否崩溃
    schedule.every(30).minutes.do(backup_job)   # 每隔半小时执行一次备份
    schedule.every().day.at("11:53").do(stop_and_restart_job)   # 每天11:53重启服务器

# 启动任务调度器
schedule_job()

print(f"检测{program_name}程序是否在执行:{check_process_running(program_name)}")
# if not check_process_running(program_name):
#     print("程序未执行,启动程序更新")
#     run_steamcmd_update_command_realtime()
#     start_program()
while True:
    schedule.run_pending()  # 检查是否有任务需要执行
    time.sleep(1) # 避免过于频繁的检查

配置文件翻译

    Difficulty=None, ; 难度,None 或 Difficulty 
    DayTimeSpeedRate=1.000000, ; 白天流逝速度倍率 
    NightTimeSpeedRate=1.000000, ; 夜晚流逝速度倍率 
    ExpRate=1.000000, ; 经验值倍率 
    PalCaptureRate=1.000000, ; 捕捉概率倍率 
    PalSpawnNumRate=1.000000, ; 帕鲁出现数量倍率 
    PalDamageRateAttack=1.000000, ; 帕鲁攻击伤害倍率 
    PalDamageRateDefense=1.000000, ; 帕鲁承受伤害倍率 
    PlayerDamageRateAttack=1.000000, ; 玩家攻击伤害倍率 
    PlayerDamageRateDefense=1.000000, ; 玩家承受伤害倍率 
    PlayerStomachDecreaceRate=1.000000, ; 玩家饱食度降低倍率 
    PlayerStaminaDecreaceRate=1.000000, ; 玩家耐力降低倍率 
    PlayerAutoHPRegeneRate=1.000000, ; 玩家生命值自然恢复倍率 
    PlayerAutoHpRegeneRateInSleep=1.000000, ; 玩家睡眠时生命恢复倍率 
    PalStomachDecreaceRate=1.000000, ; 帕鲁饱食度降低倍率 
    PalStaminaDecreaceRate=1.000000, ; 帕鲁耐力降低倍率 
    PalAutoHPRegeneRate=1.000000, ; 帕鲁生命值自然恢复倍率 
    PalAutoHpRegeneRateInSleep=1.000000, ; 帕鲁睡眠时生命恢复倍率 
    BuildObjectDamageRate=1.000000, ; 对建筑物伤害倍率 
    BuildObjectDeteriorationDamageRate=1.000000, ; 建筑物劣化速度倍率 
    CollectionDropRate=1.000000, ; 可采集物品掉落倍率 
    CollectionObjectHpRate=1.000000, ; 可采集物品生命值倍率 
    CollectionObjectRespawnSpeedRate=1.000000, ; 可采集物品生成速率 
    EnemyDropItemRate=1.000000, ; 敌方掉落物品倍率 
    DeathPenalty=All, ; 死亡惩罚,None 不掉落,Item 只掉物品不掉装备,ItemAndEquipment 掉物品和装备,All 全都掉 
    bEnablePlayerToPlayerDamage=False, ; 启用玩家对玩家伤害功能 
    bEnableFriendlyFire=False, ; 启用友军伤害功能 
    bEnableInvaderEnemy=True, ; 启用袭击事件功能 
    bActiveUNKO=False, ; 启用 UNKO 功能 
    bEnableAimAssistPad=True, ; 启用手柄瞄准辅助功能 
    bEnableAimAssistKeyboard=False, ; 启用键盘瞄准辅助功能 
    DropItemMaxNum=3000, ; 掉落物品最大数量 
    DropItemMaxNum_UNKO=100, ; 掉落物品最大数量_UNKO 
    BaseCampMaxNum=128, ; 大本营最大数量 
    BaseCampWorkerMaxNum=15, ; 大本营工人最大数量 
    DropItemAliveMaxHours=1.000000, ; 掉落物品存在最大时长(小时) 
    bAutoResetGuildNoOnlinePlayers=False, ; 自动重置没有在线玩家的公会 
    AutoResetGuildTimeNoOnlinePlayers=72.000000, ; 无在线玩家时自动重置公会的时间(小时) 
    GuildPlayerMaxNum=20, ; 公会玩家最大数量 
    PalEggDefaultHatchingTime=72.000000, ; 帕鲁蛋默认孵化时间(小时) 
    WorkSpeedRate=1.000000, ; 工作速度倍率 
    bIsMultiplay=False, ; 是否为多人游戏
    bIsPvP=False, ; 是否为 PvP 游戏 
    bCanPickupOtherGuildDeathPenaltyDrop=False, ; 是否可以拾取其他公会的死亡掉落物 
    bEnableNonLoginPenalty=True, ; 是否启用不登录惩罚 
    bEnableFastTravel=True, ; 是否启用快速旅行 
    bIsStartLocationSelectByMap=True, ; 是否通过地图选择起始位置 
    bExistPlayerAfterLogout=False, ; 是否在登出后保留玩家 
    bEnableDefenseOtherGuildPlayer=False, ; 是否启用对其他公会玩家的防御 
    CoopPlayerMaxNum=4, ; 合作玩家最大数量 
    ServerPlayerMaxNum=32, ; 服务器玩家最大数量 
    ServerName="Default Palworld Server", ; 服务器名称 
    ServerDescription="", ; 服务器描述 
    AdminPassword="", ; 管理员密码 
    ServerPassword="", ; 服务器密码 
    PublicPort=8211 ; 公共端口
最后修改:2024 年 11 月 10 日
如果觉得我的文章对你有用,请随意赞赏