有一台 Ubuntu 主机,人在外面时想看它是否还在运行、磁盘还剩多少空间,最直接的练习就是做一张只读状态页,再通过星空组网地址访问它。本文从官网登录开始,带零基础读者完成组网确认、代码创建、Ubuntu 部署和跨设备验证。页面只展示时间、运行时长和根分区剩余空间,不需要安装第三方 Python 包。
1. 登录官网,确认两端已经入网
打开星空组网官网,进入登录页,选择账号登录。登录后先看管理平台首页的“组网状态”:这次操作时,两台设备都显示在线,页面提示组网成功。已有账号和设备的读者可直接进入下一步。


如果你还没有把电脑和 Ubuntu 加入同一个组网,先按官方设备与成员管理和 Linux 客户端说明完成安装与登录。官方文档将“成员账号”和设备关联:需要长期同时在线的设备,分别使用成员账号。本次演示沿用了已经在线的设备,没有新建成员账号。
2. 在 Ubuntu 上确认环境和组网 IP
登录 Ubuntu 终端,先执行:
cat /etc/os-release | head -n 3
python3 --version
ip -4 addr
我实际使用的是 Ubuntu 22.04 LTS、Python 3.10.12。ip -4 addr 输出中,StarVPN 接口的地址是 192.168.188.5/24。这里的 192.168.188.5 就是后面启动服务时要绑定的地址;你的设备可能不同。若找不到组网 IP,先检查客户端是否在线,不要直接用公网 IP 代替。

3. 创建状态页项目
在 Ubuntu 上建一个演示目录:
mkdir -p ~/starvpn-status-demo-20260925
cd ~/starvpn-status-demo-20260925
nano server.py
把下面的完整代码粘贴进 server.py,保存退出。它使用 Python 标准库读取 /proc/uptime、根分区磁盘信息和当前时间,再由内置 HTTP 服务返回页面。/proc/uptime 是 Linux 提供的数据文件,因此这份示例应在 Ubuntu 上运行。
"""A tiny read-only Ubuntu status page for a StarVPN demo."""
import argparse
import ipaddress
import shutil
from datetime import datetime
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from string import Template
PAGE = Template("""<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Ubuntu 状态卡片</title>
<style>
* { box-sizing: border-box; }
body { margin: 0; min-height: 100vh; display: grid; place-items: center;
background: #eef3f8; color: #172b4d; font: 16px/1.6 system-ui, sans-serif; }
main { width: min(900px, calc(100% - 40px)); padding: 42px 0; }
.eyebrow { color: #2771b8; font-size: 14px; font-weight: 700; letter-spacing: .08em; }
h1 { margin: 6px 0; font-size: clamp(32px, 5vw, 48px); }
.intro { margin: 0 0 30px; color: #58708d; }
.cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; }
article { min-height: 170px; padding: 24px; border: 1px solid #dce5f0;
border-radius: 18px; background: #fff; box-shadow: 0 12px 30px #20436a0c; }
article h2 { margin: 0 0 18px; color: #58708d; font-size: 15px; font-weight: 600; }
strong { display: block; font-size: 25px; line-height: 1.3; }
small { display: block; margin-top: 8px; color: #6d8097; }
footer { margin-top: 22px; color: #6d8097; font-size: 14px; }
@media (max-width: 700px) { .cards { grid-template-columns: 1fr; } article { min-height: 0; } }
</style>
</head>
<body>
<main>
<div class="eyebrow">STARVPN · 远程查看</div>
<h1>Ubuntu 状态卡片</h1>
<p class="intro">打开组网地址,即可查看这台主机的实时状态。</p>
<section class="cards" aria-label="主机状态">
<article><h2>当前时间</h2><strong>$now</strong><small>以 Ubuntu 主机时区为准</small></article>
<article><h2>已运行</h2><strong>$uptime</strong><small>系统自上次启动以来</small></article>
<article><h2>根分区剩余</h2><strong>$free_gib GiB</strong><small>总容量 $total_gib GiB</small></article>
</section>
<footer>刷新页面可获取最新数据 · 本页仅用于组网内演示</footer>
</main>
</body>
</html>
""")
def status_page() -> bytes:
with open("/proc/uptime", encoding="ascii") as uptime_file:
seconds = int(float(uptime_file.read().split()[0]))
days, remainder = divmod(seconds, 86400)
hours, minutes = divmod(remainder, 3600)
minutes //= 60
disk = shutil.disk_usage("/")
page = PAGE.substitute(
now=datetime.now().astimezone().strftime("%Y-%m-%d %H:%M:%S %z"),
uptime=f"{days} 天 {hours} 小时 {minutes} 分钟",
free_gib=f"{disk.free / 1024**3:.1f}",
total_gib=f"{disk.total / 1024**3:.1f}",
)
return page.encode("utf-8")
class Handler(BaseHTTPRequestHandler):
def do_GET(self) -> None:
if self.path != "/":
self.send_error(404, "Not Found")
return
try:
content = status_page()
except (OSError, ValueError) as exc:
self.log_error("读取主机状态失败: %s", exc)
self.send_error(500, "Internal Server Error")
return
self.send_response(200)
self.send_header("Content-Type", "text/html; charset=utf-8")
self.send_header("Cache-Control", "no-store")
self.send_header("X-Content-Type-Options", "nosniff")
self.send_header("Content-Length", str(len(content)))
self.end_headers()
self.wfile.write(content)
def main() -> None:
parser = argparse.ArgumentParser(description="只在指定组网 IP 上展示 Ubuntu 状态")
parser.add_argument("bind_ip", help="Ubuntu 客户端显示的组网 IP")
parser.add_argument("port", type=int, help="监听端口,例如 18097")
args = parser.parse_args()
try:
ipaddress.IPv4Address(args.bind_ip)
except ipaddress.AddressValueError as exc:
parser.error(f"无效的 IPv4 地址: {exc}")
if not 1 <= args.port <= 65535:
parser.error("端口必须在 1 到 65535 之间")
try:
with ThreadingHTTPServer((args.bind_ip, args.port), Handler) as server:
print(f"状态页已启动:http://{args.bind_ip}:{args.port}/", flush=True)
server.serve_forever()
except OSError as exc:
parser.exit(1, f"启动失败: {exc}n")
except KeyboardInterrupt:
print("n服务已停止")
if __name__ == "__main__":
main()
代码只有一个页面 /,其他路径返回 404;读取状态失败时返回 500。页面数据每次请求时重新读取,刷新浏览器即可更新。服务只监听命令中指定的 IPv4 地址,示例里使用组网 IP;不要把它替换为 0.0.0.0。
4. 在 Ubuntu 上启动服务
确认端口 18097 没被占用,然后在项目目录执行:
cd ~/starvpn-status-demo-20260925
ss -ltnp | grep ':18097' || true
nohup python3 server.py 192.168.188.5 18097 >run.log 2>&1 &
echo $! >run.pid
ss -ltnp | grep ':18097'
curl -sS -o /dev/null -w 'HTTP %{http_code}n' http://192.168.188.5:18097/
nohup 让服务在关闭当前终端后继续运行,run.pid 记录本次进程号。我的终端中可以看到 192.168.188.5:18097 处于 LISTEN,本机 HTTP 请求返回 200。

