Change prod workflow and add automatic deployment
All checks were successful
Release Build / build-docker (push) Successful in 9s
Release Build / build-android-and-release (push) Successful in 2m18s

This commit is contained in:
Rene Kievits
2025-12-24 07:51:51 +01:00
parent c140bb8292
commit 8552b44ffd
14 changed files with 250 additions and 82 deletions

View 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 }}

View File

@@ -1,2 +0,0 @@
CAP_ENV=dev
VITE_API_URL=https://zenkanji-api.crylia.de

2
client/.gitignore vendored
View File

@@ -1,5 +1,3 @@
node_modules node_modules
.env .env
.env.android .env.android
my-release-key.jks
gradle.properties

View File

@@ -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;"]

View File

@@ -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

View File

@@ -0,0 +1,2 @@
org.gradle.jvmargs=-Xmx1536m
android.useAndroidX=true

View File

@@ -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
View 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";
}
}

View File

@@ -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
View 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
View File

View 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:

View File

@@ -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);

View File

@@ -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;