Archive of

Cloudflare DNS Update Script

Motivation: though reinstalling OS is rare, but I still want to automate the DNS update process.

Get API Token

  1. Go to https://dash.cloudflare.com/profile/api-tokens
  2. Click "Create Token"
  3. Use "Edit zone DNS" template
  4. Select your domain in "Zone Resources"
  5. Copy the generated token

Setup API Token in bashrc

Add to ~/.bashrc:

export CF_API_TOKEN="your_api_token_here"

Reload: source ~/.bashrc

Enhanced Update Script

Create update-dns.sh:

#!/bin/bash

# Usage function
usage() {
    echo "Usage: $0 <record_name> [ip_address]"
    echo "       $0 <domain> <subdomain> [ip_address]"
    echo "Examples:"
    echo "  $0 home.example.com                    # Uses Tailscale IP"
    echo "  $0 example.com                         # Root domain with Tailscale IP"
    echo "  $0 server.example.com 192.168.1.100   # Uses specified IP"
    echo "  $0 example.com home                    # Legacy format"
    echo "  $0 example.com @ 1.2.3.4              # Legacy root domain"
    exit 1
}

# Check parameters
if [ $# -lt 1 ]; then
    usage
fi

# Check if API token is set
if [ -z "$CF_API_TOKEN" ]; then
    echo "Error: CF_API_TOKEN not set. Add it to ~/.bashrc"
    exit 1
fi

# Parse arguments - detect format
FIRST_ARG="$1"
SECOND_ARG="$2"
THIRD_ARG="$3"

# Check if first argument contains multiple dots (FQDN format)
if [[ "$FIRST_ARG" == *.*.* ]] || ([[ "$FIRST_ARG" == *.* ]] && [[ "$SECOND_ARG" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]); then
    # FQDN format: script.sh home.example.com [ip]
    RECORD_NAME="$FIRST_ARG"
    CUSTOM_IP="$SECOND_ARG"
    
    # Extract domain by removing first subdomain part
    DOMAIN=$(echo "$RECORD_NAME" | cut -d'.' -f2-)
elif [[ "$FIRST_ARG" == *.* ]] && [ -z "$SECOND_ARG" ]; then
    # Root domain format: script.sh example.com
    RECORD_NAME="$FIRST_ARG"
    DOMAIN="$FIRST_ARG"
    CUSTOM_IP=""
elif [[ "$FIRST_ARG" == *.* ]] && [ -n "$SECOND_ARG" ] && [[ ! "$SECOND_ARG" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
    # Legacy format: script.sh example.com home [ip]
    DOMAIN="$FIRST_ARG"
    SUBDOMAIN="$SECOND_ARG"
    CUSTOM_IP="$THIRD_ARG"
    
    if [ "$SUBDOMAIN" = "@" ]; then
        RECORD_NAME="$DOMAIN"
    else
        RECORD_NAME="$SUBDOMAIN.$DOMAIN"
    fi
else
    echo "Error: Invalid arguments format"
    usage
fi

# Get IP address
if [ -n "$CUSTOM_IP" ]; then
    NEW_IP="$CUSTOM_IP"
    echo "Using provided IP: $NEW_IP"
else
    if command -v tailscale >/dev/null 2>&1; then
        NEW_IP=$(tailscale ip -4)
        echo "Using Tailscale IP: $NEW_IP"
    else
        echo "Error: tailscale not found and no IP provided"
        exit 1
    fi
fi

# Validate IP format
if ! echo "$NEW_IP" | grep -E '^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$' >/dev/null; then
    echo "Error: Invalid IP address format: $NEW_IP"
    exit 1
fi

echo "Updating DNS record: $RECORD_NAME -> $NEW_IP"

# Get Zone ID
echo "Getting Zone ID for $DOMAIN..."
ZONE_RESPONSE=$(curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
    "https://api.cloudflare.com/client/v4/zones?name=$DOMAIN")

ZONE_ID=$(echo "$ZONE_RESPONSE" | jq -r '.result[0].id')

if [ "$ZONE_ID" = "null" ] || [ -z "$ZONE_ID" ]; then
    echo "Error: Could not find zone for domain $DOMAIN"
    echo "Response: $ZONE_RESPONSE"
    exit 1
fi

echo "Zone ID: $ZONE_ID"

# Get Record ID
echo "Getting Record ID for $RECORD_NAME..."
RECORD_RESPONSE=$(curl -s -H "Authorization: Bearer $CF_API_TOKEN" \
    "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?name=$RECORD_NAME&type=A")

RECORD_ID=$(echo "$RECORD_RESPONSE" | jq -r '.result[0].id')

if [ "$RECORD_ID" = "null" ] || [ -z "$RECORD_ID" ]; then
    echo "Error: Could not find A record for $RECORD_NAME"
    echo "Available records:"
    echo "$RECORD_RESPONSE" | jq -r '.result[] | "\(.name) (\(.type))"'
    exit 1
fi

echo "Record ID: $RECORD_ID"

# Update DNS record
echo "Updating DNS record..."
UPDATE_RESPONSE=$(curl -s -X PUT \
    "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RECORD_ID" \
    -H "Authorization: Bearer $CF_API_TOKEN" \
    -H "Content-Type: application/json" \
    --data "{\"type\":\"A\",\"name\":\"$RECORD_NAME\",\"content\":\"$NEW_IP\"}")

SUCCESS=$(echo "$UPDATE_RESPONSE" | jq -r '.success')

if [ "$SUCCESS" = "true" ]; then
    echo "✅ DNS record updated successfully!"
    echo "   $RECORD_NAME now points to $NEW_IP"
else
    echo "❌ Failed to update DNS record"
    echo "Response: $UPDATE_RESPONSE"
    exit 1
fi

Make executable: chmod +x update-dns.sh

Usage Examples

# New FQDN format (recommended)
./update-dns.sh home.example.com                    # Uses Tailscale IP
./update-dns.sh server.example.com 192.168.1.100   # Uses custom IP
./update-dns.sh example.com                         # Root domain with Tailscale IP

# Legacy format (still supported)
./update-dns.sh example.com home                    # Subdomain with Tailscale IP
./update-dns.sh example.com @ 1.2.3.4              # Root domain with custom IP

# Multiple subdomains
./update-dns.sh web.example.com
./update-dns.sh api.example.com
./update-dns.sh db.example.com 10.0.0.5

Features

  • ✅ API token stored securely in .bashrc
  • ✅ Auto-discovers Zone ID and Record ID
  • ✅ Supports custom IP or auto-detects Tailscale IP
  • ✅ Supports root domain updates with @
  • ✅ Input validation and error handling
  • ✅ Clear success/failure messages
  • ✅ Usage help and examples

Alternative: Using flarectl

# Install
go install github.com/cloudflare/cloudflare-go/cmd/flarectl@latest

# Update (simpler)
export CF_API_TOKEN="your_token"
flarectl dns update --zone yourdomain.com --name subdomain --content $(tailscale ip -4) --type A

PopOS System Recovery and Partition Migration Notes

Background

Yesterday, my PopOS system broke unexpectedly. The refresh install option failed, and attempting to reinstall while preserving the old /var and /home partitions was unsuccessful.

Solution: Fresh Custom Installation

Performed a clean custom installation with:

  • /boot/efi - formatted
  • / - formatted
  • /recovery - formatted

This approach worked successfully, then migrated data from old partitions.

Partition Migration Process

Tools Used

parted --list    # Get partition information
blkid           # Get partition UUIDs

Note: Couldn't stop GDM or enter single-user mode (system would hang). Performed migration on running system instead.

1. Home Partition Migration (/dev/nvme0n1p8)

sudo mv /home /home.bak          # Backup current home
sudo mkdir /home
sudo mount /dev/nvme0n1p8 /home
ls /home                         # Confirm correct partition
sudo blkid /dev/nvme0n1p8       # Get UUID
sudo vi /etc/fstab

Added to /etc/fstab:

UUID=32e4ed56-6ed9-4f14-9585-ffff54f997b2 /home ext4 defaults 0 2

Rebooted to verify system stability.

2. Backup Partition Setup (/dev/nvme0n1p10)

sudo parted --list
sudo blkid /dev/nvme0n1p10
sudo vi /etc/fstab

Added to /etc/fstab:

UUID=8aba0dd5-7058-4036-8a08-fcd1e0e002bc /backup ext4 defaults 0 2
sudo mount -a                   # Test mount configuration

3. Var Partition Migration (/dev/nvme0n1p7)

# Mount old var partition and backup data
sudo mkdir /mnt/var
sudo mount /dev/nvme0n1p7 /mnt/var
sudo tar -czpvf /backup/old-var-backup.tar.gz -C /mnt var

or backup docker/crontabs only

sudo tar -czpvf /backup/old-docker-backup.tar.gz -C /mnt var/lib/docker
sudo tar -czpvf /backup/old-cron-backup.tar.gz -C /mnt var/spool/cron

# Prepare new var partition
sudo umount /mnt/var
sudo mkfs.ext4 /dev/nvme0n1p7   # Format old var partition

# Copy current var data to new partition
sudo mkdir /mnt/new-var
sudo mount /dev/nvme0n1p7 /mnt/new-var
sudo cp -av /var/* /mnt/new-var/

# Update fstab and switch to new partition
sudo blkid /dev/nvme0n1p7
sudo vi /etc/fstab              # Add var partition entry
sudo mv /var /var.old && sudo mkdir /var && sudo mount -a

Restore old var data

# update system
apt update && apt upgrade

sudo apt purge nano # enable vi EDITOR in visudo
sudo apt install curl

# re-enable davidwei ALL=(ALL:ALL) NOPASSWD:ALL

# install docker
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor --yes -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"| sudo tee /etc/apt/sources.list.d/docker.list > /dev/null # add docker apt source
sudo apt update && sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# add current user to docker group
sudo usermod -aG docker davidwei

# restore old docker/cron data
# 1. Stop Docker services properly
sudo systemctl stop docker.socket
sudo systemctl stop docker.service

# 2. Extract docker data in one command
sudo tar -xzpf /backup/old-var-backup.tar.gz -C / var/lib/docker
sudo tar -xzpf /backup/old-var-backup.tar.gz -C / var/spoon/cron

or if there are spearate backups:

sudo tar -xzpf /backup/old-var-backup.tar.gz -C / var/lib/docker
sudo tar -xzpf /backup/old-var-backup.tar.gz -C / var/spool/cron/crontabs

sudo chmod og+rx /var/spool/cron/crontabs/
sudo chown davidwei:davidwei /var/spool/cron/crontabs/davidwei
sudo systemctl restart cron

# 3. Start Docker services
sudo systemctl start docker.service
sudo systemctl start docker.socket

# 4. Verify
docker volume ls
docker images
docker ps -a

# re-install tailscale / set no-expiry / rename to xps / change cloudflare dns to new IP
curl -fsSL https://tailscale.com/install.sh | sh

# re-install google-chrome
sudo dpkg -i ~/Downloads/google-chrome-stable_current_amd64.deb

# reinstall other utils
sudo apt install tree ncdu vim gnome-sushi mc clang ninja-build mesa-utils libgtk-3-dev cmake mysql-client-core-8.0 libstdc++-12-dev build-essential pkg-config htop mc ssh meld

# reinstall fcitx
sudo apt install fcitx5-rime fcitx5-chinese-addons

Key Lessons Learned

  • Fresh installation with custom partitioning was more reliable than trying to preserve old partitions during install
  • System migration on a running system worked fine when rescue mode wasn't accessible
  • Always backup critical data before formatting partitions
  • Test each partition mount configuration before proceeding to the next step

Status: Migration completed successfully. System running normally with all data preserved.

Quick Tip: Get GitHub PR Diffs Easily

Need to share changes from a GitHub PR (even closed ones)? Just add .diff or .patch to the PR URL:

Original URL:

https://github.com/owner/repo/pull/123

For diff format:

https://github.com/owner/repo/pull/123.diff

For patch format:

https://github.com/owner/repo/pull/123.patch

Both work for open, closed, or merged PRs. Perfect for code reviews, investigations, or sharing changes with others.

For LLM/AI analysis: Use .diff format - it's cleaner and more standardized than .patch which includes extra email headers.

Fix Rider WinForms Designer Build Locks

Problem

Rider's WinForms Designer locks files, preventing builds. It is super annoying!

Solution

  1. File → Settings (Ctrl+Alt+S)
  2. Tools → Windows Forms Designer
  3. Uncheck "Enable Windows Forms Designer"
  4. Apply and restart Rider

Emergency Fix

Kill locked processes:

taskkill /IM dotnet.exe /F

Alternative

Use Visual Studio for form design, Rider for code.

Apache Guacamole + PopOS RDP 搭建教程

使用 Apache Guacamole 和 PopOS 搭建基于网页浏览器的无客户端远程桌面访问系统的完整指南。

概述

  • Guacamole: 基于 Web 的远程桌面网关(HTML5,无需安装客户端软件)
  • 目标系统: PopOS 配置 xrdp 服务器
  • 访问方式: 任何设备通过浏览器访问
  • 支持协议: RDP、VNC、SSH、Telnet、Kubernetes 连接

1. PopOS RDP 服务器配置

安装和配置 xrdp

# 安装 xrdp(比 GNOME 自带的远程桌面更稳定)
sudo apt update
sudo apt install xrdp

# 启动并设置开机自启
sudo systemctl enable xrdp
sudo systemctl start xrdp

# 验证服务状态
sudo systemctl status xrdp

解决 PolicyKit 权限问题

创建文件 /etc/polkit-1/localauthority/50-local.d/45-allow-colord.pkla

[Allow Colord All Users]
Identity=unix-user:*
Action=org.freedesktop.color-manager.*
ResultAny=no
ResultInactive=no
ResultActive=yes

[Allow PackageKit All Users]
Identity=unix-user:*
Action=org.freedesktop.packagekit.*
ResultAny=no
ResultInactive=no
ResultActive=yes

可选:防火墙配置

# 如果使用 Docker 和局域网 IP,通常不需要开放端口
# 只有需要外部访问时才开放
sudo ufw allow 3389

2. Guacamole Docker 配置

更新的 Docker Compose 配置

services:
  guacdb:
    container_name: guacamoledb
    image: mariadb:10.11
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD: 'MariaDBRootPass2024'
      MYSQL_DATABASE: 'guacamole_db'
      MYSQL_USER: 'guacamole_user'
      MYSQL_PASSWORD: 'MariaDBUserPass2024'
    volumes:
      - db-data:/var/lib/mysql
    networks:
      - guacamole-network

  guacd:
    container_name: guacd
    image: guacamole/guacd:1.6.0
    restart: unless-stopped
    devices:
      - /dev/snd:/dev/snd # 映射声音设备
    networks:
      - guacamole-network

  guacamole:
    container_name: guacamole
    image: guacamole/guacamole:1.6.0
    restart: unless-stopped
    ports:
      - "8080:8080"
    environment:
      GUAC_AUDIO: "audio/L16"  # 改用更好的音频格式
      GUAC_AUDIO_RATE: "44100"  # 设置音频采样率
      GUACD_HOSTNAME: "guacd"
      MYSQL_HOSTNAME: "guacdb"
      MYSQL_DATABASE: "guacamole_db"
      MYSQL_USER: "guacamole_user"
      MYSQL_PASSWORD: "MariaDBUserPass2024"
      TOTP_ENABLED: "true"
      WEBAPP_CONTEXT: "ROOT"  # 允许直接通过 http://localhost:8080 访问
    depends_on:
      - guacdb
      - guacd
    networks:
      - guacamole-network

networks:
  guacamole-network:
    driver: bridge

volumes:
  db-data:

数据库初始化

# 1. 创建目录并设置
mkdir guacamole
cd guacamole

# 2. 生成数据库初始化文件
docker run --rm guacamole/guacamole:1.6.0 /opt/guacamole/bin/initdb.sh --mysql > initdb.sql

# 3. 启动服务
docker-compose up -d

# 4. 等待数据库准备就绪
sleep 30

# 5. 导入数据库架构
docker exec -i guacamoledb mysql -u root -pMariaDBRootPass2024 guacamole_db < initdb.sql

# 6. 验证表已创建
docker exec -it guacamoledb mysql -u root -pMariaDBRootPass2024 -e "USE guacamole_db; SHOW TABLES;"

3. Guacamole 配置

初始设置

  1. 访问 Guacamole:http://localhost:8080(注意:没有 /guacamole 后缀!)
  2. 默认登录:guacadmin / guacadmin
  3. 重要:立即更改默认密码

创建 RDP 连接

  1. 前往 设置连接新建连接

  2. 编辑连接

    • 名称:PopOS 桌面(或任意名称)
    • 协议:RDP
  3. 参数

    • 主机名:使用实际局域网 IP(例如:192.168.1.100
    • 端口:3389
    • 用户名:你的 PopOS 用户名
    • 密码:你的 PopOS 密码
  4. 可选设置

    • 安全模式:anyrdp
    • 忽略服务器证书:✓(勾选)
    • 禁用身份验证:✓(如果需要)

Guacamole 代理参数

  • 通常保持空白(主机名、端口、加密字段)
  • 只有在 guacd 运行在不同服务器时才需要配置

4. 关键网络配置说明

⚠️ Docker 网络注意事项

  • 在连接主机名中使用 IP 地址,不要用域名
  • 如果使用 Tailscale/VPN:使用物理网络 IP,不要用 VPN IP
  • 容器 DNS 解析可能指向无法访问的网络
  • Docker 可以通过局域网 IP 访问主机端口,无需防火墙规则

IP 地址查找示例

# 查找你的 PopOS IP
ip addr show | grep "inet 192"
# 在 Guacamole 连接中使用这个 IP,不要用 localhost 或域名

5. 测试

验证 RDP 服务

# 在 PopOS 上测试本地 RDP
telnet localhost 3389

测试 Guacamole 连接

  1. 登录 Guacamole Web 界面
  2. 点击你的连接
  3. 应该能在浏览器中看到 PopOS 桌面

故障排除

常见问题

  • PolicyKit 密码提示:添加上述 polkit 配置
  • 黑屏:在本地popOS上退出登录

调试命令

# 检查 xrdp 状态
sudo systemctl status xrdp

# 检查端口监听
sudo netstat -tlnp | grep 3389

# 检查 Guacamole 日志
docker logs guacamole
docker logs guacd

# 重启 PolicyKit(如果需要)
sudo systemctl restart polkitd
# 或者简单重启
sudo reboot

生产环境增强

Nginx 反向代理(可选)

添加 HTTPS 和自定义域名访问,支持 WebSocket:

server {
    listen 443 ssl;
    server_name your-domain.com;
    
    location / {
        proxy_pass http://localhost:8080;
        proxy_buffering off;
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $http_connection;
        access_log off;
    }
}

性能说明

  • 优秀适用场景:办公工作、编程、系统管理
  • 良好适用场景:局域网内一般桌面使用
  • 不适合场景:高帧率游戏、视频播放
  • 最佳环境:低延迟的本地网络

支持的协议

  • RDP:Windows/Linux 远程桌面
  • VNC:跨平台图形界面访问
  • SSH:终端访问和文件传输
  • Telnet:传统终端协议
  • Kubernetes:容器编排访问

安全考虑

  • 更改 Guacamole 默认密码 (问题不大,有二次验证)
  • 使用 HTTPS(添加反向代理)

实用价值

通过这个配置,你可以:

  • 在任何地方用任何设备(手机、平板、电脑)通过浏览器访问你的桌面
  • 统一的 Web 入口管理所有远程连接
  • 无需在每个设备安装各种客户端软件
  • 搭建个人的"云工作台"

本配置已经在 PopOS 22.04 上通过测试

主要参考来源: