普通视图

Received before yesterday

是 IPv6 吖 — V6 重生记

2025年3月31日 10:27

上周五的时候,在杜老师的聊天室聊天,hehe 提到一个关于家里 ipv6 地址申请的问题。这时候才想到自己家里的网络应该也是有 ipv6 的地址的。至于地址是不是公网的那就不知道了。

而至于想要弄这个东西,其实还有一个原因是 he.net 的 ipv6 徽章已经很久没更新了,还差最后一步 ipv6 only 的网络访问测试,而测试的域名就是 h4ck.org.cn。
IPv6 Certification Badge for obaby

为了通过这个测试,自然要折腾一下。通过之后,he说会免费邮寄一个 T 恤衫,尺码和地址都更新了。不过这跨国的快递,不知道能不能收到。

至于能不能收到,这就只能耐心等待啦。

远程登录路由器,直接访问 ip 地址,然后高级的一幕就出现了,竟然直接打开了路由器的登录页面:

那么也就是说联通在 v6 协议下竟然没有封禁 80 端口,这样的话我忽然就有了个大胆的想法。如果路由器将 v6 的映射打开,直接访问 v6 的地址,忽略证书错误。然后网站就顺利打开了:

既然如此,那么这一来也解决了自己的 cdn 流量超限的问题。

这个月流量超限之后,买了 100G 的扩展包,结果就用了四天就又没了。为了解决流量问题,文章中的视频,直接通过 url 转发了。而至于首页右下角的图片就直接去掉了。不知道是访问量还是神马问题,这些图片一天跑十几个 G 的流量。

然而,到现在就出现了另外几个问题,要想让网站直接在互联网上访问,没有任何的防护措施,的确感觉不怎么靠谱。

1.家里的 V6 地址也是动态的,需要能够动态更新 ipv6 的 AAAA 记录。

2.在家里的主机上安装 waf 系统,提供基础的防御功能。

3.其他的未知问题。

AAAA 记录

在测试的时候,添加 AAAA 记录,会因为存在 cname 记录而导致添加失败,AAAA 记录和 CNAME 记录有冲突,请先删除或暂停现有的 CNAME 记录后重试:

此时针对不同的线路分别添加解析就 ok 了:

那么,在这之后就来到了第二个问题,怎么获取本地的公网 ipv6地址。

最直接的想法是直接通过获取 ipv4 地址类似的写法,来获取 ipv6 的地址,让 cursor 给写了类似的代码:

def get_ipv6_by_httpbin():
    """通过 httpbin.org 获取 IPv6 地址"""
    url = 'https://api6.ipify.org?format=json'
    try:
        resp = request.urlopen(url=url, timeout=10).read()
        data = json.loads(resp.decode("utf-8"))
        if 'ip' in data:
            return data['ip']
        return None
    except Exception as e:
        logging.warning("get_ipv6_by_httpbin FAILED, error: %s", str(e))
        return None

def get_ipv6_by_icanhazip():
    """通过 icanhazip.com 获取 IPv6 地址"""
    url = 'https://icanhazip.com'
    try:
        resp = request.urlopen(url=url, timeout=10).read()
        ip = resp.decode("utf-8").strip()
        if regex_ipv6.match(ip):
            return ip
        return None
    except Exception as e:
        logging.warning("get_ipv6_by_icanhazip FAILED, error: %s", str(e))
        return None

def get_ipv6_by_ident_me():
    """通过 ident.me 获取 IPv6 地址"""
    url = 'https://v6.ident.me'
    try:
        resp = request.urlopen(url=url, timeout=10).read()
        ip = resp.decode("utf-8").strip()
        if regex_ipv6.match(ip):
            return ip
        return None
    except Exception as e:
        logging.warning("get_ipv6_by_ident_me FAILED, error: %s", str(e))
        return None

实际证明,代码写的不错,在自己的 mac 电脑上的确可以获取到 ipv6 的地址。

然而,在家里的服务器上却使用无法获取 ip 地址,所有 v6 协议的服务都是超时状态。搜索了一堆,问了一大圈的 ai,最终都没解决问题。

后来猜测是不是路由器的问题,于是重新登录路由器的 v6 配置页面,来回切换配置:

看网上有文章会所需要改为 slaac 模式,改过去之后无效,切换成原来的自动,继续沿用上面的配置。断线重连结果网络就好啦。注意,这两个 dns 是腾讯的 dns,不是联通默认的 dns。

然而,此时就出现了另外一个问题,直到这时候我才发现,获取到的这个地址是本地的 v6 地址,而不是路由器的 v6 地址,当然,更恐怖的是这个 v6 地址也是可以在互联网直接访问的。

那么怎么自动更新这个 dns 记录就成了问题,总不能自己去天天改啊。

问小杜无果之后,继续尝试通过路由或者 tracerout 的方式获取,最终都以失败告终。至此,简单的方法算是彻底没了招了,那么就剩下一条路了,之计通过路由器获取,然鹅,tplink 的企业路由器并没有开放相关的 api。只能自己去找接口。

结果在登录页面就被来了个下马威,获取到接口,让 cursor 写完代码之后登录不了。

看起来页面很简单不是,但是这个东西恶心的地方在于登录的密码是加密过得,直接使用明文密码是登录不了的。不过好在这个密码不是动态加密的,直接使用密码登录,截取登录的加密后密码进行登录就 ok 了。剩下的就是获取 ipv6 地址,更新 dnspod 的aaaa 记录:

tplink 相关代码:

import requests
import json
import urllib.parse

def login_tplink(ip, username, password):
    """
    Login to TP-Link router
    :param ip: Router IP address
    :param username: Login username
    :param password: Login password
    :return: Response from the router and stok if successful
    """
    url = f"http://{ip}/"
    
    headers = {
        'Accept': 'text/plain, */*; q=0.01',
        'Accept-Language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive',
        'Content-Type': 'application/json; charset=UTF-8',
        'Origin': f'http://{ip}',
        'Pragma': 'no-cache',
        'Referer': f'http://{ip}/login.htm',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36',
        'X-Requested-With': 'XMLHttpRequest'
    }
    
    data = {
        "method": "do",
        "login": {
            "username": username,
            "password": password
        }
    }
    
    try:
        response = requests.post(
            url,
            headers=headers,
            json=data,
            verify=False
        )
        if response.status_code == 200:
            try:
                response_data = response.json()
                if 'stok' in response_data:
                    return response_data['stok']
            except json.JSONDecodeError:
                print("Failed to parse login response as JSON")
        return None
    except requests.exceptions.RequestException as e:
        print(f"Login error occurred: {e}")
        return None

