2025-08-30 19:31:32
早期用过群晖的NAS,有个很好的功能是自带SD读卡器,可以直接插卡,然后按一下按键,就能自动把SD卡上的文件复制到NAS的硬盘上。群晖上有个USB Copy的套件,也可以实现自动插卡后就复制卡上的文件。
前一段自己组了个NAS,用了飞牛系统,飞牛的备份策略还是比较弱,虽然能备份外置USB读卡器或者外置USB硬盘的文件,但没有自动插卡后执行备份的功能,必须手工操作,于是就打算结合AI编程,自己写个脚本实现这个功能。
飞牛还有很多NAS系统的底层实际上都是Linux系统,所以基本上方法是类似的。
其实核心是利用了Udev,它可以管理挂载的设备,相关说明:https://www.reactivated.net/writing_udev_rules.html
通过它,实现插入USB设备后,能自动执行脚本。
实现起来也很简单,就3步
为什么要把脚本变成Service而不是直接执行脚本,是因为有个坑是Udev调用脚本有个默认超时时间,而如果复制文件的时间较长,超时了,脚本就会自动中断,这个也是我一开始测试的时候遇到的坑,AI编程工具没有意识到这个问题,而是遇到自动退出问题后反复的增加了各种监控和测试脚本。看来AI也不是万能的。
文件复制脚本其实内容可以很简单,插上SD卡以后,看一下SD卡自动挂载的路径就可以了,飞牛的话,是会固定挂载在 /vol00/MassStorageClass
目录下,每次都一样,当然,如果你的NAS同时插了好几个USB设备比如U盘、读卡器、USB硬盘之类的,那么可能路径会不一样,自己用SSH登录飞牛系统df命令看一下就可以了。
而目标路径,一般飞牛的话第一个储存空间是/vol1/1000/
,第二个就是/vol2/1000/
,以此类推,看你想把SD卡的文件复制到哪个就选具体的路径就可以了。
脚本里可以用cp命令直接复制 比如 cp /vol00/MassStorageClass/* /vol1/1000/backup/
,也可以用rsync命令 rsync -av /vol00/MassStorageClass/ /vol1/1000/backup/
,rsync命令还可以加 --exclude-from
参数,后面跟一个文本文件,里面写上你希望排除不复制的文件,比如 .* 表示不复制 .开头的隐藏文件等等。
当然,还可以加各种功能比如统计复制时间,统计复制了多少个文件,复制完成以后删除卡上的文件等功能,建议再结合 Server酱或者pushover、gotify之类的工具,在复制完成后发一个推送消息到手机,这样就能及时知道复制完了。以上功能都可以找一个AI编程工具,说清楚需求自动生成即可。
比如可以用类似如下的提示词(等待5秒是怕刚插入SD卡时还没完成挂载就运行复制命令,会报错):
写一个Linux下的BASH脚本,脚本能够使用rsync命令将/vol00/MassStorageClass/下的文件均复制到 /vol1/1000/backup/,需要在复制前检查源目录是否存在,不存在的话等待5秒重试。需要检查目标目录空间是否足够。统计复制了多少个文件及耗时,复制成功后将源目录下的文件均删除。
以上提示词可以根据需要修改。
这一步比较常规,就是在 /etc/systemd/system/
目录下创建一个Service文件,比如usbcopy.service 内容类似如下:
[Unit]
Description=功能描述
[Service]
ExecStart=/usr/bin/bash /root/usbcopy.sh (替换为你脚本的路径)
Restart=on-failure
[Install]
WantedBy=multi-user.target
然后执行 systemctl start usbcopy 试一下能否成功运行,可以的话就OK了。
这一步也比较简单,就是在 /etc/udev/rules.d/
目录下创建一个规则文件,比如usbcopy.rule ,写入类似如下内容:
ACTION=="add", SUBSYSTEM=="block", KERNEL=="sd[a-z][0-9]*", ENV{ID_BUS}=="usb", ENV{ID_FS_TYPE}!="", RUN+="/bin/systemctl start usbcopy.service"
这里我是匹配了所有设备
KERNEL=="sd[a-z][0-9]*"
当然你可以根据需要直接写你机器上SD卡对应的设备,比如sda 就是 KERNEL=="sda"
这个通过df命令也可以直接看到。
其实意思就是当机器上增加了sda设备后,执行 /bin/systemctl start usbcopy.service
也就是刚才增加的Systemd服务。
添加后,刷新一下规则
sudo udevadm control --reload-rules
sudo udevadm trigger
然后插个SD卡测试一下就可以了。
如果脚本加入了删除文件的功能,记得多测试几次后再正式使用,避免复制没成功又把有用的文件删除了。
以下示例代码仅供参考,实现从/vol00/MassStorageClass
到/vol2/1000/photos/sdbackup
的文件复制,在/home/szqp/autocopy/exclude-list.txt
中写入了排除不复制的文件列表。统计复制文件数量和时间,完成后删除源目录下文件,并通过Server酱发送提醒信息。
因为基本是AI生成的,虽然在我的NAS上能用,但不代表你的也可以,要自行修改相关配置,不能直接运行:
#!/bin/bash
# USB自动复制脚本
# 功能:从固定USB挂载点复制文件到指定目录
# 日志文件
LOG_FILE="/var/log/usb_autocopy.log"
SOURCE_DIR="/vol00/MassStorageClass"
TARGET_DIR="/vol2/1000/photos/sdbackup"
# 信息发送函数
sc_send() {
local text=$1
local desp=$2
local key="你Server酱的key"
postdata="text=$text&desp=$desp"
opts=(
"--header" "Content-type: application/x-www-form-urlencoded"
"--data" "$postdata"
)
# 判断 key 是否以 "sctp" 开头,选择不同的 URL
if [[ "$key" =~ ^sctp([0-9]+)t ]]; then
# 使用正则表达式提取数字部分
num=${BASH_REMATCH[1]}
url="https://${num}.push.ft07.com/send/${key}.send"
else
url="https://sctapi.ftqq.com/${key}.send"
fi
# 使用动态生成的 url 发送请求
result=$(curl -X POST -s -o /dev/null -w "%{http_code}" "$url" "${opts[@]}")
echo "$result"
}
# 记录日志函数
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
}
# 检查USB挂载点是否存在
check_usb_mount() {
# 等待挂载点出现,最多等待10秒
for i in {1..10}; do
if [ -d "$SOURCE_DIR" ] && [ "$(ls -A "$SOURCE_DIR" 2>/dev/null)" ]; then
log_message "发现USB挂载点: $SOURCE_DIR (第${i}次检查)"
return 0
fi
if [ $i -eq 1 ]; then
log_message "等待USB挂载点出现: $SOURCE_DIR"
fi
sleep 1
done
log_message "USB挂载点不存在或为空: $SOURCE_DIR"
return 1
}
# 检查USB挂载点
log_message "检查USB挂载点..."
if ! check_usb_mount; then
log_message "错误: USB挂载点不可用或为空"
exit 1
fi
# 使用固定的挂载点
MOUNT_POINT="$SOURCE_DIR"
log_message "使用USB挂载点: $MOUNT_POINT"
# 验证挂载点的可访问性
if [ ! -r "$MOUNT_POINT" ]; then
log_message "错误: 挂载点 $MOUNT_POINT 不可读"
exit 1
fi
# 创建目标目录(如果不存在)
if [ ! -d "$TARGET_DIR" ]; then
mkdir -p "$TARGET_DIR"
if [ $? -ne 0 ]; then
log_message "错误: 无法创建目标目录 $TARGET_DIR"
exit 1
fi
log_message "创建目标目录: $TARGET_DIR"
fi
# 生成唯一的子目录名(基于时间戳)
TIMESTAMP=$(date '+%Y%m%d_%H%M%S')
COPY_DIR="$TARGET_DIR/usb_backup_${TIMESTAMP}"
# 创建复制目录
mkdir -p "$COPY_DIR"
if [ $? -ne 0 ]; then
log_message "错误: 无法创建复制目录 $COPY_DIR"
exit 1
fi
log_message "开始复制文件到: $COPY_DIR"
# 检查源目录是否为空
if [ -z "$(ls -A "$MOUNT_POINT" 2>/dev/null)" ]; then
log_message "警告: USB设备为空,没有文件需要复制"
COPY_RESULT=0
SOURCE_FILE_COUNT=0
else
# 计算源文件数量和大小
SOURCE_FILE_COUNT=$(find "$MOUNT_POINT" -type f 2>/dev/null | wc -l)
SOURCE_SIZE=$(du -sb "$MOUNT_POINT" 2>/dev/null | cut -f1)
log_message "检测到 $SOURCE_FILE_COUNT 个文件,总大小: $(echo $SOURCE_SIZE | awk '{printf "%.2f MB", $1/1024/1024}')"
# 检查目标目录可用空间
AVAILABLE_SPACE=$(df "$TARGET_DIR" | tail -1 | awk '{print $4}')
AVAILABLE_BYTES=$((AVAILABLE_SPACE * 1024))
if [ "$SOURCE_SIZE" -gt "$AVAILABLE_BYTES" ]; then
ERROR_MSG="磁盘空间不足。需要: $(echo $SOURCE_SIZE | awk '{printf "%.2f MB", $1/1024/1024}'),可用: $(echo $AVAILABLE_BYTES | awk '{printf "%.2f MB", $1/1024/1024}')"
log_message "错误: $ERROR_MSG"
sc_send "USB文件复制失败" "$ERROR_MSG"
rm -rf "$COPY_DIR"
exit 1
fi
# 记录复制开始时间
COPY_START_TIME=$(date +%s)
# 使用rsync进行文件复制
if command -v rsync >/dev/null 2>&1; then
log_message "使用rsync进行文件复制"
rsync -av --exclude-from='/home/szqp/autocopy/exclude-list.txt' "$MOUNT_POINT/" "$COPY_DIR/" 2>&1
COPY_RESULT=$?
COPY_METHOD="rsync"
else
log_message "使用cp进行文件复制"
cp -r "$MOUNT_POINT/"* "$COPY_DIR/" 2>&1
COPY_RESULT=$?
COPY_METHOD="cp"
fi
# 计算复制耗时
COPY_END_TIME=$(date +%s)
COPY_DURATION=$((COPY_END_TIME - COPY_START_TIME))
fi
if [ $COPY_RESULT -eq 0 ]; then
log_message "文件复制成功完成"
# 发送成功通知
if [ $SOURCE_FILE_COUNT -gt 0 ]; then
# 格式化复制时间
if [ $COPY_DURATION -ge 60 ]; then
DURATION_TEXT="${COPY_DURATION}秒 ($(($COPY_DURATION / 60))分$(($COPY_DURATION % 60))秒)"
else
DURATION_TEXT="${COPY_DURATION}秒"
fi
SUCCESS_MSG="成功复制 $SOURCE_FILE_COUNT 个文件,耗时: $DURATION_TEXT。总大小: $(echo $SOURCE_SIZE | awk '{printf "%.2f MB", $1/1024/1024}')。目标目录: $COPY_DIR"
log_message "复制了 $SOURCE_FILE_COUNT 个文件,耗时 $DURATION_TEXT"
# 删除USB设备上的源文件
log_message "开始删除USB设备上的源文件..."
if rm -rf "$MOUNT_POINT/"* 2>/dev/null; then
log_message "成功删除USB设备上的源文件"
sc_send "USB文件复制成功,清理完成" "$SUCCESS_MSG 。文件清理完成"
else
ERROR_MSG="删除USB设备上的源文件失败"
log_message "错误: $ERROR_MSG"
sc_send "USB文件复制成功,清理失败" "$SUCCESS_MSG 。错误:$ERROR_MSG"
fi
else
sc_send "USB设备为空" "USB设备中没有文件需要复制"
fi
else
ERROR_MSG="文件复制失败,错误代码: $COPY_RESULT"
log_message "$ERROR_MSG"
sc_send "USB文件复制失败" "$ERROR_MSG。复制方法: $COPY_METHOD。源目录: $MOUNT_POINT。目标目录: $COPY_DIR"
fi
log_message "脚本执行完成"
exit 0
© 2025, QP. 版权所有.
2025-06-23 20:11:12
这两年最火的可能就是AI了,从ChatGPT开始,各种AI应用层出不穷。趁着周末的时间,我也试了一下用AI编程。
我原来以为的AI编程还是让AI回答一些编程中的问题,或者给一些参考示例代码,结果没想到现在的AI编程已经进化到了另一个层次了。
我试用的是字节出的Trae,分国内版和国外版,主要区别是能使用的大模型的不同,以及国内版目前好像还没开始收费。我试了一下Claude、Gemini、国内的豆包还有deekseek这几个模型,总的来说Claude的效果最好。
现在的AI编程,你只需要输入需求,就能自动生成完整可用的全部代码并且可以执行,我已经很久没碰Python了,Flet框架更是以前没用过,但花了几个小时就做出来了 机柜图生成器 这么个小工具,而整个过程中我只做了非常少的修改,99%的代码都是靠AI自动生成的。
而且AI对需求的理解力也很强。很多时候我不需要说的特别精确或者特别详细,AI就会自动补充完整。比如我只是说我要增加一个能输入机柜高度的选项,AI就会自动在页面上生成机柜高度的输入框,提示文字,并把页面上输入内容和程序处理逻辑中关于机柜高度的部分关联。
当代码出现问题时,AI还能自动读取输出结果和报错信息,去分析错误原因并自动修改。很多时候代码产生了错误,让AI自己纠正就可以了。
当然,AI还并不是完美的,也会出现一些比较傻的错误,比如把变量的大小写弄错,或者弄错了代码的缩进格式等等,但基本上是非常可用的了。整体能力我觉得替代一个初级的程序员应该完全没问题。
以后可能遇到问题,真的不需要找一个工具去解决,而是可以自己做一个工具去解决了。
© 2025, QP. 版权所有.
2025-02-26 19:54:52
今年偷了个懒,没有去做年终总结,不过趁有时间,把手上的摄影器材做了个初步的整理。
增加了个统计展示页面
虽然我是个器材党,不过不算不知道自己买了这么多器材,虽然有些已经出了,但大部分还在手上,特别是去年喜欢上了胶卷相机,收了不少老胶卷相机。可惜胶卷没拍多少,攒了3卷冲洗了一次,还大部分翻车了。
为了统计器材,还试用了一下各类线上的多维表服务。老牌的Airtable功能还是最强的,可惜很多功能要付费,国内的很多一般用也没问题,但要共享视图嵌入到网站很多都不行或者效果不好,最后还是用了Teable,功能不多但够用,而且视图嵌入网站的效果比较简洁。
以后试试写写我用过的这些摄影器材吧。
© 2025, QP. 版权所有.
2024-07-09 13:50:45
家里有个Bose Soundlink Mini 2的蓝牙音箱,之前有一段时间没用,结果就无法充电了。接电源,电源灯一直红灯闪烁,也无法开机。
一开始以为是电池过放坏了,看了一下淘宝也有电池卖,不贵,但是要自己焊接线,有点麻烦。
无意中网上搜了一下,居然找到了个解决方法,不用换电池。
用数据线把音箱接电脑上,注意要用能传输数据的MicroUSB线,有些MicroUSB线只能充电不能传输数据是不行的。
接好后,去Bose的网站升级音箱的固件:
如果提示已经是最新的固件了,顺序按“A、D、V、上键、下键” 这五个键,就会出现可以刷新固件了。
刷新到75%左右的时候,应该已经可以听到音箱的提示音了,然后电源灯也会变成长亮的红灯。等待固件刷新完成。
刷新完成后记得不能马上拔掉数据线,要让音箱继续插在电脑上冲一会儿电。接电脑充电比较慢,可能需要半个小时以上,电源灯变成黄色长亮,就基本说明修复成功了。之后可以接充电头充电,这样比较快。音箱也可以正常开机使用了。
© 2024, QP. 版权所有.
2024-05-27 08:13:51
因公到台北出差,上一次还是2013年自由行来台湾玩了一圈,在这样一个时期,还是比较难得的。
记录一下见闻。
1 申请的商务签注。台湾那边申请入台证比较快,大陆这边申请赴台签注比较慢,要先准备很多材料到当地的台湾办公室申请,大概经过一个多月会拿到赴台批复,然后凭借这个批复去出入境那边申请台湾通行证和赴台签注。
2 给台湾办公室的材料里除了邀请函和公司简介,还得列明赴台的日程表,批复里会根据行程表写明允许你赴台的时间。不过真正的赴台签注又不会规定的这么具体,只有三个月一次,一年多次等几种类型,还不清楚如果没有按批复里写赴台时间去了会怎么样。
3 10年后再到台湾,感觉物价变化不大,日常吃饭和购物的价格感觉跟大陆一线城市差不多,甚至还更便宜一点。比如写字楼密集的地方,中午一份快餐基本在100台币左右,也就是20多人民币。奶茶大概六七十到100台币左右一杯。热炒店,类似大陆的大排档,一个菜大概300到500台币左右,比大陆要贵。
4 街上有共享单车,但没有大陆共享单车密集,更类似以前政府运营那种单车,有固定的桩,取车还车都得到固定桩,有app可以查固定桩的位置,也可以扫码或者刷卡借车。
5 公交车和捷运,10年前来的时候,基本上博爱座一般人是不坐的,经常捷运上满人博爱座也空着。但这次来,好像也是经常有年轻人会坐博爱座了。
6 实体店和夜市生意看起来不错,游客挺多日本人和韩国人。欧美游客好像不是特别多。
7 有外卖,Uber eat和foodpanda,据说后者还要被前者收购了。但我注册Uber eat一直不成功,刚注册完账号就被锁定,foodpanda可以使用,点外卖感觉送的比大陆还快一些,但选择不多,很多餐厅还是不支持网上点餐。另外就是除了夜市之外,很多餐厅会比较早关门。
8 加班不严重,大部分人5点半到6点左右就下班了。我去的地方科技公司比较多,鸿海,英伟达,亚马逊,英飞凌之类的都在附近,晚上也看不到很多加班的人,基本上7点以后就都走光,街上没什么人了。
9 庙很多,不少还在闹市区,很多人路过庙门口也会拜一拜。
10 本地人特别喜欢排队凑热闹,不管卖什么的,都喜欢凑过去排队,好像不排队买一个就吃亏了。夜市特别明显,有些摊位的队伍会长达几百米。
11 支付宝和微信有一些连锁超市和便利店可以用,台北故宫的文创店也能用,但总体上能用的地方还是不多。本地人用apple pay,line pay,街口支付什么的,用悠游卡和信用卡的也多,现金也占很大一部分,很多小店和摊贩基本只能用现金。
12 网上购物也有,但比如虾皮,街上能看到一些虾皮的自提点,但总得来说没有像在大陆一样那么夸张的网上购物比例,大部分还是通过线下买。
13 公交捷运主要是悠游卡之类的实体卡,没留意能不能扫码,好像没见到有人扫码进捷运站或者坐公交。
公交上车刷一次卡,下车刷一次卡。在站台等车需要招手,不然公交车不会停,下车也是要按铃,不然公交车到站也不会停。
14 还没去吃什么特别高级的餐厅,就普通餐厅和夜市来说,也还好,口味跟福建差不多,但有些食物会跟大陆不太一样,比如很多地方夜市都有的臭豆腐,炸的和汤的都有,也不辣,汤的臭豆腐更类似于大陆的卤豆腐。还有麻婆豆腐也是完全不辣,类似于肉沫豆腐的做法。
另外就是大陆很火的烤串,台湾比较少,吃过一次也不太好吃,可能适合当地人的口味吧。
15 台北的建筑,高楼大厦不如大陆一线城市多,10层左右的楼挺多的。建筑整体比较偏老旧,可能老城市都差不多吧,类似大陆很多城市的老城,不过倒是比较干净,这方面感觉台北有点像日本的城市,当然发达程度台北比东京大阪还是差不少。
16 台北对宠物还挺宽容的,狗狗是能上捷运的,只要放在类似婴儿车的宠物车里推进站就可以。
17 台湾的发票可以抽奖,所以绝大部分地方买东西都会打印发票给你。你也可以选择提供“载具”,也就是一个电子发票账号,会给你开电子发票存进载具,自动兑奖。
更新一些图片,近期都是台北的雨季,经常下雨,而且我也没去什么特别的景点,大家凑合看吧。
© 2024, QP. 版权所有.
2024-04-26 10:27:12
以前其实比较少用Youtube和Bilibili,觉得手机上看视频并不太舒服。后来试了试在电视上看,意外发现很完美,特别是YouTube的4k,很不错。有很多评测、旅拍之类的视频,在手机上看没什么感觉,在电视上看还挺惊艳的。
YouTube推荐使用Smarttube这个APP,非常适合在电视上使用,能同步账号收藏,还能去广告。
Bilibili推荐使用BBLL,而不是官方的APP,也很方便,不过不开视频不能播放4k。
xiaye13579/BBLL: 一个第三方哔哩哔哩客户端,A third-party bilibili client。 (github.com)
另外,如果想看电视台直播节目,可以试试myTV,自带直播源,没有广告,速度也很快,就是得经常更新。
lizongying/my-tv: 我的电视 电视直播软件,安装即可使用 (github.com)
© 2024, QP. 版权所有.