Motivation: though reinstalling OS is rare, but I still want to automate the DNS update process.
Get API Token
- Go to https://dash.cloudflare.com/profile/api-tokens
- Click "Create Token"
- Use "Edit zone DNS" template
- Select your domain in "Zone Resources"
- 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
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.
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.
Problem
Rider's WinForms Designer locks files, preventing builds. It is super annoying!
Solution
- File → Settings (Ctrl+Alt+S)
- Tools → Windows Forms Designer
- Uncheck "Enable Windows Forms Designer"
- 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 搭建基于网页浏览器的无客户端远程桌面访问系统的完整指南。
概述
- 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 配置
初始设置
- 访问 Guacamole:
http://localhost:8080
(注意:没有 /guacamole 后缀!)
- 默认登录:
guacadmin
/ guacadmin
- 重要:立即更改默认密码
创建 RDP 连接
-
前往 设置 → 连接 → 新建连接
-
编辑连接:
- 名称:
PopOS 桌面
(或任意名称)
- 协议:
RDP
-
参数:
- 主机名:使用实际局域网 IP(例如:
192.168.1.100
)
- 端口:
3389
- 用户名:你的 PopOS 用户名
- 密码:你的 PopOS 密码
-
可选设置:
- 安全模式:
any
或 rdp
- 忽略服务器证书:✓(勾选)
- 禁用身份验证:✓(如果需要)
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 连接
- 登录 Guacamole Web 界面
- 点击你的连接
- 应该能在浏览器中看到 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 上通过测试
主要参考来源: