Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 64 additions & 52 deletions .github/workflows/deploy-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: deploy-test

on:
workflow_dispatch:
# push:
# branches:
# - develop
push:
branches:
- develop

permissions:
contents: read
Expand All @@ -14,10 +14,7 @@ concurrency:
cancel-in-progress: false

env:
API_IMAGE_REPOSITORY: cixing-api
POSTGRES_IMAGE: postgres:16-alpine
REDIS_IMAGE: redis:7-alpine
MAILPIT_IMAGE: axllent/mailpit:v1.29.7
ACR_REPO: registry.cn-hangzhou.aliyuncs.com/muxi/memento

jobs:
deploy:
Expand All @@ -34,36 +31,52 @@ jobs:
TEST_SERVER_SSH_KEY: ${{ secrets.TEST_SERVER_SSH_KEY }}
TEST_CONFIG_YAML: ${{ secrets.TEST_CONFIG_YAML }}
TEST_POSTGRES_PASSWORD: ${{ secrets.TEST_POSTGRES_PASSWORD }}
ACR_USERNAME: ${{ secrets.ACR_USERNAME }}
ACR_PASSWORD: ${{ secrets.ACR_PASSWORD }}
run: |
set -eu
test -n "$TEST_SERVER_HOST"
test -n "$TEST_SERVER_USERNAME"
test -n "$TEST_SERVER_SSH_KEY"
test -n "$TEST_CONFIG_YAML"
test -n "$TEST_POSTGRES_PASSWORD"
test -n "$ACR_USERNAME"
test -n "$ACR_PASSWORD"

- name: Build API image
- name: Login to ACR
env:
ACR_USERNAME: ${{ secrets.ACR_USERNAME }}
ACR_PASSWORD: ${{ secrets.ACR_PASSWORD }}
run: |
set -eu
IMAGE_LOCAL="${API_IMAGE_REPOSITORY}:${GITHUB_SHA}"
docker build --platform linux/amd64 -f deploy/docker/Dockerfile.api -t "$IMAGE_LOCAL" .
echo "CIXING_IMAGE=$IMAGE_LOCAL" >> "$GITHUB_ENV"
echo "$ACR_PASSWORD" | docker login --username "$ACR_USERNAME" --password-stdin registry.cn-hangzhou.aliyuncs.com

- name: Pull dependency images
- name: Build and push cixing-api to ACR
run: |
set -eu
docker pull --platform linux/amd64 "$POSTGRES_IMAGE"
docker pull --platform linux/amd64 "$REDIS_IMAGE"
docker pull --platform linux/amd64 "$MAILPIT_IMAGE"
IMAGE_TAG="${GITHUB_SHA}"
IMAGE_ACR="${ACR_REPO}:${IMAGE_TAG}"
docker build --platform linux/amd64 -f deploy/docker/Dockerfile.api -t "cixing-api:${IMAGE_TAG}" -t "$IMAGE_ACR" .
docker push "$IMAGE_ACR"

- name: Save deployment images
- name: Mirror base images to ACR (skip if tag exists)
run: |
set -eu
mkdir -p /tmp/cixing-deploy/images
docker save "$CIXING_IMAGE" | gzip > /tmp/cixing-deploy/images/cixing-api.tar.gz
docker save "$POSTGRES_IMAGE" | gzip > /tmp/cixing-deploy/images/postgres.tar.gz
docker save "$REDIS_IMAGE" | gzip > /tmp/cixing-deploy/images/redis.tar.gz
docker save "$MAILPIT_IMAGE" | gzip > /tmp/cixing-deploy/images/mailpit.tar.gz
mirror() {
local source="$1"
local name="$2"
local acr_tag="${ACR_REGISTRY}/${name}"
if docker manifest inspect "$acr_tag" >/dev/null 2>&1; then
echo "skip $name: already in ACR"
return
fi
echo "mirroring $source -> $acr_tag"
docker pull --platform linux/amd64 "$source"
docker tag "$source" "$acr_tag"
docker push "$acr_tag"
}
mirror "${POSTGRES_IMAGE:-postgres:16-alpine}" postgres
mirror "${REDIS_IMAGE:-redis:7-alpine}" redis
mirror "${MAILPIT_IMAGE:-axllent/mailpit:v1.29.7}" mailpit

