分类: 杂项

Armbian安装qbittorrent下载服务

安装qbittorrent

apt install qbittorrent-nox

添加到系统服务
vim /etc/systemd/system/qbittorrent-nox.service

[Unit]
Description=qBittorrent-nox
After=network.target

[Service]
User=root
Type=forking
RemainAfterExit=yes
ExecStart=/usr/bin/qbittorrent-nox -d

[Install]
WantedBy=multi-user.target

开机自启systemctl enable qbittorrent-nox.service
启动服务systemctl start qbittorrent-nox.service

默认web访问端口8080,用户名admin,密码adminadmin

Armbian自动挂载硬盘

查看系统所检测到的磁盘,这里的 sda1检测到的硬盘但是没有被挂载(注意:这里sda1 是’1’ 而不是’L’,有些可能是sdb1

lsblk         //查看信息

在根目录新建一个目录用于挂载硬盘,命令如下:

cd /.               //进入根目录
mkdir mnts            //新建目录名为‘mnts’可用'ls'查看

挂载新增的磁盘sda1(所有新增硬盘都在/dev/目录下)

mount /dev/sda1 /mnts/        //挂载到mnts
cd /mnts/                //进入挂载的硬盘 'ls'查看内容

开机自动挂载:

这条命令可以显示硬盘信息,并记下UUID,为下一步做准备,这里以sda1为例

blkid /dev/sda1

修改 /etc/fstab 即可。例如我就是在 fstab 最后添加这行:

UUID=722059EC2059B835   /mnts      ntfs    defaults        0 0
vi /etc/fstab         //修改fstab

最后保存并应用, 则成功自定挂载,开机也会自动挂载(注意:这里只对只一个硬盘有效)

mount -a             //应用并启动

斐讯N1盒子安装 Home Assistant Supervised(官方支持版本)

感谢以下链接教程

本文说明

本文主要是基于以上链接进行整理实操记录一下过程,希望有网友会用到吧
本文所使用的设备为PDD购买扩容64G版本的N1盒子
U盘使用大于8G的U盘
网络环境最好是 kexue 环境下
本文所使用的系统版本
https://github.com/ophub/amlogic-s9xxx-armbian/releases
文件名 Armbian_22.08.0_Aml_s905d_bullseye_5.10.134_server_2022.07.30.img.gz

Supervisor:

(中文=管理员)就是以前的HassIO/Hass.io,是用来管理和更新Home Assistant Core,管理操作系统,管理docker(HA和加载项),以及管理前三者之前的API和互动,它自己在docker容器里面,并且管理着其他容器。

Home Assistant Core:

这个以前就叫Home Assistant(core=核心)

Home Assistant OS(HAOS):

以前叫HassOS,是官方为树莓派打造的基于Linux的操作系统,包含了Home Assistant core, Supervisor,也就是完整的全套,可以直接安装于或者虚拟机,这是官方推荐安装方法

Home Assistant Supervised:

这个也是全套,跟HAOS的区别是可以装在普通Linux上因此适合更多硬件,N1用的就是这个。安装原理就是手动把docker,Home Assistant Core、Supervisor和其他所有必要组件安装在普通Linux系统上。为了花更多精力提升HA本上而不是debug各种兼容性问题,去年官方大幅减少支持的环境,目前唯一支持的是Debian 11,否则,轻则安装完后显示“不支持的操作系统”,重则无法安装)

操作步骤

1、将镜像写入大于8G的U盘 我这里用的是 balenaEtcher
2、U盘镜像制作完成后 N1断电 插入U盘【要插入HDIM旁边的USB】,连接显示屏,插上键盘,插网线,插上电源,进入系统。重新设置root密码、创建新用户、新用户密码、设置时区、设置语言等。
完事后执行: armbian-install 将系统安装到EMMC 【如果没有升级内存没有测试 据说很大内存会不够用】,等待提示迁移成功就好了 然后 poweroff 关机
3、拔掉U盘显示器键盘等 然后插电重新开机 这时候路由器会重新分配一个IP 然后用电脑ssh这个IP地址 用户名root 密码就是新修改的root密码
4、进入系统后执行以下代码 更新源 安装必要组件 安装docker

sudo -i
apt update && sudo apt upgrade -y && sudo apt autoremove -y
apt --fix-broken install
apt-get install jq curl avahi-daemon apparmor-utils udisks2 libglib2.0-bin network-manager dbus wget -y
curl -fsSL get.docker.com | sh

以上代码执行完毕后重启机器
可以关机后重新拔插电源 或者执行重启命令
poweroff 关机 reboot 重启

安装OS agent

执行以下代码 安装OS agent。Supervisor通过OS agent对接操作系统,官方已经强制要求
所有版本网址在这里
https://github.com/home-assistant/os-agent/releases
N1的架构是aarch64因此选择代码中这个文件

wget https://github.com/home-assistant/os-agent/releases/download/1.2.2/os-agent_1.2.2_linux_aarch64.deb
dpkg -i os-agent_1.2.2_linux_aarch64.deb

以上代码执行完毕后重启机器
可以关机后重新拔插电源 或者执行重启命令
poweroff 关机 reboot 重启

安装Homeassistant Supervised

依次执行以下命令

sudo -i
wget https://github.com/home-assistant/supervised-installer/releases/latest/download/homeassistant-supervised.deb
dpkg -i homeassistant-supervised.deb

过一会儿会进入一个蓝屏让你选择系统架构,选择qemuarm-64
安装完成之后 等着就行了 他会在后台下载很大的docker镜像 最大的1.5G左右 所以N1一定要科学上网环境
否则就会遇到我的问题 一直打不开端口访问不了

安装HACS

HACS(Home Assistant Community Store)即Home Assistant官方的插件商店,提供各种设备集成、前端装饰等的下载,是Home Assistant必备的插件。

1)安装HACS可以通过 https://github.com/hacs/integration/releases/ 下载离线包,解压后将hacs文件夹通过FTP软件拷贝至/usr/share/Hassio/homeassistant/custom_components(没有此路径的话新建一个)。

2)或者在SSH中输入以下命令一键安装。

wget -O - https://get.hacs.xyz | bash -

然后在后台界面选择“配置”-“系统”,右上角点击“重新启动”。
重启后,在“配置”-“设备与服务”中添加集成。
我没有用到蓝牙,如有用到蓝牙的可以参考其他教程处理 我尝试过但是失败哈哈哈哈 或者是我没搞明白怎么用 打算再刷OpenWrt当软路由用了

前端使用Canvas生成海报

最近项目需要涉及到前端海报合成分享功能,前端靠不上只能自己上...
现学现卖,相关链接 :

CSS代码如下

 * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
}

HTML代码如下

<button id="btn" style="height:50px;width:100%;margin:0 auto">点我生成海报</button>
<!--合成后的海报图-->
<img id="out" src="" style="width:100%;border:1px solid black;margin-top:20px;display: none;">

