Change prod workflow and add automatic deployment
This commit is contained in:
108
.gitea/workflows/release.yaml
Normal file
108
.gitea/workflows/release.yaml
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
name: Release Build
|
||||||
|
run-name: Build and Release by ${{ gitea.actor }}
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Build and Push Server
|
||||||
|
run: |
|
||||||
|
cd server
|
||||||
|
docker build -t ${{ vars.REGISTRY_URL }}/zen-kanji-server:latest .
|
||||||
|
docker push ${{ vars.REGISTRY_URL }}/zen-kanji-server:latest
|
||||||
|
|
||||||
|
- name: Build and Push Client
|
||||||
|
run: |
|
||||||
|
cd client
|
||||||
|
docker build \
|
||||||
|
--target production-stage \
|
||||||
|
--build-arg VITE_API_URL=${{ vars.VITE_API_URL }} \
|
||||||
|
-t ${{ vars.REGISTRY_URL }}/zen-kanji-client:latest .
|
||||||
|
docker push ${{ vars.REGISTRY_URL }}/zen-kanji-client:latest
|
||||||
|
|
||||||
|
build-android-and-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: build-docker
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "20"
|
||||||
|
|
||||||
|
- name: Set up JDK 21
|
||||||
|
uses: actions/setup-java@v4
|
||||||
|
with:
|
||||||
|
java-version: "21"
|
||||||
|
distribution: "temurin"
|
||||||
|
|
||||||
|
- name: Setup Android SDK (Manual)
|
||||||
|
run: |
|
||||||
|
export ANDROID_HOME=$HOME/android-sdk
|
||||||
|
CMDLINE_VERSION=11076708
|
||||||
|
|
||||||
|
mkdir -p $ANDROID_HOME/cmdline-tools
|
||||||
|
|
||||||
|
echo "Downloading SDK..."
|
||||||
|
wget -q https://dl.google.com/android/repository/commandlinetools-linux-${CMDLINE_VERSION}_latest.zip -O cmdline-tools.zip
|
||||||
|
|
||||||
|
unzip -q cmdline-tools.zip
|
||||||
|
mv cmdline-tools $ANDROID_HOME/cmdline-tools/latest
|
||||||
|
rm cmdline-tools.zip
|
||||||
|
|
||||||
|
yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses || true
|
||||||
|
|
||||||
|
echo "ANDROID_HOME=$ANDROID_HOME" >> $GITHUB_ENV
|
||||||
|
echo "ANDROID_SDK_ROOT=$ANDROID_HOME" >> $GITHUB_ENV
|
||||||
|
echo "$ANDROID_HOME/cmdline-tools/latest/bin" >> $GITHUB_PATH
|
||||||
|
echo "$ANDROID_HOME/platform-tools" >> $GITHUB_PATH
|
||||||
|
|
||||||
|
- name: Install and Build Web App
|
||||||
|
working-directory: client
|
||||||
|
env:
|
||||||
|
VITE_API_URL: ${{ vars.VITE_API_URL }}
|
||||||
|
run: |
|
||||||
|
npm ci
|
||||||
|
npm run build:android
|
||||||
|
|
||||||
|
- name: Sync Capacitor to Android
|
||||||
|
working-directory: client
|
||||||
|
run: npx cap sync android
|
||||||
|
|
||||||
|
- name: Decode Keystore
|
||||||
|
run: |
|
||||||
|
cd client/android/app
|
||||||
|
echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 -d > my-release-key.jks
|
||||||
|
|
||||||
|
- name: Build Release APK
|
||||||
|
working-directory: client/android
|
||||||
|
env:
|
||||||
|
RELEASE_KEY_PASSWORD: ${{ secrets.ORG_GRADLE_PROJECT_RELEASE_KEY_PASSWORD }}
|
||||||
|
RELEASE_KEY_ALIAS: ${{ secrets.ORG_GRADLE_PROJECT_RELEASE_KEY_ALIAS }}
|
||||||
|
run: |
|
||||||
|
chmod +x gradlew
|
||||||
|
./gradlew assembleRelease
|
||||||
|
|
||||||
|
- name: Create Release
|
||||||
|
uses: https://gitea.com/actions/gitea-release-action@v1
|
||||||
|
with:
|
||||||
|
tag_name: v1.0.${{ gitea.run_number }}
|
||||||
|
name: Release v1.0.${{ gitea.run_number }}
|
||||||
|
body: |
|
||||||
|
Automated release for commit ${{ gitea.sha }}.
|
||||||
|
|
||||||
|
**Docker Images:**
|
||||||
|
- Server: `${{ vars.REGISTRY }}/${{ vars.SERVER_IMAGE }}:latest`
|
||||||
|
- Client: `${{ vars.REGISTRY }}/${{ vars.CLIENT_IMAGE }}:latest`
|
||||||
|
files: |
|
||||||
|
client/android/app/build/outputs/apk/release/*.apk
|
||||||
|
api_key: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
CAP_ENV=dev
|
|
||||||
VITE_API_URL=https://zenkanji-api.crylia.de
|
|
||||||
2
client/.gitignore
vendored
2
client/.gitignore
vendored
@@ -1,5 +1,3 @@
|
|||||||
node_modules
|
node_modules
|
||||||
.env
|
.env
|
||||||
.env.android
|
.env.android
|
||||||
my-release-key.jks
|
|
||||||
gradle.properties
|
|
||||||
|
|||||||
@@ -1,15 +1,31 @@
|
|||||||
FROM node:24-alpine AS dev-stage
|
# Stage 1: Build the Application
|
||||||
|
FROM node:20-alpine AS build-stage
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
ARG VITE_API_URL
|
||||||
|
ENV VITE_API_URL=$VITE_API_URL
|
||||||
|
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
|
|
||||||
RUN npm ci
|
RUN npm ci
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
FROM node:20-alpine AS dev-stage
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm ci
|
||||||
COPY . .
|
COPY . .
|
||||||
EXPOSE 5173
|
EXPOSE 5173
|
||||||
CMD ["npm", "run", "dev", "--", "--host"]
|
CMD ["npm", "run", "dev", "--", "--host"]
|
||||||
|
|
||||||
FROM dev-stage AS build-stage
|
FROM nginx:alpine AS production-stage
|
||||||
ARG VITE_API_URL
|
RUN mkdir -p /run/nginx
|
||||||
ENV VITE_API_URL=$VITE_API_URL
|
|
||||||
RUN npm run build
|
COPY --from=build-stage /app/dist /usr/share/nginx/html
|
||||||
|
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
|
|||||||
@@ -16,12 +16,28 @@ android {
|
|||||||
}
|
}
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
release {
|
release {
|
||||||
|
if (file("my-release-key.jks").exists()) {
|
||||||
storeFile file("my-release-key.jks")
|
storeFile file("my-release-key.jks")
|
||||||
storePassword RELEASE_KEY_PASSWORD
|
if (project.hasProperty("RELEASE_KEY_PASSWORD")) {
|
||||||
keyAlias "my-key-alias"
|
storePassword RELEASE_KEY_PASSWORD
|
||||||
keyPassword RELEASE_KEY_PASSWORD
|
keyPassword RELEASE_KEY_PASSWORD
|
||||||
|
} else if (System.getenv("RELEASE_KEY_PASSWORD") != null) {
|
||||||
|
storePassword System.getenv("RELEASE_KEY_PASSWORD")
|
||||||
|
keyPassword System.getenv("RELEASE_KEY_PASSWORD")
|
||||||
|
} else {
|
||||||
|
storePassword "missing_password"
|
||||||
|
keyPassword "missing_password"
|
||||||
|
}
|
||||||
|
if (project.hasProperty("RELEASE_KEY_ALIAS")) {
|
||||||
|
keyAlias RELEASE_KEY_ALIAS
|
||||||
|
} else if (System.getenv("RELEASE_KEY_ALIAS") != null) {
|
||||||
|
keyAlias System.getenv("RELEASE_KEY_ALIAS")
|
||||||
|
} else {
|
||||||
|
keyAlias "my-key-alias"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
signingConfig signingConfigs.release
|
signingConfig signingConfigs.release
|
||||||
|
|||||||
2
client/android/gradle.properties
Normal file
2
client/android/gradle.properties
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx1536m
|
||||||
|
android.useAndroidX=true
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
|
||||||
<title>Zen Kanji</title>
|
<title>Zen Kanji</title>
|
||||||
<link rel="icon" type="image/x-icon" href="/assets/favicon.ico">
|
<link rel="icon" type="image/x-icon" href="/src/assets/favicon.ico">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
16
client/nginx.conf
Normal file
16
client/nginx.conf
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, no-transform";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,29 +1,33 @@
|
|||||||
import { fileURLToPath, URL } from 'node:url';
|
import { fileURLToPath, URL } from 'node:url';
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig, loadEnv } from 'vite';
|
||||||
import vue from '@vitejs/plugin-vue';
|
import vue from '@vitejs/plugin-vue';
|
||||||
import vueDevTools from 'vite-plugin-vue-devtools';
|
import vueDevTools from 'vite-plugin-vue-devtools';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig(({ mode }) => {
|
||||||
plugins: [
|
const env = loadEnv(mode, process.cwd(), '');
|
||||||
vue(),
|
|
||||||
vueDevTools(),
|
return {
|
||||||
],
|
plugins: [
|
||||||
resolve: {
|
vue(),
|
||||||
alias: {
|
vueDevTools(),
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
server: {
|
|
||||||
allowedHosts: [
|
|
||||||
'zenkanji.crylia.de',
|
|
||||||
],
|
],
|
||||||
host: true,
|
resolve: {
|
||||||
port: 5173,
|
alias: {
|
||||||
strictPort: true,
|
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||||
hmr: {
|
},
|
||||||
host: 'zenkanji.crylia.de',
|
|
||||||
protocol: 'wss',
|
|
||||||
clientPort: 443,
|
|
||||||
},
|
},
|
||||||
},
|
server: {
|
||||||
|
allowedHosts: [
|
||||||
|
'localhost',
|
||||||
|
],
|
||||||
|
host: true,
|
||||||
|
port: 5173,
|
||||||
|
strictPort: true,
|
||||||
|
hmr: {
|
||||||
|
host: 'localhost',
|
||||||
|
protocol: 'ws',
|
||||||
|
clientPort: 5173,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
33
docker-compose.dev.yml
Normal file
33
docker-compose.dev.yml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
services:
|
||||||
|
mongo:
|
||||||
|
image: mongo:6
|
||||||
|
container_name: zen_mongo
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "27017:27017"
|
||||||
|
volumes:
|
||||||
|
- mongo-data:/data/db
|
||||||
|
networks:
|
||||||
|
- zen-network
|
||||||
|
|
||||||
|
server:
|
||||||
|
depends_on:
|
||||||
|
- mongo
|
||||||
|
environment:
|
||||||
|
- MONGO_URI=mongodb://mongo:27017/zenkanji
|
||||||
|
volumes:
|
||||||
|
- ./server:/app
|
||||||
|
- /app/node_modules
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
command: npm run dev
|
||||||
|
|
||||||
|
client:
|
||||||
|
build:
|
||||||
|
target: dev-stage
|
||||||
|
ports:
|
||||||
|
- "5173:5173"
|
||||||
|
volumes:
|
||||||
|
- ./client:/app
|
||||||
|
- /app/node_modules
|
||||||
|
command: npm run dev -- --host
|
||||||
0
docker-compose.prod.yml
Normal file
0
docker-compose.prod.yml
Normal file
@@ -1,47 +1,23 @@
|
|||||||
services:
|
services:
|
||||||
mongo:
|
|
||||||
image: mongo:6
|
|
||||||
container_name: zen_mongo
|
|
||||||
restart: always
|
|
||||||
ports:
|
|
||||||
- "27017:27017"
|
|
||||||
volumes:
|
|
||||||
- mongo-data:/data/db
|
|
||||||
networks:
|
|
||||||
- zen-network
|
|
||||||
|
|
||||||
server:
|
server:
|
||||||
build: ./server
|
build:
|
||||||
|
context: ./server
|
||||||
container_name: zen_server
|
container_name: zen_server
|
||||||
restart: always
|
|
||||||
ports:
|
|
||||||
- "3000:3000"
|
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- ./server/.env
|
||||||
depends_on:
|
|
||||||
- mongo
|
|
||||||
networks:
|
networks:
|
||||||
- zen-network
|
- zen-network
|
||||||
volumes:
|
|
||||||
- ./server:/app
|
|
||||||
- /app/node_modules
|
|
||||||
|
|
||||||
client:
|
client:
|
||||||
build:
|
build:
|
||||||
context: ./client
|
context: ./client
|
||||||
target: dev-stage
|
|
||||||
container_name: zen_client
|
container_name: zen_client
|
||||||
ports:
|
|
||||||
- "5173:5173"
|
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- ./client/.env
|
||||||
depends_on:
|
depends_on:
|
||||||
- server
|
- server
|
||||||
networks:
|
networks:
|
||||||
- zen-network
|
- zen-network
|
||||||
volumes:
|
|
||||||
- ./client:/app
|
|
||||||
- /app/node_modules
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
mongo-data:
|
mongo-data:
|
||||||
|
|||||||
@@ -11,29 +11,32 @@ const fastify = Fastify({ logger: true });
|
|||||||
await connectDB();
|
await connectDB();
|
||||||
|
|
||||||
const allowedOrigins = [
|
const allowedOrigins = [
|
||||||
'http://192.168.0.26:5169',
|
process.env.SERVER_EXT_ACCESS,
|
||||||
'http://192.168.0.26:5173',
|
process.env.SERVER_INT_ACCESS,
|
||||||
'http://localhost:5173',
|
|
||||||
'http://localhost',
|
|
||||||
'https://localhost',
|
'https://localhost',
|
||||||
'capacitor://localhost',
|
'capacitor://localhost',
|
||||||
'https://10.0.2.2:5173',
|
'https://10.0.2.2:5173',
|
||||||
'https://zenkanji.crylia.de'
|
'http://localhost:5173'
|
||||||
];
|
].filter(Boolean).map(uri => uri.replace(/\/$/, ''));
|
||||||
|
|
||||||
if (process.env.CORS_ORIGINS) {
|
|
||||||
const prodOrigins = process.env.CORS_ORIGINS.split(',');
|
|
||||||
allowedOrigins.push(...prodOrigins);
|
|
||||||
}
|
|
||||||
|
|
||||||
await fastify.register(cors, {
|
await fastify.register(cors, {
|
||||||
origin: allowedOrigins,
|
origin: (origin, cb) => {
|
||||||
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
if (!origin) return cb(null, true);
|
||||||
|
|
||||||
|
if (allowedOrigins.includes(origin)) {
|
||||||
|
return cb(null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`CORS BLOCKED: Browser sent "${origin}". Allowed list:`, allowedOrigins);
|
||||||
|
|
||||||
|
cb(new Error("Not allowed by CORS"));
|
||||||
|
},
|
||||||
|
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||||
credentials: true
|
credentials: true
|
||||||
});
|
});
|
||||||
|
|
||||||
await fastify.register(jwt, {
|
await fastify.register(jwt, {
|
||||||
secret: JWT_SECRET
|
secret: process.env.JWT_SECRET
|
||||||
});
|
});
|
||||||
|
|
||||||
fastify.decorate('authenticate', async function (req, reply) {
|
fastify.decorate('authenticate', async function (req, reply) {
|
||||||
@@ -67,7 +70,6 @@ await fastify.register(routes);
|
|||||||
const start = async () => {
|
const start = async () => {
|
||||||
try {
|
try {
|
||||||
await fastify.listen({ port: PORT, host: '0.0.0.0' });
|
await fastify.listen({ port: PORT, host: '0.0.0.0' });
|
||||||
console.log(`Server running at http://localhost:${PORT}`);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
fastify.log.error(err);
|
fastify.log.error(err);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
export const PORT = process.env.PORT || 3000;
|
export const PORT = 3000;
|
||||||
export const MONGO_URI = process.env.MONGO_URI || 'mongodb://mongo:27017/zenkanji' || 'mongodb://192.168.0.26:27017/zenkanji';
|
export const MONGO_URI = process.env.MONGO_URI
|
||||||
export const SRS_TIMINGS_HOURS = [0, 2, 4, 8, 23, 47];
|
|
||||||
export const JWT_SECRET = process.env.JWT_SECRET;
|
export const JWT_SECRET = process.env.JWT_SECRET;
|
||||||
|
|||||||
Reference in New Issue
Block a user