- name: Prepare deployment files
env:
Expand All @@ -75,14 +88,13 @@ jobs:
cp deploy/compose/docker-compose.prod.yml /tmp/cixing-deploy/docker-compose.yml
printf '%s' "$TEST_CONFIG_YAML" > /tmp/cixing-deploy/config.yaml
{
echo "CIXING_IMAGE=${CIXING_IMAGE}"
echo "POSTGRES_IMAGE=${POSTGRES_IMAGE}"
echo "REDIS_IMAGE=${REDIS_IMAGE}"
echo "MAILPIT_IMAGE=${MAILPIT_IMAGE}"
echo "CIXING_IMAGE=${ACR_REPO}:${GITHUB_SHA}"
echo "POSTGRES_IMAGE=postgres:16-alpine"
echo "REDIS_IMAGE=redis:7-alpine"
echo "MAILPIT_IMAGE=axllent/mailpit:v1.29.7"
echo "POSTGRES_PASSWORD=${TEST_POSTGRES_PASSWORD}"
echo "API_PORT=8080"
echo "MAILPIT_UI_PORT=8025"
echo "DOMAIN=test.memento.muxixyz.com"
} > /tmp/cixing-deploy/deploy.env

- name: Configure SSH
Expand All @@ -96,36 +108,33 @@ jobs:
chmod 600 ~/.ssh/id_test_server
ssh-keyscan -H "$TEST_SERVER_HOST" >> ~/.ssh/known_hosts

- name: Upload deployment assets
- name: Upload deployment files
env:
TEST_SERVER_HOST: ${{ secrets.TEST_SERVER_HOST }}
TEST_SERVER_USERNAME: ${{ secrets.TEST_SERVER_USERNAME }}
run: |
set -eu
SCP_OPTS="-o ConnectTimeout=10 -o ServerAliveInterval=15 -i ~/.ssh/id_test_server"

ssh -i ~/.ssh/id_test_server "$TEST_SERVER_USERNAME@$TEST_SERVER_HOST" \
"sudo mkdir -p /opt/cixing/config /opt/cixing/images && sudo chown -R $TEST_SERVER_USERNAME /opt/cixing"
scp -i ~/.ssh/id_test_server /tmp/cixing-deploy/docker-compose.yml \
"sudo mkdir -p /opt/cixing/config && sudo chown -R $TEST_SERVER_USERNAME /opt/cixing"

scp $SCP_OPTS /tmp/cixing-deploy/docker-compose.yml \
"$TEST_SERVER_USERNAME@$TEST_SERVER_HOST:/opt/cixing/docker-compose.yml"
scp -i ~/.ssh/id_test_server /tmp/cixing-deploy/deploy.env \
scp $SCP_OPTS /tmp/cixing-deploy/deploy.env \
"$TEST_SERVER_USERNAME@$TEST_SERVER_HOST:/opt/cixing/deploy.env"
scp -i ~/.ssh/id_test_server /tmp/cixing-deploy/config.yaml \
scp $SCP_OPTS /tmp/cixing-deploy/config.yaml \
"$TEST_SERVER_USERNAME@$TEST_SERVER_HOST:/opt/cixing/config/config.yaml"
scp -i ~/.ssh/id_test_server /tmp/cixing-deploy/images/cixing-api.tar.gz \
"$TEST_SERVER_USERNAME@$TEST_SERVER_HOST:/opt/cixing/images/cixing-api.tar.gz"
scp -i ~/.ssh/id_test_server /tmp/cixing-deploy/images/postgres.tar.gz \
"$TEST_SERVER_USERNAME@$TEST_SERVER_HOST:/opt/cixing/images/postgres.tar.gz"
scp -i ~/.ssh/id_test_server /tmp/cixing-deploy/images/redis.tar.gz \
"$TEST_SERVER_USERNAME@$TEST_SERVER_HOST:/opt/cixing/images/redis.tar.gz"
scp -i ~/.ssh/id_test_server /tmp/cixing-deploy/images/mailpit.tar.gz \
"$TEST_SERVER_USERNAME@$TEST_SERVER_HOST:/opt/cixing/images/mailpit.tar.gz"

