fix(run): 增强数据库初始化与安全配置

- 从环境变量读取密钥,未设置时随机生成
- 自动初始化数据库并显式使用 UTF-8 读取 schema
- 移除默认管理员账号,更新页面署名与链接
- 新增 .gitignore、LICENSE 和 README
这个提交包含在:
2026-08-21 16:45:20 +08:00
父节点 bff5252ff5
当前提交 25b72bad5d
共修改 7 个文件,包含 180 行新增和 19 行删除
+30
查看文件
@@ -0,0 +1,30 @@
# 数据库文件(含本地数据,不提交)
*.db
*.sqlite
*.sqlite3
# Python
__pycache__/
*.py[cod]
*.egg-info/
.Python
venv/
.venv/
env/
# 环境变量 / 密钥
.env
.env.*
# 运行时 / 日志
*.log
.pyc
# 操作系统
.DS_Store
Thumbs.db
# 编辑器
.idea/
.vscode/
*.swp
+21
查看文件
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 wali-Navi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+102
查看文件
@@ -0,0 +1,102 @@
# 瓦力导航 · wali-Navi
一个基于 Flask 的轻量级网址导航站,支持用户自定义收藏、管理员维护公共导航库、访问统计等功能。数据使用 SQLite 存储,开箱即用,可一键部署到 Docker。
## 功能特性
- 🔖 **公共导航库**:管理员可维护分类导航(搜索类 / 快捷方式类)
- 👤 **用户系统**:支持注册、登录、退出;普通用户可自定义个人收藏
- 🎨 **自定义收藏**:用户可增删改个人搜索与快捷导航,支持图标、颜色、标签、分组
- 📊 **访问统计**:记录每日访问量与在线人数,提供数据分析页面
- 🔍 **多引擎搜索**:可配置多个搜索引擎
- 🗺️ **自动 Sitemap**:自动生成 `sitemap.xml` 便于 SEO
- 🐳 **Docker 支持**:提供 `Dockerfile` 与 `docker-compose.yml` 一键部署
## 技术栈
- 后端:Python 3.9 + Flask 2.2
- 缓存:Flask-Caching(SimpleCache)
- 服务器:gevent WSGI
- 前端:Fomantic-UI(Semantic UI)、jQuery
- 数据库:SQLite
## 目录结构
```
navi/
├── run.py # 应用主入口
├── collect.py # 导航数据采集脚本(参考)
├── schema.sql # 数据库表结构
├── site.txt # 初始导航数据(CSV 格式)
├── requirements.txt # Python 依赖
├── Dockerfile # 基于 python:3.9-slim 的镜像
├── Dockerfile-ubuntu # 基于 Ubuntu 的镜像(备选)
├── docker-compose.yml # 编排文件
├── static/ # 静态资源(CSS/JS/图片)
└── templates/ # Jinja2 模板
```
## 快速开始
### 本地运行
1. 安装依赖:
```bash
pip install -r requirements.txt
```
2. 配置环境变量(可选):
| 变量名 | 说明 | 默认值 |
| --- | --- | --- |
| `NAVI_SECRET_KEY` | Flask 会话签名密钥,**生产环境务必设置** | 未设置时随机生成(重启失效) |
| `NAVI_SITE_URL` | 站点域名,用于生成 sitemap | 空(相对路径) |
```bash
export NAVI_SECRET_KEY="your-strong-secret-key"
export NAVI_SITE_URL="https://your-domain.com"
```
3. 启动应用:
```bash
python run.py
```
首次运行会自动按 `schema.sql` 创建数据库与表结构(生成空的 `navi.db`)。
4. 访问 `http://localhost:18100`。
> 默认未内置管理员账号。如需创建管理员,可在数据库生成后执行:
> ```sql
> insert into user (username, password, status, type) values ('admin', 'your-strong-password', 1, 1);
> ```
> 其中 `type=1` 表示管理员。
### Docker 部署
```bash
docker compose up -d --build
```
容器映射端口 `18100`,数据库 `navi.db` 通过卷挂载持久化。
### 初始化导航数据
`site.txt` 为 CSV 格式的初始导航数据,可参考 `collect.py` 中的逻辑将其导入 `library` 表。
## 配置说明
- 应用默认监听 `0.0.0.0:18100`,可在 `run.py` 末尾的 `app.run(...)` 修改。
- 缓存默认超时 300 秒,访问统计缓存 30 秒,可在 `run.py` 中调整。
## 安全建议
- **务必设置 `NAVI_SECRET_KEY` 环境变量**,否则会话可被伪造。
- 生产环境请关闭 Flask 的 `debug` 模式,并使用 gevent 等生产级 WSGI 服务器(代码中已预留 `WSGIServer` 用法)。
- 默认管理员密码请替换为强密码。
## 开源协议
本项目基于 [MIT License](./LICENSE) 开源。
+18 -4
查看文件
@@ -1,11 +1,14 @@
import datetime,time import datetime,time
import os
import sqlite3 import sqlite3
from gevent.pywsgi import WSGIServer from gevent.pywsgi import WSGIServer
from flask import Flask, abort, render_template, g, escape, request, make_response, session, redirect, url_for from flask import Flask, abort, render_template, g, escape, request, make_response, session, redirect, url_for
from flask_caching import Cache from flask_caching import Cache
app = Flask(__name__) app = Flask(__name__)
app.secret_key = b'f98d5992f84753d9004ef6b86f8dc603e105d6f201b3a7ac0e0d6d674e3b71a3' # 从环境变量读取密钥,未设置时生成临时随机密钥(重启后失效,生产环境请务必设置)
import os
app.secret_key = os.environ.get('NAVI_SECRET_KEY') or os.urandom(24)
# ===========db # ===========db
DATABASE = './navi.db' DATABASE = './navi.db'
cache = Cache(config={"DEBUG": True,'CACHE_TYPE': 'SimpleCache',"CACHE_DEFAULT_TIMEOUT": 300}) cache = Cache(config={"DEBUG": True,'CACHE_TYPE': 'SimpleCache',"CACHE_DEFAULT_TIMEOUT": 300})
@@ -29,12 +32,22 @@ def close_connection(exception):
def init_db(): def init_db():
with app.app_context(): with app.app_context():
db = get_db() db = get_db()
with app.open_resource('./schema.sql', mode='r') as f: # 显式以 utf-8 读取,避免 Windows(GBK) 下因中文注释解码失败
with app.open_resource('./schema.sql', mode='r', encoding='utf-8') as f:
db.cursor().executescript(f.read()) db.cursor().executescript(f.read())
db.commit() db.commit()
# init_db() def ensure_db():
"""确保数据库文件及表结构存在,首次运行自动按 schema.sql 初始化。"""
if not os.path.exists(DATABASE):
print("数据库不存在,正在初始化:%s" % DATABASE)
init_db()
# 启动时检查并初始化数据库
ensure_db()
# init_db() # 如需重置数据库结构可手动调用
def query_db(query, args=(), one=False): def query_db(query, args=(), one=False):
cur = get_db().execute(query, args) cur = get_db().execute(query, args)
@@ -385,10 +398,11 @@ def sitemap():
pages = [] pages = []
ten_days_ago = (datetime.datetime.now() - datetime.timedelta(days=7)).date().isoformat() ten_days_ago = (datetime.datetime.now() - datetime.timedelta(days=7)).date().isoformat()
# static pages # static pages
site_url = os.environ.get('NAVI_SITE_URL', '').rstrip('/')
for rule in app.url_map.iter_rules(): for rule in app.url_map.iter_rules():
if "GET" in rule.methods and len(rule.arguments) == 0: if "GET" in rule.methods and len(rule.arguments) == 0:
pages.append( pages.append(
["https://nav.iwali.top" + str(rule.rule), ten_days_ago] [site_url + str(rule.rule), ten_days_ago]
) )
sitemap_xml = render_template('sitemap_template.xml', pages=pages) sitemap_xml = render_template('sitemap_template.xml', pages=pages)
response = make_response(sitemap_xml) response = make_response(sitemap_xml)
+2 -3
查看文件
@@ -8,9 +8,8 @@ CREATE TABLE user
type INTEGER not null default 0 type INTEGER not null default 0
); );
insert into user (username, password,status,type) -- 默认管理员账号请自行通过初始化脚本创建,例如:
values ('admin', 'admin', 1, 1), -- insert into user (username, password, status, type) values ('admin', '请修改为强密码', 1, 1);
('wangcl', 'wangcl', 1, 0);
DROP TABLE IF EXISTS search; DROP TABLE IF EXISTS search;
CREATE TABLE search CREATE TABLE search
+3 -3
查看文件
@@ -10,12 +10,12 @@
<title>瓦力导航 · wali-Navi | A Navigation Website for wali</title> <title>瓦力导航 · wali-Navi | A Navigation Website for wali</title>
<meta property="og:title" content="瓦力导航 · wali-Navi" /> <meta property="og:title" content="瓦力导航 · wali-Navi" />
<meta name="author" content="iROCKBUNNY" /> <meta name="author" content="wali-Navi" />
<meta property="og:locale" content="zh_CN" /> <meta property="og:locale" content="zh_CN" />
<meta name="description" content="A Navigation Website for wali" /> <meta name="description" content="A Navigation Website for wali" />
<meta property="og:description" content="A Navigation Website for wali" /> <meta property="og:description" content="A Navigation Website for wali" />
<link rel="canonical" href="https://nav.iwali.top/" /> <link rel="canonical" href="/" />
<meta property="og:url" content="https://nav.iwali.top/" /> <meta property="og:url" content="/" />
<meta property="og:site_name" content="瓦力导航 · wali-Navi" /> <meta property="og:site_name" content="瓦力导航 · wali-Navi" />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta name="twitter:card" content="summary" /> <meta name="twitter:card" content="summary" />
+4 -9
查看文件
@@ -2,27 +2,22 @@
<div class="ui container"> <div class="ui container">
<div class="ui stackable inverted divided equal height stackable grid"> <div class="ui stackable inverted divided equal height stackable grid">
<div class="six wide column"> <div class="six wide column">
<h4 class="ui inverted header">站群</h4> <h4 class="ui inverted header">友情链接</h4>
<div class="ui inverted link list"> <div class="ui inverted link list">
<a class="ui mini label" href="https://iwali.top/" target="_blank">iwali</a> <a class="ui mini label" href="/donate/" target="_blank">Donate</a>
<a class="ui mini label" href="https://status.iwali.top/" target="_blank">监控</a>
<a class="ui mini label" href="https://wakapi.iwali.top/" target="_blank">wakapi</a>
<a class="ui mini label" href="https://disk.iwali.top/" target="_blank">云盘</a>
<a class="ui mini label" href="https://kb.iwali.top/" target="_blank">看板</a>
</div> </div>
</div> </div>
<div class="three wide column"> <div class="three wide column">
<h4 class="ui inverted header">About</h4> <h4 class="ui inverted header">About</h4>
<div class="ui inverted link list"> <div class="ui inverted link list">
<a class="item" href="https://iwali.top" target="_blank">iwali</a>
<a class="item" href="/donate/">Donate</a> <a class="item" href="/donate/">Donate</a>
</div> </div>
</div> </div>
<div class="seven wide column"> <div class="seven wide column">
<h4 class="ui inverted header">瓦力导航 · wali-Navi</h4> <h4 class="ui inverted header">瓦力导航 · wali-Navi</h4>
<div class="ui inverted link list"> <div class="ui inverted link list">
<div class="item">&copy; 2023 iwali</div> <div class="item">&copy; 2023 wali-Navi</div>
<div class="item">Designed and built by iwali.</div> <div class="item">Designed and built by wali-Navi.</div>
<a class="item" href="/analytics/"> <a class="item" href="/analytics/">
Visits / Page Views: Visits / Page Views:
<span id="today-visits">{{ get_today_data()['views'] }}</span> / <span id="today-actions">{{ get_today_data()['online'] }}</span> (Today) <span id="today-visits">{{ get_today_data()['views'] }}</span> / <span id="today-actions">{{ get_today_data()['online'] }}</span> (Today)