def get_network_info(ip, stok):
    """
    Get network information from TP-Link router
    :param ip: Router IP address
    :param stok: Session token from login
    :return: Network information response
    """
    url = f"http://{ip}/stok={stok}/ds"
    
    headers = {
        'Accept': 'text/plain, */*; q=0.01',
        'Accept-Language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive',
        'Content-Type': 'application/json; charset=UTF-8',
        'Origin': f'http://{ip}',
        'Pragma': 'no-cache',
        'Referer': f'http://{ip}/',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36',
        'X-Requested-With': 'XMLHttpRequest'
    }
    
    data = {
        "method": "get",
        "network": {
            "table": "if_info"
        }
    }
    
    try:
        response = requests.post(
            url,
            headers=headers,
            json=data,
            verify=False
        )
        return response
    except requests.exceptions.RequestException as e:
        print(f"Network info error occurred: {e}")
        return None

def get_wan1_pppoe_addresses(response_data):
    """
    Parse IPv4 and IPv6 addresses from network info response
    :param response_data: JSON response data
    :return: Dictionary containing IPv4 and IPv6 addresses
    """
    addresses = {
        'ipv4': None,
        'ipv6': None
    }
    
    try:
        if_info = response_data.get('network', {}).get('if_info', [])
        for interface in if_info:
            if 'wan1_pppoe' in interface:
                wan_data = interface['wan1_pppoe']
                if 'ipaddr' in wan_data:
                    addresses['ipv4'] = wan_data['ipaddr']
                if 'ip6addr' in wan_data:
                    addresses['ipv6'] = urllib.parse.unquote(wan_data['ip6addr'])
                break
    except Exception as e:
        print(f"Error parsing wan1_pppoe addresses: {e}")
    
    return addresses

def update_ipv6_nat_mapping(ip, stok, dest_ip):
    """
    Update IPv6 NAT mapping on TP-Link router
    :param ip: Router IP address
    :param stok: Session token from login
    :param dest_ip: Destination IPv6 address
    :return: Response from the router
    """
    url = f"http://{ip}/stok={stok}/ds"
    
    headers = {
        'Accept': 'text/plain, */*; q=0.01',
        'Accept-Language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive',
        'Content-Type': 'application/json; charset=UTF-8',
        'Origin': f'http://{ip}',
        'Pragma': 'no-cache',
        'Referer': f'http://{ip}/',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36',
        'X-Requested-With': 'XMLHttpRequest'
    }
    
    # URL encode the IPv6 address
    encoded_dest_ip = urllib.parse.quote(dest_ip)
    
    data = {
        "method": "set",
        "firewall": {
            "redirect_4313056213": {
                "name": "mac_server_v6",
                "ip_proto": "IPv6",
                "if": ["WAN"],
                "src_dport": "443",
                "dest_port": "443",
                "dest_ip": encoded_dest_ip,
                "proto": "ALL",
                "loopback_ipaddr": "",
                "enable": "on",
                "src_dport_start": "65536",
                "src_dport_end": "65536",
                "dest_port_start": "65536",
                "dest_port_end": "65536"
            }
        }
    }
    
    try:
        response = requests.post(
            url,
            headers=headers,
            json=data,
            verify=False
        )
        return response
    except requests.exceptions.RequestException as e:
        print(f"Error updating IPv6 NAT mapping: {e}")
        return None

if __name__ == "__main__":
    # Disable SSL warnings
    requests.packages.urllib3.disable_warnings()
    
    # Router credentials
    ip = '192.168.1.1'
    username = 'obaby'
    password = '123456***加密后密码'
    
    # First login to get stok
    stok = login_tplink(ip, username, password)
    
    if stok:
        print(f"Login successful! Got stok: {stok}")
        
        # Get network information using the stok
        network_response = get_network_info(ip, stok)
        
        if network_response:
            try:
                response_data = network_response.json()
                addresses = get_wan1_pppoe_addresses(response_data)
                
                print("\nWAN1 PPPoE Addresses:")
                if addresses['ipv4']:
                    print(f"IPv4: {addresses['ipv4']}")
                if addresses['ipv6']:
                    print(f"IPv6: {addresses['ipv6']}")
                    
                    # Update NAT mapping with the IPv6 address
                    nat_response = update_ipv6_nat_mapping(ip, stok, addresses['ipv6'])
                    if nat_response:
                        print(f"NAT mapping update response: {nat_response.text}")
                    else:
                        print("Failed to update NAT mapping")
            except json.JSONDecodeError:
                print("Failed to parse network response as JSON")
        else:
            print("Failed to get network information")
    else:
        print("Login failed!")

至此第一个问题解决了。

开始第二个小问题,更新 aaaa 记录,这个就比较简单了,直接让 curosr 写就行了:

def get_record_id(domain, sub_domain, record_type='A', record_line='默认'):
    """获取记录ID,支持A和AAAA记录,以及不同的记录线路"""
    url = 'https://dnsapi.cn/Record.List'
    params = parse.urlencode({
        'login_token': cfg['login_token'],
        'format': 'json',
        'domain': domain
    })
    req = request.Request(url=url, data=params.encode('utf-8'), method='POST', headers=header())
    try:
        resp = request.urlopen(req).read().decode()
    except (error.HTTPError, error.URLError, socket.timeout):
        return None
    records = json.loads(resp).get('records', {})
    for item in records:
        if (item.get('name') == sub_domain and 
            item.get('type') == record_type and 
            item.get('line') == record_line):
            return item.get('id')
    return None

def update_ipv6_record(current_ipv6):
    """更新IPv6记录,支持多个记录和不同的记录线路"""
    ipv6_count = int(cfg.get('ipv6_count', '1'))
    ipv6_pool = cfg.get('ipv6_pool', '').split(',')[:ipv6_count]
    cfg['current_ipv6'] = current_ipv6
    
    if current_ipv6 not in ipv6_pool:
        logging.info("new IPv6 found: %s", current_ipv6)
        ipv6_pool.insert(0, current_ipv6)
        cfg['ipv6_pool'] = ','.join([str(x) for x in ipv6_pool[:ipv6_count]])
        
        # 获取所有需要更新的AAAA记录配置
        aaaa_records = cfg.get('aaaa_records', '').split(',')
        for record in aaaa_records:
            if not record.strip():
                continue
            try:
                sub_domain, record_line = record.strip().split(':')
                if update_record('AAAA', current_ipv6, record_line, sub_domain):
                    logging.info(f"成功更新AAAA记录: {sub_domain}.{cfg['domain']} 线路: {record_line}")
                else:
                    logging.error(f"更新AAAA记录失败: {sub_domain}.{cfg['domain']} 线路: {record_line}")
            except ValueError:
                logging.error(f"无效的AAAA记录配置: {record}")
        save_config()
    else:
        logging.info('IPv6 地址无变化,跳过更新')

