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
72 changes: 54 additions & 18 deletions .github/workflows/deploy-api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ on:
env:
REGISTRY_ALIAS: m8q5m4u3
REPOSITORY: mega/campsite-api
HARBOR_REGISTRY: registry.xuanwu.openatom.cn
RUBY_VERSION: 3.3.4
NODE_VERSION: 18.16.1
BUNDLER_VERSION: 2.3.14
Expand Down Expand Up @@ -68,14 +69,22 @@ jobs:
with:
registry-type: public

- name: Build & push amd64 image (AWS)
- name: Login to Harbor
uses: docker/login-action@v3
with:
registry: ${{ env.HARBOR_REGISTRY }}
username: ${{ secrets.HARBOR_USERNAME }}
password: ${{ secrets.HARBOR_PASSWORD }}

- name: Build & push amd64 image (AWS + Harbor)
env:
AWS_REGISTRY: ${{ steps.login-ecr-public.outputs.registry }}
IMAGE_TAG: ${{ needs.prepare.outputs.image_tag }}
run: |
set -euo pipefail

AWS_IMAGE_BASE="$AWS_REGISTRY/${{ env.REGISTRY_ALIAS }}/${{ env.REPOSITORY }}"
HARBOR_IMAGE_BASE="${{ env.HARBOR_REGISTRY }}/${{ env.REPOSITORY }}"

docker buildx build \
--platform linux/amd64 \
Expand All @@ -88,6 +97,9 @@ jobs:
-t "$AWS_IMAGE_BASE:${IMAGE_TAG}" \
-t "$AWS_IMAGE_BASE:${IMAGE_TAG}-amd64" \
-t "$AWS_IMAGE_BASE:latest-amd64" \
-t "$HARBOR_IMAGE_BASE:${IMAGE_TAG}" \
-t "$HARBOR_IMAGE_BASE:${IMAGE_TAG}-amd64" \
-t "$HARBOR_IMAGE_BASE:latest-amd64" \
--push ./api

# Merge locally-built arm64 image (pushed separately) with CI-built amd64
Expand All @@ -111,6 +123,13 @@ jobs:
with:
registry-type: public

- name: Login to Harbor
uses: docker/login-action@v3
with:
registry: ${{ env.HARBOR_REGISTRY }}
username: ${{ secrets.HARBOR_USERNAME }}
password: ${{ secrets.HARBOR_PASSWORD }}

- name: Create & push manifest lists
env:
REGISTRY: ${{ steps.login-ecr-public.outputs.registry }}
Expand All @@ -119,23 +138,40 @@ jobs:
set -euo pipefail

IMAGE_BASE="$REGISTRY/${{ env.REGISTRY_ALIAS }}/${{ env.REPOSITORY }}"

docker manifest create "$IMAGE_BASE:latest" \
"$IMAGE_BASE:latest-amd64" \
"$IMAGE_BASE:latest-arm64"
docker manifest push "$IMAGE_BASE:latest"
echo "Pushed manifest: $IMAGE_BASE:latest"

if docker manifest inspect "$IMAGE_BASE:${IMAGE_TAG}-arm64" >/dev/null 2>&1; then
docker buildx imagetools create \
-t "$IMAGE_BASE:${IMAGE_TAG}" \
"$IMAGE_BASE:${IMAGE_TAG}-amd64" \
"$IMAGE_BASE:${IMAGE_TAG}-arm64"
echo "Pushed multi-arch manifest: $IMAGE_BASE:${IMAGE_TAG}"
else
echo "Using amd64-only tag: $IMAGE_BASE:${IMAGE_TAG}"
echo "Push arm64 locally to upgrade to multi-arch: ./script/demo/build-images-arm64-local.sh ${IMAGE_TAG} --push"
fi
HARBOR_BASE="${{ env.HARBOR_REGISTRY }}/${{ env.REPOSITORY }}"

push_latest() {
local base="$1"
local refs=("$base:latest-amd64")
if docker manifest inspect "$base:latest-arm64" >/dev/null 2>&1; then
refs+=("$base:latest-arm64")
else
echo "WARN: $base:latest-arm64 not found; publishing amd64-only latest"
fi
docker manifest create "$base:latest" "${refs[@]}"
docker manifest push "$base:latest"
echo "Pushed manifest: $base:latest"
}

