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¶
a laboratory of (usually slightly outdated) awesome in the domicile. See https://icyleaf.com/2022/02/how-to-homelab-part-0