标签 docker 下的文章

这两年,大模型几乎成了开发者的“标配工具”:
写代码、查资料、做总结、当智能助手。

但你有没有认真想过一个问题:

我们真的必须把所有请求都发到云端 API 吗?

随着模型体积持续下降、硬件性能快速提升,以及 Ollama 这类工具逐渐成熟,
本地运行大模型,已经从早期的“极客尝鲜”,演进为一种可以在真实项目中落地的工程方案

这篇文章,我们就来完整走一遍:

如何使用 LangChain,基于最新 Runnable API,调用本地启动的 Ollama 模型,构建一个真正可用的本地大模型应用。

一、为什么选择 LangChain + Ollama?

先给结论:

Ollama 解决“模型怎么跑”,LangChain 解决“能力怎么用”。

这是目前本地大模型场景中,最自然、最稳定的一种组合方式


1️⃣ Ollama:本地大模型的“Docker”

你可以把 Ollama 理解为:
专门为大模型设计的一层运行时基础设施。

它解决的问题非常聚焦:

  • 统一模型的下载、管理与启动
  • 对外提供标准化 HTTP API(默认端口 11434
  • 支持 LLaMA、Qwen、Mistral、DeepSeek 等主流模型
  • Mac / Linux / Windows 全平台可用
  • 天然适合 Docker / 私有化部署

一句话总结:

Ollama 把“跑模型”这件事,做成了基础设施能力。

2️⃣ LangChain:AI 应用的“控制中心”

如果你只是想“问一句、回一句”,直接调 Ollama API 当然也没问题。
但一旦进入真实工程场景,需求会迅速复杂化:

  • Prompt 如何复用、版本化?
  • 对话上下文如何管理?
  • 如何组合多步推理?
  • 后续怎么接 RAG、Agent、工具调用?

这些正是 LangChain 擅长的事情:

  • Prompt 模板与结构化输入
  • Runnable / LCEL 编排能力
  • 对话历史(Memory)管理
  • Tool、RAG、Agent 的统一抽象
  • 可自然演进到 LangGraph

所以一个非常自然的分工是:

LangChain 负责“编排与逻辑”,Ollama 负责“模型与算力”。

二、准备工作:本地启动 Ollama 模型

1️⃣ 使用 Docker 部署 Ollama(推荐)

docker run \
-d \
--restart=always \
--name ollama \
--gpus=all \
-p 11434:11434 \
-v /home/data/ollama:/root/.ollama \
ollama/ollama

如果你对部署细节感兴趣,可以参考我之前的文章:

  • 《如何使用 Ollama 打造你的本地 AI 助手》
  • 《为本地部署的大模型添加 API Key 认证:Nginx 实现方案》

2️⃣ 拉取并运行模型

qwen3:8b 为例:

ollama pull qwen3:8b

简单测试:

ollama run qwen3:8b

如果可以正常对话,说明模型已经在本地成功运行。


三、LangChain 接入本地 Ollama(OpenAI 协议)

接下来进入核心部分:
如何用 LangChain 调用本地 Ollama?


1️⃣ 安装依赖

pip install langchain langchain-openai

这里我们使用 OpenAI 兼容协议,这是目前最稳定、生态最完整的一种方式。


2️⃣ 创建 Ollama LLM(ChatOpenAI)

from langchain_openai import ChatOpenAI

llm = ChatOpenAI(
    name="ollama-ai",
    model="qwen3:8b",
    base_url="http://localhost:11434/v1",
    api_key="your api key",
    temperature=0.7,
    timeout=300,
)

几个关键点说明:

  • model 必须与 Ollama 中的模型名称一致
  • base_url 指向 Ollama,并注意使用 /v1 后缀
  • 这里使用的是 OpenAI 标准协议,不是 Ollama 私有 API

3️⃣ 最简单的一次调用

response = llm.invoke("用一句话解释什么是 LangChain")
print(response)

到这里,你已经完成了:

LangChain → 本地 Ollama → 本地大模型

这条完整调用链。


四、进阶用法:Prompt + Runnable(LCEL)

在真实项目中,几乎不会直接“裸调”模型。


1️⃣ PromptTemplate

from langchain_core.prompts import PromptTemplate

prompt = PromptTemplate(
    input_variables=["question"],
    template="你是一个资深后端工程师,请用简洁、专业的语言回答:{question}",
)

2️⃣ 输出解析(StrOutputParser)

from langchain_core.output_parsers import StrOutputParser

parser = StrOutputParser()

显式的输出解析,是 LangChain 新 API 的重要特征:

  • 输出类型清晰
  • 便于后续切换为 JSON / Pydantic
  • 更适合工程化

3️⃣ Runnable 组合(推荐写法)

chain = prompt | llm | parser

response = chain.invoke({
    "question": "为什么本地部署大模型越来越流行?"
})
print(response)

这就是 LangChain 当前主推的 LCEL(表达式)写法
比早期的 LLMChain 更透明、也更可组合。


五、加入 Memory:真正的本地对话能力

⚠️ 一个非常重要的变化

在新的 Runnable 体系中,
Memory 不再是 Chain 的“隐藏参数”,而是显式的状态管理。


1️⃣ 定义对话历史存储

from langchain_core.chat_history import InMemoryChatMessageHistory

store = {}

def get_session_history(session_id: str):
    if session_id not in store:
        store[session_id] = InMemoryChatMessageHistory()
    return store[session_id]

2️⃣ Prompt 显式消费 history(关键)

from langchain_core.prompts import PromptTemplate

prompt = PromptTemplate(
    input_variables=["history", "question"],
    template="""
         你是一个资深后端工程师。

         以下是之前的对话历史:
         {history}

         当前用户问题:
         {question}

         请基于上下文给出连贯、准确的回答。
    """.strip()
)
这是很多人第一次使用 RunnableWithMessageHistory 时最容易忽略的一点:
历史是否生效,取决于 Prompt 是否显式使用 {history}

3️⃣ 构建带记忆的 Runnable

from langchain_core.runnables.history import RunnableWithMessageHistory

chain = prompt | llm | parser

chat_chain = RunnableWithMessageHistory(
    chain,
    get_session_history,
    input_messages_key="question",
    history_messages_key="history",
)

4️⃣ 调用(带 session_id)

config = {"configurable": {"session_id": "local-chat"}}

print(chat_chain.invoke(
    {"question": "什么是 Ollama?"},
    config=config
))

print(chat_chain.invoke(
    {"question": "它和 LangChain 有什么关系?"},
    config=config
))

到这里,你已经拥有了一个:

  • 支持上下文
  • 完全本地
  • 状态可控

的对话系统。

而且 所有数据都只存在你的本地机器上


六、这套方案适合谁?

非常适合:

  • ✅ 本地工具 / 桌面应用
  • ✅ 内部知识库 / 私有 RAG
  • ✅ 研发辅助工具(代码、文档、SQL)
  • ✅ 对数据安全敏感的企业场景
  • ✅ 学习大模型工程化的开发者

不太适合:

  • ❌ 超大并发场景
  • ❌ 极限性能 / 超大模型
  • ❌ 面向公网的 C 端产品

七、一些来自实践的工程建议

最后分享几点真实踩坑后的经验:

  1. 模型别贪大

    • 7B / 8B 是当前本地部署的性价比甜点位
  2. Prompt 比模型更重要

    • 本地模型对 Prompt 非常敏感
  3. LangChain 要“模块化使用”

    • Prompt / LLM / Parser / Memory 明确分层
  4. Memory 要可演进

    • InMemory → Redis → 数据库 → Checkpointer
  5. Ollama 非常适合私有化场景

    • Docker + 内网 + 权限控制,工程成本极低

结语

过去一年,我们讨论最多的问题是:

“该用哪个云端大模型?”

而现在,越来越多开发者开始认真思考:

“哪些能力,其实可以放回本地?”

LangChain + Ollama 并不是为了“替代云”,
而是为我们提供了一个:

真正可控、可组合、可落地的本地大模型方案。

如果你正在做:

  • 本地 AI 工具
  • 私有化大模型
  • Agent / RAG 工程实践

那么这套组合,非常值得一试。


如果你觉得这篇文章对你有帮助,欢迎 点赞 / 转发 / 收藏
下一篇,我会继续分享 LangGraph 在本地大模型场景下的实战用法

0.0.0.0 和 127.0.0.1 的区别:为什么改个 IP 就能通了?

刚开始部署服务到服务器(或者在 Docker 容器里跑应用)的时候,很多同学都遇到过这样一个“灵异事件”

你在服务器上启动了一个 Web 服务,默认配置监听 127.0.0.1:8080。你满怀信心地在服务器本地用 curl 测试,一切正常。但是当你回到自己的电脑,试图通过服务器的公网 IP 访问时,浏览器却转圈转到超时,死活连不上。

经过一番搜索,老鸟告诉你:“把监听地址改成 0.0.0.0 试试。”
你半信半疑地改了,重启服务——通了!

这时候你可能会纳闷:都是代表“本机”,为什么 127.0.0.1 对外不通,0.0.0.0 就可以?它们到底有什么本质区别?


🎯 核心差异:你是在“自言自语”还是“广而告之”?

如果不理解网络接口(Network Interface)的概念,这两个地址看起来确实很像。但实际上,它们的监听范围完全不同。

我们可以用一个简单的比喻:

  • 127.0.0.1(回环地址):就像你在写日记

    • 只有你自己能看(本机访问)。
    • 无论你怎么喊,房间外面的人(外部网络)都听不到。
  • 192.168.x.x(局域网 IP):就像你在会议室里发言

    • 会议室里的人(同网段机器)能听到。
    • 会议室外面的人听不到。
  • 0.0.0.0(通配符地址):就像你在全频道广播

    • 你同时在写日记、在会议室发言、拿着大喇叭对着窗外喊。
    • 所有能连接到你的渠道,都能听到你的声音。

🧠 底层原理:Socket 绑定的艺术

在操作系统层面,服务器程序启动时需要创建一个 Socket 并绑定(Bind)到一个 IP 和端口上。这个“绑定”动作决定了操作系统会将哪些数据包交给这个进程处理。

1. 绑定 127.0.0.1

// 伪代码 (Go 语言)
net.Listen("tcp", "127.0.0.1:8080")

当你绑定 127.0.0.1 时,你告诉操作系统:“只接收目标地址是 127.0.0.1 的数据包。”
因为 127.0.0.1 是一个虚拟的回环接口(Loopback Interface),物理网卡(网线插口/Wi-Fi)根本不认识它。外部请求的数据包目标 IP 是你的局域网 IP(如 192.168.1.5)或公网 IP,操作系统一看:“这包是给 192.168.1.5 的,但那个进程只接 127.0.0.1 的客”,于是直接丢弃或拒绝。

2. 绑定 0.0.0.0 (INADDR_ANY)

// 伪代码 (Go 语言)
net.Listen("tcp", "0.0.0.0:8080")

0.0.0.0 在服务端编程中是一个特殊的通配符,代表“本机的所有 IP 地址”。
当你绑定它时,你告诉操作系统:“只要是发给这台机器的,不管目标 IP 是回环地址、局域网 IP 还是公网 IP,统统交给我处理。”


🔍 图解:数据包是如何“迷路”的

当你监听 0.0.0.0 时:

graph TD
    User[外部用户] -->|访问 192.168.1.5| NIC[物理网卡 eth0<br>192.168.1.5]
    Local[本机客户端] -->|访问 127.0.0.1| LO[回环接口 lo<br>127.0.0.1]
    
    NIC --> App[你的应用<br>监听 0.0.0.0:8080]
    LO --> App
    
    style App fill:#d4edda,stroke:#28a745,stroke-width:2px

当你监听 127.0.0.1 时:

graph TD
    User[外部用户] -->|访问 192.168.1.5| NIC[物理网卡 eth0<br>192.168.1.5]
    Local[本机客户端] -->|访问 127.0.0.1| LO[回环接口 lo<br>127.0.0.1]
    
    NIC -.->|❌ 被操作系统拦截| App[你的应用<br>监听 127.0.0.1:8080]
    LO --> App
    
    style App fill:#f8d7da,stroke:#dc3545,stroke-width:2px

💻 最常见的“坑”:Docker 容器

这是新人最容易踩坑的场景。

错误配置:
你在 Docker 容器里的代码写死监听 127.0.0.1

// main.go
http.ListenAndServe("127.0.0.1:5000", nil)

后果:
容器启动了,端口映射也做了(-p 5000:5000),但外部就是访问不了。

为什么?
因为 Docker 容器本身就是一个独立的网络环境(Network Namespace)。

  • 容器里的 127.0.0.1容器自己的回环接口
  • Docker 转发流量时,是从宿主机转发到容器的虚拟网卡(eth0)上。
  • 你的应用只监听了容器的“日记本”(lo),却无视了容器的“大门”(eth0)。

正确姿势:
在容器内,必须监听 0.0.0.0

// main.go
http.ListenAndServe("0.0.0.0:5000", nil)

🛡️ 安全思考:为什么不永远用 0.0.0.0?

既然 0.0.0.0 这么方便,为什么默认配置里(比如 Redis、MongoDB)经常还是 127.0.0.1

为了安全(Security by Default)。

想象一下,你在公司服务器上装了个 Redis 做缓存,没设密码。

  • 如果你监听 0.0.0.0:所有知道你服务器 IP 的人(包括公网上的黑客扫描器)都能直连你的 Redis,轻松拿走数据或植入挖矿脚本。
  • 如果你监听 127.0.0.1:只有这台服务器上的其他应用(比如你的后端代码)能访问 Redis。外部黑客扫描到了端口也连不上。

最佳实践案例:

  • Nginx/对外 API:监听 0.0.0.0(需要对外服务)。
  • 数据库/Redis/内部 Admin:监听 127.0.0.1(仅限本机微服务调用)。

📝 总结:一张表看懂怎么选
监听地址含义谁能访问?适用场景
127.0.0.1绑定回环接口只有本机的进程数据库、缓存、内部消息队列、本地调试
192.168.x.x绑定特定网卡同一局域网内的机器内网服务、公司内部工具
0.0.0.0绑定所有接口任何人(取决于防火墙)对外 Web 服务器、Docker 容器内部应用
💡 面试官的加分项

下次面试官问这个问题,你可以这样“降维打击”:

“这本质上是 Socket 绑定时 INADDR_LOOPBACKINADDR_ANY 的区别。
127.0.0.1 只能处理回环流量,数据包不走物理网卡;
而 0.0.0.0 是一个通配符,它让操作系统把所有网卡收到的、目标端口匹配的数据包都交给进程。
在云原生环境下,这个区别尤为重要,因为 Pod 或容器默认必须监听 0.0.0.0 才能接收来自 Service 或 Ingress 的流量,否则探针(Probe)会直接失败。”

懂了吗?想让世界听到你的声音,记得拿起广播(0.0.0.0),而不是躲在被窝里写日记(127.0.0.1)!

掘金、思否

⚡️ 别把时间浪费在低效复习上

很多人复习抓不住重点。作为过来人,我分析了100+份大厂面试记录,将 Go/Java/AI 的核心考察点、高频题、易错点 浓缩进了一份 PDF。

不搞虚的,全是干货。

加我微信:wangzhongyang1993,备注 【面经】 免费发你,立即纠正你的复习方向,把时间用在刀刃上。

前言

自己用过很多自托管的导航页,始终没有找到适合自己心意的,要么太重,要么太简陋。于是通过 Vibe coding 设计了一个满足自己的导航页,多说无益,欢迎大家来使用。

✨ 核心特性

  • 书签和仪表盘:书签采用可收缩侧边栏的形式,最大化的留出仪表盘空间。
  • 多端同步:所有终端设备浏览器访问的是同一个布局,同一个书签库,在任何设备上编辑会自动同步。
  • 自由布局:基于 Grid 网格,目前已开发备忘录,待办事项,日历,天气等小组件,大小随便拉。
  • 私有部署:提供 Docker 镜像,数据全部存在你自己的 VPS 或 NAS 上 (JSON 格式),没有广告,没有追踪。

🚀 快速部署

docker run -d -p 3000:3000 -v ./data:/app/data ghcr.io/wtfllix/navidash:latest

🔗 链接

在线演示: navidash.vercel.app

GitHub: github.com/wtfllix/navidash (求个 Star 🌟)

Sample:

  1. 物理机直接安装 ubuntu, 所有应用都部署在 docker
  2. ssh 只允许密钥登录, 禁止 root 用户登录
  3. 所有访问( http, tcp)都通过 nginx 代理, ufw 只暴露固定的几个端口, nginx 开启 https 证书
  4. nginx 配置 geolite2, 禁止任何 国外 ip 访问, 异常访问基本都是国外 ip
  5. fail2ban 自动封禁所有 nginx 日志里面国外 ip
  6. 不安装 1panel,宝塔等任何 web 管理工具, 直接 ssh 到机器上命令行管理

分享下我的 nginx 配置

load_module "modules/ngx_http_geoip2_module.so";
load_module "modules/ngx_stream_geoip2_module.so";

worker_processes 4;

error_log /var/log/nginx/nginx_error.log;
error_log /var/log/nginx/nginx_error.log notice;
error_log /var/log/nginx/nginx_error.log info;

pid /var/log/nginx/nginx.pid;

events {
    worker_connections 1024;
}


http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    geoip2 /etc/nginx/geoip/GeoLite2-Country.mmdb {
      auto_reload 24h;
      $geoip_country_code  default=Unknown source=$remote_addr country iso_code;
      $geoip_country_name  country  names  en;
    }
    geoip2 /etc/nginx/geoip/GeoLite2-City.mmdb {
      auto_reload 24h;
      $geoip_city   default=Unknown city names en;
    }

    map $geoip_country_code $allowed_country {
        default no;
        CN yes;
    }

    map $remote_addr $allowed {
        default $allowed_country;
        127.0.0.1 yes;
        ~^192\.168\.\\d+\.\\d+$ yes;
        ~^172\.16\.0\.\\d+$ yes;
        ~^172\.17\.\\d+\.\\d+$ yes;
    }

    map $http_upgrade $connection_upgrade {
        default upgrade;
        '' "";
    }

    log_format json_analytics escape=json '{'
    '"timestamp": "$msec", ' # request unixtime in seconds with a milliseconds resolution
    '"request_id": "$request_id", ' # the unique request id
    '"request_length": "$request_length", ' # request length (including headers and body)
    '"body_bytes_sent": "$body_bytes_sent", '
    '"remote_addr": "$remote_addr", ' # client IP
    '"time_iso8601": "$time_iso8601", '
    '"request_uri": "$request_uri", ' # full path and arguments if the request
    '"code": "$status", ' # response status code
    '"http_host": "$http_host", ' # the request Host: header
    '"server_name": "$server_name", ' # the name of the vhost serving the request
    '"request_time": "$request_time", ' # request processing time in seconds with msec resolution
    '"upstream": "$upstream_addr", ' # upstream backend server for proxied requests
    '"request_method": "$request_method", ' # request method
    '"allowed": "$allowed", '
    '"geoip_country_code": "$geoip_country_code", '
    '"geoip_country_name": "$geoip_country_name", '
    '"geoip_city": "$geoip_city"'
    '}';

    access_log /var/log/nginx/access.log json_analytics;
    error_log /var/log/nginx/error.log warn;

    set_real_ip_from 0.0.0.0/0;
    real_ip_header X-Real-IP;
    real_ip_recursive on;

    sendfile on;
    server_tokens off;
    keepalive_timeout 65;

    gzip on;
    gzip_vary on;
    gzip_comp_level 4;
    gzip_min_length 256;
    gzip_proxied expired no-cache no-store private no_last_modified no_etag auth;
    gzip_types application/atom+xml application/javascript application/json application/ld+json application/manifest+json application/rss+xml application/vnd.geo+json application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml application/xml font/opentype image/bmp image/svg+xml image/x-icon text/cache-manifest text/css text/plain text/vcard text/vnd.rim.location.xloc text/vtt text/x-component text/x-cross-domain-policy;

    proxy_buffering off;
    proxy_buffers 4 128k;
    proxy_buffer_size 256k;
    proxy_busy_buffers_size 256k;



    ssl_session_timeout 5m;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers ALL:!DH:!EXPORT:!RC4:+HIGH:+MEDIUM:-LOW:!aNULL:!eNULL;

    ssl_certificate /etc/nginx/ssl/fullchain.cer;
    ssl_certificate_key /etc/nginx/ssl/xxx.cc.key;


    include /etc/nginx/conf.d/*.conf;

}


stream {

    geoip2 /etc/nginx/geoip/GeoLite2-Country.mmdb {
      auto_reload 24h;
      $geoip_country_code  default=Unknown source=$remote_addr country iso_code;
      $geoip_country_name  country  names  en;
    }
    geoip2 /etc/nginx/geoip/GeoLite2-City.mmdb {
      auto_reload 24h;
      $geoip_city   default=Unknown city names en;
    }



    map $geoip_country_code $allowed_country {
        default no;
        CN yes;
    }

    map $remote_addr $allowed {
        default $allowed_country;
        127.0.0.1 yes;
        ~^192\.168\.\\d+\.\\d+$ yes;
        ~^172\.16\.0\.\\d+$ yes;
        ~^172\.17\.\\d+\.\\d+$ yes;
    }

    log_format json_analytics escape=json '{'
    '"timestamp": "$msec", ' # request unixtime in seconds with a milliseconds resolution
    '"connection": "$connection", ' # connection serial number
    '"pid": "$pid", ' # process pid
    '"remote_addr": "$remote_addr", ' # client IP
    '"remote_port": "$remote_port", ' # client port
    '"time_iso8601": "$time_iso8601", ' # local time in the ISO 8601 standard format
    '"upstream": "$upstream_addr", '
    '"protocol": "$protocol", '
    '"allowed": "$allowed", '
    '"request_method": "STREAM", '
    '"geoip_country_code": "$geoip_country_code", '
    '"geoip_country_name": "$geoip_country_name", '
    '"geoip_city": "$geoip_city"'
    '}';

    access_log /var/log/nginx/access.log json_analytics;
    error_log /var/log/nginx/error.log warn;

    include /etc/nginx/stream.d/*.conf;
}

ssh 代理

map $allowed $ssh_server {
    yes ssh;
}

upstream ssh {
    server  192.168.5.1:1234;
}

server {
    listen    5678;
    listen [::]:5678;
    proxy_pass $ssh_server;
    proxy_connect_timeout 30s;
    proxy_timeout 60s;

    ssl_preread on;
}

http 代理

server {
    server_name x.x.com;
    listen 1233 ssl;
    listen [::]:1233 ssl;

    http2 on;
    charset "utf-8";

    if ($allowed != yes) {
        return 404;
    }

    error_page 497 =307 https://$host:$server_port$request_uri;

    client_max_body_size 512M;
    proxy_buffering off;


    set $backend "http://192.168.5.1:1234";
    include /etc/nginx/conf.d/basic/no_log.conf;

    location / {
        proxy_redirect off;
        proxy_set_header Host $host:$server_port;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_pass $backend;
    }

}

点赞 + 关注 + 收藏 = 学会了

整理了一个NAS小专栏,有兴趣的工友可以关注一下 👉 《NAS邪修》

有些非技术出身的工友入手NAS后,只用来存照片、存电影,却不知道开启SSH后,NAS能变得更强大、更好用。其实SSH一点都不复杂,不用懂代码、不用搞技术,跟着这篇教程,就能轻松开启SSH,用电脑远程操控NAS。

先搞明白核心问题:我们为什么要费功夫开启SSH?

SSH简单说就是「安全的远程操控通道」。相当于给你的NAS装了一把“远程钥匙”,不用NAS客户端,不用浏览器也能让电脑操控NAS做一些事情。

有时候用 Docker 客户端拉不下的镜像,用 ssh 的方式可能能拉下来(跳过网页端或者客户端的限制和缓存)。

本文聊聊如何在 NAS 开启 SSH,并且用电脑的终端连上去。至于连上去之后能做什么,以后的文章会讲到。

NAS 开启 SSH

不管你用的是什么品牌的 NAS,开启 SSH 的核心逻辑都都差不多。

我手上只有绿联和群晖这两个品牌的 NAS,所以只介绍这两台。

绿联的话,在「控制面板 - 终端机」里启用 SSH,然后点击“应用”就能开启了。

端口可以自定义,默认是 22

在「高级设置」里面还可以设置是否允许在外网的情况下访问。

在群晖这边其实也是差不多,「控制面板 - 终端机和 SNMP」,然后勾选“启动 SSH 功能”即可。

用终端连 NAS

如果你使用 Windows 电脑,系统自带的 powershell (可以在开始菜单里面搜索)可以用 SSH 的方式连接 NAS。

macOS 的话就使用「终端」。

用法都是一样的。

ssh username@NAS_IP -p port

# 翻译过来就是
ssh 用户名@NAS的IP -p 端口(例如22)

我在内网使用,我的 NAS 的 IP 是 192.168.31.202,SSH 配置的端口是 22,所以整句命令就是 ssh 用户名@192.168.31.202 -p 22

需要注意,这里的“用户名”指的是你在 NAS 登录时用到的用户名。

首次登录的话会问你知不知道自己在干嘛,回复 yes 即可。

然后要输入密码,这里输入的密码是不会展示出来的,不管你输入什么展示的都是“空白”,但其实你按的每一个键都成功输入的。

输入的密码正确的话,就能连上 NAS 了。

连上的标志是⬇️

用户名@NAS主机名:~$

接下来就可以在终端控制 NAS 了。


以上就是本文的全部内容啦,有疑问可以在评论区讨论~

想了解更多NAS玩法可以关注《NAS邪修》👏

点赞 + 关注 + 收藏 = 学会了

点赞 + 关注 + 收藏 = 学会了

整理了一个NAS小专栏,有兴趣的工友可以关注一下 👉 《NAS邪修》

Haptic 是一款开源极简的 Markdown 编辑器区别于传统编辑器的 “编辑 / 预览分屏” 模式,它能实时渲染 Markdown 语法(输入即显示最终效果,无割裂感),界面极简无多余干扰,还支持丰富格式,操作流畅如普通文本编辑,比同类工具更贴近 “自然书写” 的体验。

我很喜欢 Typora 这种”所见即所得“的编辑器,Typora 收费后我一直找同类产品。对于我来说在编辑体验方面 Haptic 是能取代 Typora 的。

这次我用群晖的 NAS 部署 Haptic,其他品牌的 NAS 部署流程差不多。

在”File Station“的”docker“文件夹下创建一个”haptic“文件夹。

打开”Container Manager“,新增一个「项目」。

填入以下信息。

输入以下代码,然后点击“下一步”。

services:
  haptic:
    image: chroxify/haptic-web:latest
    container_name: haptic
    ports:
      - 3002:80

「网页门户设置」这里要开启“通过 Web Station 设置网页门户”。

完成上述操作后,打开”Web Station“(没有的话就去「套件中心」下载),新增一个”网络门户“。填入以下信息。

注意,端口要设置一个和其他项目不冲突的数字,比如我这里设置的是 2388

完成上面的操作后,等待 Haptic 镜像下载成功后,打开浏览器输入 你NAS的IP + haptic端口 就能使用 Haptic 了。

比如我的是 http://192.168.31.85:2388


以上就是本文的全部内容啦,有疑问可以在评论区讨论~

想了解更多NAS玩法可以关注《NAS邪修》👏

点赞 + 关注 + 收藏 = 学会了

之前一直用 Redir-host ,但是最近发现,openclash 默认推荐 FakeIP ,包括 Surge 也是用的 FakeIP ,尝试把 openclash 改成了 FakeIP 模式,感受是网页载入是要快一些。
结果问题接踵而来,最开始是 DDNS 全部失效,尝试把我的域名加入 FakeIP 加入到 FakeIP Filter 。ddns 暂时恢复。
接下来就是 Docker ,部署在 NAS Docker 里面的服务全部无法访问。NAS 也无法通过域名访问。
大家用的什么模式的代理方式呢?

刚出的榜单,Go掉得挺多

今年1月的TIOBE编程语言排行榜出来了。有个事儿挺显眼的,Go语言这次排到了第16名。

要知道,2024年11月它还在第7名呢,这才过了多久,直接掉了9名。

很多写Go的朋友看到这个可能心里会犯嘀咕:这语言是不是不行了?以后还能不能用它找工作了?

咱们先别急着下结论。

在讨论这个问题之前,咱们得先搞清楚这个榜单到底是怎么回事,这次排名下降是不是真的代表Go语言出了大问题。

TIOBE指数到底是啥?

TIOBE这个榜单,它统计的数据来源其实是各大搜索引擎。

简单说,就是看有多少人在百度、谷歌、必应这些地方搜这门语言的名字。

它反映的是一种“搜索热度”。这里面有个逻辑大家要明白:一门语言搜的人多,不代表用的人就多;

反过来,搜的人少,也不代表用的人就少。

通常什么样的人会去搜?新手刚开始学的时候,或者遇到报错搞不定的时候,搜得最多。

如果一门语言大家都会用了,或者它运行很稳定、没啥新花样,大家反倒不去搜了。

所以,TIOBE的排名主要代表的是大家对这门语言的“好奇心”和“陌生感”,而不是它在实际项目里的使用率。

为啥这次Go掉到了第16?

那Go语言这次为啥排名掉得这么明显?我觉得有这么几个实实在在的原因。

Rust语言现在确实很受欢迎

在这次榜单上,Rust排到了第13名。Rust在安全性、系统底层开发这些方面确实有优势,吸引了很多开发者的注意力。

本来有些可能打算学Go的人,现在可能转头去研究Rust了。大家的关注点分散了,搜Go的人自然就少了一些。

还有就是Go语言现在太“稳”了

它现在的版本兼容性做得很好,依赖管理也成熟了。以前大家可能会经常搜“Go怎么配置环境”、“Go这个库怎么用”,现在这些问题都解决了,不需要老去搜。

而且,Go语言现在主要用在服务器后台、云计算这些地方。大家用Docker、用Kubernetes,底层其实都是Go写的,但大家平时操作的是命令行,不需要直接去写Go代码,也就不会去搜它。

实际情况到底怎么样?

排名虽然掉了,但咱们看看实际工作中的情况。

现在的互联网公司,特别是做后端开发的,用Go的还是非常多。像很多大厂的核心系统,依然是用Go在写。

在云原生这个领域,Go的位置目前还是很稳固的,没什么语言能轻易替代它。

Go语言有个很大的优点,就是简单、直接。代码写起来快,跑起来性能也不错,维护起来也方便。

对于公司来说,这能省成本,能提高效率。只要这个优势还在,公司就不会轻易把它换掉。

总结

所以看到排名下降,不用太担心。这个榜单反映的是当下的关注度和话题度,不是实际的市场占有率。

Go语言现在进入了一个平稳发展的阶段,不像刚出来时那么有新鲜感,但它在实际工作中还是非常有用的。

大家该学还是学,该用还是用。选编程语言,看的是能不能解决实际问题,能不能帮你把活干好,而不是看它在榜单上排第几。

只要它还能帮你高效地开发系统,它就是有价值的。

⚡️ 别把时间浪费在低效复习上

很多人复习抓不住重点。作为过来人,我分析了100+份大厂面试记录,将 Go/Java/AI 的核心考察点、高频题、易错点 浓缩进了一份 PDF。

不搞虚的,全是干货。

加我微信:wangzhongyang1993,备注 【面经】 免费发你,立即纠正你的复习方向,把时间用在刀刃上。

Clawdbot:爆火的开源AI智能体网关,堪称AI助理完全体

最近,你的技术圈子是不是被一只“龙虾”(Clawdbot 的 Logo)刷屏了?甚至听说它让二手的 Mac Mini 价格都应声上涨?

作为一个刚入坑稀土掘金的新人,今天我就带大家扒一扒这个让无数极客彻夜未眠的开源项目——Clawdbot。它到底是什么?为什么它被称为“AI 助理的完全体”?以及,它真的能成为你的 Jarvis 吗?

🧐 什么是 Clawdbot?

简单来说,Clawdbot 是一个开源的 AI 智能体网关(Agent Gateway)。

如果不讲术语,你可以这样理解:
Clawdbot = 大模型的大脑 (Claude/GPT) + 即时通讯软件的嘴巴 (Telegram/WhatsApp) + 本地电脑的手脚 (Terminal/文件系统) + 永久记忆

与我们在网页上用的 ChatGPT 或 Claude 不同,Clawdbot 不是运行在浏览器里的,而是运行在你自己的服务器或电脑(如 Mac Mini、树莓派)上的一个后台程序。它就像一个住在你电脑里的“数字管家”,你通过聊天软件给它发指令,它在你的电脑上直接干活。

🌟 核心特点:为什么它如此特别?

Clawdbot 之所以能爆火,是因为它解决了当前 AI 应用的几个核心痛点:

1. 它是“活”在本地的 (Local First)

目前大多数 AI 都在云端,不仅有隐私顾虑,而且无法操作你的本地文件。Clawdbot 运行在你的本地设备上:

  • 数据隐私:除了与 LLM 对话的内容,你的记忆文件、配置、本地数据都存在自己硬盘里。
  • 本地权限:它可以直接读取你的文档、运行 Python 脚本、甚至执行终端命令(Terminal)。

2. 对话即交互 (ChatOps)

你不需要下载专门的 App。Clawdbot 接入了 WhatsApp, Telegram, Discord, Slack, iMessage 等几乎所有主流通讯软件。

  • 场景:你在外面用手机给家里的 Clawdbot 发微信:“帮我查一下服务器日志,把报错的部分发给我。”
  • 结果:它直接通过 SSH 连上服务器,跑完命令,把结果截图或文本回传给你。

3. 真正的“长短期记忆”

Clawdbot 使用本地的 Markdown 文件(通常是 MEMORY.md)来存储关于你的信息。
它记得你的偏好、你家人的生日、你的服务器密码(需谨慎)、你正在做的项目进度。
这种记忆是持久的,不会因为关闭窗口就消失。

4. 强大的工具调用能力 (Agentic Capabilities)

这是它最“炸裂”的地方。它不仅能陪聊,还能干活。通过 MCP (Model Context Protocol) 或内置工具,它可以:

  • 浏览网页:帮你查资料并总结。
  • 写代码并运行:它可以写一个 Python 脚本来处理 Excel 表格,然后直接在你电脑上运行这个脚本,最后把处理好的 Excel 发给你。
  • 管理日程:读取你的日历,帮你安排会议。

🛠 Clawdbot 能帮我们干什么?

这就是想象力发挥的地方了。目前社区里已经有了很多硬核玩法:

1. 24/7 个人秘书

  • 自动处理邮件:让它监控你的 Gmail,自动归档垃圾邮件,把重要邮件摘要发到 Telegram 给你。
  • 每日简报:每天早上 8 点,它会根据你的日历、关注的新闻源、天气情况,给你发一份定制的“早安简报”。

2. 也是最强的“结对编程”伙伴

  • 代码助手:你可以让它读取你整个项目的代码库(因为它在本地,读取速度极快),然后问它:“utils.py 里的那个函数怎么优化?”
  • 运维监控:当它检测到某个进程挂了,可以自动发消息报警,甚至在你授权下尝试重启服务。

3. 自动化繁琐任务

  • 文件整理:对它说“把 Downloads 文件夹里所有的 PDF 发票整理一下,按月份归档到 Documents/Invoices 目录里”。它会自己写 Shell 脚本瞬间完成。
  • 比价购物:让它去几个电商网站爬取价格,整理成表格给你。

⚠️ 风险提示(必读!)

虽然 Clawdbot 很酷,但它目前更像是一个极客的玩具,而不是普通用户的消费级产品。

  1. 安全风险(高危):你实际上是给了 AI 访问你电脑文件系统和终端(Terminal)的权限。虽然有权限控制,但如果 AI "幻觉"了,或者被提示注入攻击,理论上它能执行 rm -rf /(删库)。建议尽量在沙箱环境或独立的 Mac Mini/虚拟机中运行。
  2. 成本问题:虽然软件免费,但它背后调用的是 API(如 Claude 3.5 Sonnet 或 GPT-4o)。如果你让它处理大量任务,API 账单可能会让你肉疼。
  3. 配置门槛:需要懂一点 Docker、Node.js 或者命令行的知识才能部署起来。

🔚 总结

Clawdbot 代表了 AI 的下一个阶段:从“聊天机器人”进化为“智能代理(Agent)”。它不再是被动等待提问的百科全书,而是有了手脚、能主动帮你解决问题的数字员工。

如果你有一台闲置的电脑,并且喜欢折腾技术,Clawdbot 绝对值得一试。但请记得:能力越大,风险越大,请管好你的 API Key 和系统权限!

欢迎在评论区分享你的 Clawdbot 玩法!

RustFS 默认通过 9001 端口登录控制台,9000 端口使用 API,为了安全合规,通常采用启用 HTTPS、反向代理(诸如 nginx、traefik、caddy 等)的方式来更加安全的使用 RustFS。本文分享一种更加安全的方式,通过 Cloudflare tunnel 来访问你的 RustFS 实例。

安装 RustFS

RustFS 支持二进制、Docker 以及 Helm Chart 的安装方式,详细方法可以查看官网安装指南。将如下内容写入 docker-compose.yml 文件:

services:
  rustfs:
    image: rustfs/rustfs:latest
    container_name: rustfs
    hostname: rustfs
    environment:
      - RUSTFS_VOLUMES=/data/rustfs{1...4}
      - RUSTFS_ADDRESS=0.0.0.0:9000
      - RUSTFS_CONSOLE_ENABLE=true
      - RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9001
      - RUSTFS_ACCESS_KEY=rustfsadmin
      - RUSTFS_SECRET_KEY=rustfsadmin
      - RUSTFS_TLS_PATH=/opt/tls
    ports:
      - "9000:9000"  # API endpoint
      - "9001:9001"  # Console
    volumes:
      - data1:/data/rustfs1
      - data2:/data/rustfs2
      - data3:/data/rustfs3
      - data4:/data/rustfs4
      - ./certs:/opt/tls

    networks:
      - rustfs

networks:
  rustfs:
    driver: bridge
    name: rustfs

volumes:
  data1:
  data2:
  data3:
  data4:

运行如下命令

docker compose up -d

即可安装好一个 RustFS 实例:

docker compose ps
NAME      IMAGE                          COMMAND                  SERVICE   CREATED          STATUS          PORTS
rustfs    rustfs/rustfs:1.0.0-alpha.81   "/entrypoint.sh rust…"   rustfs    22 minutes ago   Up 22 minutes   0.0.0.0:9000-9001->9000-9001/tcp, [::]:9000-9001->9000-9001/tcp

配置 Cloudflare tunnel

配置 Cloudflare tunnel 大体分为 域名配置tunnel 配置 两部分。

域名配置

域名配置是为了后期能够更方便的访问 RustFS。

  • 使用 Cloudflare 账号登录 Cloudflare Domain 界面;
  • 在左侧导航栏,Account home,如果你已经有域名,则选择 Onboard a domain,否则可选择 Buy a domain
  • 如果选择 Onboard a domain,点击该选项后,在出现的界面中输入你的域名,然后继续往下走,直到在最后选择 Continue to activation
  • 如果一切顺利,可以在域名管理首页看到添加成功的域名,其 Status 会显示为 Active

image.png

tunnel 配置

  • 使用 Cloudflare 账号登录 Cloudflare Dashboard
  • 在左侧导航栏,选择 Networks -> Connectors,在右侧界面点击 Create a tunnel
  • 在 tunnel 类型中,选择 Select Clouflared
  • Install and run connectors 中,根据 RustFS 实例所在服务器的操作系统信息,选择相应的安装方式。安装完毕后,可以在服务器上查看 cloudflared 服务的状态。运行正常后点击 Next

    systemctl status cloudflared
    ● cloudflared.service - cloudflared
         Loaded: loaded (/etc/systemd/system/cloudflared.service; enabled; preset: enabled)
         Active: active (running) since Fri 2026-01-16 21:18:53 CST; 6 days ago
       Main PID: 2538004 (cloudflared)
          Tasks: 10 (limit: 4375)
         Memory: 31.3M (peak: 38.7M swap: 8.2M swap peak: 15.3M)
            CPU: 18min 16.159s
         CGroup: /system.slice/cloudflared.service
  • Route Traffic 中,配置 HostnameService 信息。

    • Hostname 中填写的域名可用于后续访问 RustFS 实例,可以在 Domain 字段中选择 域名配置 部分添加好的域名。如果想通过子域名访问,也可以在 Subdomain 字段中输入子域名名称。
    • Service 选择服务类型和 URL。对于上述安装的 RustFS 实例,Type 可以选择 HTTP/HTTPS(如果启用了 HTTPS,可选择 HTTPS,否则用 HTTP),URL 为 localhost:9001

    image.png

  • 点击 Complete setup 完成配置。

上述配置结束后,可以在 Connectors 界面看到添加好的 tunnel,如果一切顺利,则可以看到 Status 为绿色的 HEALTHY

在 Hostname 和 Service 设置页面的 Additional application settings 部分,点击 HTTP Settings,在 HTTP Host Header 部分,输入访问 RustFS 的域名,这是为了避免后续使用出现签名错误。

登录验证

恭喜你,如果你顺利完成了上述两部分的配置后,那么现在你就可以通过你配置好的域名来访问 RustFS 实例了。本文配置的域名为 rustfs.xiaomage.vip,所以在浏览器中输入 https://rustfs.xiaomage.vip 即可访问 RustFS 实例:

image.png

输入 rustfsadmin/rustfsadmin 即可登录。

接下来就可以通过多种方式来使用 RustFS 实例了,比如 mcrc 以及 rclone

通过 mc 使用 RustFS

mc 是 Minio 的专属客户端,由于 RustFS 是 S3 兼容的,而且是 Minio 的平替,所以可以用 mc 来操作 RustFS。

前提

mc --version
mc version RELEASE.2025-08-29T21-30-41Z (commit-id=f7560841be167a94b7014bf8a504e0820843247f)
Runtime: go1.24.6 darwin/arm64
Copyright (c) 2015-2025 MinIO, Inc.
MinIO Enterprise License

使用

# 添加 `alias`
mc alias set rustfs https://rustfs.xiaomage.vip rustfsadmin rustfsadmin

# 创建存储桶
mc mb rustfs/hello

# 列出存储桶
mc ls rustfs
[2026-01-23 21:39:36 CST]     0B hello/
[2026-01-23 20:12:59 CST]     0B test/

# 上传文件到存储桶
echo "123456" > 1.txt
mc cp 1.txt rustfs/hello
/tmp/1.txt:                         ██████████████████████████████████████████████████████████████████████████████████ 100.0% 7 B       1 B/s      

# 查看上传的文件
mc ls rustfs/hello
[2026-01-23 21:40:44 CST]     7B STANDARD 1.txt

更多用法可自行探索。

通过 rclone 使用 RustFS

rclone是一个命令行工具,可以对不同云提供商上的文件和目录进行同步。

前提

rclone --version
rclone v1.72.1
- os/version: ubuntu 24.04 (64 bit)
- os/kernel: 6.8.0-71-generic (x86_64)
- os/type: linux
- os/arch: amd64
- go/version: go1.25.5
- go/linking: static
- go/tags: none

使用

  • 配置 rclone

执行 rclone config 命令,根据 RustFS 实例信息,一步步进行配置。配置完成后,会生成一个 ~/.config/rclone/rclone.conf 文件,一般内容如下:

[rustfs]
type = s3
provider = Minio
access_key_id = rustfsadmin
secret_access_key = rustfsadmin
endpoint = https://rustfs.xiaomage.vip
region = us-east-1
force_path_style = true
由于目前 RustFS 还未向 rclone 官方提 PR 以增加 RustFS provider 信息,因此使用 Minio 作为 provider。
  • 开始使用
# 列出存储桶和对象

rclone ls rustfs: --s3-sign-accept-encoding=false
        7 hello/1.txt
    11792 test/1.log
   520512 test/123.mp3
     7394 test/2.log
   147240 test/321.mp3
   

# 查看某个对象内容
rclone cat rustfs:hello/1.txt --s3-sign-accept-encoding=false
123456 

对于其他用法,可以通过 rclone --help 来自行探索。

注意:添加 --s3-sign-accept-encoding=false 参数是因为 Cloudflare 会对 Accept-Encoding 参数进行修改,在 S3 协议中,这种变更会导致 SignatureDoesNotMatch 错误,详情可以查看 RustFS issue

通过 rc 使用 RustFS

rc 是 RustFS 的 Client,用来对 RustFS 进行操作。目前,刚发布 0.1.1。可以使用 cargo 或源码编译安装。

rc --version
rc 0.1.1

目前提供 aliaslsmbrb 等多种常规命令。使用方式和 mc 类似。

# 设置 alias
rc alias set rustfs https://rustfs.xiaomage.vip rustfsadmin rustfsadmin
✓ Alias 'rustfs' configured successfully.

# 列出存储桶
rc ls rustfs
[2026-01-23 13:39:36]         0B hello/
[2026-01-23 13:56:57]         0B rclone/
[2026-01-23 12:12:59]         0B test/

# 创建存储桶
rc mb rustfs/client
✓ Bucket 'rustfs/client' created successfully.

更多用法,可以通过 rc --help 进行查看并自行探索,使用过程中有任何问题,可以在 GitHub Issue中进行反馈。

Running a Cronjob Under Docker Container

当您想要安排计划任务,可以使用内置在 macOS 和 Linux 中的常见工具,比如 cron,或者像 AWS Lambda 这样的特殊工具。Cron 不如 AWS Lambda 强大,但它在 Unix 系统的后台任务中工作得很好,特别是在使用容器的情况下。然而,对于 Docker 来说这有点复杂,因为不能简单地从终端开始新的 cron 作业,并期望它工作。

How to Dockerize a Cron Job

要在 Docker 容器中运行 cron 作业,您需要使用 cron 并在 Docker 容器的前台运行它。

下面是一个如何设置的例子:

Create Cron File

创建一个文件,其中包含要在 Docker 容器下运行的所有 cron 作业。

cat cron

我们的示例文件如下:

* * * * * echo "Current date is `date`" > /var/log/cron

Create Dockerfile

接下来,创建一个安装 cron 服务的 Dockerfile,并将脚本复制到容器。

在这里,我们提供了 3 个 Dockerfile 示例,它们使用不同的操作系统。

Dockerfile with Alpine Linux

FROM alpine:3

# Copy cron file to the container
COPY cron /etc/cron.d/cron

# Give the permission
RUN chmod 0644 /etc/cron.d/cron

# Add the cron job
RUN crontab /etc/cron.d/cron

# Link cron log file to stdout
RUN ln -s /dev/stdout /var/log/cron

# Run the cron service in the foreground
CMD [ "crond", "-l", "2", "-f" ]

Dockerfile with Apache and PHP

FROM php:8.0-apache

# Install cron
RUN apt update && \
    apt -y install cron

# Copy cron file to the container
COPY cron /etc/cron.d/cron

# Give the permission
RUN chmod 0644 /etc/cron.d/cron

# Add the cron job
RUN crontab /etc/cron.d/cron

# Link cron log file to stdout
RUN ln -s /dev/stdout /var/log/cron

# Start cron service
RUN sed -i 's/^exec /service cron start\n\nexec /' /usr/local/bin/apache2-foreground

Dockerfile with Ubuntu Linux

FROM ubuntu:latest

# Install cron deamon
RUN apt update && apt install -y cron

# Copy cron file to the container
COPY cron /etc/cron.d/cron

# Give the permission 
RUN chmod 0644 /etc/cron.d/cron

# Add the cron job
RUN crontab /etc/cron.d/cron

# Link cron log file to stdout
RUN ln -s /dev/stdout /var/log/cron

# Run the cron service in the foreground
CMD ["cron", "-f"]

Build and Run Container

当前目录中有两个文件,一个是 cron, 它包含了 cronjob。 一个是 Dockerfile, 它有 Docker 的构建指令。运行以下命令使用 Dockerfile 构建 Docker 镜像。

docker build -t my_cron .

镜像构建成功后,启动容器:

docker run -d my_cron

这将启动容器下的 cron 守护进程,它将执行 cron 文件中定义的所有计划作业。

Test Setup

我们已经链接了 cron 日志文件 /var/log/cron/dev/stdout ,Cron 服务生成的所有日志
可以使用 docker logs 命令查看。

首先,使用 docker ps 命令查找容器 id 或名称。

docker ps

然后检查 Docker 容器的日志文件。

docker logs container_id

在 cronjobs 中,我打印了当前日期并把它们写入日志中。

Running Cronjobs in Docker

输出如上所示,这意味着 cron 作业在 Docker 容器下正常运行。

很多团队在业务发展到一定阶段后,都会认真评估一次:
用户行为分析系统,是继续用现成产品,还是自己搭一套?

实际上,当企业需要埋点分析时,往往已经没有太多时间成本可投入
业务方希望尽快看到数据结果,管理层关注投入产出比,而完全从零自建埋点系统,周期长、风险高、不可控。
因此,基于成熟开源方案快速上线,再按需求自己二开,是目前更常见、也更可控的一种选择。

这篇文章不讨论“埋点的重要性”,只做一件事:
以自建埋点分析系统为参照,给出一个成本参考,并对比基于 ClkLog 开源方案的实际投入。

完全自建一套埋点分析系统成本通常在几十万,且建设周期长、不可控因素多。
基于ClkLog开源方案搭建首期成本可控制在几万最快一周完成部署集成,可以快速交付使用,并具备持续扩展的能力。

一、自建埋点分析系统,通常需要哪些模块?
很多团队低估了“自建”的工作量,下面只列最基础、不可回避的部分
1.数据采集层(SDK + 埋点规范)
这个阶段往往被低估,但实际上 SDK 会长期伴随业务演进,需要持续维护。
2.数据接入与处理层
核心目标是稳定接住数据:常见技术栈包括接入服务 + Kafka / MQ。
3.数据存储层
通常会选择 ClickHouse / Doris / Druid 这类分析型数据库,同时需要设计分区、冷热数据策略。
4.复杂分析计算
这是自建中最耗精力的部分,很多团队会发现:统计不难,难的是保证分析口径正确且性能可用。
5.管理后台与可视化
这部分前端和交互成本往往被严重低估。
6.运维与长期维护
系统上线只是开始,后续还包括各项调优、异常排查等运维工作。

二、为什么很多团队不会选择「完全自建」?
问题不在“能不能做”,而在是否划算
●早期业务验证阶段,数据系统很难直接创造业务价值
●自建系统容错成本高,试错周期长
因此,越来越多团队会选择:
在成熟的开源埋点分析系统基础上建设,而不是从零开始。

三、ClkLog开源方案能解决什么问题?
ClkLog提供了一套可直接落地的开源埋点分析方案,不依赖第三方SaaS服务。全面覆盖了埋点系统中最重、最复杂的核心能力:

1.数据采集层
支持神策SDK与自研鸿蒙SDK
2.数据接受层
进行日志数据接收与存储
3.数据处理层
进行数据处理、归档等服务
4.数据存储层
使用clickhouse进行大量数据查询
5.数据可视化
内置多种成熟分析模型,开箱即用

企业无需从零搭建底层能力,只需要围绕自身业务场景完成部署、运维和少量定制,即可形成一套可用的自有埋点分析系统。

四、基于 ClkLog,企业实际需要投入哪些成本?
1. 基础运行环境(参考)
以 ClkLog 社区版为例,在 1万日活应用规模下,采用Docker方式部署,单台服务器即可满足基础使用需求。
推荐配置参考:8核CPU;32GB内存
在常见的云厂商环境中,约1-2万/年,即可覆盖服务器、云盘、流量、备份等成本。

2. 软件部署与集成
●获取ClkLog代码(Github/Gitee)
●自行部署ClkLog服务(docker部署最快10分钟完成)
●接入埋点SDK(兼容web/小程序/iOS/安卓等)
●常规运维数据库和服务
整体实施周期短,最快一天即可完成部署并交付使用。

3. 业务层面的工作
●埋点规范梳理
●事件与指标定义
●少量业务定制
ClkLog已经内置十几种行业标准分析模型,可供业务直接开箱使用。若还有更多定制业务需要分析,可以通过自定义事件或二次开发来实现,与完全自建相比,省去的是部门团队沟通、大量底层系统设计与长期维护成本。

五、写在最后
对于大多数团队来说,先把系统跑起来、用起来、产生价值,比一开始追求完美更重要。
如果团队希望:
●完全掌控数据
●又不想长期投入基础设施研发
●把精力更多放在业务分析而不是系统本身
那么,基于成熟开源方案搭建自己的埋点分析系统,是一个性价比较高、风险更可控的选择


前言

本节详细聊一下基于envoy的可观测性

日志

首先是日志,配置日志的方式也很简单

static_resources:
  listeners:
    - name: ingress_listener
      address:
        socket_address:
          address: 0.0.0.0
          port_value: 10000
      filter_chains:
        - filters:
            - name: envoy.filters.network.http_connection_manager
              typed_config:
                "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
                stat_prefix: ingress_http
                ...
                access_log:
                - name: envoy.access_loggers.stdout
                  typed_config:
                    "@type": type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog
                    log_format:
                      text_format: "[%START_TIME%] \"%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\" %RESPONSE_CODE% %BYTES_SENT% %DURATION% %REQ(X-REQUEST-ID)% \"%REQ(USER-AGENT)%\" \"%REQ(X-FORWARDED-FOR)%\" %UPSTREAM_HOST% %UPSTREAM_CLUSTER% %RESPONSE_FLAGS%\n"
  • 该配置是将日志输出在控制台,也可以直接输出为文件,然后通过工具采集走path: /var/log/envoy/access.log
  • 也可以直接将日志输出至kafka,并且按比例采集、只采集4xx、5xx等都可以配置,这里就不在赘述了

admin管理页面

envoy有默认的admin页面,方便查看统计信息、打开某些功能的开关等

admin:
  address:
    socket_address:
      address: 0.0.0.0
      port_value: 9901

打开9901页面:

watermarked-envoy_ob_1.png

可以查看相关的统计信息、也可以打开某些开关,功能还是很丰富的

merics接入prometheus

打开了admin之后,就默认提供了相关的prometheus stats http://10.105.148.194:9901/stats/prometheus

这时只需在k8s集群外弄一个prometheus,并且采集该envoy即可

prometheus.yml

global:
  scrape_interval: 5s
  evaluation_interval: 5s

rule_files:
  - /etc/prometheus/*.rules

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
    - targets: ['localhost:9090']

  - job_name: "envoy"
    metrics_path: /stats/prometheus
    static_configs:
    - targets: ["10.105.148.194:9901"]
docker run -d --name prometheus \
  -p 9090:9090 \
  -v ./prometheus.yml:/etc/prometheus/prometheus.yml \
  -v /usr/share/zoneinfo/Asia/Shanghai:/etc/localtime \
  registry.cn-beijing.aliyuncs.com/wilsonchai/prometheus:v3.5.0

traces接入jaeger

jaeger的安装可以参考这里: opentelemetry全链路初探--埋点与jaeger

jaeger启动之后,改造一下envoy的配置,这里要特别注意,不同版本的配置不一样,我这里envoy的版本是:v1.32

static_resources:
  listeners:
    - name: ingress_listener
      filter_chains:
        - filters:
            - name: envoy.filters.network.http_connection_manager
              typed_config:
                ...

                tracing:

                  provider:
                    name: envoy.tracers.opentelemetry
                    typed_config:
                      "@type": type.googleapis.com/envoy.config.trace.v3.OpenTelemetryConfig
                      service_name: envoy-proxy
                      grpc_service:
                        envoy_grpc:
                          cluster_name: jaeger_otlp_collector
                ...

  clusters:
    ...
    - name: jaeger_otlp_collector
      type: LOGICAL_DNS
      connect_timeout: 5s
      lb_policy: ROUND_ROBIN
      http2_protocol_options: {}

      load_assignment:
        cluster_name: jaeger_otlp_collector
        endpoints:
        - lb_endpoints:
          - endpoint:
              address:
                socket_address:
                  address: 10.22.12.178
                  port_value: 4317
    ...

修改完成之后重启下envoy

jaeger成功接收到了来自envoy的trace

watermarked-envoy_ob_2.png
watermarked-envoy_ob_3.png

由于只在envoy配置了trace,没有和后端服务联动,所有只显示了envoy这一段的trace信息,如果要联动后端,可以参考这个系列的文章: 全链路监控配置

小结

至此,logs、metrics、traces三大可观测的指标建设完成,envoy可观测性的建设也结束了

联系我

  • 联系我,做深入的交流

 title=


至此,本文结束
在下才疏学浅,有撒汤漏水的,请各位不吝赐教...

前言

刚好有机会接触到卫生行业的运维赛,这里只有机会接触到测试赛,简单谢谢wp吧,测试赛的话主办方也是用心了的,难度的话相比较决赛的内容还是比较简单的。

题目

【题目背景】 模拟了一台公网上的服务器,内置 MYSQL\SSH\WEB 等应用服务,但是因为安全意识不到位导致该服务器存在若干安全风险,现在要求对该服务器实施断网并作全方面的安全检查,由于服务的迁移,在系统中保留了一些历史服务的流量包,需要对流量包进行分析和研判。

【题目要求】

1、通过修改服务器登录相关的配置文件实现:密码有效期90天、连续输错三次密码,账号锁定五分钟。

2、通过修改mysql相关配置实现:开启数据库查询日志、限制任意地址登录,只允许127.0.0.1登录。

3、从 /root/analyse.pcapng 文件中分析被读取走的flag值,写到 /root/flag.txt 中。

4、对系统中存在的其他安全隐患进行排查和处置(恶意配置后门请直接删除)。

5、还有其他的一些要求,但是没在题干中

【注意事项】

1、不涉及修改密码和修改私钥的动作,如果因为修改密码或私钥导致无法得分,选手自行负责。

2、禁止使用防火墙等相关IP封锁技术对IP进行隔离,如果因为隔离IP导致无法得分,选手自行负责。

【接入信息】

1、SSH服务端口22,账号密码为root/root

2、MYSQL服务账号密码为root/mysql

步骤

登录ssh,发现处于docker容器内,其实这里很多命令是无法使用的。

先修改登录过期事件

vim /etc/login.def

PASS_MAX_DAYS 90

连续输入错误三次,锁定5分钟

vim /etc/pam.d/sshd

auth required pam_tally.so deny=3 onerr=fail unlock_time=300 #最夯一行添加配置文件

auth required pam_faillock.so preauth audit silent deny=3 unlock_time=300
auth required pam_faillock.so authfail audit deny=3 unlock_time=300

或者修改

/etc/pam.d/common-auth

隐藏后门

查看发现异常用户hacker

userdel -f hacker

这里需要强制删除,因为不添加参数的话会重新创建该用户。

访问控制

访问控制在/etc/hosts.allow中发现存在异常的访问控制,删除该文件即可

安全配置

mysql暴力破解用户名密码root/mysql

mysql -uroot -pmysql

SET GLOBAL general_log = 'ON'; //开启数据库查询日志

或者图形化界面执行修改也可以

访问控制2

限制登录地址为127.0.0.1

修改配置文件

vim /etc/mysql/mysql.conf.d/mysql.cnf

bind-address = 127.0.0.1

定时任务

这个定时任务题目有问题,没有定时任务但是需要删除root的定时任务

rm /var/spool/cron/crontabs/root

其实这里的定时任务文件是没内容的,但是check的机制就是检测文件是否存在

特殊权限

find / -type f -perm -4000 -exec ls -l {} \;

find /:从根目录开始查找(你也可以指定特定的目录,例如 /usr/bin)。

-type f:只查找文件,不查找目录。

-perm -4000:查找设置了 SUID 权限的文件(SUID 权限对应的数字是 4000)。

-ls:显示详细信息,包括文件的权限、所有者、大小、修改时间等。

所有具有suid权限的文件都在/bin下,一般whoami权限是没有suid权限的,所以这个文件被动过,所以这里干掉这个文件就可以了。

流量分析

需要开启SFTP服务,注释掉配置文件

#RSAAuthentication yes 这个配置文件是老版本openssh

另外添加ftp配置文件

Subsystem sftp internal-sftp

检索关键字,追踪tcp流分析找到一串base64编码内容

导出分组字节流解码得到flag

echo "flag{d9d2c4b2-7cf2-472f-a8e8-2aad1e466099}" > /root/flag.txt

web漏洞

目录扫描发现info目录,发现属于xxe的报错,构造xxe语句

system是可执行文件,url路径需要传参,fuzz无果手工测试

回显显示需要参数,简单测试构造发现存在任意文件读取

修复直接就是定位到位置点儿进行修复即可。其实这里最简单的就是直接代码审计,审计即可,因为前期导完数据包的时候环境有问题,修改配置文件无法SFTP连接获取源码,所以就黑盒进行FUZZ了。

1 月 20 日,由清华大学自然语言处理实验室、中国人民大学、面壁智能与 OpenBMB 开源社区联合研发的 8B 端侧写作智能体 AgentCPM-Report 正式开源。

 

在当前深度研究场景中,企业与科研人员常面临两难抉择:依赖云端大模型虽能获得顶级调研能力,却需承担核心数据泄密风险;选择断网或本地小模型保障安全,又往往因性能局限导致报告逻辑浅薄、实用性不足。

 

为此,AgentCPM-Report 以端侧模型为核心,来实现本地化部署与 SOTA 性能的双重突破,力求无需昂贵算力集群,也无需上传任何信息,即可在本地构建专家级调研助手。

 

据悉,该智能体的核心亮点集中在两大维度。

 

第一,极致效能与“以小博大”的突破:通过平均 40 轮深度检索与近 100 轮思维链推演,AgentCPM-Report 以仅 8B 的参数规模,实现了对复杂信息的全方位挖掘与重组,能够产出逻辑严密、洞察深刻的万字长文,在深度调研任务上性能对标顶级闭源系统。

 

第二,物理隔绝的本地安全保障:专为高隐私场景设计,支持完全离线的敏捷部署,彻底杜绝云端泄密风险;依托开源的 UltraRAG 框架,可高效挂载并理解本地私有知识库,让核心机密数据在"不出域"的前提下,转化为高价值的专业决策报告。

 

在 DeepResearch Bench、Deep Consult、DeepResearch Gym 三大主流深度调研评测基准中,其综合评分达到甚至超越顶级闭源系统:在最考验核心能力的洞察性指标上排名第一,全面性指标位居第一梯队,仅次于基于 Claude 的复杂写作框架。其中在 DeepResearch Gym 评测中,AgentCPM-Report 以 98.48 的综合得分领跑,在深度、广度、洞察力等关键维度均斩获满分。

官方展示的实战场景中,该智能体可基于《三体》原文知识库,完成从线索挖掘、大纲规划到万字长文撰写的全流程,精准生成"面壁计划"深度调查报告。

 

部署便捷性方面,AgentCPM-Report 支持 Docker 一键启动,无需编写代码即可通过拖拽方式将 PDF、TXT 等本地文档导入后台,系统自动完成切片与向量化索引,用户输入研究课题后,即可生成结构化、带引用的专业报告,实现沉浸式深度调研体验。

 

技术层面,两大创新支撑其“以弱胜强”的表现:一是“写作即推理”模式,通过“起草-深化”两阶段循环与渐进式优化,将长篇写作拆解为微小目标,避免小模型逻辑崩塌;二是“多阶段智能体学习”,拆解智能检索、流畅写作、科学规划、精准决策四大核心能力,通过有监督微调、原子能力强化、全流程优化三阶段训练,实现端到端全链路能力提升。

 

目前,AgentCPM-Report 已在 GitHub、HuggingFace、ModelScope、GitCode、魔乐社区等多个平台开源,UltraRAG 框架也同步开放获取。

 

UltralRAG 框架开源地址:https://github.com/OpenBMB/UltraRAG

GitHub:https://github.com/OpenBMB/AgentCPM

HuggingFace:https://huggingface.co/openbmb/AgentCPM-Report

ModelScope:https://modelscope.cn/models/OpenBMB/AgentCPM-Report

GitCode:https://gitcode.com/OpenBMB/AgentCPM

魔乐社区:https://modelers.cn/models/OpenBMB/AgentCPM-Report

起因

之前看到 @fatekey 佬 写的 雨云无限白嫖 FRP 服务器攻略(无 aff) - 福利羊毛 / 福利羊毛,Lv2 - LINUX DO ,照着搭了个 FRP。

原帖提到了自动签到的 Docker 版本,但续费还得手动调 API。想着既然每天签到攒积分,不如直接做成全自动:签到 + 到期检测 + 自动续费,一劳永逸。

于是在 fatekey 佬的 Docker 版基础上改了改,加上了自动续费功能。

本来想 fork 后提合并建议的,但想了想改的太多了,于是把仓库独立出来了

功能

  • 每日自动签到(验证码识别)

  • 游戏云服务器到期检测

  • 积分自动续费(到期前 7 天自动续)

  • Server 酱通知 (也有其他通知,都是原版自带的,我把
    server 酱优化了一下)

部署


git clone https://github.com/Jielumoon/Rainyun-Qiandao.git

cd Rainyun-Qiandao

# 编辑 .env 填入账号 cp .env.example .env # 运行

docker-compose up --build

环境变量

| 变量 | 说明 |

|------|------|

| RAINYUN_USER | 雨云用户名 |

| RAINYUN_PWD | 雨云密码 |

| RAINYUN_API_KEY | API 密钥(可选,用于自动续费) |

| PUSH_KEY | Server 酱推送密钥(可选) |

| RENEW_PRODUCT_IDS | 续费白名单:只续费指定的产品 ID(可选) |

API 密钥在雨云后台 → 用户中心 → API 密钥获取。

定时任务

 # 每天早上 8 点执行

0 8 * * * docker compose -f /path/to/docker-compose.yml run --rm rainyun-qiandao

致谢

| 作者 | 仓库 | 说明 |

|------|------|------|

| SerendipityR | 原版 | Python 版本 |

| fatekey | 二改 | Docker 化 |

仓库

项目地址GitHub - Jielumoon/Rainyun-Qiandao: 三改版雨云签到工具的 docker 版


有问题欢迎反馈~


📌 转载信息
原作者:
JasonZhang
转载时间:
2026/1/24 16:04:51

一、Debian 安装 Docker 1. 更新并安装一些必要系统工具。


sudo apt-get update

sudo apt-get upgrade

sudo apt-get install \\
	apt-transport-https \\
	software-properties-common \\
    ca-certificates \\
    curl \\
    gnupg \\
    lsb-release 

2. 安装 GPG 证书。

curl -fsSL <https://mirrors.aliyun.com/docker-ce/linux/debian/gpg> | apt-key add -

3. 写入软件源信息。

add-apt-repository "deb [arch=amd64] <https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/debian> $(lsb_release -cs) stable" 

4. 更新并安装社区版 Docker-ce

sudo apt-get update

sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

二、配置开机自启

1. 开机自启

sudo systemctl enable docker

2. 启动

sudo systemctl start docker


📌 转载信息
原作者:
FAT64
转载时间:
2026/1/24 16:04:31

“我的笔记本是 16G 内存的 M3 Pro ,为什么我还需要一台只有 4 核 8G 的服务器?”

在 Reddit 的 r/indiehackers 板块,这是新手最常问的问题之一。在 Serverless (如 Vercel )和 PaaS (如 Supabase )横行的今天,VPS ( Virtual Private Server ,虚拟专用服务器)似乎显得有些“老派”。

但现实是:真正能跑通商业闭环、实现长期盈利的独立开发者,手里一定攥着几台 VPS 。

本文将从独立开发的 7 个核心痛点出发,深度解析为什么 VPS 是你迈向专业化、摆脱“代码玩具”的必经之路。


1. 摆脱“本地焦虑”:解决 node_modules 与 Docker 的空间黑洞

独立开发者最昂贵的资产是笔记本,而最廉价的则是笔记本硬盘。这波 AI 编程大部分都是 NextJS ,这也就带来了 node_modules 灾难。其实还有 cc 居然也喜欢拉 bb 。如果观察 cc 的执行过程,会发现它一直要写东西去 /tmp 目录

  • 痛点:硬盘与性能的双重榨干


    • node_modules 爆炸:同时维护 10 个项目,node_modules 能吃掉 50GB 以上的 SSD 。
    • Docker 镜像堆积:在本地运行容器会让系统响应迟滞,风扇咆哮。
    • 计算占用:本地运行 PostgreSQL 或 Redis 等中间件会显著拖慢 IDE 的响应速度。
  • 解决方案:VPS 作为“重型计算中心”
    你只需在本地保留一个轻量的 VS Code + Cursor,通过 Remote SSH 连接 VPS 。所有的重型依赖和环境都在云端运行,笔记本只负责显示 UI 。

图 1:本地开发负载 vs. VPS 远程卸载对比

2. 拒绝“SaaS 账单勒索”:从商业逻辑看成本控制

独立开发最怕的不是没用户,而是用户还没付钱,SaaS 账单先爆了。最近几年做 AI 编程,难免会接触到 supabase ,clerk 等工具,其实包括 vercel 也一样,用下来会发现一开始很爽,然后爽着爽着,账单就爆炸了。vercel 有个很有意思的坑,就是 Image 组件,编译的时候会提示最好用 <Image 组件,听起来很贴心对吧?但这个组件默认走 Vercel 的图片优化服务——每优化一张图就计费一次。流量大的站点,光图片优化费用就能超过主机费用。

Vercel 的 Hobby 免费套餐非常诱人——部署、CDN 、SSL 全包。但一旦你的项目有了流量,噩梦就开始了。

超额收费一览

资源 Pro 套餐包含 超出后收费
带宽 1 TB/月 $0.15/GB(即 $150/TB )
Edge Requests 1000 万/月 $2/百万
Serverless 执行时间 40 小时/月 $5/小时
图片优化 5000 张/月 $5/1000 张
  • 痛点:被绑架的扩展成本


    • PaaS 陷阱:Firebase 的免费额度诱人,但一旦涉及复杂备份或高并发,价格呈指数级增长。
    • 身份验证收费:Clerk 等按月活用户收费,对高频低客单价应用是噩梦。
  • 解决方案:全栈自建( Self-hosting )
    在 $5/月 的 VPS 上,你可以利用 Docker 跑满性能,同时运行:数据库( PostgreSQL )、验证系统( PocketBase )和统计系统( Umami )。

图 2:SaaS 订阅 vs. VPS 固定成本曲线对比

💡 公平地说:自建服务确实需要一定的运维能力。但最近很多海外开发者分享了自己维护 PostgreSQL 的经验——比想象中简单得多,尤其是有了 Docker 和自动备份脚本之后。后面我会详细讲怎么做。

3. 真正的 CI/CD:构建“一人 IT 部门”的自动化流水线

独立开发者的核心竞争力在于迭代速度。部署到 vercel 、cloudflare 、Netfily 等 servless 平台在早期验证需求的时候,是非常好的,但是这些平台的问题是,它们的 node 实现是不完备的,一些长时间的任务就没法跑。以前本地打包机器就开始呼啸,通过 github 的 action ,这个事不用操心了,弄好就是 docker 镜像,然后,起飞了。

  • 执行时间限制:Serverless 函数通常有 10-60 秒的超时限制,一般默认是 10s

  • 无持久进程:WebSocket 、长连接、后台任务都很别扭

  • 冷启动延迟:首次请求可能需要等待数秒

  • 痛点:手动部署的低效与错误
    如果你还在用手动执行 git pull,你不仅在浪费生命,还在增加生产事故的概率。

  • 解决方案:基于 VPS 的轻量自动化
    利用 VPS 运行 GitHub Actions Runner


    1. Git Push 触发流水线。
    2. VPS 自动拉取代码并构建 Docker 镜像。
    3. Docker Compose 自动重启容器,实现零停机更新。

图 3:基于 VPS 的自动化 CI/CD 流水线示意图

不知道是不是这个原因,现在 cloudflare 也不咋推 pages 了,又回到 worker ,感觉挺难用的,你怎么看?

4. 解决“网络壁垒”:从静默爬虫到跨境访问

很多项目在本地跑不通,不是代码问题,而是网络环境问题。开发用都的很多 npm 包,或者其他的资源,常常会因为网络,把人给气死,累死,折腾死,烦死。

  • 痛点:变动的 IP 与受限的出口


    • 固定 IP 需求:对接 Stripe 、PayPal 或银行 API 时,通常需要固定的公网 IP 做白名单。家庭宽带的动态 IP 根本没法用。
    • 网络环境问题:开发时用到的很多 npm 包、Docker 镜像、GitHub 资源,经常因为网络问题把人折腾得够呛。
    • 反爬虫封禁:如果你在做数据采集相关的项目,家庭宽带 IP 极易被反爬策略封禁。
  • 解决方案:VPS 作为全局网络枢纽


    • 固定身份标识:为业务提供永久的公网 IP ,Stripe Webhook 、OAuth 回调都能稳定工作。
    • 反向代理中心:一个 VPS 配合 Nginx 或 Caddy ,可以管理 10+ 个域名并映射到不同的本地端口。
    • 开发环境加速:npm install 、docker pull 都在 VPS 上执行,下载速度飞快,不再受本地网络限制。

image.png

和 nginx proxy manager 有仇,已经好几次了,弄它的 Docker ,能占 10 来 G 的空间,完全不理解,caddy 就小巧很多。

5. 守护“睡后收入”:24/7 监控与容灾

独立开发最痛苦的时刻,是早上醒来发现服务已经挂了一整晚,而你毫无察觉。(希望是伪命题,真来钱的项目,还是很上心的!)

痛点:缺乏哨兵

  • 本地电脑会休眠,没法做持续监控
  • 免费的外部监控工具检测频率太低(如 5 分钟/次),发现问题时用户早就流失了
  • 很多问题是"偶发性"的,等你手动检查时一切正常

解决方案:自建监控站

在 VPS 上部署 Uptime Kuma(或类似工具),每 30-60 秒检测一次全球访问状况。一旦挂掉,立即通过 Telegram 、Discord 或邮件通知。

监控清单建议

监控项 检测频率 告警方式
HTTP 状态码 60 秒 Telegram 即时通知
SSL 证书到期 每天 提前 14 天预警
服务器资源 5 分钟 CPU/内存超 80% 告警
数据库连接 60 秒 连接失败立即通知

进阶玩法

  • Uptime Kuma 做可用性监控
  • BezelNetdata 做服务器资源监控,Bezel 还挺好用的。Netdata 稍微重点。
  • 两者结合,形成完整的监控闭环

图 4:全天候监控与即时告警闭环

6. 数据主权:独立开发的“最后防线”

  • 痛点:平台依赖风险

    如果你的数据全在 Firebase ,某天账号因为合规问题被封,你的所有努力将瞬间清零。

  • 解决方案:VPS 本地化存储 + 异地备份


    • 数据隔离:数据库文件完全属于你。
    • 自动化备份:编写一个简单的 Cron 任务,每天定时将数据加密并同步到 S3 或你的本地存储。

image.png

7. 独立开发者的资源规划:“1 + N” 策略

针对 2026 年的典型开发场景,我们建议采用以下阵列:

类型 规格建议 核心作用
1 台主领地 2 核 4G 或 4 核 8G 运行 Nginx 、核心数据库、核心产品。
N 台哨兵机 1 核 1G 或更低 运行 Uptime Kuma 监控、小型爬虫、测试环境。
为什么需要分开?
  • 监控服务不应该和被监控的服务在同一台机器——否则机器挂了你也收不到告警
  • 测试环境和生产环境隔离,避免误操作
  • 多台小机器比一台大机器更有弹性

image.png

Reddit 上 Hetzner 被反复提及为"性价比之王":同样的价格,配置通常是美国云服务商的 2-3 倍。缺点是机房主要在欧洲,亚洲访问延迟较高。

咋说呢? 数据库还是很重要的,如果精力有限,就还是用 neon 或者 supabase 之类的。

总结:从“玩票”到“专业”的入场券

拥有 VPS 的那一刻起,你就不再只是一个“写代码的人”,而是一个 “系统的掌控者”。它为你提供了:

  • 确定性:不再受本地环境变化的干扰。
  • 连续性:产品 24 小时独立生存。
  • 商业性:以最低的边际成本支撑业务增长。

正如独立开发圈子里流传的一句话:“你的第一个服务器 IP ,就是你产品的第一张名片。”(我编的)

VPS 入门:为什么独立开发者需要一台 VPS ?( 2026 深度版)

Traefik 优势与考量:本地部署的理想选择

Traefik 是一款功能强大的云原生边缘路由器(Edge Router),它为 Docker 等容器化环境带来了显著的便利和优势:

主要优势

  • 服务自动发现与配置: Traefik 能够自动检测容器中运行的新服务,并即时自动配置相应的反向代理(Reverse Proxy)和负载均衡规则,无需手动修改配置文件。
  • 简化的 SSL/TLS 管理: 它内置了对 Let's Encrypt 的支持,可以实现域名的 SSL 证书自动申请与自动续签,大大减轻了运维负担
  • 端口暴露最小化: 极大地提高了安全性。对于宿主机而言,Traefik 只需要对外暴露标准的 80 和 443端口,无需再为每个服务暴露额外的端口。

局限与考量

尽管 Traefik 优势显著,但在配置灵活性方面,它不如传统反向代理工具(如 Nginx)那样直观和强大:

  • 非容器化应用集成复杂: 对于不在 Docker 等容器中部署的传统应用,Traefik 的反向代理配置会相对复杂和繁琐。它主要面向动态的云原生环境,对静态配置的支持不如 Nginx 灵活
  • 特定配置的挑战: 在需要进行复杂、细致的反代逻辑配置时,可能会不如 Nginx 的配置文件那样灵活易读。
    在快速启动前,有必要说明一下,本教程是使用CF 作为域名ns进行申请泛域名证书,如果你想使用其他提供商,可以在 Traefik 的文档 更改 Provider Code和 Environment Variables 这两个值,当然我会在本篇配置文件有注释提醒。
    另外如果没有额外配置反代的需求(指不跑在docker的服务),需要建立config.yml 文件,当然还需要在traefik.yml 关闭注释。

快速启动 Traefik

请按照一下文件目录创建文件,其中acme.json只需要创建文件即可(注意必须要交建立哦,config文件根据自己需求建立即可)

文件目录:

|   .env    #文件配置
|   docker-compose.yaml        # docker-compose 文件
|
\---data
        acme.json    # SSL 文件
        config.yml    # 额外配置文件(配置额外反代例如宿主机的)
        traefik.yml # Traefik 配置文件

docker-compose.yaml 文件:

services:
  traefik:  # 定义名为 traefik 的服务
    image: traefik:v3.0  # 使用 Traefik 的 v3.0 版本镜像
    container_name: traefik  # 容器名称为 traefik
    restart: unless-stopped  # 容器自动重启,除非手动停止
    security_opt:
      - no-new-privileges:true  # 增加安全性,防止提权
    networks:
      - traefik-net  # 连接到名为 proxy 的外部网络
    ports:
      - 80:80  # 映射主机的 80 端口到容器的 80 端口 (HTTP)
      - 443:443  # 映射主机的 443 端口到容器的 443 端口 (HTTPS)
      - 443:443/tcp  # 映射主机的 443 TCP 端口到容器的 443 端口 (TCP 协议)
      - 443:443/udp  # 映射主机的 443 UDP 端口到容器的 443 端口 (UDP 协议)
    environment:
      CF_DNS_API_TOKEN_FILE: ${CF_DNS_API_TOKEN}  # 设置环境变量,使用 Cloudflare API 令牌,根据Traefik文档 选择你的服务提供商的token
      TRAEFIK_DASHBOARD_CREDENTIALS: ${TRAEFIK_DASHBOARD_CREDENTIALS}  # 设置环境变量,定义 Traefik 仪表板的凭据
    env_file: .env  # 从 .env 文件中加载环境变量
    volumes:
      - /etc/localtime:/etc/localtime:ro  # 挂载主机的时间设置到容器,确保时间同步,且只读
      - /var/run/docker.sock:/var/run/docker.sock:ro  # 挂载 Docker 的 socket 文件,允许 Traefik 访问 Docker API,只读
      - ./data/traefik.yml:/traefik.yml:ro  # 挂载本地的 traefik.yml 配置文件到容器内,只读
      - ./data/acme.json:/acme.json  # 挂载本地的 acme.json 文件,存储 SSL 证书信息
      - ./data/config.yml:/config.yml:ro  # 可选的配置文件挂载路径,若需要可取消注释
    labels:  # 设置 Traefik 的相关标签,用于路由和中间件配置
      - "traefik.enable=true"  # 启用 Traefik 服务
      - "traefik.http.routers.traefik.entrypoints=http"  # 配置 HTTP 入口点
      - "traefik.http.routers.traefik.rule=Host(`${TRAEFIK_DASHBOARD_HOST}`)" # 定义 Traefik 仪表板的访问规则
      - "traefik.http.middlewares.traefik-auth.basicauth.users=${TRAEFIK_DASHBOARD_CREDENTIALS}"  # 为仪表板配置基本身份验证
      - "traefik.http.middlewares.traefik-https-redirect.redirectscheme.scheme=https"  # 配置 HTTP 到 HTTPS 的重定向
      - "traefik.http.middlewares.sslheader.headers.customrequestheaders.X-Forwarded-Proto=https"  # 添加自定义请求头
      - "traefik.http.routers.traefik.middlewares=traefik-https-redirect"  # 将重定向中间件应用到 HTTP 路由
      - "traefik.http.routers.traefik-secure.entrypoints=https"  # 配置 HTTPS 入口点
      - "traefik.http.routers.traefik-secure.rule=Host(`${TRAEFIK_DASHBOARD_HOST}`)" # 定义 HTTPS 路由的访问规则
      - "traefik.http.routers.traefik-secure.middlewares=traefik-auth"  # 为 HTTPS 路由应用基本身份验证中间件
      - "traefik.http.routers.traefik-secure.tls=true"  # 启用 TLS (HTTPS)
      - "traefik.http.routers.traefik-secure.tls.certresolver=${NS_Domain}"  # 使用 DNS服务提供商 code 根据Traefik文档 选择你的服务提供商code
      - "traefik.http.routers.traefik-secure.tls.domains[0].main=${TLS_MAIN_DOMAIN}"  # 定义主域名
      - "traefik.http.routers.traefik-secure.tls.domains[0].sans=${TLS_SANS_DOMAIN}"  # 定义子域名通配符
      - "traefik.http.routers.traefik-secure.service=api@internal"  # 使用 Traefik 内部 API 服务

networks:
  traefik-net:
    external: false  # 使用外部定义的名为 proxy 的网络

.env 文件:


# .env 文件

# CF API
CF_DNS_API_TOKEN=

NS_Domain=cloudflare #根据你使用的DNS服务提供商 code 根据Traefik文档 选择你的服务提供商code
# 设置环境变量,定义 Traefik 仪表板的凭据 ,默认账户名密码:admin
TRAEFIK_DASHBOARD_CREDENTIALS=admin:$$2y$$05$$aOXINGgHfnZ//t.kUs7o9ej3faUbj2yNxc8k3WVrBybFOxxaTsLTe

# Traefik Dashboard 域名
TRAEFIK_DASHBOARD_HOST=dash.docker.localhost

# TLS 主域名和子域名
TLS_MAIN_DOMAIN=docker.localhost
TLS_SANS_DOMAIN=*.docker.localhost

traefik.yml 文件:


api:
  dashboard: true  # 启用 Traefik 的仪表板,可以通过指定的路由访问
  debug: true  # 启用调试模式,输出更多的日志信息

entryPoints:
  http:
    address: ":80"  # 定义 HTTP 入口点,监听 80 端口
    http:
      redirections:
        entryPoint:
          to: https  # 重定向 HTTP 请求到 HTTPS
          scheme: https  # 使用 HTTPS 作为重定向的目标协议

  https:
    address: ":443"  # 定义 HTTPS 入口点,监听 443 端口

serversTransport:
  insecureSkipVerify: true  # 在与后端服务器通信时,跳过 TLS 证书验证(不推荐在生产环境中使用)

providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"  # 指定 Docker API 的 socket 文件路径,Traefik 使用它来检测和管理 Docker 容器
    exposedByDefault: false  # 默认情况下,Docker 容器不会自动暴露给 Traefik,必须显式指定
    watch: true

  file:
    filename: /config.yml  # (已注释) 可选的文件提供者配置,用于从外部文件加载配置
    watch: true  # 允许 Traefik 自动监控和加载配置文件变化


certificatesResolvers:
  cloudflare: # 使用 DNS服务提供商 code 根据Traefik文档 选择你的服务提供商code
    acme:
      email: youremail@email.com  # 申请 ACME 证书时使用的电子邮件地址
      storage: acme.json  # 存储证书信息的文件路径
      # caServer: https://acme-v02.api.letsencrypt.org/directory # 正式环境的 Let's Encrypt 服务器 (默认)
      caServer: https://acme-staging-v02.api.letsencrypt.org/directory # 测试环境的 Let's Encrypt 服务器 (用于调试)

      dnsChallenge:
        provider: cloudflare  # 使用 DNS服务提供商 code 根据Traefik文档 选择你的服务提供商code 进行 DNS 验证以获取证书
        #disablePropagationCheck: true # (已注释) 如果通过 Cloudflare 获取证书有问题,可以取消注释此行以禁用传播检查
        #delayBeforeCheck: 60s # (已注释) 如果需要确保 TXT 记录准备就绪,可以取消注释此行并设置检查延迟
        resolvers:
          - "223.5.5.5:53"  # AliDNS 解析器
          - "119.29.29.29:53"  # 备用 DNS 解析器
          - "1.1.1.1" # 备用 DNS 解析器

config.yml 文件

可以选择配置,如果你宿主机有ng反代服务,你使用taerfik 的话会端口冲突,可以配置,但不过要把 docker-compose 和 Traefik的配置文件注释去掉即可:


http:
  #region routers 
  routers:
    hexo:
      entryPoints:
        - "https"  # 指定使用 HTTPS 入口点
      rule: "Host(`hexo.docker.localhost`)"  # 当访问的主机名为 hexo.local.shellscience.top 时,触发此路由
      middlewares:
        - default-headers  # 应用默认的安全头中间件
        - https-redirectscheme  # 应用 HTTPS 重定向中间件
      tls: {}  # 启用 TLS 加密
      service: hexo  # 指定将请求转发到名为 hexo 的服务

  #region services
  services:
    hexo:
      loadBalancer:
        servers:
          - url: "http://127.0.0.1:5000"  # 指定 Hexo 服务的后端服务器 URL
        passHostHeader: true  # 传递原始的 Host 头信息到后端服务
  #endregion

  middlewares:
    https-redirectscheme:
      redirectScheme:
        scheme: https  # 将 HTTP 请求重定向为 HTTPS
        permanent: true  # 使用永久重定向(HTTP 301)

    default-headers:
      headers:
        frameDeny: true  # 禁止网页被嵌入到框架中,防止点击劫持攻击
        browserXssFilter: true  # 启用浏览器的 XSS 过滤器,增强安全性
        contentTypeNosniff: true  # 防止浏览器 MIME 类型嗅探
        forceSTSHeader: true  # 强制启用 HSTS(HTTP 严格传输安全)
        stsIncludeSubdomains: true  # HSTS 规则应用于所有子域
        stsPreload: true  # 允许将域名加入 HSTS 预加载列表
        stsSeconds: 15552000  # HSTS 头的有效期(秒),这里是 180 天
        customFrameOptionsValue: SAMEORIGIN  # 允许内容在同源的 iframe 中加载
        customRequestHeaders:
          X-Forwarded-Proto: https  # 设置 X-Forwarded-Proto 头为 https,用于指示原始请求协议

    default-whitelist:
      ipAllowList:
        sourceRange:
        - "10.0.0.0/8"  # 允许来自 10.0.0.0/8 网段的 IP 地址
        - "192.168.0.0/16"  # 允许来自 192.168.0.0/16 网段的 IP 地址
        - "172.16.0.0/12"  # 允许来自 172.16.0.0/12 网段的 IP 地址

    secured:
      chain:
        middlewares:
        - default-whitelist  # 应用默认的 IP 白名单中间件
        - default-headers  # 应用默认的安全头中间件

配置完毕我们docker-compose up -d如果配置没有问题你就可以通过你配置的域名成功访问Traefik的面板。

反代代理Dcoekr应用

这里拿Memos的程序来举例子:

下面是我的Memos的docker-compose.yaml 文件,我们只需要把暴露的端口删除,添加labels标签以及下面几个配置(你想访问的域名、容器的端口、开启https、使用tls证书)以及让我们的程序接入Traefik的网络就好了。

version: "3.0"
services:
  memos:
    image: ghcr.io/usememos/memos:latest
    container_name: memos
    volumes:
      - ./data/:/var/opt/memos
    environment:
      - driver=sqlite
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.memos.rule=Host(`memos.local.com`)"
      - "traefik.http.services.memos.loadbalancer.server.port=<程序的端口>"
      - "traefik.http.routers.memos.entrypoints=https"
      - "traefik.http.routers.memos.tls=true"
    networks:
      - traefik-net

networks:
  traefik-net:
    external: true

Traefik DNS服务提供文档:https://doc.traefik.io/traefik/https/acme/#providers

Traefik Docker配置文档:https://doc.traefik.io/traefik/routing/providers/docker/

总结

这个是博主自己在搭建Traefik 时的总结与分享,当然在搭建时也去借鉴了很多的资料。

本文原发于我的博客:landonVPS

点赞 + 关注 + 收藏 = 学会了

整理了一个NAS小专栏,有兴趣的工友可以关注一下 👉 《NAS邪修》

withoutbg 是一款 AI 图片去背景工具,支持本地免费离线处理(隐私保护)和 Pro 版高质量处理,能通过 Docker 轻松部署到 NAS。

这次用的是绿联DXP4800Plus。

打开 Docker,打开“镜像”模块,搜索“withoutbg”。

下载红框这个。

下载完成后,打开“本地镜像”,点击“withoutbg/app”右侧的加号创建一个容器。

“自动重启”建议开启。

然后往下滑,NAS端口设置一个其他项目没用过的数字。比如我这里设置的是 39155

接着在浏览器打开 绿联NAS的IP + 39155 就可以使用 withoutbg 抠图了。

试了一下,物品、动物、人物都可以抠。

但缺点就是它自己去识别抠什么,并没提供一个涂抹工具,让我涂什么它就在我涂抹的区域去抠图。

而且界面没中文。


以上就是本文的全部内容啦,想了解更多NAS玩法可以关注《NAS邪修》

点赞 + 关注 + 收藏 = 学会了

点赞 + 关注 + 收藏 = 学会了

整理了一个n8n小专栏,有兴趣的工友可以关注一下 👉 《n8n修炼手册》

不管是在电脑还是 NAS 通过 Docker 部署 n8n,环境变量没配置好的话,使用 Read/Write Files from Disk 节点「读取本地本地」或者「保存文件到本地」,有可能出现这个报错。

这是 Docker + n8n 文件系统权限/路径隔离 的经典问题,不是 n8n 节点用错,而是容器只能访问被允许的目录

⚠️⚠️⚠️

想解决这个问题,首先要将你 n8n 上已有的工作流等数据找个地方保存好。因为要改环境变量,有可能会丢失数据。

⚠️⚠️⚠️

在电脑用 Docker 部署

打开 Docker,首先要在 Containers 里删掉部署好的 n8n。

然后到 Images,假设你没删掉 n8n 镜像的话,重新点击一下运行按钮。

删掉镜像了就重新拉一遍吧。可以参考《『n8n』环境搭建》

点击运行按钮后,需要添加在 Volumes 里添加一项(下图红框)。

在你的电脑,找个位置创建要给文件夹。

  • 上图红框的 Host path 这项就填入你在电脑创建的文件夹的绝对路径。
  • Container path 这项填入 /home/node/.n8n-files,必须是这个值!一个字一个符号都不能少!

然后点击“Run”按钮(弹窗右下角蓝色底色那个按钮)。

之后再浏览器输入 localhost:5678 就能运行 n8n 了。

接下来使用 Read/Write Files from Disk 节点读写文件,都是指向你刚刚在电脑创建的那个文件夹。

比如我的 /home/node/.n8n-files 指向了 文稿/n8n-data 这个文件夹,里面有一个 hello.txt 文件。

在 n8n 里使用 Read/Write Files from Disk 节点时,File(s) Selector 项需要这么写:

/home/node/.n8n-files/hello.txt

可以看到文件读取成功了。

记住记住!用法是这样的,别问为什么⬇️⬇️⬇️

/home/node/.n8n-files/文件名.后缀

在绿联 NAS 部署

如果你是在 NAS 上部署 n8n,通常使用 Docker 部署的吧~

不管你是用群晖还是其他牌子的NAS,如果使用新建项目,用是 yaml 拉镜像。

services:
  n8n:
    image: n8nio/n8n:latest   # 为了汉化成功,这里需要指定镜像版本号
    container_name: n8n
    ports:
      - 5678:5678
    volumes:
      - n8n:/home/node/.n8n # 冒号前面映射n8n文件夹绝对路径
      - n8n-files:/home/node/.n8n-files # 冒号前面映射n8n-files文件夹绝对路径
    restart: unless-stopped

那么 yaml 的代码必须在 volumes 里加一项 - n8n-files:/home/node/.n8n-files。冒号前面的 n8n-files 是允许 n8n 读写文件的文件夹的绝对路径

如果你是使用《『NAS』不止娱乐,NAS也是生产力,在绿联部署AI工作流工具-n8n》里提到的方法,在 Docker 的「镜像」模块里搜索 n8n 下载部署的话,需要这么做。

我用绿联 NAS 举例,其他品牌的 NAS 操作方法大同小异。

在 Docker 的「容器」里找到 n8n,停止运行。

然后编辑它。

在 NAS 的「文件管理」里创建一个文件夹,用来给 n8n 读写文件使用的。

然后在「编辑容器」的「存储空间」里添加一项 /home/node/.n8n-files 指向那个文件夹,提供“读写”权限,如下图红框所示。

点击“保存”按钮,然后运行项目。

我在 NAS 的 n8n-files 文件夹里准备了一个 雷猴世界.txt 文件。

在 n8n 里,使用 /home/node/.n8n-files/雷猴世界.txt 这个路径就能读取到上面这个文件了。

同样,也是这个格式:

/home/node/.n8n-files/文件名.后缀

以上就是本文的全部内容啦,想了解更多n8n玩法欢迎关注《n8n修炼手册》👏

如果你有 NAS,我非常建议你在 NAS 上部署一套 n8n,搞搞副业也好,帮你完成工作任务也好 《『NAS』不止娱乐,NAS也是生产力,在绿联部署AI工作流工具-n8n》

点赞 + 关注 + 收藏 = 学会了

AutoGLM-GUI v1.5 正式发布啦。这个版本主要聚焦在生产力场景,让 AI
自动化真正可以投入日常使用。

从 v1.4.1 到 v1.5.5,经过 65 个提交,新增了大量功能:

  • 定时任务系统
  • Docker 部署支持
  • 对话历史保存
  • 模拟器零配置直连
  • MCP 服务器集成


核心功能更新

  1. 定时任务系统 - 自动化的自动化

现在可以设置定时任务,让 AI 自动执行重复性操作。比如:

  • 每天早上 8 点自动打卡
  • 每周定时清理应用缓存
  • 定期执行数据同步操作

使用场景:

  • 工作日自动处理固定流程
  • 定期维护设备状态
  • 批量任务的定时执行


一行命令部署到服务器,配合定时任务实现真正的无人值守:

docker run -p 8000:8000 Package autoglm-gui · GitHub

特性:

  • 支持 x86_64 和 ARM64 架构
  • 开箱即用,无需配置环境
  • 配合 ADB over Network 实现远程控制

实际场景:
你可以把 AutoGLM-GUI 部署在家里的 NAS 或者云服务器上,通过 ADB WiFi 连接手机,实现:

  • 远程执行自动化任务
  • 服务器端定时任务调度
  • 多设备集中管理


  1. 对话历史 - 所有任务都有记录

现在所有的对话和操作都会自动保存到本地数据库,支持:

  • 查看完整执行记录
  • 检索历史对话
  • 统计任务执行情况

实用价值:

  • 出问题时可以回溯完整执行过程
  • 优化任务提示词的参考依据
  • 任务执行日志的长期保存


  1. 模拟器直连 - 开发环境零配置

Android 模拟器(Android Studio、雷电、夜神等)现在可以直接连接,无需任何配置:

  • 自动检测本地模拟器
  • 无需 QR 码配对
  • 即插即用

开发者友好:
如果你在开发环境测试自动化流程,现在可以直接用模拟器,省去真机配对的麻烦。


  1. MCP 服务器 - 成为其他 AI 的工具

AutoGLM-GUI 现在内置了 MCP (Model Context Protocol) 服务器,可以作为工具被其他 AI
应用调用。

工作原理

MCP 服务器集成在 AutoGLM-GUI 的 FastAPI 应用中,通过 HTTP 协议提供服务。你需要先启动 AutoGLM-GUI 服务,然后其他 AI 应用就能通过 MCP 协议调用它的功能。

提供的能力

・chat (device_id, message) - 向设备发送自然语言任务
・list_devices () - 列出所有已连接的设备和状态

使用示例

场景 1:从 Claude Desktop 控制手机

你:帮我用手机打开微信
Claude:[调用 MCP chat 工具] → AutoGLM-GUI 执行 → 返回结果

场景 2:在 Cursor/Cline 中集成

agent.run("帮我清理后台应用")
# → 自动调用 MCP 工具操作你的设备 

场景 3:自定义 AI 工作流

  • 定时任务触发 MCP 工具
  • 多设备批量操作
  • AI 决策调用手机操作

配置方式

首先启动 AutoGLM-GUI 服务:

autoglm-gui --base-url YOUR_API_ENDPOINT --port 8000

MCP 服务器端点为 http://localhost:8000/mcp(使用 SSE 传输协议)。

根据你使用的 MCP 客户端(Claude Desktop、Cursor、Cline 等),在配置文件中添加 MCP 服务器连接。具体配置方式请参考各客户端的 MCP 集成文档。


📌 转载信息
原作者:
OverL1nk
转载时间:
2026/1/22 13:14:19