push_tag_if_arm64() {
local base="$1"
if docker manifest inspect "$base:${IMAGE_TAG}-arm64" >/dev/null 2>&1; then
docker buildx imagetools create \
-t "$base:${IMAGE_TAG}" \
"$base:${IMAGE_TAG}-amd64" \
"$base:${IMAGE_TAG}-arm64"
echo "Pushed multi-arch manifest: $base:${IMAGE_TAG}"
else
echo "Using amd64-only tag: $base:${IMAGE_TAG}"
echo "Push arm64 locally to upgrade to multi-arch: ./script/demo/build-images-arm64-local.sh ${IMAGE_TAG} --push"
fi
}

push_latest "$IMAGE_BASE"
push_tag_if_arm64 "$IMAGE_BASE"

push_latest "$HARBOR_BASE"
push_tag_if_arm64 "$HARBOR_BASE"

deploy-aws:
if: ${{ github.repository == 'web3infra-foundation/campsite' }}
Expand Down
8 changes: 8 additions & 0 deletions api/app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,14 @@ def self.from_omniauth(access_token)
found.confirm if found.skip_confirmation_notification! && found.save
end

if access_token.provider.to_s == "github"
github_login = access_token.info.nickname.presence
if github_login.present? && found.github_login != github_login
found.github_login = github_login
found.save! if found.persisted?
end
end

ImportRemoteUserAvatarJob.perform_async(found.id) if found.avatar_path && found.persisted?
found
end
Expand Down
4 changes: 4 additions & 0 deletions api/app/models/user/null_user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ def username
""
end

def github_login
nil
end

def onboarded_at
""
end
Expand Down
1 change: 1 addition & 0 deletions api/app/serializers/current_user_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class CurrentUserSerializer < ApiSerializer
api_field :cover_photo_url, nullable: true
api_field :email
api_field :username
api_field :github_login, nullable: true
api_field :display_name

api_field :onboarded_at, nullable: true
Expand Down
1 change: 1 addition & 0 deletions api/app/serializers/sync_user_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ class SyncUserSerializer < ApiSerializer
api_association :avatar_urls, blueprint: AvatarUrlsSerializer
api_field :display_name
api_field :username
api_field :github_login, nullable: true
api_field :email
api_field :integration?, name: :integration, type: :boolean
api_field :notifications_paused?, name: :notifications_paused, type: :boolean
Expand Down
1 change: 1 addition & 0 deletions api/app/serializers/user_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ class UserSerializer < ApiSerializer
api_field :cover_photo_url, nullable: true
api_field :email
api_field :username
api_field :github_login, nullable: true
api_field :display_name
api_field :system?, name: :system, type: :boolean
api_field :integration?, name: :integration, type: :boolean
Expand Down
8 changes: 8 additions & 0 deletions api/db/migrate/20260723023000_add_github_login_to_users.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# frozen_string_literal: true

class AddGithubLoginToUsers < ActiveRecord::Migration[7.2]
def change
add_column :users, :github_login, :string
add_index :users, :github_login, unique: true
end
end
4 changes: 3 additions & 1 deletion api/db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.2].define(version: 2026_01_29_075537) do
ActiveRecord::Schema[7.2].define(version: 2026_07_23_023000) do
create_table "attachments", id: { type: :bigint, unsigned: true }, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "public_id", limit: 12, null: false
t.text "file_path", null: false
Expand Down Expand Up @@ -1472,8 +1472,10 @@
t.datetime "notification_pause_expires_at"
t.string "preferred_timezone"
t.datetime "notifications_paused_at"
t.string "github_login"
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
t.index ["email"], name: "index_users_on_email", unique: true, length: 320
t.index ["github_login"], name: "index_users_on_github_login", unique: true
t.index ["login_token"], name: "index_users_on_login_token", unique: true
t.index ["omniauth_provider", "omniauth_uid"], name: "index_users_on_omniauth_provider_and_omniauth_uid", unique: true
t.index ["public_id"], name: "index_users_on_public_id", unique: true
Expand Down
45 changes: 32 additions & 13 deletions api/script/demo/build-images-arm64-local.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,25 @@
#
# Usage:
# ./script/demo/build-images-arm64-local.sh [IMAGE_TAG] [--push]
# --push Also push to remote registry; if omitted, image is only built and loaded locally.
# --push Also push to remote registries; if omitted, image is only built and loaded locally.
#
# Registries (both tagged when --push):
# - public.ecr.aws/m8q5m4u3/mega/campsite-api
# - registry.xuanwu.openatom.cn/mega/campsite-api
#
# Requirements:
# - Docker with buildx enabled ("docker buildx create --use")
# - If using --push you must already be logged into the destination registry.
# - If using --push you must already be logged into the destination registries.

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
API_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)"
cd "$API_DIR"