JS代码如下

 CanvasRenderingContext2D.prototype.wrapText = function (text, x, y, maxWidth, lineHeight) {
        if (typeof text != 'string' || typeof x != 'number' || typeof y != 'number') {
            return;
        }
        var context = this;
        var canvas = context.canvas;
        if (typeof maxWidth == 'undefined') {
            maxWidth = (canvas && canvas.width) || 300;
        }
        if (typeof lineHeight == 'undefined') {
            lineHeight = (canvas && parseInt(window.getComputedStyle(canvas).lineHeight)) || parseInt(window.getComputedStyle(document.body).lineHeight);
        }
        // 字符分隔为数组
        var arrText = text.split('');
        var line = '';
        for (var n = 0; n < arrText.length; n++) {
            var testLine = line + arrText[n];
            var metrics = context.measureText(testLine);
            var testWidth = metrics.width;
            if (testWidth > maxWidth && n > 0) {
                context.fillText(line, x, y);
                line = arrText[n];
                y += lineHeight;
            } else {
                line = testLine;
            }
        }
        context.fillText(line, x, y);
    };

    function nickname(x, y) {
        context.fillStyle = 'grey';
        context.font = '32px  SC';
        context.wrapText("iuu 邀请你一起学习", x, y, 700, 60)
    }

    // 课程标题
    function goodsTitle(x, y) {
        context.fillStyle = 'black';
        context.font = '40px  bold';
        context.wrapText("第131讲:如何提升社会科学类核心期刊投稿的命中率", x, y, 700, 60)
    }

    // 描述
    function shopMsg(x, y) {
        context.fillStyle = 'grey';
        context.font = '25px  bold';
        context.wrapText("— 科研写作研究所", x, y, 700, 60)
    }
    // 图片描述
    function imgMsg(x, y) {
        context.fillStyle = 'grey';
        context.font = '24px  SC';
        context.fillText("长按保存图片", 310, 1250)
    }
    // 背景
    function bgImgScaleToFill(img) {
        var scale = Math.max(canvas.width / img.width, canvas.height / img.height);
        var x = (canvas.width / 2) - (img.width / 2) * scale;
        var y = (canvas.height / 2) - (img.height / 2) * scale;
        context.drawImage(img, x, y, img.width * scale, img.height * scale);
    }

    // 商品图
    function goodsImgScaleToFit(img, x, y) {
        var scale = Math.min(canvas.width / img.width, canvas.height / img.height);
        context.drawImage(img, x, y, img.width * scale, img.height * scale);
    }
    // 二维码
    function qrImgScaleToFill(img, x, y) {
        context.drawImage(img, x, y, img.width, img.height);
    }
    // 头像
    function avatarImgScaleToFill(img, x, y, w, h) {
        context.drawImage(img, x, y, w, h);
    }
    var canvas = document.createElement("canvas");
    var context = canvas.getContext("2d");
    window.onload = function () {
        var btn = document.getElementById("btn");
        var QrImgUrl = "http://fenxiao-guogao.wukongkeyan.com/tm_ca6379f807f89f5e96ebbce4c81203f7_20220719155912.png"
        var AvatarImgUrl = "http://wechatavator-1252524126.file.myqcloud.com/appzissbssa6278/image/compress/u_api_61edfdfd1c284_PWGOuVUIpM.png"
        var GoodsImgUrl = "http://wechatapppro-1252524126.file.myqcloud.com/appzissbssa6278/image/b_u_6170d73860ce7_73CZtcAY/l4z0x8yo02hu.jpg"

        btn.onclick = function () {
            var all = {avatarImg: null, goodsImg: null, qrImg: null}

            var QrImg = new Promise(function (resolve, reject) {
                var qrImg = new Image();
                qrImg.crossOrigin = 'anonymous';
                qrImg.src = QrImgUrl
                qrImg.onload = function () {
                    resolve(qrImg);
                }
            })

            var AvatarImgCircle = new Promise(function (resolve, reject) {
                var cs = document.createElement('canvas');
                var ctx = cs.getContext('2d');
                var avatar = new Image();
                avatar.crossOrigin = 'anonymous';
                avatar.src = AvatarImgUrl
                avatar.onload = function () {
                    cs.width = avatar.width;
                    cs.height = avatar.height;
                    var width = avatar.width;
                    var height = avatar.height;
                    var circle = {x: width / 2, y: height / 2, r: width / 2}
                    ctx.clearRect(0, 0, width, height);
                    ctx.save();
                    ctx.beginPath();
                    ctx.arc(circle.x, circle.y, circle.r, 0, Math.PI * 2, false);
                    ctx.clip();
                    ctx.drawImage(avatar, 0, 0);
                    ctx.restore();
                    // var base64 =
                    var avatarImg = new Image()
                    avatarImg.src = cs.toDataURL("image/png");
                    resolve(avatarImg);
                }
            })
            var GoodsImg = new Promise(function (resolve, reject) {
                var goodsImg = new Image();
                goodsImg.crossOrigin = 'anonymous';
                goodsImg.src = GoodsImgUrl
                goodsImg.onload = function () {
                    resolve(goodsImg);
                }
            })
            Promise.all([QrImg, GoodsImg, AvatarImgCircle]).then(res => {
                all.avatarImg = res[2]
                all.goodsImg = res[1]
                all.qrImg = res[0]
                var bgImg = new Image();
                bgImg.crossOrigin = 'anonymous';
                bgImg.src = 'http://commonresource-1252524126.cdn.xiaoeknow.com/image/l5c2twsm0lm1.png';
                bgImg.onload = function () {
                    canvas.width = 750;
                    canvas.height = 1334;
                    bgImgScaleToFill(bgImg)
                    goodsImgScaleToFit(all.goodsImg, 0, 0)
                    qrImgScaleToFill(all.qrImg, 300, 1060)
                    avatarImgScaleToFill(all.avatarImg, 25, 680, 80, 80)
                    nickname(145, 740)
                    goodsTitle(40, 900)
                    shopMsg(40, 1020)
                    imgMsg(310, 1250)
                    var base64 = canvas.toDataURL();
                    document.getElementById('out').src = base64;
                    document.getElementById('out').style.display = "block";
                }
            })
        }
    }

