威联通NAS+Tailscale组建虚拟局域网

背景 家里有一台威联通NAS,有时在公司想上NAS上下载电影电视剧,所以需要远程访问NAS。对比frp+阿里云服务器进行内网穿透和Tailscale局域网虚拟组网方案,最终选择Tailscale局域网虚拟组网方案。 方案对比 对比下来frp+阿里云服务器适合,将本地的一些网站或者服务暴露外网的需求。而Tailscale局域网虚拟组网则适合只是需要像本地局域网一样进行远程访问的需求,同时由于不对外提供服务,Tailscale方案有较高的安全性。 Tailscale方案安装好之后几乎不需要进行什么配置,而frp则需要修改配置文件等。 Tailscale方案点对点隧道打通之后延迟7-15ms左右非常低,远程查看或者下载比较大的文件带宽只受两边宽带的影响,速度比较快。如果是frp经云服务器中转的方案则带宽还受到云服务器的限制。

2025年02月19日 · 1 min · 6 words · 姜红杰

打包构建威联通App

背景 由于威联通官方APP市场中可用的App数量比较少,有时需要自己进行打包安装。 这里要说明几点: 打包需要在威联通NAS上进行,不能通过自己的电脑进行。 需要能通过ssh连接上NAS服务器,下面称为命令行后台。 需要能登录威联通NAS UI页面后台,下面称为页面后台。 整个过程主要分为4个步骤: 下载安装QDK 创建要打包的App环境 配置要打包的App 打包 下载安装QDK 下载链接:http://wiki.qnap.com/wiki/QPKG_Development_Guidelines#Downloads 文件下载到本地,通过页面后台手动安装。 创建要打包的App环境 进入命令行后台 # 进入QDK的安装目录 cd `getcfg QDK Install_Path -f /etc/config/qpkg.conf` # 创建你要打包的APP,最后一个参数是APP的名字 qbuild --create-env MyQPKG # 进入MyQPKG目录 cd MyQPKG/ 配置要打包的App 在MyQPKG/目录下,修改qpkg.cfg文件的内容 # 打开配置文件 vim qpkg.cfg # 重点关注下面几个参数 # APP名称 QPKG_NAME="MyQPKG" # APP版本 QPKG_VER="0.1" # APP启动和停止时执行的脚本 QPKG_SERVICE_PROGRAM="MyQPKG.sh" 在MyQPKG/shared目录下修改启动和停止脚本,脚本文件名是上面配置的也就是MyQPKG.sh,这里如果脚本不存在则创建一个。原理就是APP启动时通过脚本把服务启动,停止时把服务杀死。脚本内容如下 #!/bin/sh CONF=/etc/config/qpkg.conf QPKG_NAME="MyQPKG" QPKG_ROOT=`/sbin/getcfg $QPKG_NAME Install_Path -f ${CONF}` APACHE_ROOT=`/sbin/getcfg SHARE_DEF defWeb -d Qweb -f /etc/config/def_share.info` export QNAP_QPKG=$QPKG_NAME case "$1" in start) ENABLED=$(/sbin/getcfg $QPKG_NAME Enable -u -d FALSE -f $CONF) if [ "$ENABLED" != "TRUE" ]; then echo "$QPKG_NAME is disabled." exit 1 fi # 这里时APP启动时执行的命令 /bin/chmod -Rf 777 $QPKG_ROOT/* cd $QPKG_ROOT ./MyQPKG 2>&1 & disown ;; stop) # 这里时APP停止时执行的命令 killall -9 MyQPKG ;; restart) $0 stop $0 start ;; *) echo "Usage: $0 {start|stop|restart}" exit 1 esac exit 0 打包 将依赖文件放入对应的目录,shared/ 中存放平台无关的公共文件,arm-x09/ arm-x19/ arm-x31/ arm-x41(TS-x31+)/ x86/ x86_ce53xx/ x86_64/ 中存放对应平台下的可执行文件等。 比如App可执行文件是打包好的x86_64平台的MyQPKG二进制可执行文件,就把MyQPKG这个文件放到MyQPKG/x86_64/目录下面。 ...

2025年02月05日 · 1 min · 150 words · 姜红杰

MacOS访达设置文件夹中的文件自动排列

背景 默认情况下访达中的文件不会自动排列,拖入的时候放在哪个位置就是哪个位置。 修改设置 打开访达 点击左上角显示按钮 在弹出的下拉选项中选择查看显示选项 按照自己的需求点选排序方式中的选项,我使用的是名称 点击最下面的用作默认

2024年12月24日 · 1 min · 8 words · 姜红杰