REGISTRY_ALIAS="m8q5m4u3" # Public ECR alias
REGISTRY="public.ecr.aws/${REGISTRY_ALIAS}"
ECR_REGISTRY="public.ecr.aws/${REGISTRY_ALIAS}"
HARBOR_REGISTRY="${HARBOR_REGISTRY:-registry.xuanwu.openatom.cn}"
REPOSITORY="mega/campsite-api"

# ----------------------------------------
Expand Down Expand Up @@ -60,12 +69,16 @@ NODE_VERSION="${NODE_VERSION:-18.16.1}"
BUNDLER_VERSION="${BUNDLER_VERSION:-2.3.14}"

IMAGE_TAG="${IMAGE_TAG_BASE}-${ARCH_SUFFIX}"
IMAGE_NAME="${REGISTRY}/${REPOSITORY}:${IMAGE_TAG}"
LATEST_ARCH_TAG="${REGISTRY}/${REPOSITORY}:latest-${ARCH_SUFFIX}"
CACHE_IMAGE="${REGISTRY}/${REPOSITORY}:buildcache-${ARCH_SUFFIX}"
ECR_IMAGE="${ECR_REGISTRY}/${REPOSITORY}:${IMAGE_TAG}"
ECR_LATEST_ARCH="${ECR_REGISTRY}/${REPOSITORY}:latest-${ARCH_SUFFIX}"
HARBOR_IMAGE="${HARBOR_REGISTRY}/${REPOSITORY}:${IMAGE_TAG}"
HARBOR_LATEST_ARCH="${HARBOR_REGISTRY}/${REPOSITORY}:latest-${ARCH_SUFFIX}"
CACHE_IMAGE="${ECR_REGISTRY}/${REPOSITORY}:buildcache-${ARCH_SUFFIX}"
CACHE_DIR="${BUILDX_CACHE_DIR:-${HOME}/.cache/campsite-api-buildx/${ARCH_SUFFIX}}"

echo "Building ${IMAGE_NAME} (${PLATFORM}) …"
echo "Building (${PLATFORM}) …"
echo " ECR: $ECR_IMAGE"
echo " Harbor: $HARBOR_IMAGE"

# Ensure buildx is available
if ! docker buildx version >/dev/null 2>&1; then
Expand All @@ -78,13 +91,15 @@ BUILD_ARGS=(
--platform "$PLATFORM"
--provenance=false
--sbom=false
-t "$IMAGE_NAME"
-t "$LATEST_ARCH_TAG"
-t "$ECR_IMAGE"
-t "$ECR_LATEST_ARCH"
-t "$HARBOR_IMAGE"
-t "$HARBOR_LATEST_ARCH"
--build-arg "RUBY_VERSION=$RUBY_VERSION"
--build-arg "NODE_VERSION=$NODE_VERSION"
--build-arg "BUNDLER_VERSION=$BUNDLER_VERSION"
--cache-from "type=registry,ref=${CACHE_IMAGE}"
--cache-from "type=registry,ref=${IMAGE_NAME}"
--cache-from "type=registry,ref=${ECR_IMAGE}"
)

if $DO_PUSH; then
Expand All @@ -100,12 +115,16 @@ if ! docker buildx build "${BUILD_ARGS[@]}" .; then
exit 1
fi

echo "Image built successfully: $IMAGE_NAME"
echo "Image built successfully: $ECR_IMAGE / $HARBOR_IMAGE"