这个代码主要是进行海报合成,其中注意点事 所有得图片都必须加载完成之后进行合成操作 所以用到了Promise来进行处理

Armbian使用Docker安装IPsec VPN 服务器

项目地址:https://github.com/hwdsl2/setup-ipsec-vpn
首先获取镜像

docker pull hwdsl2/ipsec-vpn-server

创建配置文件config.env

# Note: All the variables to this image are optional.
# See README for more information.
# To use, uncomment and replace with your own values.

# Define IPsec PSK, VPN username and password
# - DO NOT put "" or '' around values, or add space around =
# - DO NOT use these special characters within values: \ " '
VPN_IPSEC_PSK=密钥
VPN_USER=账号
VPN_PASSWORD=密码

# Define additional VPN users
# - DO NOT put "" or '' around values, or add space around =
# - DO NOT use these special characters within values: \ " '
# - Usernames and passwords must be separated by spaces
# VPN_ADDL_USERS=additional_username_1 additional_username_2
# VPN_ADDL_PASSWORDS=additional_password_1 additional_password_2

# Use a DNS name for the VPN server
# - The DNS name must be a fully qualified domain name (FQDN)
# VPN_DNS_NAME=vpn.example.com

# Specify a name for the first IKEv2 client
# - Use one word only, no special characters except '-' and '_'
# - The default is 'vpnclient' if not specified
# VPN_CLIENT_NAME=your_client_name

# Use alternative DNS servers
# - By default, clients are set to use Google Public DNS
# - Example below shows Cloudflare's DNS service
# VPN_DNS_SRV1=1.1.1.1
# VPN_DNS_SRV2=1.0.0.1

# Protect IKEv2 client config files using a password
# - By default, no password is required when importing IKEv2 client configuration
# - Uncomment if you want to protect these files using a random password
# VPN_PROTECT_CONFIG=yes

运行镜像

docker run -d \
    -p 500:500/udp \
    -p 4500:4500/udp \
    --privileged=true \ # 特权模式运行
    --restart=always \  # 开启自启动
    --name ipsec-vpn-server \
    --env-file /home/iuu/Software/Docker/ipsec-vpn-server/config.env \ # 加载配置文件
    -v /home/iuu/Software/Docker/ipsec-vpn-server/ikev2-vpn-data:/etc/ipsec.d \
    -v /lib/modules:/lib/modules:ro \ # 挂载运行库目录 但是只有只读权限
    hwdsl2/ipsec-vpn-server

启动容器后查看容器状态
防火墙开4500 500 UDP端口
如果是内网穿透,路由器也得做对应的端口转发

1 2 3 4