威联通NAS安装配置AList+Aria2并实现NAS下载网盘中的文件到本地

背景 使用威联通远程下载各种网盘中的文件,主要分为三步: 安装AList,挂载网盘。 安装Aria2,并配置RPC。 在AList中配置Aria2的RPC链接,实现下载。 建议配合Tailscale局域网虚拟组网使用,实现公司等任何地点都可以下载资源。 下载AList威联通手动安装包 https://github.com/iranee/qnap-alist-webdav/releases 根据自己的机器选择对应的文件。 x86架构:alist_3.38.0_x86_64.qpkg arm架构:alist_3.38.0_arm_64.qpkg 如果网络不行可以下载我整理好的文件,夸克链接:https://pan.quark.cn/s/9130f0e533d2 手动安装 进入威联通管理后台 打开AppCenter 右上角手动安装按钮 点击左边的浏览按钮选择刚才下载的的文件 点击右边的安装按钮 等待安装完成 去桌面点击AList图标即可跳转打开 修改密码 Alist的默认账号密码是:账号:admin 密码:123456 进入系统先修改密码,如果忘记了密码可以通过ssh连接威联通 # 进入AList安装目录 cd /share/CACHEDEV1_DATA/.qpkg/alist # 查看管理员信息,低于v3.25.0版本可直接查看密码 ./alist admin # 高于v3.25.0版本 # 随机生成一个密码 ./alist admin random # 手动设置一个密码,`NEW_PASSWORD`是指你需要设置的密码 ./alist admin set NEW_PASSWORD 配置下载工具 安装Aria2 下载Aria2威联通安装包 https://www.myqnap.org/product/aria2/ 夸克链接:https://pan.quark.cn/s/2a392c41cc01 手动安装Aria2 威联通控制台->网络访问->应用程序->Web服务器->启用(参数不用改) 配置Aria2 通过ssh或者其他软件登录威联通服务器(不是页面那个后台),进入威联通后台点击Aria2即可进入AriaNg页面 进入AriaNg页面->Aria2设置->基本设置->下载路径,修改为自己的存储位置,否则默认文件会下载到/share/CACHEDEV1_DATA/.qpkg/AriaII这个安装目录 修改aria2.conf配置文件 # 打开配置文件 vim /share/CACHEDEV1_DATA/.qpkg/AriaII/aria2.conf # rpc-secret这个key对应的值就是Aria2 RPC密钥,复制出来一会儿要填入AriaNg设置->RPC设置 # 将下面这行添加进去,这个很关键,因为AList调用Aria2需要请求http协议的Aria2 RPC,不加这个参数会报跨域请求错误 rpc-allow-origin-all=true 威联通后台AppCenter中找到Aria2,先停止在打开重启一下Aria2 进入AriaNg页面->AriaNg设置->添加RPC,地址就填威联通的局域网地址,端口默认6800,路径默认jsonrpc,密钥填刚才复制出来的rpc-secret,配置完Aria2状态应该会显示已连接 配置AList 参考AList官网文档挂载网盘 AList网盘页面右下角点击本地设置,填入Aria2 RPC 链接,类似这种格式http://127.0.0.1:6800/jsonrpc,地址改成威联通的内网地址。 下载文件到NAS本地 找到网盘中要下载到本地的文件,选中之后页面底部会弹出来一排按钮,点击下载,选择发送到Aria2,就可以自动下载了。 原文地址 威联通NAS安装配置AList并实现NAS下载网盘中的文件到本地 ...

2024年12月24日 · 1 min · 78 words · 姜红杰

网络编程TCP socket简单demo

通过该简单的demo学习socket网络编程,注释会详细解释每一行的作用,并且代码可以编译运行。 server.c #include <sys/types.h> /* basic system data types */ #include <sys/socket.h> /* basic socket definitions */ #include <sys/time.h> /* timeval{} for select() */ #include <time.h> /* timespec{} for pselect() */ #include <netinet/in.h> /* sockaddr_in{} and other Internet defns */ #include <arpa/inet.h> /* inet(3) functions */ #include <errno.h> #include <fcntl.h> /* for nonblocking */ #include <netdb.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> /* for S_xxx file mode constants */ #include <sys/uio.h> /* for iovec{} and readv/writev */ #include <unistd.h> #include <sys/wait.h> #include <sys/un.h> /* for Unix domain sockets */ #include <sys/select.h> /* for convenience */ #include <sys/sysctl.h> #include <poll.h> /* for convenience */ #include <strings.h> /* for convenience */ #include <sys/ioctl.h> #include <pthread.h> size_t readn(int fd, void *buffer, size_t size) { char *buffer_pointer = buffer; int length = size; while (length > 0) { int result = read(fd, buffer_pointer, length); if (result < 0) { if (errno == EINTR) continue; /* 考虑非阻塞的情况,这里需要再次调用read */ else return (-1); } else if (result == 0) break; /* EOF(End of File)表示套接字关闭 */ length -= result; buffer_pointer += result; } return (size - length); /* 返回的是实际读取的字节数*/ } void read_data(int sockfd) { ssize_t n; char buf[1024]; int time = 0; for (;;) { fprintf(stdout, "block in read\n"); if ((n = readn(sockfd, buf, 1024)) == 0) return; time++; fprintf(stdout, "1K read for %d \n", time); // 接收设置延迟时间,观察client发送数据和server接收数据,client将数据写入到缓冲区之后就会立即返回成功 sleep(5); } } int main(int argc, char **argv) { int listenfd, connfd; socklen_t clilen; // 声明地址信息结构体 struct sockaddr_in cliaddr, servaddr; // 创建套接字 listenfd = socket(AF_INET, SOCK_STREAM, 0); // 将指定内存区域的前 n 个字节全部设置为零,用于初始化内存区域 bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; // INADDR_ANY IPv4通配地址 IN6ADDR_ANY IPv6通配地址 servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 设置端口号,设置为0时由操作系统选择一个空闲的端口 // htons函数:将一个无符号短整型(通常是16位)的主机数值转换为网络字节顺序 servaddr.sin_port = htons(12345); // bind到本地地址,端口为12345 // bind函数套接字和地址关联 bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)); // 初始化创建的套接字,可以认为是一个"主动"套接字,其目的是之后主动发起请求 // 通过 listen 函数,可以将原来的"主动"套接字转换为"被动"套接字 listen(listenfd, 1024); /* 循环处理用户请求 */ for (;;) { clilen = sizeof(cliaddr); // 客户端发起连接请求,accept函数接受请求建立连接 // 第一个参数listenfd是用于监听的套接字,返回的是新建立连接的套接字 connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen); read_data(connfd); /* 读取数据 */ close(connfd); /* 关闭连接套接字,注意不是监听套接字*/ } } client.c #include <sys/types.h> /* basic system data types */ #include <sys/socket.h> /* basic socket definitions */ #include <sys/time.h> /* timeval{} for select() */ #include <time.h> /* timespec{} for pselect() */ #include <netinet/in.h> /* sockaddr_in{} and other Internet defns */ #include <arpa/inet.h> /* inet(3) functions */ #include <errno.h> #include <fcntl.h> /* for nonblocking */ #include <netdb.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> /* for S_xxx file mode constants */ #include <sys/uio.h> /* for iovec{} and readv/writev */ #include <unistd.h> #include <sys/wait.h> #include <sys/un.h> /* for Unix domain sockets */ #include <sys/select.h> /* for convenience */ #include <sys/sysctl.h> #include <poll.h> /* for convenience */ #include <strings.h> /* for convenience */ #include <sys/ioctl.h> #include <pthread.h> #include <stdarg.h> /* ANSI C header file */ #include <syslog.h> /* for syslog() */ # define MESSAGE_SIZE 102400 void error(int status, int err, char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); if (err) fprintf(stderr, ": %s (%d)\n", strerror(err), err); if (status) exit(status); } void send_data(int sockfd) { char *query; query = malloc(MESSAGE_SIZE + 1); for (int i = 0; i < MESSAGE_SIZE; i++) { query[i] = 'a'; } query[MESSAGE_SIZE] = '\0'; const char *cp; cp = query; size_t remaining = strlen(query); while (remaining) { int n_written = send(sockfd, cp, remaining, 0); fprintf(stdout, "send into buffer %d \n", n_written); if (n_written <= 0) { error(1, errno, "send failed"); return; } remaining -= n_written; cp += n_written; } return; } int main(int argc, char **argv) { int sockfd; struct sockaddr_in servaddr; if (argc != 2) error(1, 0, "usage: tcpclient <IPaddress>"); sockfd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(12345); inet_pton(AF_INET, argv[1], &servaddr.sin_addr); int connect_rt = connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)); if (connect_rt < 0) { error(1, errno, "connect failed "); } send_data(sockfd); exit(0); } 对于 send 来说,返回成功仅仅表示数据写到发送缓冲区成功,并不表示对端已经成功收到。 对于 read 来说,需要循环读取数据,并且需要考虑 EOF 等异常条件。 ...

2024年12月12日 · 3 min · 628 words · 姜红杰