Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf082d04e4 |
@@ -0,0 +1,13 @@
|
||||
.git
|
||||
.gitignore
|
||||
.impeccable
|
||||
.venv
|
||||
__pycache__
|
||||
*.py[cod]
|
||||
*.xls
|
||||
*.xlsx
|
||||
screenshots
|
||||
tests
|
||||
docs
|
||||
design-system
|
||||
流水模板
|
||||
@@ -0,0 +1 @@
|
||||
APP_PORT=4173
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PYTHONPATH=/app/src \
|
||||
APP_HOST=0.0.0.0 \
|
||||
APP_PORT=4173
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN addgroup --system app && adduser --system --ingroup app app
|
||||
|
||||
COPY requirements.txt ./
|
||||
RUN python -m pip install --no-cache-dir --requirement requirements.txt
|
||||
|
||||
COPY server.py ./
|
||||
COPY src ./src
|
||||
COPY web ./web
|
||||
|
||||
USER app
|
||||
|
||||
EXPOSE 4173
|
||||
|
||||
HEALTHCHECK --interval=10s --timeout=3s --start-period=5s --retries=5 \
|
||||
CMD ["python", "-c", "import os, urllib.request; port = os.getenv('APP_PORT', '4173'); urllib.request.urlopen(f'http://127.0.0.1:{port}/', timeout=2)"]
|
||||
|
||||
CMD ["python", "server.py"]
|
||||
@@ -24,6 +24,24 @@ python -m unittest discover -s tests -v
|
||||
python server.py
|
||||
```
|
||||
|
||||
## Docker 部署
|
||||
|
||||
Windows PowerShell 一键启动:
|
||||
|
||||
```powershell
|
||||
.\start-docker.ps1
|
||||
```
|
||||
|
||||
Linux 或 macOS 一键启动:
|
||||
|
||||
```sh
|
||||
./start-docker.sh
|
||||
```
|
||||
|
||||
默认访问 `http://localhost:4173/`。如需更换宿主机端口,可运行
|
||||
`.\start-docker.ps1 -Port 8080`,或运行 `APP_PORT=8080 ./start-docker.sh`。
|
||||
停止服务使用 `docker compose down`,查看日志使用 `docker compose logs -f app`。
|
||||
|
||||
访问地址:
|
||||
|
||||
- 登录入口:`http://127.0.0.1:4173/`
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
image: caiwuzongzhang:local
|
||||
ports:
|
||||
- "${APP_PORT:-4173}:4173"
|
||||
environment:
|
||||
APP_HOST: 0.0.0.0
|
||||
APP_PORT: 4173
|
||||
init: true
|
||||
restart: unless-stopped
|
||||
read_only: true
|
||||
tmpfs:
|
||||
- /tmp:size=64m,mode=1777
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
@@ -106,9 +106,10 @@ class AppHandler(SimpleHTTPRequestHandler):
|
||||
|
||||
|
||||
def main() -> None:
|
||||
host = os.environ.get("APP_HOST", "127.0.0.1")
|
||||
port = int(os.environ.get("APP_PORT", "4173"))
|
||||
server = ThreadingHTTPServer(("127.0.0.1", port), AppHandler)
|
||||
print(f"Serving on http://127.0.0.1:{port}")
|
||||
server = ThreadingHTTPServer((host, port), AppHandler)
|
||||
print(f"Serving on http://{host}:{port}")
|
||||
server.serve_forever()
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
param(
|
||||
[ValidateRange(1, 65535)]
|
||||
[int]$Port = 4173
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
docker compose version | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Docker Compose is not available."
|
||||
}
|
||||
|
||||
docker info | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Docker is not running."
|
||||
}
|
||||
|
||||
$env:APP_PORT = $Port.ToString()
|
||||
docker compose up --build --detach
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Docker Compose failed to start the application."
|
||||
}
|
||||
|
||||
$containerId = (docker compose ps --quiet app).Trim()
|
||||
if (-not $containerId) {
|
||||
throw "The application container was not created."
|
||||
}
|
||||
|
||||
for ($attempt = 0; $attempt -lt 60; $attempt++) {
|
||||
$state = docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' $containerId
|
||||
if ($state -eq "healthy") {
|
||||
Write-Host "Application is ready: http://localhost:$Port"
|
||||
exit 0
|
||||
}
|
||||
if ($state -eq "unhealthy" -or $state -eq "exited" -or $state -eq "dead") {
|
||||
docker compose logs --no-color --tail 100 app
|
||||
throw "The application container entered state: $state"
|
||||
}
|
||||
Start-Sleep -Seconds 1
|
||||
}
|
||||
|
||||
docker compose logs --no-color --tail 100 app
|
||||
throw "Timed out waiting for the application health check."
|
||||
Executable
+48
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
APP_PORT="${APP_PORT:-${1:-4173}}"
|
||||
export APP_PORT
|
||||
|
||||
case "$APP_PORT" in
|
||||
''|*[!0-9]*)
|
||||
echo "APP_PORT must be an integer between 1 and 65535." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
if [ "$APP_PORT" -lt 1 ] || [ "$APP_PORT" -gt 65535 ]; then
|
||||
echo "APP_PORT must be an integer between 1 and 65535." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
docker compose version >/dev/null
|
||||
docker info >/dev/null
|
||||
docker compose up --build --detach
|
||||
|
||||
container_id="$(docker compose ps --quiet app)"
|
||||
if [ -z "$container_id" ]; then
|
||||
echo "The application container was not created." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
attempt=0
|
||||
while [ "$attempt" -lt 60 ]; do
|
||||
state="$(docker inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$container_id")"
|
||||
case "$state" in
|
||||
healthy)
|
||||
echo "Application is ready: http://localhost:${APP_PORT}"
|
||||
exit 0
|
||||
;;
|
||||
unhealthy|exited|dead)
|
||||
docker compose logs --no-color --tail 100 app
|
||||
echo "The application container entered state: $state" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
attempt=$((attempt + 1))
|
||||
sleep 1
|
||||
done
|
||||
|
||||
docker compose logs --no-color --tail 100 app
|
||||
echo "Timed out waiting for the application health check." >&2
|
||||
exit 1
|
||||
@@ -0,0 +1,17 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from unittest import TestCase
|
||||
from unittest.mock import patch
|
||||
|
||||
import server
|
||||
|
||||
|
||||
class ServerConfigurationTests(TestCase):
|
||||
@patch.dict(os.environ, {"APP_HOST": "0.0.0.0", "APP_PORT": "8080"})
|
||||
@patch("server.ThreadingHTTPServer")
|
||||
def test_main_uses_configured_host_and_port(self, http_server) -> None:
|
||||
server.main()
|
||||
|
||||
http_server.assert_called_once_with(("0.0.0.0", 8080), server.AppHandler)
|
||||
http_server.return_value.serve_forever.assert_called_once_with()
|
||||
Reference in New Issue
Block a user