chore(repo): initialize team collaboration repository
CI / Python 3.12 (push) Waiting to run
CI / Python 3.9 (push) Waiting to run

This commit is contained in:
2026-07-27 20:40:12 +08:00
commit c91a64fddb
109 changed files with 21121 additions and 0 deletions
+35
View File
@@ -0,0 +1,35 @@
# FlashOps production deployment
- Domain: `flashops.imagebrewing.com`
- App root: `/data/wangzhan/app/storage-labos`
- Backend bind: `127.0.0.1:18080`
- Process manager: `systemd` unit `flashops.service`
- Reverse proxy: Baota Nginx vhost `flashops.imagebrewing.com.conf`
- Persistent data: `/data/wangzhan/app/storage-labos/flashops/var`
- Private environment file: `/etc/flashops/flashops.env`
The service deliberately uses one worker because the current production store is
SQLite. The current preview console is anonymously readable and writable through
Nginx; do not connect real hardware or production data until application RBAC and
approval gates are implemented. The ACME challenge path remains available so
certificate renewal can complete.
Before enabling Agent enrollment, create the environment file without putting
the secret in the repository or unit:
```bash
sudo install -d -o root -g root -m 0755 /etc/flashops
sudo install -o root -g root -m 0600 /dev/null /etc/flashops/flashops.env
sudoedit /etc/flashops/flashops.env
```
Add `FLASHOPS_AGENT_ENROLLMENT_TOKEN=<random secret>`, then restart the service.
Useful checks:
```bash
sudo systemctl status flashops
curl http://127.0.0.1:18080/api/v1/health
sudo nginx -t
sudo certbot renew --dry-run
```
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sh
set -eu
/usr/bin/systemctl reload nginx
+31
View File
@@ -0,0 +1,31 @@
[Unit]
Description=FlashOps control plane
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=ubuntu
Group=ubuntu
WorkingDirectory=/data/wangzhan/app/storage-labos/flashops
EnvironmentFile=-/etc/flashops/flashops.env
Environment=PYTHONPATH=/data/wangzhan/app/storage-labos/flashops/services/control-plane:/data/wangzhan/app/storage-labos/flashops/services/host-agent
Environment=FLASHOPS_ENV=production
Environment=FLASHOPS_API_HOST=127.0.0.1
Environment=FLASHOPS_API_PORT=18080
Environment=FLASHOPS_DATABASE_URL=sqlite+aiosqlite:////data/wangzhan/app/storage-labos/flashops/var/flashops.db
Environment=FLASHOPS_OBJECT_STORE_URL=/data/wangzhan/app/storage-labos/flashops/var/objects
Environment=FLASHOPS_CORS_ORIGINS=https://flashops.imagebrewing.com
ExecStart=/data/wangzhan/app/storage-labos/flashops/.venv/bin/uvicorn flashops_control.main:app --host 127.0.0.1 --port 18080 --workers 1 --proxy-headers --forwarded-allow-ips=127.0.0.1
Restart=on-failure
RestartSec=3
TimeoutStopSec=15
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=full
ProtectHome=true
ReadWritePaths=/data/wangzhan/app/storage-labos/flashops/var
UMask=0027
[Install]
WantedBy=multi-user.target
+41
View File
@@ -0,0 +1,41 @@
# Gitea deployment
The team Git service runs as Gitea 1.27.0 on the existing CVM:
- public URL: `https://git.imagebrewing.com`;
- container HTTP bind: `127.0.0.1:13000`;
- persistent data: `/data/gitea/data`;
- database: Gitea-managed SQLite;
- Git transport: HTTPS only; no extra public SSH port;
- anonymous users may browse public repositories;
- account registration requires administrator approval before sign-in.
- daily backup target: `/chucun/wangzhan-production/backups/gitea/`.
The service is intentionally separate from the FlashOps runtime and database.
Back up `/data/gitea/data` before an image upgrade. Pin the image version and
review the Gitea release notes before changing it.
The current server does not have the Docker Compose plugin. The checked-in
`docker-compose.yml` is the declarative service record for future rebuilds;
the running container was created with the equivalent pinned `docker run`
settings. Routine checks and restarts therefore use Docker directly:
```bash
sudo docker ps --filter name=^gitea$
sudo docker restart gitea
sudo docker logs --tail 100 gitea
curl -fsS http://127.0.0.1:13000/api/healthz
```
Install Compose or recreate the container from the checked-in definition only
during a planned maintenance window, after a verified backup.
Nginx terminates HTTPS and proxies to the loopback-only container port. Run
`nginx -t` before every reload. Credentials and API tokens are never stored in
this directory or committed to Git.
`gitea-backup.timer` runs daily at 03:20 Asia/Shanghai with a randomized delay.
It uses Gitea's own dump command, writes the ZIP and SHA-256 sidecar to the
mounted COS bucket, and removes the temporary local dump after a successful copy.
The live repository and SQLite database remain on the local filesystem because
COSFS is a backup destination, not a POSIX database filesystem.
+25
View File
@@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -euo pipefail
backup_dir=/chucun/wangzhan-production/backups/gitea
container_dump=/data/gitea-backup.zip
host_dump=/data/gitea/data/gitea-backup.zip
stamp=$(date -u +%Y%m%dT%H%M%SZ)
target="$backup_dir/gitea-$stamp.zip"
install -d -o ubuntu -g ubuntu -m 0770 "$backup_dir"
if [ -e "$host_dump" ]; then
unlink "$host_dump"
fi
docker exec -u git gitea sh -c \
'mkdir -p /tmp/gitea-dump && find /tmp/gitea-dump -mindepth 1 -delete'
docker exec -u git gitea gitea dump \
--config /data/gitea/conf/app.ini \
--file "$container_dump" \
--tempdir /tmp/gitea-dump
cp "$host_dump" "$target"
sha256sum "$target" > "$target.sha256"
unlink "$host_dump"
printf 'Gitea backup created: %s\n' "$target"
+28
View File
@@ -0,0 +1,28 @@
services:
gitea:
image: gitea/gitea:1.27.0
container_name: gitea
restart: unless-stopped
environment:
USER_UID: "1000"
USER_GID: "1000"
GITEA__database__DB_TYPE: sqlite3
GITEA__database__PATH: /data/gitea/gitea.db
GITEA__server__DOMAIN: git.imagebrewing.com
GITEA__server__ROOT_URL: https://git.imagebrewing.com/
GITEA__server__HTTP_PORT: "3000"
GITEA__server__DISABLE_SSH: "true"
GITEA__server__OFFLINE_MODE: "true"
GITEA__service__DISABLE_REGISTRATION: "false"
GITEA__service__REGISTER_MANUAL_CONFIRM: "true"
GITEA__service__REQUIRE_SIGNIN_VIEW: "false"
GITEA__service__DEFAULT_ALLOW_CREATE_ORGANIZATION: "false"
GITEA__repository__DEFAULT_PRIVATE: public
GITEA__security__INSTALL_LOCK: "true"
GITEA__cron__ENABLED: "true"
GITEA__log__MODE: console
GITEA__log__LEVEL: Info
ports:
- "127.0.0.1:13000:3000"
volumes:
- /data/gitea/data:/data
@@ -0,0 +1,11 @@
[Unit]
Description=Back up Gitea to mounted COS bucket
Requires=docker.service
After=docker.service network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/flashops-gitea-backup
Nice=10
IOSchedulingClass=best-effort
IOSchedulingPriority=7
+11
View File
@@ -0,0 +1,11 @@
[Unit]
Description=Daily Gitea backup timer
[Timer]
OnCalendar=*-*-* 03:20:00 Asia/Shanghai
RandomizedDelaySec=10m
Persistent=true
Unit=flashops-gitea-backup.service
[Install]
WantedBy=timers.target
+25
View File
@@ -0,0 +1,25 @@
server {
listen 80;
server_name git.imagebrewing.com;
access_log /www/wwwlogs/git.imagebrewing.com.log;
error_log /www/wwwlogs/git.imagebrewing.com.error.log;
location ^~ /.well-known/acme-challenge/ {
root /var/www/html;
default_type text/plain;
}
location / {
proxy_pass http://127.0.0.1:13000;
proxy_http_version 1.1;
proxy_set_header Host $host;
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_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
}
+47
View File
@@ -0,0 +1,47 @@
server {
listen 80;
server_name git.imagebrewing.com;
location ^~ /.well-known/acme-challenge/ {
root /var/www/html;
default_type text/plain;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
http2 on;
server_name git.imagebrewing.com;
ssl_certificate /etc/letsencrypt/live/git.imagebrewing.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/git.imagebrewing.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_timeout 1d;
ssl_session_cache shared:GiteaSSL:10m;
ssl_session_tickets off;
access_log /www/wwwlogs/git.imagebrewing.com.log;
error_log /www/wwwlogs/git.imagebrewing.com.error.log;
client_max_body_size 100m;
add_header X-Content-Type-Options nosniff always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header X-Frame-Options SAMEORIGIN always;
location / {
proxy_pass http://127.0.0.1:13000;
proxy_http_version 1.1;
proxy_set_header Host $host;
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_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
}
+26
View File
@@ -0,0 +1,26 @@
server {
listen 80;
server_name flashops.imagebrewing.com;
access_log /www/wwwlogs/flashops.imagebrewing.com.log;
error_log /www/wwwlogs/flashops.imagebrewing.com.error.log;
location ^~ /.well-known/acme-challenge/ {
auth_basic off;
root /var/www/html;
default_type text/plain;
}
location / {
proxy_pass http://127.0.0.1:18080;
proxy_http_version 1.1;
proxy_set_header Host $host;
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_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
}
+47
View File
@@ -0,0 +1,47 @@
server {
listen 80;
server_name flashops.imagebrewing.com;
location ^~ /.well-known/acme-challenge/ {
auth_basic off;
root /var/www/html;
default_type text/plain;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
http2 on;
server_name flashops.imagebrewing.com;
ssl_certificate /etc/letsencrypt/live/flashops.imagebrewing.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/flashops.imagebrewing.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_timeout 1d;
ssl_session_cache shared:FlashOpsSSL:10m;
ssl_session_tickets off;
access_log /www/wwwlogs/flashops.imagebrewing.com.log;
error_log /www/wwwlogs/flashops.imagebrewing.com.error.log;
add_header X-Content-Type-Options nosniff always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
add_header X-Frame-Options SAMEORIGIN always;
location / {
proxy_pass http://127.0.0.1:18080;
proxy_http_version 1.1;
proxy_set_header Host $host;
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_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
}