HomeLab

Introduction

[HomeLab] 是一个比较好的环境,可以跑一些docker和linux脚本,

在配置一个基本的Server的过程中,软件上有不少的坑,都记录在这里,方便查阅。

My Default Configs

部分配置由于方便进行同步和管理,使用了Github进行同步。 默认的文件夹名为 deploy .

DDNS

curl -OL https://raw.githubusercontent.com/hotchilipowder/my_config/refs/heads/main/scripts/ddns/ddns_dp.py
ddns-python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@author: hotchilipowder
@file: ddns_dp.py
@time: 2024/10/14
"""

import os
import json
import logging
import subprocess
import argparse
import platform
import urllib
import urllib.request
import urllib.parse

DP_ID = "xxx"  # replace with your ID
DP_TOKEN = "xxx"  # replace with your Token

DOMAIN = "utlab.ltd"

GETIPV6 = "https://ipv6.ddnspod.com"
GETIPV4 = "https://ipv4.ddnspod.com"
# for all subdomain, just use *.xx
parser = argparse.ArgumentParser(
    description="After input DP_ID, DP_TOKEN, you can use `python ddns_dp.py -d utlab.ltd -s *.xxx -t A`"
)
parser.add_argument("--domain", "-d", default=DOMAIN, help=f"Domain, default {DOMAIN}")
parser.add_argument("--sub_domain", "-s", default="xxx", help="Subdomain, default: xxx")
parser.add_argument(
    "--record_type", "-t", default="AAAA", help="Record Type, e.g., AAAA(default), A"
)
parser.add_argument(
    "--local_ip_type",
    "-lt",
    default=None,
    help="Use local IPV4(A) or IPV6(AAAA), default: None, e.g., -lt A (it will get local ipv4)",
)
args = parser.parse_args()

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)


def get_public_ipv6():
    """just curl -6 -s $GETIPV6"""

    url = GETIPV6
    req = urllib.request.Request(url)
    req.add_header("Host", url.lstrip("https://").lstrip("http://").split("/")[0])
    response = urllib.request.urlopen(req)
    content = response.read().decode("utf-8")
    return content


def get_public_ipv4():
    """just curl -s $GETIPV6"""

    url = GETIPV4
    req = urllib.request.Request(url)
    req.add_header("Host", url.lstrip("https://").lstrip("http://").split("/")[0])
    response = urllib.request.urlopen(req)
    content = response.read().decode("utf-8")
    return content

def get_local_ipconfig_cmd():
    system_name = platform.system()
    if system_name == "Windows":
        return "ipconfig"
    elif system_name == "Darwin":  # MacOS 系统返回 "Darwin"
        return "ifconfig"
    elif system_name == "Linux":
        return "ip a"
    else:
        return ""


def get_local_ipv4():
    cmd = get_local_ipconfig_cmd()
    cmdline = rf"{cmd} | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1' | grep -v '172*'| head -n 1"
    res = subprocess.run(cmdline, shell=True, capture_output=True)
    ip = res.stdout.decode("utf8").strip()
    logger.warning(ip)
    return ip



def get_local_ipv6():
    cmd = get_local_ipconfig_cmd()
    cmdline = rf'{cmd} | grep inet6 | grep -v "::1" | grep -Eo "(2[a-f0-9:]+:+)+[a-f0-9]+" | head -n 1'
    res = subprocess.run(cmdline, shell=True, capture_output=True)
    ip = res.stdout.decode("utf8").strip()
    logger.warning(f"ip: {ip}")
    return ip


class DNSPod(object):
    """DNSPod ddns application."""

    def __init__(self, params):
        """Initialize with params."""
        self._params = params

    def post_data(self, url, data):
        encoded_data = urllib.parse.urlencode(data).encode()

        req = urllib.request.Request(url, data=encoded_data, method="POST")
        with urllib.request.urlopen(req) as response:
            response_data = response.read().decode("utf-8")
            return response_data

    def run(self, params=None):
        if params is None:
            params = self._params
        public_ip = None
        if args.local_ip_type is None:
            if params["record_type"] == "AAAA":
                public_ip = get_public_ipv6()
            elif params["record_type"] == "A":
                public_ip = get_public_ipv4()
        elif args.local_ip_type == "A":
            public_ip = get_local_ipv4()
        elif args.local_ip_type == "AAAA":
            public_ip = get_local_ipv6()
        if public_ip is None:
            logger.error("IP unknown")
            return
        # get record_id of sub_domain
        record_list = self.get_record_list(params)
        if record_list["code"] == "10" or record_list["code"] == "26":
            # create record for empty sub_domain
            record_id = self.create_record(params, public_ip)
            remote_ip = public_ip
        elif record_list["code"] == "1":
            # get record id
            remote_ip = record_list["ip_value"]
            if remote_ip == public_ip:
                logger.warning("same ip: " + remote_ip)
                return -1
            else:
                logger.warning(record_list)
                params["record_id"] = record_list["record_id"]
                res = self.ddns(params, public_ip)
                logger.warning(res)
        else:
            logger.error("Update not work")
            logger.error(record_list)
            return -1
        current_ip = remote_ip
        logger.warning("current_ip: " + current_ip)

    def get_record_list(self, params):
        """Get record list.
        https://www.dnspod.cn/docs/records.html#record-list
        :return: dict of code, record_id and IP value
        """
        record_list_url = "https://dnsapi.cn/Record.List"

        r = self.post_data(record_list_url, data=params)
        jd = json.loads(r)
        code = jd["status"]["code"]
        record_id = jd["records"][0]["id"] if code == "1" else ""
        ip_value = jd["records"][0]["value"] if code == "1" else ""
        logger.warning("query_record")
        logger.warning(jd)
        return dict(code=code, record_id=record_id, ip_value=ip_value)

    def create_record(self, params, ip):
        """Create record if not created before.
        https://www.dnspod.cn/docs/records.html#record-create
        :return: record_id of new record
        """
        if len(ip) == 0: 
            logger.error("ip not set")
            return
        params["value"] = ip
        record_create_url = "https://dnsapi.cn/Record.Create"
        logger.warning(params)
        r = self.post_data(record_create_url, data=params)
        jd = json.loads(r)
        assert jd["status"]["code"] == "1", jd
        record_id = jd["record"]["id"]
        logger.warning("create_record")
        logger.warning(jd)
        return record_id

    def ddns(self, params, ip):
        """Update ddns ip.
        https://www.dnspod.cn/docs/records.html#dns
        """
        logger.warning(ip)
        logger.warning(params)
        params["value"] = ip
        ddns_url = "https://dnsapi.cn/Record.Ddns"
        r = self.post_data(ddns_url, data=params)
        jd = json.loads(r)
        logger.warning("update_ddns")
        logger.warning(jd)
        return jd["status"]["code"] == "1"


def main():
    params = dict(
        login_token=("%s,%s" % (DP_ID, DP_TOKEN)),
        format="json",
        domain=args.domain,
        sub_domain=args.sub_domain,
        record_line="默认",
        record_type=args.record_type,
    )

    dnspod = DNSPod(params)
    dnspod.run()


def test():
    get_local_ipv4()


if __name__ == "__main__":
    main()

Configurations For MacOS

从Macbook pro “反向升级”到Macbook Air。(不过,Intel到Apple Silicon,去掉了touch bar,键盘感觉回归,这怎么能是反向呢) 直接两个电脑雷电连接,然后迁移助理,花时间大概30分钟就迁移完成接近200G的文件。 然后就是更重要的部分,如何从Intel到AppleSilicon。 首先,通过是Homebrew,传统的安装是在 /usr/local目录下,而目前的路径已经修改到了 /opt/homebrew所以先使用脚本进行卸载,然后重新安装。具体 这个连接

Font Install

brew tap homebrew/cask-fonts  # You only need to do this once!
brew search '/font-.*-nerd-font/'

# get homebrew/cask-fonts/font-hack-nerd-font

brew install font-fira-code-nerd-font

Configurations For Linux

Install Basic Developments

autojump

之前一直用 autojump , 但是似乎已经没有人在维护了,并且发现了一个 rust写的替代产品 zoxide ,遂转到这个。

docker-compose

Just see https://docs.docker.com/compose/install/standalone/ and use

curl -SL https://github.com/docker/compose/releases/latest/download/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

and just use following links for /usr/bin/docker-compose

ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

htop

Download from https://github.com/htop-dev/htop/releases/download/3.2.2/htop-3.2.2.tar.xz Then, ./configure;make;make install

Python Source

Dependecy:

apt-get install build-essential gdb lcov pkg-config \
  libbz2-dev libffi-dev libgdbm-dev libgdbm-compat-dev liblzma-dev \
  libncurses5-dev libreadline6-dev libsqlite3-dev libssl-dev \
  lzma lzma-dev tk-dev uuid-dev zlib1g-dev
curl -OL https://www.python.org/ftp/python/3.13.2/Python-3.13.2.tar.xz
tar -xvf Python-*.tar.xz
./configure --enable-optimizations --with-lto --prefix=~/.local
make -j 4
make install
curl -OL https://www.python.org/ftp/python/3.12.8/Python-3.12.8.tar.xz
tar -xvf Python-3.12.8

./configure --enable-optimizations --with-lto --prefix=~/.local
make -j 4
make install

References:

Docker

Install Docker

国内使用清华源安装更好

brew install --cask docker
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh

See Linux Install

sudo apt-get remove docker docker-engine docker.io containerd runc
sudo apt-get update
sudo apt-get install \
    ca-certificates \
    curl \
    gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

PVE

PVE IPv6 Issues

这个问题主要是PVE的使用下,IPv6一直无法正常使用。 首先,我的网路的入口是一个路由器,这个路由器会分发一个ipv6的地址。 但是在使用了PVE后,无法再分配对应的IPv6到各个虚拟机。 事实上,我当时无法获取ipv6地址的问题是PVE7.0之后的一个问题。 See Proxmox网桥通过SLAAC配置公网ipv6地址 - 海运的博客

Proxmox安装后默认没有通过SLAAC配置公网ipv6地址,使用debian/ubuntu的方法配置ipv6提示错误不支持的方法auto。

iface vmbr0 inet6 auto

原来Proxmox使用的是ifupdown2,非debian/ubuntu使用ifupdown。 查看内核也已经开启ipv6自动配置:

cat /proc/sys/net/ipv6/conf/vmbr0/accept_ra
1
cat /proc/sys/net/ipv6/conf/vmbr0/autoconf
1
cat /proc/sys/net/ipv6/conf/vmbr0/forwarding
1

需要将accept_ra值改成2才能自动配置SLAAC ipv6地址: 在/etc/sysctl.conf文件末添加

net.ipv6.conf.all.accept_ra=2
net.ipv6.conf.default.accept_ra=2
net.ipv6.conf.vmbr0.accept_ra=2
net.ipv6.conf.all.autoconf=1
net.ipv6.conf.default.autoconf=1
net.ipv6.conf.vmbr0.autoconf=1

然后ipv6的地址就有了。

这个时候/etc/network/interface的配置为:

source /etc/network/interfaces.d/*
auto lo
iface lo inet loopback

iface enp1s0 inet manual

auto vmbr0
iface vmbr0 inet static
   address 192.168.123.86/24
   gateway 192.168.123.1
   bridge-ports enp1s0
   bridge-stp off
   bridge-fd 0
iface vmbr0 inet6 auto

Research Server

网络接入

通常而言,内部服务器都是不连入互联网的,为了保证其内网的安全。 因此我们通常通过代理的方式连出,假设我们的代理为 http://192.168.1.1

我们可以在 ~/.bashrc文件中添加如下配置,联入互联网

1  export all_proxy=http://192.168.1.1:1081
2  export http_proxy=http://192.168.1.1:1081
3  export https_proxy=http://192.168.1.1:1081
4  export PATH=$HOME/.local/bin:$PATH
5  export LD_LIBRARY_PATH=$HOME/.local/lib:$LD_LIBRARY_PATH
6  export MANPATH=$HOME/.local/share/man:$MANPATH

Pytorch安装

由于pytorch使用较多,下面的示例安装pytorch

channels:
  - defaults
show_channel_urls: true
default_channels:
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/r
  - https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/msys2
custom_channels:
  conda-forge: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  msys2: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  bioconda: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  menpo: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  pytorch: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
  simpleitk: https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud
proxy_servers:
  http: http://192.168.1.1:1081
  https: http://192.168.1.1:1081

Nodejs Install

curl -OL https://nodejs.org/download/release/latest/node-v23.7.0-linux-x64.tar.gz
tar -xvf node-*.tar.gz  -C ~/.local --strip-components=1

see https://nodejs.org/download/release/

1curl -OL https://nodejs.org/download/release/latest-v16.x/node-v16.14.0-linux-x64.tar.gz
2tar -xvf node-*
3mv node-*/ ~/.local
4rm node-*
5pip3 install neovim

just download from https://nodejs.org/en/download

根据 Build takes so long , 可以发现,还是要使用多核编译,但是仍然非常慢!

1 ./configure --prefix=~/.local
2 make -j 4 && make install

MISC

Backup disks

# first find node_modules venv .git

find . -name '.git' -type d -prune > rm_git.sh
find . -name 'node_modules' -type d -prune > rm_node.sh
find . -name 'venv' -type d -prune > rm_venv.sh

# then using rsync

rsync -avh -P src_dir dst_dir

# find every sub dir has files
for dir in */; do echo "$dir"; find "$dir" -type f | wc -l; done

References

[HomeLab]

a laboratory of (usually slightly outdated) awesome in the domicile. See https://icyleaf.com/2022/02/how-to-homelab-part-0