- name: Deploy on ECS
env:
TEST_SERVER_HOST: ${{ secrets.TEST_SERVER_HOST }}
TEST_SERVER_USERNAME: ${{ secrets.TEST_SERVER_USERNAME }}
ACR_USERNAME: ${{ secrets.ACR_USERNAME }}
ACR_PASSWORD: ${{ secrets.ACR_PASSWORD }}
run: |
set -eu
ssh -i ~/.ssh/id_test_server "$TEST_SERVER_USERNAME@$TEST_SERVER_HOST" <<'REMOTE'
ssh -i ~/.ssh/id_test_server "$TEST_SERVER_USERNAME@$TEST_SERVER_HOST" "ACR_USERNAME='${ACR_USERNAME}' ACR_PASSWORD='${ACR_PASSWORD}' /bin/sh -s" <<'REMOTE'
set -eu
cd /opt/cixing
DOCKER="docker"
Expand Down Expand Up @@ -158,14 +167,13 @@ jobs:
compose ps
compose logs --tail=200 api
}
cleanup_deployment_artifacts() {
echo "cleaning deployment artifacts"
rm -f /opt/cixing/images/*.tar.gz || true
current_image="$(awk -F= '$1=="CIXING_IMAGE"{print $2}' /opt/cixing/deploy.env)"
if [ -z "$current_image" ]; then
cleanup_old_images() {
echo "cleaning old cixing-api images"
current_tag="$(awk -F= '$1=="CIXING_IMAGE"{print $2}' /opt/cixing/deploy.env | sed 's|.*:||')"
if [ -z "$current_tag" ]; then
return 0
fi
for image_ref in $($DOCKER images --format '{{.Repository}}:{{.Tag}}' | grep '^cixing-api:' | grep -F -x -v "$current_image" || true); do
for image_ref in $($DOCKER images --format '{{.Repository}}:{{.Tag}}' | grep 'cixing\|memento' | grep -v "$current_tag" || true); do
$DOCKER rmi "$image_ref" >/dev/null 2>&1 || true
done
}
Expand Down Expand Up @@ -200,10 +208,14 @@ jobs:
chmod 600 /opt/cixing/deploy.env
chmod 644 /opt/cixing/config/config.yaml
cleanup_existing_compose_state
gzip -dc /opt/cixing/images/cixing-api.tar.gz | $DOCKER load
gzip -dc /opt/cixing/images/postgres.tar.gz | $DOCKER load
gzip -dc /opt/cixing/images/redis.tar.gz | $DOCKER load
gzip -dc /opt/cixing/images/mailpit.tar.gz | $DOCKER load

echo "pulling images from ACR"
# Only pull cixing-api from ACR (base images already exist on host)
CIXING_IMAGE="$(grep '^CIXING_IMAGE=' /opt/cixing/deploy.env | cut -d= -f2-)"
echo "$ACR_PASSWORD" | $DOCKER login --username "$ACR_USERNAME" --password-stdin registry.cn-hangzhou.aliyuncs.com
echo "pulling $CIXING_IMAGE"
$DOCKER pull "$CIXING_IMAGE"

echo "starting dependency services"
compose up -d postgres redis mailpit
echo "running database migrations"
Expand Down Expand Up @@ -264,7 +276,7 @@ jobs:
final_api_state="$($DOCKER inspect --format '{{.State.Status}}' "$final_api_container_id")"
final_api_health="$($DOCKER inspect --format '{{if .State.Health}}{{.State.Health.Status}}{{else}}missing{{end}}' "$final_api_container_id")"
echo "final api status: container=$final_api_container_id state=$final_api_state health=$final_api_health"
cleanup_deployment_artifacts
cleanup_old_images
compose ps
compose logs --tail=80 api
REMOTE
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ Thumbs.db
.idea/
.vscode/
/docs/

# Claude Code
CLAUDE.md
7 changes: 6 additions & 1 deletion api/openapi/openapi.v1.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,7 @@ components:
properties:
method:
type: string
enum: [PUT]
enum: [POST, PUT]
url:
type: string
format: uri
Expand All @@ -537,6 +537,11 @@ components:
additionalProperties:
type: string
nullable: true
form_fields:
type: object
additionalProperties:
type: string
nullable: true
object_key:
type: string
minLength: 1
Expand Down
26 changes: 7 additions & 19 deletions config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,31 +19,19 @@ redis:
db: 0

oss:
credential_mode: "static"
ecs_role_name: "CixingEcsOssUploadRole"
disable_imdsv1: true
assume_role_arn: ""
assume_role_session_name: ""
assume_role_external_id: ""
assume_role_sts_endpoint: ""
assume_role_session_duration: 1h
bucket: "change-me"
region: "cn-hongkong"
bucket: "memento"
region: "" # 七牛云区域标识,可选。留空由 SDK 自动查询
access_key_id: ""
access_key_secret: ""
public_endpoint: ""
public_endpoint_is_cname: false
internal_endpoint: ""
internal_endpoint_is_cname: false
use_internal_endpoint: false
public_endpoint: "https://memento.muxixyz.com" # 七牛云 CDN 加速域名
put_presign_expire: 15m
get_presign_expire: 10m
upload_prefix: "uploads/"
styles:
card_4x3: "card_4x3"
square_small: "square_small"
square_medium: "square_medium"
detail_large: "detail_large"
card_4x3: "imageView2/1/w/900/h/1200|imageMogr2/auto-orient/quality/85/format/webp"
square_small: "imageView2/1/w/320/h/320|imageMogr2/auto-orient/quality/82/format/webp"
square_medium: "imageView2/1/w/640/h/640|imageMogr2/auto-orient/quality/85/format/webp"
detail_large: "imageMogr2/auto-orient/quality/88/format/webp"

jwt:
private_key_pem: |-
Expand Down
30 changes: 8 additions & 22 deletions configs/config.local.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -21,33 +21,19 @@ redis:
db: 0

oss:
credential_mode: "static"
ecs_role_name: "CixingEcsOssUploadRole"
disable_imdsv1: true
# For cross-account ECS RAM role access, set credential_mode to
# "ecs_ram_role_assume_role" and fill the target account role ARN below.
assume_role_arn: ""
assume_role_session_name: ""
assume_role_external_id: ""
assume_role_sts_endpoint: ""
assume_role_session_duration: 1h
bucket: "change-me"
region: "cn-hongkong"
bucket: "memento"
region: "" # 本地开发可不填,SDK 自动查询
access_key_id: ""
access_key_secret: ""
public_endpoint: ""
public_endpoint_is_cname: false
internal_endpoint: ""
internal_endpoint_is_cname: false
use_internal_endpoint: false
public_endpoint: "https://memento.muxixyz.com" # 七牛云 CDN 加速域名
put_presign_expire: 15m
get_presign_expire: 10m
upload_prefix: "uploads/"
upload_prefix: "test/" # 本地开发用 test/ 前缀隔离
styles:
card_4x3: "card_4x3"
square_small: "square_small"
square_medium: "square_medium"
detail_large: "detail_large"
card_4x3: "imageView2/1/w/900/h/1200|imageMogr2/auto-orient/quality/85/format/webp"
square_small: "imageView2/1/w/320/h/320|imageMogr2/auto-orient/quality/82/format/webp"
square_medium: "imageView2/1/w/640/h/640|imageMogr2/auto-orient/quality/85/format/webp"
detail_large: "imageMogr2/auto-orient/quality/88/format/webp"

jwt:
private_key_pem: |-
Expand Down
10 changes: 5 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@ module cixing
go 1.25.1

require (
github.com/aliyun/alibabacloud-oss-go-sdk-v2 v1.4.0
github.com/aliyun/credentials-go v1.4.8
github.com/getkin/kin-openapi v0.133.0
github.com/gin-gonic/gin v1.11.0
github.com/go-playground/validator/v10 v10.27.0
Expand All @@ -15,6 +13,7 @@ require (
github.com/oapi-codegen/gin-middleware v1.0.2
github.com/oapi-codegen/oapi-codegen/v2 v2.5.1
github.com/oapi-codegen/runtime v1.1.2
github.com/qiniu/go-sdk/v7 v7.26.12
github.com/redis/go-redis/v9 v9.17.3
github.com/sqlc-dev/sqlc v1.30.0
golang.org/x/crypto v0.48.0
Expand Down Expand Up @@ -44,13 +43,13 @@ require (
github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/ClickHouse/clickhouse-go v1.4.3 // indirect
github.com/GoogleCloudPlatform/grpc-gcp-go/grpcgcp v1.5.3 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect
github.com/alibabacloud-go/debug v1.0.1 // indirect
github.com/alibabacloud-go/tea v1.2.2 // indirect
github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 // indirect
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/antlr4-go/antlr/v4 v4.13.1 // indirect
github.com/apache/arrow/go/v10 v10.0.1 // indirect
Expand Down Expand Up @@ -94,6 +93,7 @@ require (
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/form3tech-oss/jwt-go v3.2.5+incompatible // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gammazero/toposort v0.1.1 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-jose/go-jose/v4 v4.1.1 // indirect
github.com/go-logr/logr v1.4.3 // indirect
Expand All @@ -108,6 +108,7 @@ require (
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/gocql/gocql v0.0.0-20210515062232-b7ef815b4556 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/gofrs/flock v0.8.1 // indirect
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
Expand Down Expand Up @@ -238,7 +239,6 @@ require (
google.golang.org/grpc v1.75.0 // indirect
google.golang.org/protobuf v1.36.9 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
modernc.org/b v1.0.0 // indirect
Expand Down
Loading
Loading