如果端口已被占用,换一个未使用端口,并在后续访问地址中同步修改。启动失败时先看 cat run.log,常见原因是 IP 填错或端口被占用。
5. 从另一台设备访问并验证
让访问端电脑也登录同一星空组网,打开:
http://192.168.188.5:18097/
我在另一台已入网电脑的浏览器中看到了 Ubuntu 的当前时间、已运行时长和根分区剩余空间。地址栏是 Ubuntu 的组网 IP,说明这次访问用的是组网地址。下图是实际打开的页面,数据会随时间变化。

浏览器地址栏可能提示“不安全”,因为这个演示应用使用普通 HTTP,没有为网页配置 HTTPS。同一组网里能够访问这台主机的成员也可能看到页面,所以它只适合作为只读练习;不要在页面展示敏感数据,也不要为了访问它而开放公网端口。
如果页面打不开,按顺序检查:两端客户端是否在线、Ubuntu 的组网 IP 是否填对、ss -ltnp 是否显示服务监听、Ubuntu 本机 curl 是否能拿到 200。这些都正常后,再检查主机防火墙对组网接口的规则。
6. 演示结束后停止和清理
临时项目不需要一直运行。先在项目目录停止本次进程,再确认端口不再监听:
cd ~/starvpn-status-demo-20260925
ps -fp "$(cat run.pid)"
kill "$(cat run.pid)"
ss -ltnp | grep ':18097' || echo '演示服务已停止'
确认目录里只有本教程创建的文件后,可删除这个演示目录:
cd ~
rm -r ~/starvpn-status-demo-20260925
这次实操已按上述顺序停止并清理,星空组网账号和原有设备连接未删除。下次要复现时,重新放入 server.py 并启动即可。
小结
整个流程是:登录管理平台确认组网在线 → 在 Ubuntu 找到组网 IP → 编写只读状态页 → 绑定组网 IP 启动 → 从另一台设备打开验证。这个小项目的价值在于把“组网成功”落到一个可访问的服务上,同时避免了数据库、Docker 和额外依赖,适合作为第一次动手练习。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/iymei4986533030/article/details/166688858