到这里网站就能正常访问了。

WAF:雷池&南墙

至于 waf 系统,其实自己之前也没怎么系统了解过,也是杜老师推荐了这两个。首先尝试的是雷池,也是杜老师最开始推荐的。

雷池:

个人版是免费的,相对来说配置也比较简单。

官网地址:https://waf-ce.chaitin.cn

自动安装一行命令即可:

bash -c "$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/manager.sh)"

安装为 docker 模式,相对来说侵入性比较小一些。并且不需要占用 80,443 端口,这一点其实相对比南墙安装配置要求要低一些。

安装之后就可以通过 9443 端口登录了。相关功能示例:

系统概览,不知道是不是因为是 v6 地址的原因,左侧地图都是空白的。

同样,这个地球上也是空白的,底部的功能都需要专业版才能查看

防护模块是全部可用的

加强防御需要专业版

通用配置模块也是 ok 的。

整体来说安装过程比较顺畅也没遇到什么问题,不过访问 ip 由于是通过路由转发进来的需要从 x-forward-for中取这个信息。

南墙

开源免费的 waf 系统

官网地址:https://waf.uusec.com/#/

在使用过程中遇到点问题,不过最后在他们的技术帮助下顺利解决了。在安装之后,首先遇到的问题就是获取的 ip 地址有问题,都是本地的地址。并且不管怎么选择地址,最后都是同一个 ip 地址。

使用测速工具测速之后,ip 地址还是一个,这肯定是有问题的。在群里问了一下,给了个指令修复这个问题:

firewall-cmd --permanent --zone=internal --change-interface=docker0
systemctl restart firewalld && systemctl daemon-reload && systemctl restart docker

不过这么执行之后可能会出现的问题就是所有的服务都访问不了了,需要在 public 区域重新开放相关服务:

sudo firewall-cmd --zone=public --permanent --add-port=10043/tcp
sudo firewall-cmd --zone=public --permanent --add-port=14443/tcp
sudo firewall-cmd --zone=public --permanent --add-port=880/tcp
sudo firewall-cmd --zone=public --permanent --add-port=3306/tcp
sudo firewall-cmd --zone=public --permanent --add-port=9443/tcp
sudo firewall-cmd --zone=public --permanent --add-port=8443/tcp

其他需要开放的服务和端口自行添加即可。

然而,这个命令并没有解决问题。包括卸载重装,其实重装这件事情对我来说有些麻烦,因为服务器的默认 80 和 443 都映射到公网了,如果直接改了也比较麻烦,只能去工控机上停掉 80 的监听,443 的修改端口重新添加映射,毕竟这台主机上相对服务少一些。

重新安装依然没解决问题,这时候提议安装主机版。

然而,更尴尬的事情粗线了,那就是主机版不支持 ubuntu,只能作罢继续使用 docker 版本。

并且安装主机版,需要提前备份数据库,安装脚本会重装 mysql。这一点一定要注意!

这时候管理员提议远程协助,于是将端口映射出去,提供账号密码,等管理员修复。

管理说可能是映射的问题,然而,雷池的没问题,那么说明一定是有解决办法的,管理提到 docker 的网络配置不一样的,于是提议修改网络配置。

最终,亲爱的管理员,成功的修复了问题:

这样这个问题算是解决了,整体而言,感觉雷池的在 v6 测速的时候更绿一些。

好啦,相对来说雷池基本所有的模块都是开放的,除了机器学习部分:

安全态势

系统信息

用户管理

日志

证书管理,这个证书管理直接上传即可,不需要去进行绑定。

cdn 加速,其实感觉更像缓存配置。

规则管理

网站管理,得添加多个。

整体来说体验还是不错的,然而,刚才去看了配置文件感觉还是 bridge 啊。奇怪了。

不过既然问题解决了,那也就不纠结了。

官方文档说明:

https://waf.uusec.com/#/guide/problems?id=%f0%9f%8d%8b-%e5%a6%82%e4%bd%95%e8%a7%a3%e5%86%b3%e5%8d%97%e5%a2%99docker%e7%89%88%e8%8e%b7%e5%8f%96%e7%9a%84%e5%ae%a2%e6%88%b7%e7%ab%afip%e4%b8%ba172%e7%9a%84%e9%97%ae%e9%a2%98%ef%bc%9f

 

🍋 解决部分南墙容器版获取的客户端ip为网桥ip的问题?

1.将Docker网桥加入到防火墙的internal区域,以便获取到真实的IP地址, 假设Docker网桥名称为docker0

firewall-cmd --permanent --zone=internal --change-interface=docker0
systemctl restart firewalld && systemctl daemon-reload && systemctl restart docker

2.如果方法1无效,可以修改docker-compose.yml文件,将uuwaf容器的网络设置为network_mode: host,同时修改数据库连接环境变量UUWAF_DB_DSN中的wafdb为127.0.0.1,并映射wafdb容器的3306端口,重启后生效。

 

其他问题

鉴于主机获取的 ipv6 地址能直接访问,其实我一度想直接把主机的地址更新到 dns aaaa 记录上,但是这么一搞,暴露主机的确不是我最终想要的。

于是想着映射本地的链路地址,然而,端口映射通过链路地址通过路由器的 v6 地址却打不开网站,但是这个链路地址在内网的主机上又能打开网站,于是只能放弃这个做法。获取 ipv6 地址的代码:

import re
import logging
import json
import subprocess
import socket
import os
from urllib import request, error, parse

# 匹配合法 IPv6 地址
regex_ipv6 = re.compile(
    r"(?:inet6\s+)?(fe80:[0-9a-fA-F:]+|"  # 特别处理链路本地地址格式
    + r"(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|"  # 标准格式
    + r"(?:[0-9a-fA-F]{1,4}:){6}:[0-9a-fA-F]{1,4}|"  # 压缩格式
    + r"(?:[0-9a-fA-F]{1,4}:){5}(?::[0-9a-fA-F]{1,4}){1,2}|"
    + r"(?:[0-9a-fA-F]{1,4}:){4}(?::[0-9a-fA-F]{1,4}){1,3}|"
    + r"(?:[0-9a-fA-F]{1,4}:){3}(?::[0-9a-fA-F]{1,4}){1,4}|"
    + r"(?:[0-9a-fA-F]{1,4}:){2}(?::[0-9a-fA-F]{1,4}){1,5}|"
    + r"(?:[0-9a-fA-F]{1,4}:){1}(?::[0-9a-fA-F]{1,4}){1,6}|"
    + r"(?::[0-9a-fA-F]{1,4}){1,7}|"
    + r"::"
    + r")")

# 特别匹配链路本地 IPv6 地址,确保能匹配到 fe80:: 开头的地址
regex_link_local_ipv6 = re.compile(r"inet6\s+(fe80:[0-9a-fA-F:]+)")

def get_ipv6():
    """获取公网 IPv6 地址,使用多个备选方法"""
    return (get_ipv6_by_ifconfig()  # 优先使用本地接口地址
        or get_ipv6_by_httpbin()
        or get_ipv6_by_icanhazip()
        or get_ipv6_by_ident_me()
        or get_ipv6_by_socket())

def get_ipv6_by_ifconfig():
    """通过 ifconfig 命令获取本地 IPv6 地址"""
    try:
        # Windows 系统使用 ipconfig
        if os.name == 'nt':
            cmd = ['ipconfig']
            output = subprocess.check_output(cmd, text=True)
        # Linux/Unix 系统使用 ifconfig
        else:
            cmd = ['ifconfig']
            output = subprocess.check_output(cmd, text=True)
            
        # 按行分割输出
        lines = output.split('\n')
        for line in lines:
            # 查找包含 inet6 的行
            if 'inet6' in line:
                # 使用正则表达式提取 IPv6 地址
                matches = regex_ipv6.findall(line)
                if matches:
                    ipv6 = matches[0]
                    # 排除本地回环地址
                    if not ipv6.startswith('::1'):
                        logging.info(f"Found IPv6 address: {ipv6}")
                        return ipv6
    except Exception as e:
        logging.warning("get_ipv6_by_ifconfig FAILED, error: %s", str(e))
    return None

def get_ipv6_by_socket():
    """通过 Python socket 库获取本地 IPv6 地址"""
    try:
        # 创建一个 IPv6 socket
        s = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
        # 连接到一个外部地址(这里使用 Google 的 IPv6 DNS)
        s.connect(('2001:4860:4860::8888', 80))
        # 获取本地地址
        local_addr = s.getsockname()[0]
        s.close()
        return local_addr
    except Exception as e:
        logging.warning("get_ipv6_by_socket FAILED, error: %s", str(e))
        return None

def get_ipv6_by_httpbin():
    """通过 httpbin.org 获取 IPv6 地址"""
    url = 'https://api6.ipify.org?format=json'
    try:
        resp = request.urlopen(url=url, timeout=10).read()
        data = json.loads(resp.decode("utf-8"))
        if 'ip' in data:
            return data['ip']
        return None
    except Exception as e:
        logging.warning("get_ipv6_by_httpbin FAILED, error: %s", str(e))
        return None

def get_ipv6_by_icanhazip():
    """通过 icanhazip.com 获取 IPv6 地址"""
    url = 'https://icanhazip.com'
    try:
        resp = request.urlopen(url=url, timeout=10).read()
        ip = resp.decode("utf-8").strip()
        if regex_ipv6.match(ip):
            return ip
        return None
    except Exception as e:
        logging.warning("get_ipv6_by_icanhazip FAILED, error: %s", str(e))
        return None

def get_ipv6_by_ident_me():
    """通过 ident.me 获取 IPv6 地址"""
    url = 'https://v6.ident.me'
    try:
        resp = request.urlopen(url=url, timeout=10).read()
        ip = resp.decode("utf-8").strip()
        if regex_ipv6.match(ip):
            return ip
        return None
    except Exception as e:
        logging.warning("get_ipv6_by_ident_me FAILED, error: %s", str(e))
        return None

def get_link_local_ipv6():
    """专门获取链路本地 IPv6 地址"""
    try:
        # Windows 系统使用 ipconfig
        if os.name == 'nt':
            cmd = ['ipconfig']
            output = subprocess.check_output(cmd, text=True)
        # Linux/Unix 系统使用 ifconfig
        else:
            cmd = ['ifconfig']
            output = subprocess.check_output(cmd, text=True)
            
        # 按行分割输出
        lines = output.split('\n')
        for line in lines:
            # 查找包含 inet6 的行
            if 'inet6' in line:
                # 提取 IPv6 地址和 prefixlen
                if 'prefixlen 64' in line and 'scopeid 0x20<link>' in line:
                    # 调试输出
                    logging.debug(f"Processing line: {line}")
                    
                    # 使用特定正则表达式提取链路本地 IPv6 地址
                    matches = regex_link_local_ipv6.findall(line)
                    if matches:
                        ipv6 = matches[0]
                        logging.info(f"Found link-local IPv6 address with new regex: {ipv6}")
                        return ipv6
                    
                    # 如果特定正则表达式没有匹配到,尝试使用一般性正则表达式
                    matches = regex_ipv6.findall(line)
                    if matches:
                        ipv6 = matches[0]
                        logging.debug(f"Original regex matched: {ipv6}")
                        # 只返回链路本地地址
                        if ipv6.startswith('fe80'):
                            logging.info(f"Found link-local IPv6 address with original regex: {ipv6}")
                            return ipv6
                        elif 'fe80' in line:
                            # 如果行中包含fe80但匹配失败,记录错误
                            logging.warning(f"Regex failed to match fe80 in: {line}")
    except Exception as e:
        logging.warning("get_link_local_ipv6 FAILED, error: %s", str(e))
    return None

# 测试
if __name__ == '__main__':
    logging.basicConfig(level=logging.DEBUG)
    
    # 测试特定的 IPv6 地址匹配
    test_line = "inet6 fe80::e4d:e9ff:fec9:9de3  prefixlen 64  scopeid 0x20<link>"
    print("Testing regex with line:", test_line)
    
    # 测试链路本地特定正则
    matches = regex_link_local_ipv6.findall(test_line)
    if matches:
        print("Link-local regex matched:", matches[0])
    else:
        print("Link-local regex failed to match")
    
    # 测试一般 IPv6 正则
    matches = regex_ipv6.findall(test_line)
    if matches:
        print("General IPv6 regex matched:", matches[0])
    else:
        print("General IPv6 regex failed to match")
    
    print("\n--- Regular program output ---")
    print("Method 1 (httpbin):", get_ipv6_by_httpbin())
    print("Method 2 (icanhazip):", get_ipv6_by_icanhazip())
    print("Method 3 (ident.me):", get_ipv6_by_ident_me())
    print("Method 4 (ifconfig):", get_ipv6_by_ifconfig())
    print("Method 5 (socket):", get_ipv6_by_socket())
    print("Link-local IPv6:", get_link_local_ipv6())

获取到 v6 地址,就可以通过 tplink 的映射代码update_ipv6_nat_mapping进行地址映射了。

如果要用这个代码,需要根据自己的路由器配置获取映射的 id。

整体来说,速度还是可以的: 

 

The post 是 IPv6 吖 — V6 重生记 appeared first on obaby@mars.

  •  

超级自恋狂 — 定制终端 ssh 欢迎语

2025年2月13日 15:36

今天宝子开学了,其实这个假期她在家也没待几天。昨天折腾完浏览器控制台自定义输出之后,目光又转向了很久之前就想做,但是一直没做的终端以及 ssh 的定制输出。

这个东西简单做法非常简单,不外乎是配置 sshd 的banner 或者/etc/update-motd.d/下的相关文件,然而,我想要的不仅仅是输出几个文字那么简单,最起码要能达到类似于昨天做的控制台输出 ascii 字符画的效果。

然而,这个实现起来的确比上文提到的控制台输出要复杂的多。毕竟作为纯文本的终端系统要输出复杂字符或者直接输出图片难度还是挺大的。

最开始是想基于 neofetch 来做:

Neofetch is a command-line system information tool written in bash 3.2+. Neofetch displays information about your operating system, software and hardware in an aesthetic and visually pleasing way.

The overall purpose of Neofetch is to be used in screen-shots of your system. Neofetch shows the information other people want to see. There are other tools available for proper system statistic/diagnostics.

The information by default is displayed alongside your operating system’s logo. You can further configure Neofetch to instead use an image, a custom ASCII file, your wallpaper or nothing at all.

You can further configure Neofetch to display exactly what you want it to. Through the use of command-line flags and the configuration file you can change existing information outputs or add your own custom ones.

Neofetch supports almost 150 different operating systems. From Linux to Windows, all the way to more obscure operating systems like Minix, AIX and Haiku. If your favourite operating system is unsupported: Open up an issue and support will be added.

地址:https://github.com/dylanaraps/neofetch/

效果:

在实际测试的时候发现左侧的图片要想实现项目首页的这个效果,竟然无法做到,按照 wiki 的指导,支持下面的 backend:

Neofetch 3.0 included a rewrite of how we handle different modes (imageascii and etc) which allowed us to add additional image backends to Neofetch. Neofetch now supports displaying images using catimglibcacachafaiterm2jp2akittypixtermpotlibsixeltermpixtycat, and w3m.

然而,不管是 iterm2 还是w3m,实际实现效果都非常差。无法正常显示要加载的图片。后来发现 viu 可以在终端显示图片,然而效果嘛,看下来还是不行,不过放到终端里面,也算是能看出人形来了。

然而,困难之处在于 neofetch 无法使用 viu 作为 backend 显示图片。

并且这个项目已经停止更新了,按照作者的说法是回家种地了。就在这个结束不久之后又出现了一堆新的 fetch。

按照评价最高的就是 fastfetch,这个东西同样支持定制化的图片输出。

Fastfetch is a neofetch-like tool for fetching system information and displaying it prettily. It is written mainly in C, with performance and customizability in mind. Currently, Linux, Android, FreeBSD, macOS, SunOS and Windows 7+ are supported.

地址:https://github.com/fastfetch-cli/fastfetch?tab=readme-ov-file

这个东西是用 c 语言开发的,不再跟 neofetch 一样是纯 bash 脚本。至于运行效率提升,应该是有一些。

同样,看项目首页的效果还是不错的:

既然选定了目标,那就继续开始折腾吧。由于家里的电脑是 windows,所以选择了远程 ssh 到 ubuntu 服务器上进行配置的方式。在测试的过程中发现基于 w3m 无法在 ssh 终端中显示图像。一番搜索之后,发现了这么一个项目:

https://github.com/sqlsec/fastfetch/tree/main?tab=readme-ov-file
Fastfetch 是一个类似 neofetch 的工具,用于获取系统信息并漂亮地显示它。它主要用 C 语言编写,并考虑了性能和可定制性。本项目是一个 Fastfetch 轮子,主要是集成了宝可梦显示和其他系列的恶搞图片,目前只在 Linux 和 macOS 平台下测试过。

里面继承了一个宝可梦的字符图片生成。那么基于这个东西既然能在 ssh 中显示,那么也就是说如果能生成宝可梦类似的文本就可以正常显示所谓的图片了。

上面的图片虽然模糊,但是生成一个网站 logo 应该是可以的。宝可梦的字符串是基于这个项目创建的:

https://gitlab.com/phoneybadger/pokemon-colorscripts.git

翻了半天代码发现,并不是通过图片生成的文本,而是本身就已经是文本:

这就尴尬了,也就是说通过这个项目的代码将图片转为文本已经不可能了。

google 上搜索 convert image/picture to text 或者convert image/picture to ascii,发现返回的都是 ocr 相关的内容,这该死的人工智能,现在已经没人有这种需求了吗?我是真的要讲图片转为文本,而不是要识别图片上的文本。翻了数十页,有用的信息 一条也没有。

这时突然灵光一现,直接搜索转化的文本:

