From 1b3f3b31108158d2cb374dd881022d63382adc4a Mon Sep 17 00:00:00 2001 From: sokol Date: Fri, 20 Feb 2026 10:16:28 +0300 Subject: [PATCH 1/8] feat: disable Download button when no config is loaded --- src/componets/FileChooser.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/componets/FileChooser.tsx b/src/componets/FileChooser.tsx index 00ed77a..b2fa6a6 100644 --- a/src/componets/FileChooser.tsx +++ b/src/componets/FileChooser.tsx @@ -60,6 +60,8 @@ export function FileChooser({ onSelected, config }: FileChooserProps) { URL.revokeObjectURL(url); } + const hasConfig = !!config; + return (
@@ -89,8 +91,8 @@ export function FileChooser({ onSelected, config }: FileChooserProps) { onClick={handleDownload} icon={Download} size="sm" - disabled={!config} - title="Download full config template" + disabled={!hasConfig} + title={hasConfig ? 'Download full config template' : 'Load or create a config first'} > Download -- 2.49.1 From 7e1f7dd24cce99b73e48c446858c8b90598ff9b1 Mon Sep 17 00:00:00 2001 From: sokol Date: Fri, 20 Feb 2026 10:19:27 +0300 Subject: [PATCH 2/8] feat: prevent downloading empty config --- src/componets/FileChooser.tsx | 2 +- src/models/Config.tsx | 18 ++++++++++++++++++ test-results/.last-run.json | 13 +++++++++++-- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/componets/FileChooser.tsx b/src/componets/FileChooser.tsx index b2fa6a6..9bb1106 100644 --- a/src/componets/FileChooser.tsx +++ b/src/componets/FileChooser.tsx @@ -60,7 +60,7 @@ export function FileChooser({ onSelected, config }: FileChooserProps) { URL.revokeObjectURL(url); } - const hasConfig = !!config; + const hasConfig = !!config && !config.isEmpty(); return (
diff --git a/src/models/Config.tsx b/src/models/Config.tsx index 1b8ba3a..e17991d 100644 --- a/src/models/Config.tsx +++ b/src/models/Config.tsx @@ -171,4 +171,22 @@ export class Config { cloned.template = this.template; return cloned; } + + /** + * Checks if config is empty (no environments or only DEFAULT with no params and empty template) + */ + public isEmpty(): boolean { + if (this.envs.length === 0) { + return true; + } + + // Check if only DEFAULT exists with no params + if (this.envs.length === 1 && this.envs[0].name === 'DEFAULT') { + const hasParams = this.envs[0].params.length > 0; + const hasTemplate = this.template.content.trim() && this.template.content !== '{}'; + return !hasParams && !hasTemplate; + } + + return false; + } } diff --git a/test-results/.last-run.json b/test-results/.last-run.json index cbcc1fb..9d69b49 100644 --- a/test-results/.last-run.json +++ b/test-results/.last-run.json @@ -1,4 +1,13 @@ { - "status": "passed", - "failedTests": [] + "status": "failed", + "failedTests": [ + "2a84b67ca6571daf27b7-7e9801b0b833c50af541", + "2a84b67ca6571daf27b7-17ce5649ea02e0d09692", + "2a84b67ca6571daf27b7-46f1d47e52a9de8bb54e", + "2a84b67ca6571daf27b7-5ac3b387f0f8104531f1", + "2a84b67ca6571daf27b7-61b44892e9e54397b980", + "2a84b67ca6571daf27b7-e38411b679b6dd8a5f2c", + "2a84b67ca6571daf27b7-ca92b197b6f7ecc9f092", + "2a84b67ca6571daf27b7-0c2a356d8582be760d5f" + ] } \ No newline at end of file -- 2.49.1 From 083d7dd662273add7d8ff1a6d791251781f0a8e9 Mon Sep 17 00:00:00 2001 From: sokol Date: Fri, 20 Feb 2026 10:29:22 +0300 Subject: [PATCH 3/8] feat: add Gitea Actions CI/CD workflows --- .gitea/workflows/README.md | 112 ++++++++++++++++++++++++++++++ .gitea/workflows/ci.yml | 46 ++++++++++++ .gitea/workflows/deploy.yml | 28 ++++++++ .gitea/workflows/docker-build.yml | 59 ++++++++++++++++ 4 files changed, 245 insertions(+) create mode 100644 .gitea/workflows/README.md create mode 100644 .gitea/workflows/ci.yml create mode 100644 .gitea/workflows/deploy.yml create mode 100644 .gitea/workflows/docker-build.yml diff --git a/.gitea/workflows/README.md b/.gitea/workflows/README.md new file mode 100644 index 0000000..b69ddd3 --- /dev/null +++ b/.gitea/workflows/README.md @@ -0,0 +1,112 @@ +# Gitea Actions Workflows + +## Workflows + +### 1. CI (`ci.yml`) +**Triggers:** Push/PR to `main`, `rewrite`, `feat` branches + +**Jobs:** +- Install dependencies +- Lint code +- Build application +- Run unit tests (Vitest) +- Run E2E tests (Playwright) +- Upload test results as artifacts + +### 2. Docker Build (`docker-build.yml`) +**Triggers:** +- Tags matching `v*` (e.g., `v1.0.0`) +- Push to `main` branch + +**Jobs:** +- Build React application +- Build Docker image +- Push to Docker registry + +**Required Secrets:** +- `DOCKER_USERNAME` - Docker registry username +- `DOCKER_PASSWORD` - Docker registry password/token + +**Optional Variables:** +- `DOCKER_REGISTRY` - Registry URL (default: `docker.io`) + +### 3. Deploy (`deploy.yml`) +**Triggers:** Push to `main` branch + +**Jobs:** +- SSH to deployment server +- Pull latest code +- Update Docker containers +- Clean up old images + +**Required Secrets:** +- `DEPLOY_HOST` - Server hostname/IP +- `DEPLOY_USERNAME` - SSH username +- `DEPLOY_KEY` - SSH private key + +**Optional Secrets:** +- `DEPLOY_PORT` - SSH port (default: 22) + +## Setup Instructions + +### 1. Enable Gitea Actions +Make sure Actions is enabled in your Gitea instance: +```ini +[actions] +ENABLED = true +``` + +### 2. Configure Secrets +Go to your repository → Settings → Secrets and add: + +**For Docker Build:** +``` +DOCKER_USERNAME=your-username +DOCKER_PASSWORD=your-password-or-token +``` + +**For Deployment:** +``` +DEPLOY_HOST=your-server.com +DEPLOY_USERNAME=deploy +DEPLOY_KEY= +DEPLOY_PORT=22 +``` + +### 3. Configure Variables (Optional) +Go to your repository → Settings → Variables and add: + +``` +DOCKER_REGISTRY=registry.example.com +``` + +## Workflow Files Location +`.gitea/workflows/` + +## Testing Workflows Locally + +You can test workflows locally using [act](https://github.com/nektos/act): + +```bash +# Install act +brew install act + +# Run CI workflow locally +act push + +# Run with specific job +act -j build-and-test + +# Run with verbose output +act -v +``` + +## Badge Examples + +Add these to your README.md: + +```markdown +[![CI](https://git.six83.ru/ssa/configucci/actions/workflows/ci.yml/badge.svg)](https://git.six83.ru/ssa/configucci/actions/workflows/ci.yml) +[![Docker Build](https://git.six83.ru/ssa/configucci/actions/workflows/docker-build.yml/badge.svg)](https://git.six83.ru/ssa/configucci/actions/workflows/docker-build.yml) +[![Deploy](https://git.six83.ru/ssa/configucci/actions/workflows/deploy.yml/badge.svg)](https://git.six83.ru/ssa/configucci/actions/workflows/deploy.yml) +``` diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..6ff6f1d --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,46 @@ +name: CI + +on: + push: + branches: [main, rewrite, feat] + pull_request: + branches: [main, rewrite, feat] + +jobs: + build-and-test: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Lint + run: npm run lint + + - name: Build + run: npm run build + + - name: Run unit tests + run: npm run test + + - name: Install Playwright browsers + run: npx playwright install --with-deps chromium + + - name: Run E2E tests + run: npm run test:e2e + + - name: Upload test results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results + path: test-results/ diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml new file mode 100644 index 0000000..0a34967 --- /dev/null +++ b/.gitea/workflows/deploy.yml @@ -0,0 +1,28 @@ +name: Deploy to Server + +on: + push: + branches: + - main + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Deploy via SSH + uses: appleboy/ssh-action@v1.0.3 + with: + host: ${{ secrets.DEPLOY_HOST }} + username: ${{ secrets.DEPLOY_USERNAME }} + key: ${{ secrets.DEPLOY_KEY }} + port: ${{ secrets.DEPLOY_PORT || 22 }} + script: | + cd /opt/configucci + git pull origin main + docker-compose pull + docker-compose up -d + docker system prune -f diff --git a/.gitea/workflows/docker-build.yml b/.gitea/workflows/docker-build.yml new file mode 100644 index 0000000..ab7594b --- /dev/null +++ b/.gitea/workflows/docker-build.yml @@ -0,0 +1,59 @@ +name: Build and Push Docker Image + +on: + push: + tags: + - 'v*' + branches: + - main + +jobs: + build-and-push: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build application + run: npm run build + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Registry + uses: docker/login-action@v3 + with: + registry: ${{ vars.DOCKER_REGISTRY || 'docker.io' }} + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ vars.DOCKER_REGISTRY }}/${{ github.repository }} + tags: | + type=ref,event=branch + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=sha,prefix=sha- + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max -- 2.49.1 From 1c27b689654ac3887b81b930c0dedda0f3005c70 Mon Sep 17 00:00:00 2001 From: sokol Date: Fri, 20 Feb 2026 10:33:17 +0300 Subject: [PATCH 4/8] feat: build Docker image locally without pushing to registry --- .gitea/workflows/README.md | 28 ++++++---------------- .gitea/workflows/docker-build.yml | 40 +++++++++++++------------------ 2 files changed, 23 insertions(+), 45 deletions(-) diff --git a/.gitea/workflows/README.md b/.gitea/workflows/README.md index b69ddd3..274b1aa 100644 --- a/.gitea/workflows/README.md +++ b/.gitea/workflows/README.md @@ -20,15 +20,13 @@ **Jobs:** - Build React application -- Build Docker image -- Push to Docker registry +- Build Docker image locally +- Save image as tarball artifact +- Upload artifact for download (30 days retention) -**Required Secrets:** -- `DOCKER_USERNAME` - Docker registry username -- `DOCKER_PASSWORD` - Docker registry password/token - -**Optional Variables:** -- `DOCKER_REGISTRY` - Registry URL (default: `docker.io`) +**Output:** +- Docker image artifact: `configucci.tar` +- Can be downloaded and loaded with: `docker load -i configucci.tar` ### 3. Deploy (`deploy.yml`) **Triggers:** Push to `main` branch @@ -59,12 +57,6 @@ ENABLED = true ### 2. Configure Secrets Go to your repository → Settings → Secrets and add: -**For Docker Build:** -``` -DOCKER_USERNAME=your-username -DOCKER_PASSWORD=your-password-or-token -``` - **For Deployment:** ``` DEPLOY_HOST=your-server.com @@ -73,12 +65,7 @@ DEPLOY_KEY= DEPLOY_PORT=22 ``` -### 3. Configure Variables (Optional) -Go to your repository → Settings → Variables and add: - -``` -DOCKER_REGISTRY=registry.example.com -``` +**No Docker secrets needed** - images are built locally and downloaded as artifacts! ## Workflow Files Location `.gitea/workflows/` @@ -107,6 +94,5 @@ Add these to your README.md: ```markdown [![CI](https://git.six83.ru/ssa/configucci/actions/workflows/ci.yml/badge.svg)](https://git.six83.ru/ssa/configucci/actions/workflows/ci.yml) -[![Docker Build](https://git.six83.ru/ssa/configucci/actions/workflows/docker-build.yml/badge.svg)](https://git.six83.ru/ssa/configucci/actions/workflows/docker-build.yml) [![Deploy](https://git.six83.ru/ssa/configucci/actions/workflows/deploy.yml/badge.svg)](https://git.six83.ru/ssa/configucci/actions/workflows/deploy.yml) ``` diff --git a/.gitea/workflows/docker-build.yml b/.gitea/workflows/docker-build.yml index ab7594b..2284bcf 100644 --- a/.gitea/workflows/docker-build.yml +++ b/.gitea/workflows/docker-build.yml @@ -1,4 +1,4 @@ -name: Build and Push Docker Image +name: Build Docker Image on: push: @@ -8,7 +8,7 @@ on: - main jobs: - build-and-push: + build: runs-on: ubuntu-latest steps: @@ -30,30 +30,22 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Login to Docker Registry - uses: docker/login-action@v3 - with: - registry: ${{ vars.DOCKER_REGISTRY || 'docker.io' }} - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Extract metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ vars.DOCKER_REGISTRY }}/${{ github.repository }} - tags: | - type=ref,event=branch - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=sha,prefix=sha- - - - name: Build and push Docker image + - name: Build Docker image uses: docker/build-push-action@v5 with: context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} + push: false + load: true + tags: configucci:latest cache-from: type=gha cache-to: type=gha,mode=max + + - name: Save Docker image as tarball + run: docker save configucci:latest -o configucci.tar + + - name: Upload Docker image artifact + uses: actions/upload-artifact@v4 + with: + name: docker-image + path: configucci.tar + retention-days: 30 -- 2.49.1 From e4b44c7b5e27f05464982c9971ea4d4f962ca2b6 Mon Sep 17 00:00:00 2001 From: sokol Date: Fri, 20 Feb 2026 11:22:56 +0300 Subject: [PATCH 5/8] refactor: improve type safety and code style --- eslint.config.js | 24 +++++++++++++++++------- src/builders/EnvBuilder.ts | 2 +- src/builders/index.ts | 2 +- src/components/ui/Button.tsx | 2 -- src/components/ui/Card.tsx | 6 +++--- src/models/Config.tsx | 2 +- src/test/ConfigReader.test.ts | 14 +++++++------- 7 files changed, 30 insertions(+), 22 deletions(-) diff --git a/eslint.config.js b/eslint.config.js index 5e6b472..332a882 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -3,21 +3,31 @@ import globals from 'globals' import reactHooks from 'eslint-plugin-react-hooks' import reactRefresh from 'eslint-plugin-react-refresh' import tseslint from 'typescript-eslint' -import { defineConfig, globalIgnores } from 'eslint/config' -export default defineConfig([ - globalIgnores(['dist']), +export default tseslint.config( + { + ignores: ['dist'], + }, { - files: ['**/*.{ts,tsx}'], extends: [ js.configs.recommended, tseslint.configs.recommended, - reactHooks.configs.flat.recommended, - reactRefresh.configs.vite, ], + files: ['**/*.{ts,tsx}'], languageOptions: { ecmaVersion: 2020, globals: globals.browser, }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, }, -]) +) diff --git a/src/builders/EnvBuilder.ts b/src/builders/EnvBuilder.ts index 3bb1efe..98068be 100644 --- a/src/builders/EnvBuilder.ts +++ b/src/builders/EnvBuilder.ts @@ -34,7 +34,7 @@ export class EnvBuilder implements IBuilder { private params(): this { const tag = ``; - for (let p of this.src.params) { + for (const p of this.src.params) { this.stack.push(this.ident); this.stack.push(tag .replace("{name}", p.name ?? "!ERR!") diff --git a/src/builders/index.ts b/src/builders/index.ts index 166618d..351090e 100644 --- a/src/builders/index.ts +++ b/src/builders/index.ts @@ -11,7 +11,7 @@ export interface IBuilder { export class Builder { public static getEnv(env: Env): IBuilder { - let b = new EnvBuilder(); + const b = new EnvBuilder(); b.src = env; return b; }; diff --git a/src/components/ui/Button.tsx b/src/components/ui/Button.tsx index c968482..0f2b4d1 100644 --- a/src/components/ui/Button.tsx +++ b/src/components/ui/Button.tsx @@ -8,7 +8,6 @@ interface ButtonProps extends ButtonHTMLAttributes { variant?: ButtonVariant; size?: ButtonSize; icon?: LucideIcon; - iconPosition?: 'left' | 'right'; isLoading?: boolean; } @@ -33,7 +32,6 @@ export const Button = forwardRef( variant = 'primary', size = 'md', icon: Icon, - iconPosition = 'left', isLoading = false, disabled, children, diff --git a/src/components/ui/Card.tsx b/src/components/ui/Card.tsx index 9d4b9a3..911f413 100644 --- a/src/components/ui/Card.tsx +++ b/src/components/ui/Card.tsx @@ -34,7 +34,7 @@ export const Card = forwardRef( Card.displayName = 'Card'; -interface CardHeaderProps extends HTMLAttributes {} +type CardHeaderProps = HTMLAttributes; export const CardHeader = forwardRef( ({ className = '', children, ...props }, ref) => { @@ -52,7 +52,7 @@ export const CardHeader = forwardRef( CardHeader.displayName = 'CardHeader'; -interface CardBodyProps extends HTMLAttributes {} +type CardBodyProps = HTMLAttributes; export const CardBody = forwardRef( ({ className = '', children, ...props }, ref) => { @@ -66,7 +66,7 @@ export const CardBody = forwardRef( CardBody.displayName = 'CardBody'; -interface CardFooterProps extends HTMLAttributes {} +type CardFooterProps = HTMLAttributes; export const CardFooter = forwardRef( ({ className = '', children, ...props }, ref) => { diff --git a/src/models/Config.tsx b/src/models/Config.tsx index e17991d..407c824 100644 --- a/src/models/Config.tsx +++ b/src/models/Config.tsx @@ -97,7 +97,7 @@ export class Config { * Updates template by adding placeholders for environment params */ public updateTemplateFromEnv(env: Env): void { - let templateObj: Record = {}; + let templateObj: Record = {}; // Try to parse existing template try { diff --git a/src/test/ConfigReader.test.ts b/src/test/ConfigReader.test.ts index ec782db..c1774c9 100644 --- a/src/test/ConfigReader.test.ts +++ b/src/test/ConfigReader.test.ts @@ -36,8 +36,8 @@ const cfgTemplate = ` test("read from a file", async ({expect})=>{ - let sut = new ConfigReader(); - let file = new File([cfgTemplate],'cfg.json.xml',{type:'application/xml'}); + const sut = new ConfigReader(); + const file = new File([cfgTemplate],'cfg.json.xml',{type:'application/xml'}); // define a missing jsdom text() function, // that presents in the real DOM @@ -46,15 +46,15 @@ test("read from a file", async ({expect})=>{ writable: true }); - let cfg = await sut.parseFromFile(file); + const cfg = await sut.parseFromFile(file); expect(cfg).not.toBeUndefined(); }); test("load environments and params", ({expect})=>{ - let sut = new ConfigReader(); + const sut = new ConfigReader(); - let cfg = sut.parseFromString(cfgTemplate); + const cfg = sut.parseFromString(cfgTemplate); expect(cfg?.envs).toHaveLength(3); expect(cfg?.envs.map(x=>x.name)) @@ -64,9 +64,9 @@ test("load environments and params", ({expect})=>{ }); test("load template", ({expect})=>{ - let sut = new ConfigReader(); + const sut = new ConfigReader(); - let cfg = sut.parseFromString(cfgTemplate); + const cfg = sut.parseFromString(cfgTemplate); expect(cfg?.template).toBeDefined(); expect(cfg?.template.content.length).toBeGreaterThan(20); -- 2.49.1 From 9c94aa1df56163835bf3f7a3bda7770c15e6ae6f Mon Sep 17 00:00:00 2001 From: sokol Date: Fri, 20 Feb 2026 11:57:56 +0300 Subject: [PATCH 6/8] test: fix E2E download test for empty config protection --- e2e/environment.spec.ts | 13 +++++++++++++ test-results/.last-run.json | 13 ++----------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/e2e/environment.spec.ts b/e2e/environment.spec.ts index f42f2c7..a8b1885 100644 --- a/e2e/environment.spec.ts +++ b/e2e/environment.spec.ts @@ -179,6 +179,19 @@ test.describe('Environment Management', () => { await page.goto('/'); await page.click('button:has-text("New Config")'); await page.waitForTimeout(500); + + // Add a parameter to make the config non-empty (Download button requires non-empty config) + await page.click('button[title="Add parameter"]'); + await page.waitForTimeout(300); + const nameInput = page.locator('input[placeholder="Parameter name"]').last(); + const valueInput = page.locator('input[placeholder="Parameter value"]').last(); + const addButton = page.locator('button[title="Add parameter"]').last(); + await nameInput.fill('host'); + await valueInput.fill('localhost'); + await addButton.click(); + await page.waitForTimeout(500); + + // Now download should work const [download] = await Promise.all([ page.waitForEvent('download'), page.click('button:has-text("Download")') diff --git a/test-results/.last-run.json b/test-results/.last-run.json index 9d69b49..cbcc1fb 100644 --- a/test-results/.last-run.json +++ b/test-results/.last-run.json @@ -1,13 +1,4 @@ { - "status": "failed", - "failedTests": [ - "2a84b67ca6571daf27b7-7e9801b0b833c50af541", - "2a84b67ca6571daf27b7-17ce5649ea02e0d09692", - "2a84b67ca6571daf27b7-46f1d47e52a9de8bb54e", - "2a84b67ca6571daf27b7-5ac3b387f0f8104531f1", - "2a84b67ca6571daf27b7-61b44892e9e54397b980", - "2a84b67ca6571daf27b7-e38411b679b6dd8a5f2c", - "2a84b67ca6571daf27b7-ca92b197b6f7ecc9f092", - "2a84b67ca6571daf27b7-0c2a356d8582be760d5f" - ] + "status": "passed", + "failedTests": [] } \ No newline at end of file -- 2.49.1 From c735d7318fd799666d222314336cc39e0775d469 Mon Sep 17 00:00:00 2001 From: sokol Date: Fri, 20 Feb 2026 12:35:51 +0300 Subject: [PATCH 7/8] ci: fix E2E tests by starting dev server --- .gitea/workflows/ci.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index 6ff6f1d..5df7d4a 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -35,6 +35,25 @@ jobs: - name: Install Playwright browsers run: npx playwright install --with-deps chromium + - name: Start dev server + run: npm run dev & + env: + HOST: 0.0.0.0 + PORT: 5173 + + - name: Wait for dev server + run: | + echo "Waiting for dev server to start..." + for i in {1..30}; do + if curl -s http://localhost:5173 > /dev/null 2>&1; then + echo "Dev server is ready!" + exit 0 + fi + sleep 1 + done + echo "Dev server failed to start" + exit 1 + - name: Run E2E tests run: npm run test:e2e -- 2.49.1 From 9e313f5b860ea0de3263eb7456493e20177da005 Mon Sep 17 00:00:00 2001 From: sokol Date: Fri, 20 Feb 2026 13:55:53 +0300 Subject: [PATCH 8/8] feat: native deploy workflow without SSH --- .gitea/workflows/README.md | 39 +++++++++---------- .gitea/workflows/deploy.yml | 74 ++++++++++++++++++++++++++++++------- 2 files changed, 81 insertions(+), 32 deletions(-) diff --git a/.gitea/workflows/README.md b/.gitea/workflows/README.md index 274b1aa..40b2711 100644 --- a/.gitea/workflows/README.md +++ b/.gitea/workflows/README.md @@ -32,18 +32,19 @@ **Triggers:** Push to `main` branch **Jobs:** -- SSH to deployment server -- Pull latest code -- Update Docker containers -- Clean up old images +- Build React application +- Build Docker image locally +- Create docker-compose.yml configuration +- Deploy container on Gitea runner (port 11088) +- Health check to verify application is running +- Cleanup old Docker images -**Required Secrets:** -- `DEPLOY_HOST` - Server hostname/IP -- `DEPLOY_USERNAME` - SSH username -- `DEPLOY_KEY` - SSH private key +**No SSH required** - Everything runs natively on the Gitea Actions runner! -**Optional Secrets:** -- `DEPLOY_PORT` - SSH port (default: 22) +**Output:** +- Application available at: `http://:11088` +- Container auto-restarts on failure +- Health check ensures successful deployment ## Setup Instructions @@ -54,18 +55,18 @@ Make sure Actions is enabled in your Gitea instance: ENABLED = true ``` -### 2. Configure Secrets -Go to your repository → Settings → Secrets and add: +### 2. Configure Runner +Ensure your Gitea runner has Docker and docker-compose installed: -**For Deployment:** -``` -DEPLOY_HOST=your-server.com -DEPLOY_USERNAME=deploy -DEPLOY_KEY= -DEPLOY_PORT=22 +```bash +# Install Docker +curl -fsSL https://get.docker.com | sh + +# Install docker-compose +sudo apt-get install docker-compose-plugin ``` -**No Docker secrets needed** - images are built locally and downloaded as artifacts! +**No secrets required** - Everything runs on the runner! ## Workflow Files Location `.gitea/workflows/` diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 0a34967..bc54480 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -8,21 +8,69 @@ on: jobs: deploy: runs-on: ubuntu-latest - + steps: - name: Checkout code uses: actions/checkout@v4 - - name: Deploy via SSH - uses: appleboy/ssh-action@v1.0.3 + - name: Setup Node.js + uses: actions/setup-node@v4 with: - host: ${{ secrets.DEPLOY_HOST }} - username: ${{ secrets.DEPLOY_USERNAME }} - key: ${{ secrets.DEPLOY_KEY }} - port: ${{ secrets.DEPLOY_PORT || 22 }} - script: | - cd /opt/configucci - git pull origin main - docker-compose pull - docker-compose up -d - docker system prune -f + node-version: '20' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build application + run: npm run build + + - name: Build Docker image + uses: docker/build-push-action@v5 + with: + context: . + push: false + load: true + tags: configucci:latest + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Create docker-compose.yml + run: | + cat > docker-compose.yml << 'EOF' + version: '3.8' + services: + configucci: + image: configucci:latest + container_name: configucci + ports: + - "11088:80" + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:80"] + interval: 30s + timeout: 10s + retries: 3 + EOF + + - name: Stop existing containers + run: docker-compose down || true + + - name: Start new container + run: docker-compose up -d + + - name: Wait for application health + run: | + echo "Waiting for application to be healthy..." + for i in {1..30}; do + if curl -s http://localhost:11088 > /dev/null 2>&1; then + echo "Application is ready!" + exit 0 + fi + sleep 2 + done + echo "Application failed to start" + exit 1 + + - name: Cleanup old images + run: docker system prune -f -- 2.49.1