mirror of
https://github.com/meienberger/runtipi.git
synced 2024-11-25 20:07:58 +03:00
ci: fix releases (#1755)
* ci: fix releases * ci: e2e tests * chore: remove QEMU setup * chore: fix e2e imports * fix: guest dashboard not displaying apps correctly * chore: add logs in playwright report * feat(install.sh): allow env-file arg during install * chore(e2e): reload page after logouts
This commit is contained in:
parent
58234abe40
commit
f5180e6de6
7
.github/workflows/alpha-release.yml
vendored
7
.github/workflows/alpha-release.yml
vendored
@ -22,10 +22,6 @@ jobs:
|
|||||||
VERSION=$(npm run version --silent)
|
VERSION=$(npm run version --silent)
|
||||||
echo "tagname=v${VERSION}-alpha.${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
|
echo "tagname=v${VERSION}-alpha.${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: rickstaa/action-create-tag@v1
|
|
||||||
with:
|
|
||||||
tag: ${{ steps.get_tag.outputs.tagname }}
|
|
||||||
|
|
||||||
build-images:
|
build-images:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: create-tag
|
needs: create-tag
|
||||||
@ -33,9 +29,6 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
- name: Log in to Docker Hub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
|
7
.github/workflows/beta-release.yml
vendored
7
.github/workflows/beta-release.yml
vendored
@ -22,10 +22,6 @@ jobs:
|
|||||||
VERSION=$(npm run version --silent)
|
VERSION=$(npm run version --silent)
|
||||||
echo "tagname=v${VERSION}-beta.${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
|
echo "tagname=v${VERSION}-beta.${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: rickstaa/action-create-tag@v1
|
|
||||||
with:
|
|
||||||
tag: ${{ steps.get_tag.outputs.tagname }}
|
|
||||||
|
|
||||||
build-images:
|
build-images:
|
||||||
needs: create-tag
|
needs: create-tag
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@ -33,9 +29,6 @@ jobs:
|
|||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
- name: Log in to Docker Hub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
|
44
.github/workflows/e2e.yml
vendored
44
.github/workflows/e2e.yml
vendored
@ -5,7 +5,7 @@ on:
|
|||||||
version:
|
version:
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
description: 'Version to test (e.g. v1.6.0-beta.1)'
|
description: "Version to test (e.g. v1.6.0-beta.1)"
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
@ -16,8 +16,18 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Log in to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
with:
|
||||||
|
version: "lab:latest"
|
||||||
|
driver: cloud
|
||||||
|
endpoint: "runtipi/runtipi-cloud-builder"
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
@ -36,8 +46,6 @@ jobs:
|
|||||||
platforms: linux/amd64
|
platforms: linux/amd64
|
||||||
push: true
|
push: true
|
||||||
tags: ghcr.io/runtipi/runtipi:e2e
|
tags: ghcr.io/runtipi/runtipi:e2e
|
||||||
cache-from: type=registry,ref=ghcr.io/runtipi/runtipi:buildcache
|
|
||||||
cache-to: type=registry,ref=ghcr.io/runtipi/runtipi:buildcache,mode=max
|
|
||||||
|
|
||||||
- name: Create cli folder
|
- name: Create cli folder
|
||||||
run: mkdir -p bin
|
run: mkdir -p bin
|
||||||
@ -79,24 +87,28 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Create .env.local
|
||||||
|
run: |
|
||||||
|
echo "LOG_LEVEL=debug" > .env.local
|
||||||
|
|
||||||
- name: Run install script
|
- name: Run install script
|
||||||
if: ${{ inputs.version }}
|
if: ${{ inputs.version }}
|
||||||
run: |
|
run: |
|
||||||
curl -s https://raw.githubusercontent.com/runtipi/runtipi/${{ inputs.version }}/scripts/install.sh > install.sh
|
curl -s https://raw.githubusercontent.com/runtipi/runtipi/${{ inputs.version }}/scripts/install.sh > install.sh
|
||||||
chmod +x install.sh
|
chmod +x install.sh
|
||||||
./install.sh --version ${{ inputs.version }} --asset runtipi-cli-linux-x86_64.tar.gz
|
./install.sh --version ${{ inputs.version }} --asset runtipi-cli-linux-x86_64.tar.gz --env-file ${{ github.workspace }}/.env.local
|
||||||
|
|
||||||
- name: Run install script
|
- name: Run install script
|
||||||
if: ${{ !inputs.version }}
|
if: ${{ !inputs.version }}
|
||||||
run: |
|
run: |
|
||||||
./scripts/install.sh --version e2e
|
./scripts/install.sh --version e2e --env-file ${{ github.workspace }}/.env.local
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
- uses: pnpm/action-setup@v4.0.0
|
- uses: pnpm/action-setup@v4.0.0
|
||||||
name: Install pnpm
|
name: Install pnpm
|
||||||
id: pnpm-install
|
id: pnpm-install
|
||||||
with:
|
with:
|
||||||
version: 9.4.0
|
version: 9.12.2
|
||||||
run_install: false
|
run_install: false
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
- name: Get pnpm store directory
|
||||||
@ -112,7 +124,7 @@ jobs:
|
|||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-pnpm-store-
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|
||||||
- name: Create .env.e2e file with Droplet IP
|
- name: Create .env.e2e file
|
||||||
run: |
|
run: |
|
||||||
echo "SERVER_IP=$(hostname -I | awk '{print $1}')" > .env.e2e
|
echo "SERVER_IP=$(hostname -I | awk '{print $1}')" > .env.e2e
|
||||||
echo "POSTGRES_PASSWORD=$(grep POSTGRES_PASSWORD runtipi/.env | cut -d '=' -f2)" >> .env.e2e
|
echo "POSTGRES_PASSWORD=$(grep POSTGRES_PASSWORD runtipi/.env | cut -d '=' -f2)" >> .env.e2e
|
||||||
@ -125,13 +137,31 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
|
|
||||||
|
- name: Get installed playwright version
|
||||||
|
id: playwright-version
|
||||||
|
run: echo "version=$(npx playwright --version)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Cache Playwright binaries
|
||||||
|
id: cache-playwright-binaries
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ~/.cache/ms-playwright
|
||||||
|
key: ${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.version }}
|
||||||
|
|
||||||
- name: Install Playwright Browsers
|
- name: Install Playwright Browsers
|
||||||
|
if: steps.cache-playwright-binaries.outputs.cache-hit != 'true'
|
||||||
run: npx playwright install --with-deps
|
run: npx playwright install --with-deps
|
||||||
|
|
||||||
- name: Run Playwright tests
|
- name: Run Playwright tests
|
||||||
id: run-e2e
|
id: run-e2e
|
||||||
run: npm run test:e2e
|
run: npm run test:e2e
|
||||||
|
|
||||||
|
- name: Dump app logs in playwright-report folder
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
mkdir -p playwright-report
|
||||||
|
cp ./runtipi/logs/* playwright-report/
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
|
7
.github/workflows/release.yml
vendored
7
.github/workflows/release.yml
vendored
@ -17,10 +17,6 @@ jobs:
|
|||||||
VERSION=$(npm run version --silent)
|
VERSION=$(npm run version --silent)
|
||||||
echo "tagname=v${VERSION}" >> $GITHUB_OUTPUT
|
echo "tagname=v${VERSION}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- uses: rickstaa/action-create-tag@v1
|
|
||||||
with:
|
|
||||||
tag: ${{ steps.get_tag.outputs.tagname }}
|
|
||||||
|
|
||||||
build-images:
|
build-images:
|
||||||
if: github.repository == 'runtipi/runtipi'
|
if: github.repository == 'runtipi/runtipi'
|
||||||
needs: create-tag
|
needs: create-tag
|
||||||
@ -29,9 +25,6 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Set up QEMU
|
|
||||||
uses: docker/setup-qemu-action@v3
|
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
- name: Log in to Docker Hub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import { expect, test } from '@playwright/test';
|
import { expect, test } from '@playwright/test';
|
||||||
import { appTable } from '@runtipi/db';
|
import { appTable } from '../packages/backend/src/core/database/schema';
|
||||||
import { loginUser } from './fixtures/fixtures';
|
import { loginUser } from './fixtures/fixtures';
|
||||||
import { clearDatabase, db } from './helpers/db';
|
import { clearDatabase, db } from './helpers/db';
|
||||||
import { setSettings } from './helpers/settings';
|
import { setSettings } from './helpers/settings';
|
||||||
|
|
||||||
test.beforeEach(async () => {
|
test.beforeEach(async () => {
|
||||||
test.fixme(true, 'This test is flaky due to incorrect revalidation of the guest dashboard');
|
test.fixme(true, 'Not working yet');
|
||||||
await clearDatabase();
|
await clearDatabase();
|
||||||
await setSettings({});
|
await setSettings({});
|
||||||
});
|
});
|
||||||
@ -16,14 +16,15 @@ test('user can activate the guest dashboard and see it when logged out', async (
|
|||||||
|
|
||||||
await page.getByRole('tab', { name: 'Settings' }).click();
|
await page.getByRole('tab', { name: 'Settings' }).click();
|
||||||
await page.getByLabel('guestDashboard').setChecked(true);
|
await page.getByLabel('guestDashboard').setChecked(true);
|
||||||
await page.getByRole('button', { name: 'Save' }).click();
|
await page.getByRole('button', { name: 'Update settings' }).click();
|
||||||
await page.getByTestId('logout-button').click();
|
await page.getByTestId('logout-button').click();
|
||||||
|
|
||||||
|
await page.reload();
|
||||||
|
|
||||||
await expect(page.getByText('No apps to display')).toBeVisible();
|
await expect(page.getByText('No apps to display')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('logged out users can see the apps on the guest dashboard', async ({ browser }) => {
|
test('logged out users can see the apps on the guest dashboard', async ({ browser }) => {
|
||||||
await setSettings({ guestDashboard: true });
|
|
||||||
await db.insert(appTable).values({
|
await db.insert(appTable).values({
|
||||||
config: {},
|
config: {},
|
||||||
isVisibleOnGuestDashboard: true,
|
isVisibleOnGuestDashboard: true,
|
||||||
@ -46,12 +47,24 @@ test('logged out users can see the apps on the guest dashboard', async ({ browse
|
|||||||
|
|
||||||
const context = await browser.newContext();
|
const context = await browser.newContext();
|
||||||
const page = await context.newPage();
|
const page = await context.newPage();
|
||||||
await page.goto('/');
|
|
||||||
|
await loginUser(page, context);
|
||||||
|
await page.goto('/settings');
|
||||||
|
|
||||||
|
await page.getByRole('tab', { name: 'Settings' }).click();
|
||||||
|
await page.getByLabel('guestDashboard').setChecked(true);
|
||||||
|
await page.getByRole('button', { name: 'Update settings' }).click();
|
||||||
|
await page.getByTestId('logout-button').click();
|
||||||
|
|
||||||
|
await page.reload();
|
||||||
|
|
||||||
await expect(page.getByText(/Hello World web server/)).toBeVisible();
|
await expect(page.getByText(/Hello World web server/)).toBeVisible();
|
||||||
const locator = page.locator('text=Actual Budget');
|
const locator = page.locator('text=Actual Budget');
|
||||||
await expect(locator).not.toBeVisible();
|
await expect(locator).not.toBeVisible();
|
||||||
|
|
||||||
const [newPage] = await Promise.all([context.waitForEvent('page'), await page.getByRole('link', { name: /Hello World/ }).click()]);
|
await page.getByRole('link', { name: /Hello World/ }).click();
|
||||||
|
|
||||||
|
const [newPage] = await Promise.all([context.waitForEvent('page'), page.getByRole('menuitem', { name: 'duckduckgo.com' }).click()]);
|
||||||
|
|
||||||
await newPage.waitForLoadState();
|
await newPage.waitForLoadState();
|
||||||
expect(newPage.url()).toBe('https://duckduckgo.com/');
|
expect(newPage.url()).toBe('https://duckduckgo.com/');
|
||||||
@ -66,10 +79,10 @@ test('user can deactivate the guest dashboard and not see it when logged out', a
|
|||||||
|
|
||||||
await page.getByRole('tab', { name: 'Settings' }).click();
|
await page.getByRole('tab', { name: 'Settings' }).click();
|
||||||
await page.getByLabel('guestDashboard').setChecked(false);
|
await page.getByLabel('guestDashboard').setChecked(false);
|
||||||
await page.getByRole('button', { name: 'Save' }).click();
|
await page.getByRole('button', { name: 'Update settings' }).click();
|
||||||
await page.getByTestId('logout-button').click();
|
await page.getByTestId('logout-button').click();
|
||||||
|
|
||||||
await page.goto('/');
|
await page.reload();
|
||||||
|
|
||||||
// We should be redirected to the login page
|
// We should be redirected to the login page
|
||||||
await expect(page.getByRole('heading', { name: 'Login' })).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'Login' })).toBeVisible();
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import { type BrowserContext, type Page, expect } from '@playwright/test';
|
import { type BrowserContext, type Page, expect } from '@playwright/test';
|
||||||
import { userTable } from '@runtipi/db';
|
|
||||||
import * as argon2 from 'argon2';
|
import * as argon2 from 'argon2';
|
||||||
|
import { userTable } from '../../packages/backend/src/core/database/schema';
|
||||||
import { testUser } from '../helpers/constants';
|
import { testUser } from '../helpers/constants';
|
||||||
import { db } from '../helpers/db';
|
import { db } from '../helpers/db';
|
||||||
|
|
||||||
export const createTestUser = async () => {
|
export const createTestUser = async () => {
|
||||||
// Create user in database
|
// Create user in database
|
||||||
const password = await argon2.hash(testUser.password);
|
const password = await argon2.hash(testUser.password);
|
||||||
await db.insert(userTable).values({ password, username: testUser.email, operator: true });
|
await db.insert(userTable).values({ password, username: testUser.email, operator: true, hasSeenWelcome: true });
|
||||||
};
|
};
|
||||||
|
|
||||||
export const loginUser = async (page: Page, _: BrowserContext) => {
|
export const loginUser = async (page: Page, _: BrowserContext) => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import * as schema from '@runtipi/db';
|
|
||||||
import { drizzle } from 'drizzle-orm/node-postgres';
|
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||||
import { Pool } from 'pg';
|
import { Pool } from 'pg';
|
||||||
|
import * as schema from '../../packages/backend/src/core/database/schema';
|
||||||
|
|
||||||
const connectionString = `postgresql://tipi:${process.env.POSTGRES_PASSWORD}@${process.env.SERVER_IP}:5432/tipi?connect_timeout=300`;
|
const connectionString = `postgresql://tipi:${process.env.POSTGRES_PASSWORD}@${process.env.SERVER_IP}:5432/tipi?connect_timeout=300`;
|
||||||
|
|
||||||
|
@ -1,11 +1,21 @@
|
|||||||
import { promises } from 'node:fs';
|
import { promises } from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
import type { settingsSchema } from '@runtipi/shared';
|
|
||||||
import { pathExists } from '@runtipi/shared/node';
|
|
||||||
import type { z } from 'zod';
|
import type { z } from 'zod';
|
||||||
|
import type { settingsSchema } from '../../packages/backend/src/app.dto';
|
||||||
|
import { userTable } from '../../packages/backend/src/core/database/schema';
|
||||||
import { BASE_PATH } from './constants';
|
import { BASE_PATH } from './constants';
|
||||||
|
import { db } from './db';
|
||||||
|
|
||||||
export const setSettings = async (settings: z.infer<typeof settingsSchema>) => {
|
const pathExists = async (path: string) => {
|
||||||
|
try {
|
||||||
|
await promises.access(path);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setSettings = async (settings: Partial<z.infer<typeof settingsSchema>>) => {
|
||||||
await promises.mkdir(path.join(BASE_PATH, 'state'), { recursive: true });
|
await promises.mkdir(path.join(BASE_PATH, 'state'), { recursive: true });
|
||||||
await promises.writeFile(path.join(BASE_PATH, 'state', 'settings.json'), JSON.stringify(settings));
|
await promises.writeFile(path.join(BASE_PATH, 'state', 'settings.json'), JSON.stringify(settings));
|
||||||
};
|
};
|
||||||
@ -22,15 +32,6 @@ export const unsetPasswordChangeRequest = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const setWelcomeSeen = async (seen: boolean) => {
|
export const setWelcomeSeen = async (seen: boolean) => {
|
||||||
const seenPath = path.join(BASE_PATH, 'state', 'seen-welcome');
|
await db.update(userTable).set({ hasSeenWelcome: seen });
|
||||||
|
|
||||||
if (seen && !(await pathExists(seenPath))) {
|
|
||||||
return promises.writeFile(seenPath, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!seen && (await pathExists(seenPath))) {
|
|
||||||
return promises.unlink(seenPath);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
|
16
package.json
16
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "runtipi",
|
"name": "runtipi",
|
||||||
"version": "1.0.0",
|
"version": "3.7.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"packageManager": "pnpm@9.12.2",
|
"packageManager": "pnpm@9.12.2",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@ -9,9 +9,12 @@
|
|||||||
"start": "node ./index.js",
|
"start": "node ./index.js",
|
||||||
"start:dev": "docker compose --project-name runtipi -f docker-compose.dev.yml up --build",
|
"start:dev": "docker compose --project-name runtipi -f docker-compose.dev.yml up --build",
|
||||||
"start:prod": "docker compose --project-name runtipi -f docker-compose.prod.yml up --build ",
|
"start:prod": "docker compose --project-name runtipi -f docker-compose.prod.yml up --build ",
|
||||||
|
"test:e2e": "NODE_ENV=test dotenv -e .env -e .env.e2e -- playwright test",
|
||||||
|
"test:e2e:ui": "NODE_ENV=test dotenv -e .env -e .env.e2e -- playwright test --ui",
|
||||||
"gen:api-client": "openapi-ts",
|
"gen:api-client": "openapi-ts",
|
||||||
"lint:ci": "biome ci . --changed --error-on-warnings --no-errors-on-unmatched",
|
"lint:ci": "biome ci . --changed --error-on-warnings --no-errors-on-unmatched",
|
||||||
"lint": "biome check"
|
"lint": "biome check",
|
||||||
|
"version": "echo $npm_package_version"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
@ -19,6 +22,15 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "^1.9.2",
|
"@biomejs/biome": "^1.9.2",
|
||||||
"@hey-api/openapi-ts": "^0.53.7",
|
"@hey-api/openapi-ts": "^0.53.7",
|
||||||
|
"@playwright/test": "^1.48.2",
|
||||||
|
"@types/pg": "^8.11.10",
|
||||||
|
"dotenv-cli": "^7.4.2",
|
||||||
"turbo": "^2.2.3"
|
"turbo": "^2.2.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"argon2": "^0.41.1",
|
||||||
|
"drizzle-orm": "^0.33.0",
|
||||||
|
"pg": "^8.12.0",
|
||||||
|
"zod": "^3.23.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ import { TranslatableError } from '@/common/error/translatable-error';
|
|||||||
import { notEmpty, pLimit } from '@/common/helpers/file-helpers';
|
import { notEmpty, pLimit } from '@/common/helpers/file-helpers';
|
||||||
import { type Architecture, ConfigurationService } from '@/core/config/configuration.service';
|
import { type Architecture, ConfigurationService } from '@/core/config/configuration.service';
|
||||||
import type { App } from '@/core/database/schema';
|
import type { App } from '@/core/database/schema';
|
||||||
|
import { LoggerService } from '@/core/logger/logger.service';
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import MiniSearch from 'minisearch';
|
import MiniSearch from 'minisearch';
|
||||||
import { AppFilesManager } from './app-files-manager';
|
import { AppFilesManager } from './app-files-manager';
|
||||||
@ -30,6 +31,7 @@ export class AppCatalogService {
|
|||||||
private readonly filesManager: AppFilesManager,
|
private readonly filesManager: AppFilesManager,
|
||||||
private readonly configuration: ConfigurationService,
|
private readonly configuration: ConfigurationService,
|
||||||
private readonly appsRepository: AppsRepository,
|
private readonly appsRepository: AppsRepository,
|
||||||
|
private readonly logger: LoggerService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private appsAvailable: AppList | null = null;
|
private appsAvailable: AppList | null = null;
|
||||||
@ -39,7 +41,7 @@ export class AppCatalogService {
|
|||||||
|
|
||||||
private async constructSingleApp(app: App) {
|
private async constructSingleApp(app: App) {
|
||||||
try {
|
try {
|
||||||
const info = await this.filesManager.getInstalledAppInfo(app.id);
|
const info = await this.filesManager.getAppInfoFromAppStore(app.id);
|
||||||
const updateInfo = await this.filesManager.getAppUpdateInfo(app.id);
|
const updateInfo = await this.filesManager.getAppUpdateInfo(app.id);
|
||||||
return info ? { app, info, updateInfo } : null;
|
return info ? { app, info, updateInfo } : null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -211,7 +213,10 @@ export class AppCatalogService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getGuestDashboardApps() {
|
public async getGuestDashboardApps() {
|
||||||
|
this.logger.debug('Getting guest dashboard apps');
|
||||||
const apps = await this.appsRepository.getGuestDashboardApps();
|
const apps = await this.appsRepository.getGuestDashboardApps();
|
||||||
|
this.logger.debug(`Got ${apps.length} guest dashboard apps`);
|
||||||
|
|
||||||
return this.constructAppList(apps);
|
return this.constructAppList(apps);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import { useUIStore } from '@/stores/ui-store';
|
|||||||
import { IconBrandGithub, IconHeart, IconLogin, IconLogout, IconMoon, IconSun } from '@tabler/icons-react';
|
import { IconBrandGithub, IconHeart, IconLogin, IconLogout, IconMoon, IconSun } from '@tabler/icons-react';
|
||||||
import { useMutation } from '@tanstack/react-query';
|
import { useMutation } from '@tanstack/react-query';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
import { Tooltip } from 'react-tooltip';
|
import { Tooltip } from 'react-tooltip';
|
||||||
import { NavBar } from '../navbar/navbar';
|
import { NavBar } from '../navbar/navbar';
|
||||||
|
|
||||||
@ -21,8 +21,13 @@ export const Header = (props: HeaderProps) => {
|
|||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const logout = useMutation({
|
const logout = useMutation({
|
||||||
...logoutMutation(),
|
...logoutMutation(),
|
||||||
|
onSuccess: () => {
|
||||||
|
navigate('/', { replace: true });
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -15,6 +15,7 @@ import { IconLock, IconLockOff } from '@tabler/icons-react';
|
|||||||
import { useSuspenseQuery } from '@tanstack/react-query';
|
import { useSuspenseQuery } from '@tanstack/react-query';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import './guest-dashboard.css';
|
import './guest-dashboard.css';
|
||||||
|
import { EmptyPage } from '@/components/empty-page/empty-page';
|
||||||
|
|
||||||
const Tile = ({ data }: { data: GuestAppsDto['installed'][number] }) => {
|
const Tile = ({ data }: { data: GuestAppsDto['installed'][number] }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -88,6 +89,7 @@ export const GuestDashboard = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="page-body">
|
<div className="page-body">
|
||||||
<div className="container-xl">
|
<div className="container-xl">
|
||||||
|
{data.installed.length === 0 && <EmptyPage title="GUEST_DASHBOARD_NO_APPS" subtitle="GUEST_DASHBOARD_NO_APPS_SUBTITLE" />}
|
||||||
<div className="row row-cards">
|
<div className="row row-cards">
|
||||||
{data.installed.map((data) => {
|
{data.installed.map((data) => {
|
||||||
return <Tile key={data.app.id} data={data} />;
|
return <Tile key={data.app.id} data={data} />;
|
||||||
|
@ -3,6 +3,8 @@ import { defineConfig, devices } from '@playwright/test';
|
|||||||
/**
|
/**
|
||||||
* See https://playwright.dev/docs/test-configuration.
|
* See https://playwright.dev/docs/test-configuration.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// biome-ignore lint/style/noDefaultExport: needed for Playwright to work
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
globalSetup: require.resolve('./e2e/helpers/global-setup'),
|
globalSetup: require.resolve('./e2e/helpers/global-setup'),
|
||||||
testDir: './e2e',
|
testDir: './e2e',
|
||||||
@ -11,21 +13,18 @@ export default defineConfig({
|
|||||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||||
forbidOnly: !!process.env.CI,
|
forbidOnly: !!process.env.CI,
|
||||||
/* Retry on CI only */
|
/* Retry on CI only */
|
||||||
retries: process.env.CI ? 2 : 0,
|
retries: process.env.CI ? 0 : 0,
|
||||||
/* Opt out of parallel tests on CI. */
|
/* Opt out of parallel tests on CI. */
|
||||||
workers: 1,
|
workers: 1,
|
||||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
reporter: 'html',
|
reporter: 'html',
|
||||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
|
|
||||||
use: {
|
use: {
|
||||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||||
baseURL: `http://${process.env.SERVER_IP}${process.env.SERVER_PORT ? `:${process.env.SERVER_PORT}` : ''}`,
|
baseURL: `http://${process.env.SERVER_IP}${process.env.SERVER_PORT ? `:${process.env.SERVER_PORT}` : ''}`,
|
||||||
|
|
||||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||||
trace: 'on-first-retry',
|
trace: 'on',
|
||||||
|
video: 'retain-on-failure',
|
||||||
video: 'on',
|
|
||||||
},
|
},
|
||||||
// timeout: 5000,
|
// timeout: 5000,
|
||||||
|
|
||||||
|
@ -7,6 +7,19 @@ settings:
|
|||||||
importers:
|
importers:
|
||||||
|
|
||||||
.:
|
.:
|
||||||
|
dependencies:
|
||||||
|
argon2:
|
||||||
|
specifier: ^0.41.1
|
||||||
|
version: 0.41.1
|
||||||
|
drizzle-orm:
|
||||||
|
specifier: ^0.33.0
|
||||||
|
version: 0.33.0(@opentelemetry/api@1.9.0)(@types/pg@8.11.10)(@types/react@18.3.6)(pg@8.12.0)(react@18.3.1)(sqlite3@5.1.7)
|
||||||
|
pg:
|
||||||
|
specifier: ^8.12.0
|
||||||
|
version: 8.12.0
|
||||||
|
zod:
|
||||||
|
specifier: ^3.23.8
|
||||||
|
version: 3.23.8
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@biomejs/biome':
|
'@biomejs/biome':
|
||||||
specifier: ^1.9.2
|
specifier: ^1.9.2
|
||||||
@ -14,6 +27,15 @@ importers:
|
|||||||
'@hey-api/openapi-ts':
|
'@hey-api/openapi-ts':
|
||||||
specifier: ^0.53.7
|
specifier: ^0.53.7
|
||||||
version: 0.53.7(typescript@5.6.2)
|
version: 0.53.7(typescript@5.6.2)
|
||||||
|
'@playwright/test':
|
||||||
|
specifier: ^1.48.2
|
||||||
|
version: 1.48.2
|
||||||
|
'@types/pg':
|
||||||
|
specifier: ^8.11.10
|
||||||
|
version: 8.11.10
|
||||||
|
dotenv-cli:
|
||||||
|
specifier: ^7.4.2
|
||||||
|
version: 7.4.2
|
||||||
turbo:
|
turbo:
|
||||||
specifier: ^2.2.3
|
specifier: ^2.2.3
|
||||||
version: 2.2.3
|
version: 2.2.3
|
||||||
@ -293,7 +315,7 @@ importers:
|
|||||||
version: 3.1.7
|
version: 3.1.7
|
||||||
geist:
|
geist:
|
||||||
specifier: ^1.3.1
|
specifier: ^1.3.1
|
||||||
version: 1.3.1(next@14.2.11(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
|
version: 1.3.1(next@14.2.11(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))
|
||||||
i18next:
|
i18next:
|
||||||
specifier: ^23.15.1
|
specifier: ^23.15.1
|
||||||
version: 23.15.1
|
version: 23.15.1
|
||||||
@ -1676,6 +1698,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
|
||||||
|
'@playwright/test@1.48.2':
|
||||||
|
resolution: {integrity: sha512-54w1xCWfXuax7dz4W2M9uw0gDyh+ti/0K/MxcCUxChFh37kkdxPdfZDw5QBbuPUJHr1CiHJ1hXgSs+GgeQc5Zw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
'@popperjs/core@2.11.8':
|
'@popperjs/core@2.11.8':
|
||||||
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
|
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
|
||||||
|
|
||||||
@ -3850,6 +3877,11 @@ packages:
|
|||||||
fs.realpath@1.0.0:
|
fs.realpath@1.0.0:
|
||||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||||
|
|
||||||
|
fsevents@2.3.2:
|
||||||
|
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||||
|
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
fsevents@2.3.3:
|
fsevents@2.3.3:
|
||||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||||
@ -5019,6 +5051,16 @@ packages:
|
|||||||
pkg-types@1.2.0:
|
pkg-types@1.2.0:
|
||||||
resolution: {integrity: sha512-+ifYuSSqOQ8CqP4MbZA5hDpb97n3E8SVWdJe+Wms9kj745lmd3b7EZJiqvmLwAlmRfjrI7Hi5z3kdBJ93lFNPA==}
|
resolution: {integrity: sha512-+ifYuSSqOQ8CqP4MbZA5hDpb97n3E8SVWdJe+Wms9kj745lmd3b7EZJiqvmLwAlmRfjrI7Hi5z3kdBJ93lFNPA==}
|
||||||
|
|
||||||
|
playwright-core@1.48.2:
|
||||||
|
resolution: {integrity: sha512-sjjw+qrLFlriJo64du+EK0kJgZzoQPsabGF4lBvsid+3CNIZIYLgnMj9V6JY5VhM2Peh20DJWIVpVljLLnlawA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
playwright@1.48.2:
|
||||||
|
resolution: {integrity: sha512-NjYvYgp4BPmiwfe31j4gHLa3J7bD2WiBz8Lk2RoSsmX38SVIARZ18VYjxLjAcDsAhA+F4iSEXTSGgjua0rrlgQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
pluralize@8.0.0:
|
pluralize@8.0.0:
|
||||||
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
|
resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@ -7571,6 +7613,10 @@ snapshots:
|
|||||||
'@pkgjs/parseargs@0.11.0':
|
'@pkgjs/parseargs@0.11.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
'@playwright/test@1.48.2':
|
||||||
|
dependencies:
|
||||||
|
playwright: 1.48.2
|
||||||
|
|
||||||
'@popperjs/core@2.11.8': {}
|
'@popperjs/core@2.11.8': {}
|
||||||
|
|
||||||
'@prisma/instrumentation@5.19.1':
|
'@prisma/instrumentation@5.19.1':
|
||||||
@ -9842,6 +9888,9 @@ snapshots:
|
|||||||
fs.realpath@1.0.0:
|
fs.realpath@1.0.0:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
fsevents@2.3.2:
|
||||||
|
optional: true
|
||||||
|
|
||||||
fsevents@2.3.3:
|
fsevents@2.3.3:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@ -9859,9 +9908,9 @@ snapshots:
|
|||||||
wide-align: 1.1.5
|
wide-align: 1.1.5
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
geist@1.3.1(next@14.2.11(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)):
|
geist@1.3.1(next@14.2.11(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)):
|
||||||
dependencies:
|
dependencies:
|
||||||
next: 14.2.11(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
next: 14.2.11(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
|
||||||
gensync@1.0.0-beta.2: {}
|
gensync@1.0.0-beta.2: {}
|
||||||
|
|
||||||
@ -10969,7 +11018,7 @@ snapshots:
|
|||||||
'@nestjs/core': 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
'@nestjs/core': 10.4.1(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.1)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
||||||
'@nestjs/swagger': 7.4.2(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)
|
'@nestjs/swagger': 7.4.2(@nestjs/common@10.4.1(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.1)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)
|
||||||
|
|
||||||
next@14.2.11(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
next@14.2.11(@babel/core@7.25.2)(@opentelemetry/api@1.9.0)(@playwright/test@1.48.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@next/env': 14.2.11
|
'@next/env': 14.2.11
|
||||||
'@swc/helpers': 0.5.5
|
'@swc/helpers': 0.5.5
|
||||||
@ -10991,6 +11040,7 @@ snapshots:
|
|||||||
'@next/swc-win32-ia32-msvc': 14.2.11
|
'@next/swc-win32-ia32-msvc': 14.2.11
|
||||||
'@next/swc-win32-x64-msvc': 14.2.11
|
'@next/swc-win32-x64-msvc': 14.2.11
|
||||||
'@opentelemetry/api': 1.9.0
|
'@opentelemetry/api': 1.9.0
|
||||||
|
'@playwright/test': 1.48.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@babel/core'
|
- '@babel/core'
|
||||||
- babel-plugin-macros
|
- babel-plugin-macros
|
||||||
@ -11283,6 +11333,14 @@ snapshots:
|
|||||||
mlly: 1.7.1
|
mlly: 1.7.1
|
||||||
pathe: 1.1.2
|
pathe: 1.1.2
|
||||||
|
|
||||||
|
playwright-core@1.48.2: {}
|
||||||
|
|
||||||
|
playwright@1.48.2:
|
||||||
|
dependencies:
|
||||||
|
playwright-core: 1.48.2
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents: 2.3.2
|
||||||
|
|
||||||
pluralize@8.0.0: {}
|
pluralize@8.0.0: {}
|
||||||
|
|
||||||
postcss@8.4.31:
|
postcss@8.4.31:
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
prefer-workspace-packages: true
|
prefer-workspace-packages: true
|
||||||
packages:
|
packages:
|
||||||
- "packages/**"
|
- "./packages/**"
|
||||||
|
@ -18,6 +18,7 @@ fi
|
|||||||
UPDATE="false"
|
UPDATE="false"
|
||||||
VERSION="latest"
|
VERSION="latest"
|
||||||
ASSET="runtipi-cli-linux-x86_64.tar.gz"
|
ASSET="runtipi-cli-linux-x86_64.tar.gz"
|
||||||
|
ENV_FILE=""
|
||||||
|
|
||||||
while [ -n "${1-}" ]; do
|
while [ -n "${1-}" ]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
@ -36,6 +37,13 @@ while [ -n "${1-}" ]; do
|
|||||||
echo "Option --asset requires a value" && exit 1
|
echo "Option --asset requires a value" && exit 1
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
--env-file)
|
||||||
|
shift # Move to the next parameter
|
||||||
|
ENV_FILE="$1" # Assign the value to ENV_FILE
|
||||||
|
if [ -z "$ENV_FILE" ]; then
|
||||||
|
echo "Option --env-file requires a value" && exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
--)
|
--)
|
||||||
shift # The double dash makes them parameters
|
shift # The double dash makes them parameters
|
||||||
break
|
break
|
||||||
@ -209,4 +217,10 @@ else
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
chmod +x ./runtipi-cli
|
chmod +x ./runtipi-cli
|
||||||
sudo ./runtipi-cli start
|
|
||||||
|
if [[ "${ENV_FILE}" != "" ]]; then
|
||||||
|
echo "Starting runtipi with env file ${ENV_FILE}"
|
||||||
|
sudo ./runtipi-cli start --env-file "${ENV_FILE}"
|
||||||
|
else
|
||||||
|
sudo ./runtipi-cli start
|
||||||
|
fi
|
||||||
|
Loading…
Reference in New Issue
Block a user