if $DO_PUSH; then
echo "Image pushed successfully: $IMAGE_NAME"
echo "Image pushed successfully:"
echo " $ECR_IMAGE"
echo " $ECR_LATEST_ARCH"
echo " $HARBOR_IMAGE"
echo " $HARBOR_LATEST_ARCH"
echo "Build cache saved to: ${CACHE_IMAGE}"
else
echo "Image loaded locally: $IMAGE_NAME"
echo "Image loaded locally: $ECR_IMAGE"
echo "Build cache saved to: ${CACHE_DIR}"
fi
Binary file removed api/vendor/cache/prism-1.4.0.gem
Binary file not shown.
Binary file added api/vendor/cache/prism-1.9.0.gem
Binary file not shown.
Binary file removed api/vendor/cache/rbs-3.9.1.gem
Binary file not shown.
Binary file added api/vendor/cache/rbs-4.0.0.gem
Binary file not shown.
Binary file removed api/vendor/cache/ruby-lsp-0.23.12.gem
Binary file not shown.
Binary file added api/vendor/cache/ruby-lsp-0.26.8.gem
Binary file not shown.
Binary file removed api/vendor/cache/ruby-lsp-rails-0.4.0.gem
Binary file not shown.
Binary file added api/vendor/cache/ruby-lsp-rails-0.4.8.gem
Binary file not shown.
Binary file removed api/vendor/cache/sorbet-runtime-0.5.11956.gem
Binary file not shown.
Binary file added api/vendor/cache/tsort-0.2.0.gem
Binary file not shown.
72 changes: 72 additions & 0 deletions ci/jenkins/campsite-api/Jenkinsfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
pipeline {
agent any

environment {
HARBOR_REGISTRY = 'registry.xuanwu.openatom.cn'
HARBOR_REPO = 'mega/campsite-api'
DOCKERFILE = 'api/Dockerfile'
BUILD_CONTEXT = 'api'
GIT_URL = 'https://github.com/benjamin-747/campsite'
GIT_BRANCH = 'main'
GIT_CREDENTIALS = 'd4cedbd2-3e68-411b-8367-0960974d7b6d'
HARBOR_CREDENTIALS = '3cefd32b-0fae-4749-b125-0a3f0c537f88'
RUBY_VERSION = '3.3.4'
NODE_VERSION = '18.16.1'
BUNDLER_VERSION = '2.3.14'
}

stages {
stage('Checkout') {
steps {
git branch: "${GIT_BRANCH}",
credentialsId: "${GIT_CREDENTIALS}",
url: "${GIT_URL}"
script {
env.SHORT_SHA = sh(script: 'git rev-parse --short=7 HEAD', returnStdout: true).trim()
env.IMAGE_SHA = "${HARBOR_REGISTRY}/${HARBOR_REPO}:${env.SHORT_SHA}"
env.IMAGE_LATEST = "${HARBOR_REGISTRY}/${HARBOR_REPO}:latest"
env.IMAGE_SHA_AMD64 = "${HARBOR_REGISTRY}/${HARBOR_REPO}:${env.SHORT_SHA}-amd64"
env.IMAGE_LATEST_AMD64 = "${HARBOR_REGISTRY}/${HARBOR_REPO}:latest-amd64"
currentBuild.description = "${HARBOR_REPO}:${env.SHORT_SHA}"
}
echo "Building image: ${env.IMAGE_SHA}"
}
}

stage('Build Docker Image') {
steps {
sh '''
/opt/docker-cli/docker build --network=host \
--build-arg RUBY_VERSION=${RUBY_VERSION} \
--build-arg NODE_VERSION=${NODE_VERSION} \
--build-arg BUNDLER_VERSION=${BUNDLER_VERSION} \
-f ${DOCKERFILE} \
-t ${IMAGE_SHA} \
${BUILD_CONTEXT}
'''
sh '/opt/docker-cli/docker tag ${IMAGE_SHA} ${IMAGE_LATEST}'
sh '/opt/docker-cli/docker tag ${IMAGE_SHA} ${IMAGE_SHA_AMD64}'
sh '/opt/docker-cli/docker tag ${IMAGE_SHA} ${IMAGE_LATEST_AMD64}'
}
}

stage('Push to Harbor') {
steps {
withCredentials([
usernamePassword(
credentialsId: "${HARBOR_CREDENTIALS}",
usernameVariable: 'HARBOR_USER',
passwordVariable: 'HARBOR_PASS'
)
]) {
sh '/opt/docker-cli/docker login ${HARBOR_REGISTRY} -u ${HARBOR_USER} -p ${HARBOR_PASS}'
sh '/opt/docker-cli/docker push ${IMAGE_SHA}'
sh '/opt/docker-cli/docker push ${IMAGE_LATEST}'
sh '/opt/docker-cli/docker push ${IMAGE_SHA_AMD64}'
sh '/opt/docker-cli/docker push ${IMAGE_LATEST_AMD64}'
sh '/opt/docker-cli/docker logout ${HARBOR_REGISTRY}'
}
}
}
}
}
Loading