https://www.google.com/search?q=%5B38%3B2%3B0%3B0%3B0m%E2%96%84%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B99%3B132%3B173m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%E2%96%84++++++++++++++++++++%0D%0A++%1B%5B38%3B2%3B0%3B0%3B0m%E2%96%84%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B99%3B132%3B173m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B99%3B132%3B173m%1B%5B48%3B2%3B99%3B132%3B173m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B0%3B0%3B0m%E2%96%80%1B%5B0m+++++++++++++++%1B%5B38%3B2%3B0%3B0%3B0m%E2%96%84%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B0%3B0%3B0m%E2%96%80%1B%5B0m++%0D%0A+%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B0%3B0%3B0m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B99%3B132%3B173m%1B%5B48%3B2%3B99%3B132%3B173m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B99%3B132%3B173m%1B%5B48%3B2%3B99%3B132%3B173m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B99%3B132%3B173m%1B%5B48%3B2%3B65%3B65%3B65m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B65%3B65%3B65m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B189%3B189%3B189m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%E2%96%84%1B%5B38%3B2%3B0%3B0%3B0m%E2%96%84%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B99%3B132%3B173m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%E2%96%84+++%1B%5B38%3B2%3B0%3B0%3B0m%E2%96%84%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%E2%96%84%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B255%3B255%3B255m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B255%3B255%3B255m%1B%5B48%3B2%3B65%3B65%3B65m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B65%3B65%3B65m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%E2%96%84%1B%5B38%3B2%3B0%3B0%3B0m%E2%96%84%0D%0A%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B0%3B0%3B0m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B99%3B132%3B173m%1B%5B48%3B2%3B90%3B99%3B123m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B99%3B132%3B173m%1B%5B48%3B2%3B99%3B132%3B173m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B99%3B132%3B173m%1B%5B48%3B2%3B65%3B65%3B65m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B65%3B65%3B65m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B255%3B255%3B255m%1B%5B48%3B2%3B99%3B132%3B173m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B189%3B189%3B189m%1B%5B48%3B2%3B99%3B132%3B173m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B255%3B255%3B255m%1B%5B48%3B2%3B99%3B132%3B173m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B255%3B255%3B255m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B255%3B255%3B255m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B255%3B255%3B255m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B65%3B65%3B65m%1B%5B48%3B2%3B65%3B65%3B65m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B99%3B132%3B173m%1B%5B48%3B2%3B99%3B132%3B173m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B90%3B99%3B123m%1B%5B48%3B2%3B99%3B132%3B173m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B0%3B0%3B0m%E2%96%80%1B%5B0m+%1B%5B38%3B2%3B0%3B0%3B0m%E2%96%84%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B255%3B255%3B255m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B189%3B189%3B189m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B255%3B255%3B255m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B255%3B255%3B255m%1B%5B48%3B2%3B132%3B132%3B132m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B132%3B132%3B132m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B189%3B189%3B189m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B255%3B255%3B255m%1B%5B48%3B2%3B0%3B0%3B0m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%E2%96%80&newwindow=1&sca_esv=0fc7dd398656b67a&biw=2519&bih=1218&sxsrf=AHTn8zrYB8iVn-PuK5p4PazbOlDmwyh7KQ%3A1739371785317&ei=CbWsZ_H3Esjo1e8P25fugAY&ved=0ahUKEwixoJXrsL6LAxVIdPUHHduLG2A4HhDh1QMIEg&uact=5&oq=%5B38%3B2%3B0%3B0%3B0m%E2%96%84%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B99%3B132%3B173m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%E2%96%84++++++++++++++++++++%0D%0A++%1B%5B38%3B2%3B0%3B0%3B0m%E2%96%84%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B99%3B132%3B173m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B99%3B132%3B173m%1B%5B48%3B2%3B99%3B132%3B173m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B0%3B0%3B0m%E2%96%80%1B%5B0m+++++++++++++++%1B%5B38%3B2%3B0%3B0%3B0m%E2%96%84%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B0%3B0%3B0m%E2%96%80%1B%5B0m++%0D%0A+%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B0%3B0%3B0m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B99%3B132%3B173m%1B%5B48%3B2%3B99%3B132%3B173m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B99%3B132%3B173m%1B%5B48%3B2%3B99%3B132%3B173m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B99%3B132%3B173m%1B%5B48%3B2%3B65%3B65%3B65m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B65%3B65%3B65m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B189%3B189%3B189m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%E2%96%84%1B%5B38%3B2%3B0%3B0%3B0m%E2%96%84%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B99%3B132%3B173m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%E2%96%84+++%1B%5B38%3B2%3B0%3B0%3B0m%E2%96%84%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%E2%96%84%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B255%3B255%3B255m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B255%3B255%3B255m%1B%5B48%3B2%3B65%3B65%3B65m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B65%3B65%3B65m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%E2%96%84%1B%5B38%3B2%3B0%3B0%3B0m%E2%96%84%0D%0A%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B0%3B0%3B0m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B99%3B132%3B173m%1B%5B48%3B2%3B90%3B99%3B123m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B99%3B132%3B173m%1B%5B48%3B2%3B99%3B132%3B173m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B99%3B132%3B173m%1B%5B48%3B2%3B65%3B65%3B65m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B65%3B65%3B65m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B255%3B255%3B255m%1B%5B48%3B2%3B99%3B132%3B173m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B189%3B189%3B189m%1B%5B48%3B2%3B99%3B132%3B173m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B255%3B255%3B255m%1B%5B48%3B2%3B99%3B132%3B173m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B255%3B255%3B255m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B255%3B255%3B255m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B255%3B255%3B255m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B65%3B65%3B65m%1B%5B48%3B2%3B65%3B65%3B65m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B99%3B132%3B173m%1B%5B48%3B2%3B99%3B132%3B173m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B90%3B99%3B123m%1B%5B48%3B2%3B99%3B132%3B173m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B0%3B0%3B0m%E2%96%80%1B%5B0m+%1B%5B38%3B2%3B0%3B0%3B0m%E2%96%84%1B%5B38%3B2%3B0%3B0%3B0m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B255%3B255%3B255m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B189%3B189%3B189m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B255%3B255%3B255m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B255%3B255%3B255m%1B%5B48%3B2%3B132%3B132%3B132m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B132%3B132%3B132m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B189%3B189%3B189m%1B%5B48%3B2%3B255%3B255%3B255m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B255%3B255%3B255m%1B%5B48%3B2%3B0%3B0%3B0m%E2%96%80%1B%5B0m%1B%5B38%3B2%3B0%3B0%3B0m%E2%96%80&gs_lp=Egxnd3Mtd2l6LXNlcnAi-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&sclient=gws-wiz-serp

nice,总算是给了点提示,ansi color,或者 ansi 。继续搜索图片转 ansi,最后终于找到了一个在线工具:

https://dom111.github.io/image-to-ansi/

那么有了这个东西,就可以将图片转化为宝可梦类似的字符串了。

下面会给出对应的 sh 脚本代码,测试效果可以直接扔到终端去测试。

图片转化解决了,剩下的就是怎么写配置文件。搜索的时候发现了这么一篇文章,同样里面给出了上面的配置文件脚本的合集:

https://www.sqlsec.com/2024/09/ubuntu.html#

参考文章给的:https://github.com/sqlsec/fastfetch里面的配置文件,编写自己的配置文件。

将上面图片转 ansi 给出的 sh 脚本保存为 sh 文件,修改配置文件的 图片为下面的内容:

"modules": [
        {
            "type": "command",
            "text": "sh $HOME/.config/fastfetch/sh/logo-80.sh",
            "key": "\n"
        },

上面的代码没有在 logo 字段内设置,目前还没发现怎么在 logo 中执行命令,如果直接将生成的字符串贴到 logo 内会报错。

通过下面的命令验证配置文件,主要文件路径要复制到用户目录下的.config/fastfetch/中。

这个是基于 zsh 的,当然如果是 iterm 那么效果就更加惊艳了。如下面:

远程 ssh 效果:

为了简化上面的操作,于是我创建了一个开源项目,如果需要可以直接拉取代码针对性修改即可。

下面正文开始啦:

1.安装 fastfetch,具体安装方法,参考https://github.com/fastfetch-cli/fastfetch

Linux

 

Some distros package an outdated fastfetch version. Older versions receive no support, so please try always to use the latest version.

Packaging status

  • Ubuntu: ppa:zhangsongcui3371/fastfetch (for Ubuntu 22.04 or newer)
  • Debian: apt install fastfetch (for Debian 13 or newer)
  • Debian / Ubuntu: Download fastfetch-linux-<proper architecture>.deb from Github release page and double-click it (for Ubuntu 20.04 or newer and Debian 11 or newer).
  • Arch Linux: pacman -S fastfetch
  • Fedora: dnf install fastfetch
  • Gentoo: emerge --ask app-misc/fastfetch
  • Alpine: apk add --upgrade fastfetch
  • NixOS: nix-shell -p fastfetch
  • openSUSE: zypper install fastfetch
  • ALT Linux: apt-get install fastfetch
  • Exherbo: cave resolve --execute app-misc/fastfetch
  • Solus: eopkg install fastfetch
  • Slackware: sbopkg -i fastfetch
  • Void Linux: xbps-install fastfetch
  • Venom Linux: scratch install fastfetch

You may need sudodoas or sup to run these commands.

If fastfetch is not packaged for your distro or an outdated version is packaged, linuxbrew is a good alternative: brew install fastfetch

macOS

 

Windows

 

  • scoopscoop install fastfetch
  • Chocolateychoco install fastfetch
  • wingetwinget install fastfetch
  • MSYS2 MinGWpacman -S mingw-w64-<subsystem>-<arch>-fastfetch

You may also download the program directly from the GitHub releases page in the form of an archive file.

FreeBSD

 

  • pkg install fastfetch

Android (Termux)

 

  • pkg install fastfetch

2.拉取项目代码:git clone https://gitee.com/obaby/baby-fetch

将项目目录下的 fastfetch 复制到用户目录下的.config 目录下

cp -rf fastfetch ~/.config/

增加一键部署脚本,下载后直接执行 deploy.sh 即可,支持 bash, zsh 终端!后续步骤可以省略了!!!

增加一键部署脚本,下载后直接执行 deploy.sh 即可,支持 bash, zsh 终端!后续步骤可以省略了!!!

增加一键部署脚本,下载后直接执行 deploy.sh 即可,支持 bash, zsh 终端!后续步骤可以省略了!!!

直接修改 welcome.sh 修改加载配置文件皆可!

直接修改 welcome.sh 修改加载配置文件皆可!

直接修改 welcome.sh 修改加载配置文件皆可!

(venv) zhongling@ZhongLingdeMBP baby_fetch % /bin/bash /Users/zhongling/PycharmProjects/baby_fetch/deploy.sh
===========================================================================
Baby fetch自动部署工具
obaby@mar
https://h4ck.org.cn
https://oba.by
https://nai.dog
===========================================================================
Start deploy:
copy files......
*************************************
Write config to terminal profile:
process .bash_profile
already exists, skip
-------------------------------------
process .zshrc
already exists, skip
-------------------------------------
process .profile
already exists, skip
All done, have a nice day
===========================================================================

 

红字部分于2015.01.13 17.25 更新!

3.修改终端配置文件,在修改之前需要确定当前运行的终端是什么 bash,zsh 或者是其他的什么东西。

例如,我这里的终端是 zsh,运行 fastfetch 查看 shell 字段:

vim ~/.zshrc

添加以下代码:

if [ -f ~/.config/fastfetch/welcome.sh ]; then
    ~/.config/fastfetch/welcome.sh
fi

如果是 bash :

vim ~/.bash_profile

同理其他的终端修改对应的位置文件,执行 source ~/.zshrc加载配置文件,如果无误应该就可以看到效果了。

目前针对 macos welcome.sh 会针对是否运行在 iterm 内加载不同的配置文件:

#!/bin/bash
if [ "$TERM_PROGRAM" = "iTerm.app" ]; then
#    echo "当前运行在 iTerm 中"
     fastfetch -c ~/.config/fastfetch/config-obaby-mac-iterm-weding.jsonc
else
#    echo "当前没有运行在 iTerm 中"
    fastfetch -c ~/.config/fastfetch/config-obaby-logo-80.jsonc
fi

同样定制化加载的文件也可以通过修改 welcome.sh实现,或者直接修改这个文件加载的配置文件实现。

为了更好的体验效果,在 mac 下建议安装 iterm,linux 下建议安装kitty。

iterm 建议通过 homebrew 安装:

brew install iterm2

https://iterm2.com

https://sw.kovidgoyal.net/kitty/binary/

4.显示问题,如果显示有乱码建议安装包含自带字体图标的字体,可以通过这里下载:https://www.nerdfonts.com/font-downloads

项目中已经包含了两个字体压缩包,可以直接安装使用。

同样,针对 ssh 的欢迎语也可以通过修改终端配置文件实现,这么改的好处是,针对不同用户可以用户自己定义。并且不需要修改 sshd配置文件,无需重启服务。

测试环境:

Mac:

 CPU Apple M1
󰋵 GPU Apple Apple M1 Ghz
󰀚 Memory 13.46 GiB / 16.00 GiB (84%)
󰘳 Shell zsh v5.9
󰀚 Disk 200.69 GiB / 228.27 GiB (88%) – apfs [Read-only]
󰀚 Disk 5.06 MiB / 5.06 MiB (100%) – hfs [External, Read-only]
󰀚 Disk 766.07 MiB / 934.23 MiB (82%) – apfs [External, Read-only]
󰘳 Network en0 192.168.8.100/24
󱦟 OS Age 20132 days
󱫐 Uptime 41 days, 23 hours, 57 mins
 Machine MacBook Pro (13-inch, M1, 2020) (Apple Inc.)

Ubuntu:

󰀶 OSUbuntu 22.04.4 LTS x86_64
󱢌 Bios449e491 (0.0)
 KernelLinux 5.15.0-92-generic
󰏗 Packages1190 (dpkg), 3 (snap)
󰹑 Display1024x768 @ 60 Hz
 Terminalsshd

● ● ● ● ● ● ● ●

 CPUIntel(R) Xeon(R) Platinum
󰋵 GPUCirrus Logic GD 5446 Ghz
󰀚 Memory1.00 GiB / 1.65 GiB (61%)
󰘳 Shellbash v5.1.16
󰀚 Disk27.71 GiB / 39.01 GiB (71%) – ext4
󰘳 Networketh0 172.23.120.93/20
󱦟 OS Age 380 days
󱫐 Uptime 89 days, 20 hours, 50 mins
 MachineAlibaba Cloud ECS (Alibaba Cloud)

项目地址:

https://gitee.com/obaby/baby-fetch

https://github.com/obaby/baby-fetch

参考链接:

https://github.com/sqlsec/fastfetch

https://www.sqlsec.com/2024/09/ubuntu.html#%E5%AE%89%E8%A3%85%E6%95%99%E7%A8%8B

https://talyian.github.io/ansicolors/

https://gitlab.com/phoneybadger/pokemon-colorscripts

 

  •  

PIP Chill–更精简的依赖包导出工具

2024年8月26日 16:57

Make requirements with only the packages you need

项目导入的 module 越多,导出的依赖库就越多,尤其是很多系统自带的库一并给导出来来了。

pip freeze 导出效果:

asgiref==3.3.4
async-timeout==4.0.3
certifi==2021.5.30
chardet==4.0.0
coreapi==2.3.3
coreschema==0.0.4
Django==3.2.3
django-admin-lightweight-date-hierarchy==1.1.0
django-comment-migrate==0.1.5
django-cors-headers==3.10.1
django-crontab==0.7.1
django-export-xls==0.1.1
django-filter==21.1
django-ranged-response==0.2.0
django-redis==5.2.0
django-restql==0.15.4
django-simple-captcha==0.5.14
django-simpleui==2022.7.29
django-timezone-field==4.2.3
djangorestframework==3.12.4
djangorestframework-simplejwt==5.1.0
drf-yasg==1.20.0
et-xmlfile==1.1.0
idna==2.10
inflection==0.5.1
itypes==1.2.0
Jinja2==3.0.1
MarkupSafe==2.0.1
openpyxl==3.0.9
packaging==20.9
paho-mqtt==1.6.1
Pillow==8.3.1
PyJWT==2.1.0
PyMySQL==1.0.2
pyparsing==2.4.7
pyPEG2==2.15.2
pypinyin==0.46.0
pypng==0.20220715.0
pytz==2021.1
qrcode==7.4.2
redis==5.0.8
requests==2.25.1
ruamel.yaml==0.18.6
ruamel.yaml.clib==0.2.8
simplejson==3.18.4
six==1.16.0
smmap==4.0.0
sqlparse==0.4.1
typing-extensions==3.10.0.0
tzlocal==2.1
ua-parser==0.10.0
uritemplate==3.0.1
urllib3==1.26.6
user-agents==2.2.0
whitenoise==5.3.0
xlwt==1.3.0

pip-chill 导出效果:

django-admin-lightweight-date-hierarchy==1.1.0
django-comment-migrate==0.1.5
django-cors-headers==3.10.1
django-crontab==0.7.1
django-export-xls==0.1.1
django-filter==21.1
django-redis==5.2.0
django-restql==0.15.4
django-simple-captcha==0.5.14
django-simpleui==2022.7.29
django-timezone-field==4.2.3
djangorestframework-simplejwt==5.1.0
drf-yasg==1.20.0
encryptpy==1.0.5
openpyxl==3.0.9
paho-mqtt==1.6.1
pip-chill==1.0.3
pycryptodome==3.20.0
pymysql==1.0.2
pypinyin==0.46.0
qrcode==7.4.2
simplejson==3.18.4
tzlocal==2.1
user-agents==2.2.0
whitenoise==5.3.0

 

整体减掉了差不多一半多,同样在构建环境的时候也少了很多可能出问题的包,尤其是跨平台 install 的时候。

官方用法:

Suppose you have installed in your virtualenv a couple packages. When you run pip freeze, you'll get a list of all packages installed, with all dependencies. If one of the packages you installed ceases to depend on an already installed package, you have to manually remove it from the list. The list also makes no distinction about the packages you actually care about and packages your packages care about, making the requirements file bloated and, ultimately, inaccurate.

On your terminal, run:

$ pip-chill
bandit==1.7.0
bumpversion==0.6.0
click==7.1.2
coverage==5.3.1
flake8==3.8.4
nose==1.3.7
pip-chill==1.0.1
pytest==6.2.1
...
Or, if you want it without version numbers:

$ pip-chill --no-version
bandit
bumpversion
click
coverage
flake8
nose
pip-chill
pytest
...
Or, if you want it without pip-chill:

$ pip-chill --no-chill
bandit==1.7.0
bumpversion==0.6.0
click==7.1.2
coverage==5.3.1
flake8==3.8.4
nose==1.3.7
pytest==6.2.1
...
Or, if you want to list package dependencies too:

$ pip-chill -v
bandit==1.7.0
bumpversion==0.6.0
click==7.1.2
coverage==5.3.1
flake8==3.8.4
nose==1.3.7
pip-chill==1.0.1
pytest==6.2.1
sphinx==3.4.3
tox==3.21.1
twine==3.3.0
watchdog==1.0.2
# alabaster==0.7.12 # Installed as dependency for sphinx
# appdirs==1.4.4 # Installed as dependency for virtualenv
# attrs==20.3.0 # Installed as dependency for pytest
# babel==2.9.0 # Installed as dependency for sphinx

 

 

  •  
❌