如何使用 Node.js 备份您的个人文件(并在此过程中学习一些 webdev 技能)
本教程的所有代码(完整包)均可在此仓库中找到。如果您觉得本教程有用,请与您的朋友和同事分享!
想要了解更多类似教程,请在 Twitter 上关注我@eagleson_alex
本教程还提供视频版本:
介绍
和许多其他人一样,我拥有不少对我来说很重要的数字文档和图片。
虽然我知道我在本地至少有一份这些副本,并且我确实使用 Dropbox 作为云存储;但我坦率地承认我不符合令人垂涎的3-2-1 备份策略标准😳。
假期期间,我收到了一个新的 4TB 硬盘,随之而来的是,我重新燃起了备份数据的兴趣(至少是重要的东西,比如我孩子的照片和财务记录。我想在最坏的情况下,我可能会更换我的《星际迷航 TNG》蓝光翻录;所以我现在会把它们分开保存)。
考虑到这一点,我决定将它与一项比我通常更深入地研究 Node.js 生态系统的练习结合起来。
本教程正是这些探索的成果,最终成果是一个可以同步计算机上任何目录备份的小工具。作为附加功能,我们将配置它以支持 Linux、Mac 和 Windows。
这篇文章在很多方面都是我最大的受益者。我想尝试一些新东西,并简单地记录了我的学习经历。它遵循了@swyx的理念——公开学习,并与更广泛的社区分享你的所学,希望每个人都能从中受益。
话虽如此,我应该非常清楚,这主要是一种学习经验,绝对不是最好的备份解决方案。
如果你真的需要远程存储,那么像Google Drive这样的应用就能帮你搞定。对于本地备份来说,设置RAID 硬盘比这个小备份应用更安全。
也就是说,这些选项没那么有趣,所以如果你愿意利用这个作为学习机会来练习你的 Node.js 技能,并从中获得一些额外的计划备份,我想你会发现这是一个非常有趣的教程。
目录
您将学到什么
-
常见的 Linux 工具,例如
rsync
(在本地和通过 SSH 复制文件)、cron
(按特定间隔安排任务)和nohup
(作为后台进程运行,在终端会话结束时不会停止) -
将 Node(Javascript)应用程序作为后台进程运行,包括使用崩溃时自动重启
pm2
,这是 Node.js 服务器的可行生产工具。 -
process
有关 Node.js 中对象可用的不同值的更多信息,包括title
和platform
-
创建一个根据所运行操作系统而行为不同的应用程序,并且可在 Bash(Mac/Linux)和 Microsoft PowerShell(Windows)上运行
-
使用 HTTP POST 请求向 webhook 发送消息,在我们的示例中,该 webhook 将是一个 Discord 机器人
尝试一下(可选)
如果您想先尝试一下,请按照以下说明操作。如果您想直接创建自己的版本,请跳过本节。
该应用程序可在 Mac/Linux(Bash)和 Windows(PowerShell)上运行。
您需要安装的是git
和nodejs
。
- 从此存储库克隆项目
npm install
从项目目录运行- 在根目录中创建一个
.env
具有以下结构的文件:
SOURCE_DIR="example-source/"
DESTINATION_DIR="example-destination/"
CRON_STRING="* * * * * *"
WEBHOOK_ID="DISCORD_WEBHOOK_ID"
更新SOURCE_DIR
为您想要复制的目录,以及DESTINATION_DIR
您想要同步的位置。
编辑CRON_STRING
以确定复制发生的计划。如果您不熟悉 cron 字符串,可以使用此工具来帮助您创建一个。
是DISCORD_WEBHOOK_ID
可选的。如果您不使用它,则不会影响应用。如果您使用它,请删除https://discord.com/api/webhooks/
Webhook URL 的相应部分,其余部分是DISCORD_WEBHOOK_ID
。
现在你可以使用以下命令运行该应用程序:
node backup.js
如果您计划将其作为长期后台进程运行,则可以使用“作为后台进程运行”部分中描述的 PM2。
配置您的机器(可选)
(注意:如果您已经拥有一台机器和一些文件夹用于备份设置,那么您可以完全跳过这一步。本节将介绍如何在一台旧笔记本电脑上设置 Ubuntu Linux,将其配置为全天候运行,并使用 SSH,以便我们可以从家庭网络上的其他机器远程访问和管理它)
我将使用这款小型蓝色戴尔 Inspiron,它拥有高达 2GB 的内存,目前闲置着,积满灰尘。
老实说,这是一台配备了 SSD 的很棒的机器,但不幸的是,它的 2GB RAM 限制了它的使用,而且没有办法升级它(我试过了)。
因此,由于这个原因,我无法充分利用它,但现在情况发生了变化。
我首先按照本教程使用 USB 棒安装 Ubuntu,然后让小型笔记本电脑启动并运行最新的 Ubuntu。
接下来,我需要确保可以从主机上的终端访问笔记本电脑。这将通过 SSH 完成,因此我按照这个在 Ubuntu 上启用 SSH 的教程操作。之后,我确认可以从我的主机成功通过 SSH 连接到笔记本电脑。
接下来,我需要确保计算机上安装了git
和,这样我才能克隆我的项目并运行它。幸运的是,Ubuntu 默认自带了 ,我可以使用以下命令安装:node
git
node
sudo apt update
sudo apt install nodejs
如果这给您带来任何麻烦,请按照本教程操作。
接下来,我插入要用作备份的外部硬盘。创建应用程序时,我会每周将一个硬盘上的一个目录指向另一个硬盘上的另一个目录进行同步。
最后,我需要对笔记本电脑做一些维护工作,以确保它能够继续运行,并且知道在盖子关闭时该做什么。
要让它不进入睡眠状态,很简单,Settings -> Power
只需关闭 即可Automatic Suspend
。基本上,禁用所有可能让你的机器进入睡眠状态的功能。
接下来我需要处理合上盖子时发生的情况。遗憾的是,我在电源设置中没有看到任何相关设置,所以我需要直接编辑配置文件:
sudo gedit /etc/systemd/logind.conf
并将以下值从默认值更改为ignore
:
HandleLidSwitch=ignore
如果它被注释掉(以 为前缀#
),则删除#
并保存。
就这样!现在我可以把机器拿过来,盖上盖子,放在阴凉的地方,连接好外置硬盘,随时准备运行。理想情况下,应该直接用以太网把它插到路由器上,这样可以最大程度地减少 Wi-Fi 问题。可惜我的笔记本电脑没有以太网,所以只能用 Wi-Fi 了。
创建项目
让我们创建一个目录并在其中初始化我们的 Javascript 项目:
npm init -y
接下来我们安装项目的三个依赖库:
npm install cron rsync dotenv
每个功能的用途如下:
-
cron:允许我们以特定的时间间隔安排备份。此软件包使用cron语法的 JavaScript 实现,而不是实际的cron守护进程,这意味着我们无需担心此软件包的操作系统兼容性问题。
-
rsync:它将负责文件的复制和同步。此包会
rsync
使用用户机器上实际安装的程序,因此我们必须在 Node.js 应用中管理它的兼容性。 -
dotenv:允许我们从项目目录中读取
.env
文件。这将允许我们包含个人目录路径以及私有的 Discord webhook,而无需在 git 仓库中共享这些数据。克隆项目的用户可以提供自己的值。
我们将创建一个名为的 Javascript 文件backup.js
并使其基本功能正常运行:
backup.js
const CronJob = require("cron").CronJob;
const Rsync = require("rsync");
// Equivalent to writing `rsync -a example-source/ example-destination/` on terminal
rsync = new Rsync()
// The -a flag means "archive" to say we are copying the full directory not just a file
.flags("a")
.source("example-source/")
.destination("example-destination/");
const job = new CronJob(
// Run this function once every minute
// To learn more about this cron string visit the below link
// https://crontab.guru/#*_*_*_*_*
"* * * * *",
() => {
rsync.execute((error, code, cmd) => {
// List of rsync status codes
// https://stackoverflow.com/a/20738063
console.log("backup completed with status code: " + code);
});
},
null,
true,
// Replace with your time zone
// https://gist.github.com/diogocapela/12c6617fc87607d11fd62d2a4f42b02a
"America/Toronto"
);
// Begin the cronjob
job.start();
example-source
还要创建名为和 的目录example-destination
。在其中example-source
创建一个名为 的 TXT 文件,sample-file.txt
其中包含您喜欢的任何内容。文件内容无关紧要,它只是用来确认我们的备份正常工作。
脚本运行之前的情况如下(请注意空example-destination
目录):
脚本运行一次后:
一切看起来都很好,我们每分钟间隔一次将example-source
目录备份到我们的example-destination
目录中。
此时,您可以用您喜欢的任何文件夹替换这些目录字符串,并在您的机器上创建常规目录备份系统。
dotenv
通过添加读取.env
文件的包作为配置的一部分,我们可以更轻松地为下载和使用我们工具的用户进行定制。
如果您已经按照本教程操作过,那么您已经dotenv
通过 NPM 安装好了该软件包,因此只需导入它即可。在此之前,我们先创建.env
文件。请务必注意,文件名.env
以 开头.
(表示隐藏文件):
.env
SOURCE_DIR="example-source/"
DESTINATION_DIR="example-destination/"
CRON_STRING="* * * * *"
现在我们可以更新代码来读取该文件了。我们只需要在代码顶部添加包require
:dotenv
backup.js
require("dotenv").config();
const CronJob = require("cron").CronJob;
const Rsync = require("rsync");
// Equivalent to writing `rsync -a example-source/ example-destination/` on terminal
rsync = new Rsync()
// The -a flag means "archive" to say we are copying the full directory not just a file
.flags("a")
// Reads from the `.env` file in the project directory
.source(process.env.SOURCE_DIR)
.destination(process.env.DESTINATION_DIR);
const job = new CronJob(
// Run this function once every minute
// To learn more about this cron string visit the below link
// https://crontab.guru/#*_*_*_*_*
process.env.CRON_STRING,
() => {
rsync.execute((error, code, cmd) => {
// List of rsync status codes
// https://stackoverflow.com/a/20738063
console.log("backup completed with status code: " + code);
});
},
null,
true,
// Replace with your time zone
// https://gist.github.com/diogocapela/12c6617fc87607d11fd62d2a4f42b02a
"America/Toronto"
);
// Begin the cronjob
job.start();
再次运行时,node backup.js
我们得到相同的结果,但这次是从文件中读取源目录和目标目录.env
。这将方便用户在下载工具时添加自己的源/目标目录和 cron 字符串。
它还提高了隐私性,因为我们将添加.env
到我们的.gitignore
文件中,所以我选择在我的计算机上复制的目录将不会包含在该项目的 git 存储库中。
事实上,我们现在就开始吧。如果你正在为自己创建这个项目,你会希望能够将其提交到远程 git 主机,所以运行:
git init
然后在根目录中创建一个.gitignore
文件:
.gitignore
node_modules
.env
nohup.out
我们排除.env
它的原因如上所述,并且node_modules
因为它将通过运行为使用我们项目的任何人重新创建npm install
。最后一个nohup.out
将包含本教程后面的一些日志,我们不需要与其他人共享,所以我们现在就提前添加它。
太棒了!现在你有了一个很棒的小工具,可以在 Linux 和 Mac 上运行……但是 Windows 呢?
事实上,我所做的所有开发工作基本上都是在 Linux 环境中进行的。
尽管我使用WSL2在 Windows 11 上进行所有日常开发,但尽管每天都登录 Windows,我仍然在原生安装的 Ubuntu 中进行所有操作。
cmd
老实说,除了我小时候记得的 DOS 命令(dir?)之外,我甚至不知道如何使用 Windows或 PowerShell......但如果我的大部分个人资料(照片和文档)都存储在 Windows 上,也许这对我来说是一个学习的好机会?
我喜欢学习新事物!我经常挑战自己:我需要怎么做才能在 Windows 上实现这个功能?
事实证明这出乎意料的简单。
跨平台支持
我们的大多数应用程序都可以在 Windows 上正常运行,这里最大的挑战是rsync。
从该链接中您可以看到,rsync
这是一个 Unix 复制工具,可在大多数 Linux 和 Mac 环境中原生使用;但不适用于 Windows。
NPM 上的包rsync
只是对安装在你操作系统上的工具的一个包装,因此backup.js
在 PowerShell 中运行它会报错。错误信息是该rsync
程序不存在。
不过,这真的很酷:Windows 不仅有一个非常相似的工具,它具有类似的 API,称为robocopy,rsync
NPM 包还允许我们链接一个executable()
接受字符串的方法。
该字符串是我们要使用的复制工具的名称。
它默认为rsync
,但是我们可以为其提供任何我们想要的名称。
我们可以检查程序在哪个操作系统上运行,当在 Windows 上运行时,process.platform
它将win32
以字符串的形式返回。
让我们更新一下backup.js
:
backup.js
require("dotenv").config();
const CronJob = require("cron").CronJob;
const Rsync = require("rsync");
// The value of process.platform will be:
// Windows: win32
// Mac: darwin
// Ubuntu: linux
const syncProgram = process.platform === "win32" ? "robocopy" : "rsync";
// Equivalent to writing `rsync -a example-source/ example-destination/` on terminal
rsync = new Rsync()
.executable(syncProgram)
// The -a flag means "archive" to say we are copying the full directory not just a file
.flags("a")
// Reads from the `.env` file in the project directory
.source(process.env.SOURCE_DIR)
.destination(process.env.DESTINATION_DIR);
const job = new CronJob(
// Run this function once every minute
// To learn more about this cron string visit the below link
// https://crontab.guru/#*_*_*_*_*
process.env.CRON_STRING,
() => {
rsync.execute((error, code, cmd) => {
let result;
if (error) {
// List of rsync status codes
// https://stackoverflow.com/a/20738063
result = `Code ${code} ${error?.message}`;
} else {
result = "Backup complete";
}
const currentDate = new Date().toISOString();
// Write log to the console, or will be redirected to a
// nohup.out file if using nohup
process.stdout.write(`${currentDate}: ${result}\n`);
});
},
null,
true,
// Replace with your time zone
// https://gist.github.com/diogocapela/12c6617fc87607d11fd62d2a4f42b02a
"America/Toronto"
);
// Begin the cronjob
job.start();
注意上面的更改。我做了检查process.platform
,如果返回结果正确win32
,我们将可执行复制程序设置为 ,robocopy
而不是 ,rsync
这样就可以在 Windows 上运行了。
幸运的是,的语法robocopy
与完全相同rsync
:
robocopy <source> <destination>
这意味着我们不需要改变程序现有的运行方式,当我们在 Windows 上时,包将以完全相同的方式rsync
调用。robocopy
我们已经准备好尝试了。为了在 Windows 上获取项目文件,我将把它们推送到 Github,然后通过 Windows 文件系统克隆它们。
所以我要做的是:我将这个项目推送到 Github。然后我打开 PowerShell。
(我是 PowerShell 新手,但我正在尽力。)
事实证明我甚至没有在 Windows 上安装git
,node
所以我不会在这里走得太远。
首先我需要下载 git以便我可以克隆项目,然后我需要下载 node以便我可以运行它。
下载并安装后,我可以在 PowerShell 中运行这两个命令并获得有效的输出:
PS C:\Users\ME> git --version
git version 2.34.1.windows.1
PS C:\Users\ME> node --version
v16.13.1
现在一切都已设置好,我可以git clone MY_PROJECT_URL
进入cd
该目录并运行:
npm install
不过,在运行项目之前,我需要创建该.env
文件,因为出于隐私原因我没有将其包含在 repo 中:
.env
SOURCE_DIR="example-source/"
DESTINATION_DIR="example-destination/"
CRON_STRING="* * * * *"
最后,在项目目录中的 PowerShell 中运行:
node backup.js
我的结果是:
在 Windows 上运行该脚本之前(注意目录为空example-destination
)
脚本在 Windows 上运行一次后:
请注意,状态代码不一定与rsync
状态代码匹配,但结果是正确的:复制过程成功。
这真是太棒了!现在你有了一个工具,可以按照你选择的时间间隔将一个目录的内容复制到另一个目录。目前我们把它设置为每分钟运行一次,这有点夸张,但多亏了crontab guru这样的工具,你可以轻松地创建你想要的时间间隔。
例如,我只需要每周备份一次我的目录,所以我打算将其设置为每周日凌晨 3 点运行。
我们现在有一个按计划运行且可在 Linux、Mac 和 Windows 上运行的备份过程!
但是...我们要如何才能让它一直运行下去呢?
如果我们简单地使用node backup.js
该进程,一旦我们关闭终端,它就会停止。我们需要这个进程在后台运行,理想情况下是全天候运行。
我们需要一个更好的解决方案。输入pm2
作为后台进程运行
在我们开始使用pm2 的最终解决方案之前,我想快速向 Mac/Linux 用户展示如何使用nohup来实现这一点,而无需安装任何附加工具。
如果您愿意,可以跳过本节nohup
并直接使用 PM2 进入最终解决方案,它只是为了让您更深入地了解如何使用本机 Unix 工具创建后台进程。
使用 nohup(可选 - 仅限 Mac 和 Linux)
您可以通过以下方式了解您的系统是否支持nohup
:
nohup --version
$ nohup --version
nohup (GNU coreutils) 8.30
如果您成功获得版本号,那么下一步就应该适合您。
nohup node backup.js &
前导符号nohup
会告诉您的系统,即使您的会话结束,您也不希望进程停止,最后的尾随&
符号表示将其作为后台守护进程运行。
您可能会得到如下输出:
[1] 7604
运行该命令后,这就是process ID
你的 Node 程序的。如果你因为任何原因丢失了它,可以使用以下命令重新找到它:
pstree -p
您将获得显示系统上运行的所有进程及其 ID 的输出。如果您注意到,在backup.js
上面的代码示例中,我们使用process.title
并赋予了 一个字符串值node-backup-script
。
这有助于我们在使用以下命令时查找和识别进程 ID pstree -p
:
请注意,node-backup-sc(7604)
其中显示的 PID 与脚本启动时给出的相同,还有title
我们设置的值process.title
,以便更容易查找和识别。
由于我们不能再简单地ctrl+C
取消节点脚本的执行,我们必须采取一些不同的措施。我们必须通过直接引用 PID 来终止进程。
为此,您可以运行:
kill -9 YOUR_PID
YOUR_PID
你的机器上给出的ID在哪里?在我上面的例子中,它是7604。这-9
告诉它覆盖所有可能停止或拦截系统终止信号的操作,无论如何你都想结束这个程序。
因此,nohup
对于支持该选项的 Unix 系统来说,只要它们全天候运行,该进程就会持续运行。不过,它存在一些问题:
- 如果您的程序崩溃,
nohup
将不会重新启动它 - 此解决方案仅适用于 Unix,不适用于 Windows
那么我们如何创建一个可以全天候运行、崩溃时重新启动并支持跨平台的解决方案?
这就是pm2的用途。
使用 PM2(跨平台)
使用 PM2 我们可以在任何系统(包括 Windows)的后台运行我们的备份脚本,我们所需要的只是安装 NPM。
我们将使用 NPM 全局安装 PM2:
npm install -g pm2
安装后,您可以通过以下方式验证它是否可用:
pm2 --version
要运行备份脚本:
pm2 start backup.js
因此现在该进程在后台运行,即使您关闭终端也会继续运行,并且在崩溃时会重新启动。
您可以随时使用查看正在运行的进程pm2 list
,并可以使用停止它,pm2 stop backup
其中“backup”是进程的名称。
您还可以通过运行来查看应用程序的日志pm2 logs backup
。您将获得如下输出:
不幸的是,这在系统完全重启后将无法继续存在。配置pm2
在重启时自动启动你的应用超出了本教程的范围,但如果你想的话,这里有非常好的说明来指导你如何处理。
您现在可以在旧笔记本电脑上或您自己的云服务器(例如每月 5 美元的Digital Ocean Droplet)上运行它。
在查看 Node 应用程序托管解决方案时要小心,虽然有很多选项,但大多数免费解决方案(例如 Heroku)有时会让您的应用程序进入“睡眠”状态,这不是一个可行的选择,因为它必须在计划的备份触发时处于唤醒状态才能正常工作。
最后,我们将添加一个小奖励教程,展示如何使用 webhook 将备份的状态输出发送到 Discord 机器人,以便我们可以轻松跟踪它。
添加 Discord Webhook(奖励)
本节将教您如何将备份操作产生的状态代码打印到 Discord 服务器上的机器人(除了 PM2 日志之外)。
首先在服务器上创建 Webhook。请按照本教程操作,直到“创建 Webhook”部分的结尾。在“快速示例:GitHub Webhook 集成”部分停止。
你只需要点击“复制 Webhook URL”按钮即可。它看起来像这样:
https://discord.com/api/webhooks/YOUR_WEBHOOK_ID
YOUR_WEBHOOK_ID 是一串长字符,其中可能包含额外的斜杠。基本上,https://discord.com/api/webhooks/
您需要将 后面的所有内容复制并粘贴到文件WEBHOOK_ID
中的密钥中.env
。
.env
SOURCE_DIR="example-source/"
DESTINATION_DIR="example-destination/"
CRON_STRING="* * * * * *"
WEBHOOK_ID="YOUR_WEBHOOK_ID"
接下来我们将更新backup.js
:
backup.js
require("dotenv").config();
const CronJob = require("cron").CronJob;
const Rsync = require("rsync");
const https = require("https");
process.title = "node-backup-script";
// Will be true if there is a Discord WEBHOOK_ID set in the `.env` file
const useDiscord = !!process.env.WEBHOOK_ID;
const options = {
hostname: "discord.com",
path: `/api/webhooks/${process.env.WEBHOOK_ID}`,
method: "POST",
headers: {
"Content-Type": "application/json",
},
};
// process.platform will be:
// Windows: win32
// Mac: darwin
// Ubuntu: linux
const syncProgram = process.platform === "win32" ? "robocopy" : "rsync";
// Equivalent to writing `rsync -a example-source/ example-destination/` on terminal
rsync = new Rsync()
.executable(syncProgram)
// The -a flag means "archive" to say we are copying the full directory not just a file
.flags("a")
// Reads from the `.env` file in the project directory
.source(process.env.SOURCE_DIR)
.destination(process.env.DESTINATION_DIR);
const job = new CronJob(
// Run this function once every minute
// To learn more about this cron string visit the below link
// https://crontab.guru/#*_*_*_*_*
process.env.CRON_STRING,
() => {
rsync.execute((error, code, cmd) => {
let result;
if (error) {
// List of rsync status codes
// https://stackoverflow.com/a/20738063
result = `Code ${code} ${error?.message}`;
} else {
result = "Backup complete";
}
const currentDate = new Date().toISOString();
// Write log to the console, or will be redirected to a
// nohup.out file if using nohup
process.stdout.write(`${currentDate}: ${result}\n`);
// Only sends the request if WEBHOOK_ID is defined
if (useDiscord) {
// Send the request to Discord with the configured options
const req = https.request(options, (res) => {
// do nothing with Discord response
});
// Discord requires a { content: string } shape for posting messages
req.write(
JSON.stringify({
content: result,
})
);
req.end();
}
});
},
null,
true,
// Replace with your time zone
// https://gist.github.com/diogocapela/12c6617fc87607d11fd62d2a4f42b02a
"America/Toronto"
);
// Begin the cronjob
job.start();
假设您正确设置了 webhook 机器人,您将看到它每次在 cron 作业启动时在您的 Discord 频道上发布一条包含备份状态的消息。
你可以使用这种方法方便地查看日志输出,而无需登录服务器并手动检查。如果出现错误,Discord 机器人会打印错误消息。例如,如果我将源更改为不存在的文件夹:
我可以查找状态代码以获取有关该问题的更多信息。
因此,我们现在已经处理了该项目所需的所有主要功能!
- 创建一个目录到另一个目录的备份
- 支持按计划时间备份
- 跨平台支持
- 成功/错误的沟通(通过 PM2 日志或 Discord)
总结
希望你从本教程中学到一些新东西。我知道它有点杂乱无章,但这正是我的想法的核心所在。我最喜欢的学习新事物的方式之一,就是将现有的技能和工具以有趣的方式联系起来。
请查看我的其他学习教程。如果您觉得其中有任何内容有用,请随时发表评论或提出问题并与他人分享:
要获取更多类似教程,请在 Twitter 上关注我@eagleson_alex。
文章来源:https://dev.to/alexeagleson/how-to-use-nodejs-to-backup-your-personal-files-and-learn-some-webdev-skills-along-the-way-541a