mirror of
https://github.com/meienberger/runtipi.git
synced 2024-08-16 23:50:34 +03:00
refactor: merge worker and dashboard
add storage path fix: fix compose file chore: use dev compose file for now refactor: try to not use hardcoded values refactor(worker): use constants instead of hardcoded values refactor(dashboard): use constants instead of hardcoded values chore: ignore new runtipi-data folder fix(worker): wrong constant chore: remove root folder host as it is not being used anywhere refactor: rename storagePath to appDataDirPath because thats what it does feat: add production dockerfile fix: add cmd to prod dockerfile fix: small fixes in prod dockerfile chore: fix path mistakes fix: re-add ROOT_FOLDER_HOST chore: revert storage path
This commit is contained in:
parent
8268a1e5e8
commit
2681630996
@ -1,7 +1,8 @@
|
||||
*
|
||||
-
|
||||
|
||||
### Includes ###
|
||||
!pnpm-*.yaml
|
||||
### Includes
|
||||
|
||||
!pnpm-_.yaml
|
||||
!package.json
|
||||
!patches/**
|
||||
!packages/**/src/**
|
||||
@ -9,9 +10,10 @@
|
||||
!**/package.json
|
||||
!**/nodemon.json
|
||||
!**/tsconfig.json
|
||||
!**/build.js
|
||||
!\*\*/build.js
|
||||
!next.config.mjs
|
||||
!sentry.*.config.ts
|
||||
!sentry._.config.ts
|
||||
!public/**
|
||||
!src/**
|
||||
!tests/**
|
||||
!tests/\*\*
|
||||
!start.\_.sh
|
||||
|
@ -11,12 +11,12 @@ STORAGE_PATH=/path/to/runtipi
|
||||
NGINX_PORT=7000
|
||||
NGINX_PORT_SSL=443
|
||||
DOMAIN=tipi.localhost
|
||||
POSTGRES_HOST=tipi-db
|
||||
POSTGRES_HOST=runtipi-db
|
||||
POSTGRES_DBNAME=tipi
|
||||
POSTGRES_USERNAME=tipi
|
||||
POSTGRES_PASSWORD=postgres
|
||||
POSTGRES_PORT=5432
|
||||
REDIS_HOST=tipi-redis
|
||||
REDIS_HOST=runtipi-redis
|
||||
REDIS_PASSWORD=redis
|
||||
DEMO_MODE=false
|
||||
LOCAL_DOMAIN=tipi.lan
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -70,3 +70,6 @@ temp
|
||||
|
||||
# Sentry Config File
|
||||
.sentryclirc
|
||||
|
||||
# Runtipi data folder
|
||||
/runtipi-data/
|
||||
|
91
Dockerfile
91
Dockerfile
@ -3,15 +3,22 @@ ARG ALPINE_VERSION="3.18"
|
||||
|
||||
FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION} AS node_base
|
||||
|
||||
# ---- BUILDER BASE ----
|
||||
FROM node_base AS builder_base
|
||||
|
||||
RUN apk add --no-cache python3 make g++
|
||||
RUN npm install pnpm -g
|
||||
RUN apk add --no-cache curl python3 make g++
|
||||
|
||||
# BUILDER
|
||||
FROM builder_base AS builder
|
||||
# ---- RUNNER BASE ----
|
||||
FROM node_base AS runner_base
|
||||
|
||||
WORKDIR /app
|
||||
RUN apk add --no-cache curl openssl git
|
||||
RUN npm install pm2 -g
|
||||
|
||||
# ---- BUILD DASHBOARD ----
|
||||
FROM builder_base AS dashboard_builder
|
||||
|
||||
WORKDIR /dashboard
|
||||
|
||||
COPY ./pnpm-lock.yaml ./
|
||||
RUN pnpm fetch
|
||||
@ -43,21 +50,73 @@ ENV NEXT_SHARP_PATH=/app/node_modules/sharp
|
||||
|
||||
RUN pnpm build
|
||||
|
||||
# APP
|
||||
FROM node_base AS app
|
||||
# ---- BUILD WORKER ----
|
||||
FROM builder_base AS worker_builder
|
||||
|
||||
ENV NODE_ENV production
|
||||
WORKDIR /worker
|
||||
|
||||
USER 1000:1000
|
||||
ARG TARGETARCH
|
||||
ENV TARGETARCH=${TARGETARCH}
|
||||
ARG DOCKER_COMPOSE_VERSION="v2.23.3"
|
||||
|
||||
WORKDIR /app
|
||||
RUN echo "Building for ${TARGETARCH}"
|
||||
|
||||
COPY --from=builder /app/next.config.mjs ./
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder /app/package.json ./package.json
|
||||
COPY --from=builder --chown=1000:1000 /app/.next/standalone ./
|
||||
COPY --from=builder --chown=1000:1000 /app/.next/static ./.next/static
|
||||
|
||||
EXPOSE 3000
|
||||
RUN if [ "${TARGETARCH}" = "arm64" ]; then \
|
||||
curl -L -o docker-binary "https://github.com/docker/compose/releases/download/$DOCKER_COMPOSE_VERSION/docker-compose-linux-aarch64"; \
|
||||
elif [ "${TARGETARCH}" = "amd64" ]; then \
|
||||
curl -L -o docker-binary "https://github.com/docker/compose/releases/download/$DOCKER_COMPOSE_VERSION/docker-compose-linux-x86_64"; \
|
||||
else \
|
||||
echo "Unsupported architecture"; \
|
||||
fi
|
||||
|
||||
CMD ["npm", "run", "start"]
|
||||
RUN chmod +x docker-binary
|
||||
|
||||
COPY ./pnpm-lock.yaml ./
|
||||
RUN pnpm fetch --ignore-scripts
|
||||
|
||||
COPY ./pnpm-workspace.yaml ./
|
||||
COPY ./packages ./packages
|
||||
|
||||
RUN pnpm install -r --prefer-offline
|
||||
|
||||
COPY ./packages/worker/build.js ./packages/worker/build.js
|
||||
COPY ./packages/worker/src ./packages/worker/src
|
||||
COPY ./packages/worker/package.json ./packages/worker/package.json
|
||||
COPY ./packages/worker/assets ./packages/worker/assets
|
||||
|
||||
ARG SENTRY_AUTH_TOKEN
|
||||
ARG SENTRY_DISABLE_AUTO_UPLOAD
|
||||
ARG TIPI_VERSION
|
||||
|
||||
ENV SENTRY_AUTH_TOKEN=${SENTRY_AUTH_TOKEN}
|
||||
ENV SENTRY_DISABLE_AUTO_UPLOAD=${SENTRY_DISABLE_AUTO_UPLOAD}
|
||||
ENV TIPI_VERSION=${TIPI_VERSION}
|
||||
|
||||
RUN pnpm -r build --filter @runtipi/worker
|
||||
|
||||
# ---- RUNNER ----
|
||||
FROM runner_base AS app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
|
||||
WORKDIR /worker
|
||||
|
||||
COPY --from=worker_builder /worker/packages/worker/dist .
|
||||
COPY --from=worker_builder /worker/packages/worker/assets ./assets
|
||||
COPY --from=worker_builder /worker/docker-binary /usr/local/bin/docker-compose
|
||||
|
||||
WORKDIR /dashboard
|
||||
|
||||
COPY --from=dashboard_builder /dashboard/next.config.mjs ./
|
||||
COPY --from=dashboard_builder /dashboard/public ./public
|
||||
COPY --from=dashboard_builder /dashboard/package.json ./package.json
|
||||
COPY --from=dashboard_builder /dashboard/.next/standalone ./
|
||||
COPY --from=dashboard_builder /dashboard/.next/static ./.next/static
|
||||
|
||||
WORKDIR /
|
||||
COPY ./start.prod.sh ./start.sh
|
||||
|
||||
EXPOSE 3000 5000
|
||||
|
||||
CMD ["sh", "start.sh"]
|
||||
|
@ -3,16 +3,34 @@ ARG ALPINE_VERSION="3.18"
|
||||
|
||||
FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION}
|
||||
|
||||
RUN apk add --no-cache python3 make g++
|
||||
RUN npm install pnpm -g
|
||||
RUN apk add --no-cache python3 make g++ curl git
|
||||
RUN npm install pnpm pm2 -g
|
||||
|
||||
ARG TARGETARCH
|
||||
ARG DOCKER_COMPOSE_VERSION="v2.23.3"
|
||||
ENV TARGETARCH=${TARGETARCH}
|
||||
ENV NODE_ENV="development"
|
||||
|
||||
# Dashboard
|
||||
WORKDIR /app
|
||||
|
||||
RUN echo "Building for ${TARGETARCH}"
|
||||
|
||||
RUN if [ "${TARGETARCH}" = "arm64" ]; then \
|
||||
curl -L -o docker-binary "https://github.com/docker/compose/releases/download/$DOCKER_COMPOSE_VERSION/docker-compose-linux-aarch64"; \
|
||||
elif [ "${TARGETARCH}" = "amd64" ]; then \
|
||||
curl -L -o docker-binary "https://github.com/docker/compose/releases/download/$DOCKER_COMPOSE_VERSION/docker-compose-linux-x86_64"; \
|
||||
fi
|
||||
|
||||
RUN chmod +x docker-binary
|
||||
|
||||
RUN mv docker-binary /usr/local/bin/docker-compose
|
||||
|
||||
COPY ./pnpm-lock.yaml ./
|
||||
RUN pnpm fetch --ignore-scripts
|
||||
|
||||
COPY ./package*.json ./
|
||||
COPY ./packages/shared ./packages/shared
|
||||
COPY ./packages ./packages
|
||||
|
||||
RUN pnpm install -r --prefer-offline
|
||||
|
||||
@ -25,4 +43,6 @@ COPY ./sentry.client.config.ts ./sentry.client.config.ts
|
||||
COPY ./sentry.edge.config.ts ./sentry.edge.config.ts
|
||||
COPY ./sentry.server.config.ts ./sentry.server.config.ts
|
||||
|
||||
CMD ["npm", "run", "dev"]
|
||||
COPY ./start.dev.sh ./start.sh
|
||||
|
||||
CMD ["sh", "start.sh"]
|
||||
|
@ -1,10 +1,10 @@
|
||||
version: '3.7'
|
||||
|
||||
services:
|
||||
tipi-reverse-proxy:
|
||||
container_name: tipi-reverse-proxy
|
||||
runtipi-reverse-proxy:
|
||||
container_name: runtipi-reverse-proxy
|
||||
depends_on:
|
||||
- tipi-dashboard
|
||||
- runtipi
|
||||
image: traefik:v2.11
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
@ -19,8 +19,8 @@ services:
|
||||
networks:
|
||||
- tipi_main_network
|
||||
|
||||
tipi-db:
|
||||
container_name: tipi-db
|
||||
runtipi-db:
|
||||
container_name: runtipi-db
|
||||
image: postgres:14
|
||||
restart: unless-stopped
|
||||
stop_grace_period: 1m
|
||||
@ -40,8 +40,8 @@ services:
|
||||
networks:
|
||||
- tipi_main_network
|
||||
|
||||
tipi-redis:
|
||||
container_name: tipi-redis
|
||||
runtipi-redis:
|
||||
container_name: runtipi-redis
|
||||
image: redis:7.2.0
|
||||
restart: unless-stopped
|
||||
command: redis-server --requirepass ${REDIS_PASSWORD} --stop-writes-on-bgsave-error no
|
||||
@ -57,48 +57,71 @@ services:
|
||||
networks:
|
||||
- tipi_main_network
|
||||
|
||||
tipi-worker:
|
||||
runtipi:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./packages/worker/Dockerfile.dev
|
||||
container_name: tipi-worker
|
||||
healthcheck:
|
||||
test: ['CMD', 'curl', '-f', 'http://localhost:3000/worker-api/healthcheck']
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 120
|
||||
start_period: 5s
|
||||
dockerfile: Dockerfile.dev
|
||||
container_name: runtipi
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
tipi-db:
|
||||
runtipi-db:
|
||||
condition: service_healthy
|
||||
tipi-redis:
|
||||
runtipi-redis:
|
||||
condition: service_healthy
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
# Hot reload
|
||||
- ./src:/app/src
|
||||
- ./packages/worker/src:/app/packages/worker/src
|
||||
- ./packages/shared/src:/app/packages/shared/src
|
||||
# Data
|
||||
- ./state:/data/state
|
||||
- ./repos:/data/repos
|
||||
- ./apps:/data/apps
|
||||
- ./logs:/data/logs
|
||||
- ./traefik:/data/traefik
|
||||
- ./user-config:/data/user-config
|
||||
- ${STORAGE_PATH:-$PWD}/app-data:/app-data
|
||||
# Static
|
||||
- ./.env:/data/.env
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- /:/mnt/host:ro
|
||||
networks:
|
||||
- tipi_main_network
|
||||
ports:
|
||||
- 3000:3000
|
||||
environment:
|
||||
NODE_ENV: development
|
||||
TIPI_VERSION: development
|
||||
volumes:
|
||||
# Dev mode
|
||||
- ./packages/worker/src:/app/packages/worker/src
|
||||
# Production mode
|
||||
- /proc:/host/proc:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./.env:/app/.env
|
||||
- ./state:/app/state
|
||||
- ./repos:/app/repos
|
||||
- ./apps:/app/apps
|
||||
- ${STORAGE_PATH:-$PWD}/app-data:/storage/app-data
|
||||
- ./logs:/app/logs
|
||||
- ./traefik:/app/traefik
|
||||
- ./user-config:/app/user-config
|
||||
networks:
|
||||
- tipi_main_network
|
||||
WORKER_APP_DIR: /app/packages/worker
|
||||
DASHBOARD_APP_DIR: /app
|
||||
NEXT_PUBLIC_TIPI_VERSION: development
|
||||
env_file:
|
||||
- .env
|
||||
labels:
|
||||
# ---- General ----- #
|
||||
traefik.enable: true
|
||||
traefik.http.services.worker.loadbalancer.server.port: 3001
|
||||
traefik.http.services.worker-api.loadbalancer.server.port: 3000
|
||||
traefik.http.middlewares.redirect-to-https.redirectscheme.scheme: https
|
||||
|
||||
# ---- Dashboard ---- #
|
||||
traefik.http.services.dashboard.loadbalancer.server.port: 3000
|
||||
# Local ip
|
||||
traefik.http.routers.dashboard.rule: PathPrefix("/")
|
||||
traefik.http.routers.dashboard.service: dashboard
|
||||
traefik.http.routers.dashboard.entrypoints: web
|
||||
# Local domain
|
||||
traefik.http.routers.dashboard-local-insecure.rule: Host(`${LOCAL_DOMAIN}`)
|
||||
traefik.http.routers.dashboard-local-insecure.entrypoints: web
|
||||
traefik.http.routers.dashboard-local-insecure.service: dashboard
|
||||
traefik.http.routers.dashboard-local-insecure.middlewares: redirect-to-https
|
||||
# Secure
|
||||
traefik.http.routers.dashboard-local.rule: Host(`${LOCAL_DOMAIN}`)
|
||||
traefik.http.routers.dashboard-local.entrypoints: websecure
|
||||
traefik.http.routers.dashboard-local.tls: true
|
||||
traefik.http.routers.dashboard-local.service: dashboard
|
||||
|
||||
# ---- Worker ---- #
|
||||
traefik.http.services.worker.loadbalancer.server.port: 5001
|
||||
traefik.http.services.worker-api.loadbalancer.server.port: 5000
|
||||
# Local ip
|
||||
traefik.http.routers.worker.rule: PathPrefix("/worker")
|
||||
traefik.http.routers.worker.service: worker
|
||||
@ -115,7 +138,7 @@ services:
|
||||
traefik.http.routers.worker-api-local-insecure.entrypoints: web
|
||||
traefik.http.routers.worker-api-local-insecure.service: worker-api
|
||||
traefik.http.routers.worker-api-local-insecure.middlewares: redirect-to-https
|
||||
# secure
|
||||
# Secure
|
||||
traefik.http.routers.worker-local.rule: Host(`${LOCAL_DOMAIN}`) && PathPrefix("/worker")
|
||||
traefik.http.routers.worker-local.entrypoints: websecure
|
||||
traefik.http.routers.worker-local.tls: true
|
||||
@ -124,60 +147,6 @@ services:
|
||||
traefik.http.routers.worker-api-local.entrypoints: websecure
|
||||
traefik.http.routers.worker-api-local.tls: true
|
||||
|
||||
tipi-dashboard:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.dev
|
||||
container_name: tipi-dashboard
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
tipi-db:
|
||||
condition: service_healthy
|
||||
tipi-redis:
|
||||
condition: service_healthy
|
||||
tipi-worker:
|
||||
condition: service_healthy
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
NODE_ENV: development
|
||||
TIPI_VERSION: development
|
||||
NEXT_PUBLIC_TIPI_VERSION: 0.0.0
|
||||
networks:
|
||||
- tipi_main_network
|
||||
ports:
|
||||
- 3000:3000
|
||||
volumes:
|
||||
# - /dashboard/node_modules
|
||||
# - /dashboard/.next
|
||||
- ./.env:/runtipi/.env
|
||||
- ./src:/app/src
|
||||
- ./packages:/app/packages
|
||||
- ./state:/runtipi/state
|
||||
- ./repos:/runtipi/repos:ro
|
||||
- ./apps:/runtipi/apps
|
||||
- ./logs:/app/logs
|
||||
- ./traefik:/runtipi/traefik
|
||||
- ${STORAGE_PATH:-$PWD}:/app/storage
|
||||
labels:
|
||||
traefik.enable: true
|
||||
traefik.http.services.dashboard.loadbalancer.server.port: 3000
|
||||
traefik.http.middlewares.redirect-to-https.redirectscheme.scheme: https
|
||||
# Local ip
|
||||
traefik.http.routers.dashboard.rule: PathPrefix("/")
|
||||
traefik.http.routers.dashboard.service: dashboard
|
||||
traefik.http.routers.dashboard.entrypoints: web
|
||||
# Local domain
|
||||
traefik.http.routers.dashboard-local-insecure.rule: Host(`${LOCAL_DOMAIN}`)
|
||||
traefik.http.routers.dashboard-local-insecure.entrypoints: web
|
||||
traefik.http.routers.dashboard-local-insecure.service: dashboard
|
||||
traefik.http.routers.dashboard-local-insecure.middlewares: redirect-to-https
|
||||
# secure
|
||||
traefik.http.routers.dashboard-local.rule: Host(`${LOCAL_DOMAIN}`)
|
||||
traefik.http.routers.dashboard-local.entrypoints: websecure
|
||||
traefik.http.routers.dashboard-local.tls: true
|
||||
traefik.http.routers.dashboard-local.service: dashboard
|
||||
|
||||
networks:
|
||||
tipi_main_network:
|
||||
driver: bridge
|
||||
|
@ -1,10 +1,10 @@
|
||||
version: '3.7'
|
||||
version: '3.9'
|
||||
|
||||
services:
|
||||
tipi-reverse-proxy:
|
||||
container_name: tipi-reverse-proxy
|
||||
runtipi-reverse-proxy:
|
||||
container_name: runtipi-reverse-proxy
|
||||
depends_on:
|
||||
- tipi-dashboard
|
||||
- runtipi
|
||||
image: traefik:v2.11
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
@ -19,8 +19,8 @@ services:
|
||||
networks:
|
||||
- tipi_main_network
|
||||
|
||||
tipi-db:
|
||||
container_name: tipi-db
|
||||
runtipi-db:
|
||||
container_name: runtipi-db
|
||||
image: postgres:14
|
||||
restart: unless-stopped
|
||||
stop_grace_period: 1m
|
||||
@ -40,8 +40,8 @@ services:
|
||||
networks:
|
||||
- tipi_main_network
|
||||
|
||||
tipi-redis:
|
||||
container_name: tipi-redis
|
||||
runtipi-redis:
|
||||
container_name: runtipi-redis
|
||||
image: redis:7.2.0
|
||||
restart: unless-stopped
|
||||
command: redis-server --requirepass ${REDIS_PASSWORD} --stop-writes-on-bgsave-error no
|
||||
@ -57,48 +57,63 @@ services:
|
||||
networks:
|
||||
- tipi_main_network
|
||||
|
||||
tipi-worker:
|
||||
runtipi:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: ./packages/worker/Dockerfile
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
- SENTRY_DISABLE_AUTO_UPLOAD=true
|
||||
- TIPI_VERSION=development
|
||||
container_name: tipi-worker
|
||||
healthcheck:
|
||||
test: ['CMD', 'curl', '-f', 'http://localhost:3000/worker-api/healthcheck']
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 120
|
||||
start_period: 5s
|
||||
depends_on:
|
||||
tipi-db:
|
||||
condition: service_healthy
|
||||
tipi-redis:
|
||||
condition: service_healthy
|
||||
SENTRY_DISABLE_AUTO_UPLOAD: true
|
||||
TIPI_VERSION: development
|
||||
container_name: runtipi
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- .env
|
||||
volumes:
|
||||
# Data
|
||||
- ./state:/data/state
|
||||
- ./repos:/data/repos
|
||||
- ./apps:/data/apps
|
||||
- ./logs:/data/logs
|
||||
- ./traefik:/data/traefik
|
||||
- ./user-config:/data/user-config
|
||||
- ${STORAGE_PATH:-$PWD}/app-data:/app-data
|
||||
# Static
|
||||
- ./.env:/data/.env
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- /:/mnt/host:ro
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
TIPI_VERSION: development
|
||||
volumes:
|
||||
- /proc:/host/proc
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./.env:/app/.env
|
||||
- ./state:/app/state
|
||||
- ./repos:/app/repos
|
||||
- ./apps:/app/apps
|
||||
- ./logs:/app/logs
|
||||
- ./traefik:/app/traefik
|
||||
- ./user-config:/app/user-config
|
||||
- ${STORAGE_PATH:-.}:/storage
|
||||
NEXT_PUBLIC_TIPI_VERSION: development
|
||||
networks:
|
||||
- tipi_main_network
|
||||
ports:
|
||||
- 3000:3000
|
||||
labels:
|
||||
# ---- General ----- #
|
||||
traefik.enable: true
|
||||
traefik.http.services.worker.loadbalancer.server.port: 3001
|
||||
traefik.http.services.worker-api.loadbalancer.server.port: 3000
|
||||
traefik.http.middlewares.redirect-to-https.redirectscheme.scheme: https
|
||||
|
||||
# ---- Dashboard ----- #
|
||||
traefik.http.services.dashboard.loadbalancer.server.port: 3000
|
||||
# Local ip
|
||||
traefik.http.routers.dashboard.rule: PathPrefix("/")
|
||||
traefik.http.routers.dashboard.service: dashboard
|
||||
traefik.http.routers.dashboard.entrypoints: web
|
||||
# Local domain
|
||||
traefik.http.routers.dashboard-local-insecure.rule: Host(`${LOCAL_DOMAIN}`)
|
||||
traefik.http.routers.dashboard-local-insecure.entrypoints: web
|
||||
traefik.http.routers.dashboard-local-insecure.service: dashboard
|
||||
traefik.http.routers.dashboard-local-insecure.middlewares: redirect-to-https
|
||||
# Secure
|
||||
traefik.http.routers.dashboard-local.rule: Host(`${LOCAL_DOMAIN}`)
|
||||
traefik.http.routers.dashboard-local.entrypoints: websecure
|
||||
traefik.http.routers.dashboard-local.tls: true
|
||||
traefik.http.routers.dashboard-local.service: dashboard
|
||||
|
||||
# ---- Worker ----- #
|
||||
traefik.http.services.worker.loadbalancer.server.port: 5001
|
||||
traefik.http.services.worker-api.loadbalancer.server.port: 5000
|
||||
# Local ip
|
||||
traefik.http.routers.worker.rule: PathPrefix("/worker")
|
||||
traefik.http.routers.worker.service: worker
|
||||
@ -115,7 +130,7 @@ services:
|
||||
traefik.http.routers.worker-api-local-insecure.entrypoints: web
|
||||
traefik.http.routers.worker-api-local-insecure.service: worker-api
|
||||
traefik.http.routers.worker-api-local-insecure.middlewares: redirect-to-https
|
||||
# secure
|
||||
# Secure
|
||||
traefik.http.routers.worker-local.rule: Host(`${LOCAL_DOMAIN}`) && PathPrefix("/worker")
|
||||
traefik.http.routers.worker-local.entrypoints: websecure
|
||||
traefik.http.routers.worker-local.tls: true
|
||||
@ -124,58 +139,6 @@ services:
|
||||
traefik.http.routers.worker-api-local.entrypoints: websecure
|
||||
traefik.http.routers.worker-api-local.tls: true
|
||||
|
||||
tipi-dashboard:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
args:
|
||||
- SENTRY_DISABLE_AUTO_UPLOAD=true
|
||||
- TIPI_VERSION=0.0.0
|
||||
container_name: tipi-dashboard
|
||||
depends_on:
|
||||
tipi-db:
|
||||
condition: service_healthy
|
||||
tipi-redis:
|
||||
condition: service_healthy
|
||||
tipi-worker:
|
||||
condition: service_healthy
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
TIPI_VERSION: 0.0.0
|
||||
NEXT_PUBLIC_TIPI_VERSION: 0.0.0
|
||||
networks:
|
||||
- tipi_main_network
|
||||
ports:
|
||||
- 3000:3000
|
||||
volumes:
|
||||
- ./.env:/runtipi/.env:ro
|
||||
- ./state:/runtipi/state
|
||||
- ./repos:/runtipi/repos:ro
|
||||
- ./apps:/runtipi/apps
|
||||
- ./logs:/app/logs
|
||||
- ./traefik:/runtipi/traefik
|
||||
- ${STORAGE_PATH:-.}:/app/storage
|
||||
labels:
|
||||
traefik.enable: true
|
||||
traefik.http.services.dashboard.loadbalancer.server.port: 3000
|
||||
traefik.http.middlewares.redirect-to-https.redirectscheme.scheme: https
|
||||
# Local ip
|
||||
traefik.http.routers.dashboard.rule: PathPrefix("/")
|
||||
traefik.http.routers.dashboard.service: dashboard
|
||||
traefik.http.routers.dashboard.entrypoints: web
|
||||
# Local domain
|
||||
traefik.http.routers.dashboard-local-insecure.rule: Host(`${LOCAL_DOMAIN}`)
|
||||
traefik.http.routers.dashboard-local-insecure.entrypoints: web
|
||||
traefik.http.routers.dashboard-local-insecure.service: dashboard
|
||||
traefik.http.routers.dashboard-local-insecure.middlewares: redirect-to-https
|
||||
# secure
|
||||
traefik.http.routers.dashboard-local.rule: Host(`${LOCAL_DOMAIN}`)
|
||||
traefik.http.routers.dashboard-local.entrypoints: websecure
|
||||
traefik.http.routers.dashboard-local.tls: true
|
||||
traefik.http.routers.dashboard-local.service: dashboard
|
||||
|
||||
networks:
|
||||
tipi_main_network:
|
||||
driver: bridge
|
||||
|
@ -6,8 +6,8 @@ import { execRemoteCommand } from './write-remote-file';
|
||||
|
||||
export const setSettings = async (settings: z.infer<typeof settingsSchema>) => {
|
||||
if (process.env.REMOTE === 'true') {
|
||||
await execRemoteCommand(`mkdir -p ./runtipi/state`);
|
||||
await execRemoteCommand(`echo '${JSON.stringify(settings)}' > ./runtipi/state/settings.json`);
|
||||
await execRemoteCommand(`mkdir -p ./data/state`);
|
||||
await execRemoteCommand(`echo '${JSON.stringify(settings)}' > ./data/state/settings.json`);
|
||||
} else {
|
||||
// Create state folder if it doesn't exist
|
||||
await promises.mkdir('./state', { recursive: true });
|
||||
@ -18,7 +18,7 @@ export const setSettings = async (settings: z.infer<typeof settingsSchema>) => {
|
||||
|
||||
export const setPassowrdChangeRequest = async () => {
|
||||
if (process.env.REMOTE === 'true') {
|
||||
await execRemoteCommand('touch ./runtipi/state/password-change-request');
|
||||
await execRemoteCommand('touch ./data/state/password-change-request');
|
||||
} else {
|
||||
await promises.writeFile('./state/password-change-request', '');
|
||||
}
|
||||
@ -26,7 +26,7 @@ export const setPassowrdChangeRequest = async () => {
|
||||
|
||||
export const unsetPasswordChangeRequest = async () => {
|
||||
if (process.env.REMOTE === 'true') {
|
||||
await execRemoteCommand('rm ./runtipi/state/password-change-request');
|
||||
await execRemoteCommand('rm ./data/state/password-change-request');
|
||||
} else if (await pathExists('./state/password-change-request')) {
|
||||
await promises.unlink('./state/password-change-request');
|
||||
}
|
||||
@ -34,11 +34,11 @@ export const unsetPasswordChangeRequest = async () => {
|
||||
|
||||
export const setWelcomeSeen = async (seen: boolean) => {
|
||||
if (seen && process.env.REMOTE === 'true') {
|
||||
return execRemoteCommand('touch ./runtipi/state/seen-welcome');
|
||||
return execRemoteCommand('touch ./data/state/seen-welcome');
|
||||
}
|
||||
|
||||
if (!seen && process.env.REMOTE === 'true') {
|
||||
return execRemoteCommand('rm ./runtipi/state/seen-welcome');
|
||||
return execRemoteCommand('rm ./data/state/seen-welcome');
|
||||
}
|
||||
|
||||
if (seen && !(await pathExists('./state/seen-welcome'))) {
|
||||
|
@ -20,7 +20,7 @@
|
||||
"start:dev-container": "./.devcontainer/filewatcher.sh && npm run start:dev",
|
||||
"start:rc": "docker compose -f docker-compose.rc.yml --env-file .env up --build",
|
||||
"start:dev": "docker compose -f docker-compose.dev.yml up --build",
|
||||
"start:prod": "docker compose --env-file ./.env -f docker-compose.prod.yml up --build",
|
||||
"start:prod": "docker compose -f docker-compose.prod.yml up --build",
|
||||
"start:pg": "docker run --name test-db -p 5433:5432 -d --rm -e POSTGRES_PASSWORD=postgres postgres:14",
|
||||
"version": "echo $npm_package_version",
|
||||
"release:rc": "./scripts/deploy/release-rc.sh",
|
||||
|
@ -13,7 +13,6 @@ export const envSchema = z.object({
|
||||
redisPassword: z.string(),
|
||||
architecture: z.nativeEnum(ARCHITECTURES),
|
||||
dnsIp: z.string().ip().trim(),
|
||||
rootFolder: z.string(),
|
||||
internalIp: z.string(),
|
||||
version: z.string(),
|
||||
jwtSecret: z.string(),
|
||||
@ -21,7 +20,7 @@ export const envSchema = z.object({
|
||||
appsRepoUrl: z.string().url().trim(),
|
||||
domain: z.string().trim(),
|
||||
localDomain: z.string().trim(),
|
||||
storagePath: z
|
||||
appDataDirPath: z
|
||||
.string()
|
||||
.trim()
|
||||
.optional()
|
||||
@ -98,7 +97,7 @@ export const settingsSchema = envSchema
|
||||
postgresPort: true,
|
||||
appsRepoUrl: true,
|
||||
domain: true,
|
||||
storagePath: true,
|
||||
appDataDirPath: true,
|
||||
localDomain: true,
|
||||
demoMode: true,
|
||||
guestDashboard: true,
|
||||
|
@ -1,2 +1,3 @@
|
||||
export const ROOT_FOLDER = '/app';
|
||||
export const STORAGE_FOLDER = '/storage';
|
||||
export const APP_DIR = process.env.WORKER_APP_DIR || '/worker';
|
||||
export const DATA_DIR = '/data';
|
||||
export const APP_DATA_DIR = '/app-data';
|
||||
|
@ -15,9 +15,9 @@ import { logger } from '@/lib/logger';
|
||||
import { AppExecutors, RepoExecutors } from './services';
|
||||
import { SocketManager } from './lib/socket/SocketManager';
|
||||
import { setupRoutes } from './api';
|
||||
import { DATA_DIR } from './config';
|
||||
|
||||
const rootFolder = '/app';
|
||||
const envFile = path.join(rootFolder, '.env');
|
||||
const envFile = path.join(DATA_DIR, '.env');
|
||||
|
||||
const setupSentry = (release?: string) => {
|
||||
Sentry.init({
|
||||
@ -111,7 +111,7 @@ const main = async () => {
|
||||
appExecutor.startAllApps();
|
||||
|
||||
const app = new Hono().basePath('/worker-api');
|
||||
serve({ fetch: app.fetch, port: 3000 }, (info) => {
|
||||
serve({ fetch: app.fetch, port: 5000 }, (info) => {
|
||||
startWorker();
|
||||
|
||||
setupRoutes(app);
|
||||
|
@ -2,7 +2,7 @@ import path from 'path';
|
||||
import { execAsync, pathExists } from '@runtipi/shared/node';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { getEnv } from '@/lib/environment';
|
||||
import { ROOT_FOLDER, STORAGE_FOLDER } from '@/config/constants';
|
||||
import { APP_DATA_DIR, DATA_DIR } from '@/config/constants';
|
||||
|
||||
const composeUp = async (args: string[]) => {
|
||||
logger.info(`Running docker compose with args ${args.join(' ')}`);
|
||||
@ -22,13 +22,13 @@ const composeUp = async (args: string[]) => {
|
||||
*/
|
||||
export const compose = async (appId: string, command: string) => {
|
||||
const { arch, appsRepoId } = getEnv();
|
||||
const appDataDirPath = path.join(STORAGE_FOLDER, 'app-data', appId);
|
||||
const appDirPath = path.join(ROOT_FOLDER, 'apps', appId);
|
||||
const appDataDirPath = path.join(APP_DATA_DIR, appId);
|
||||
const appDirPath = path.join(DATA_DIR, 'apps', appId);
|
||||
|
||||
const args: string[] = [`--env-file ${path.join(appDataDirPath, 'app.env')}`];
|
||||
|
||||
// User custom env file
|
||||
const userEnvFile = path.join(ROOT_FOLDER, 'user-config', appId, 'app.env');
|
||||
const userEnvFile = path.join(DATA_DIR, 'user-config', appId, 'app.env');
|
||||
if (await pathExists(userEnvFile)) {
|
||||
args.push(`--env-file ${userEnvFile}`);
|
||||
}
|
||||
@ -41,11 +41,11 @@ export const compose = async (appId: string, command: string) => {
|
||||
}
|
||||
args.push(`-f ${composeFile}`);
|
||||
|
||||
const commonComposeFile = path.join(ROOT_FOLDER, 'repos', appsRepoId, 'apps', 'docker-compose.common.yml');
|
||||
const commonComposeFile = path.join(DATA_DIR, 'repos', appsRepoId, 'apps', 'docker-compose.common.yml');
|
||||
args.push(`-f ${commonComposeFile}`);
|
||||
|
||||
// User defined overrides
|
||||
const userComposeFile = path.join(ROOT_FOLDER, 'user-config', appId, 'docker-compose.yml');
|
||||
const userComposeFile = path.join(DATA_DIR, 'user-config', appId, 'docker-compose.yml');
|
||||
if (await pathExists(userComposeFile)) {
|
||||
args.push(`--file ${userComposeFile}`);
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ const environmentSchema = z
|
||||
})
|
||||
.transform((env) => {
|
||||
const {
|
||||
STORAGE_PATH = '/app',
|
||||
STORAGE_PATH,
|
||||
ARCHITECTURE,
|
||||
ROOT_FOLDER_HOST,
|
||||
APPS_REPO_ID,
|
||||
@ -63,3 +63,4 @@ const environmentSchema = z
|
||||
});
|
||||
|
||||
export const getEnv = () => environmentSchema.parse(process.env);
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { DATA_DIR } from '@/config/constants';
|
||||
import { FileLogger } from '@runtipi/shared/node';
|
||||
import path from 'node:path';
|
||||
|
||||
export const logger = new FileLogger('worker', path.join('/app', 'logs'), true);
|
||||
export const logger = new FileLogger('worker', path.join(DATA_DIR, 'logs'), true);
|
||||
|
@ -2,7 +2,7 @@ import path from 'path';
|
||||
import pg from 'pg';
|
||||
import { migrate } from '@runtipi/postgres-migrations';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { ROOT_FOLDER } from '@/config/constants';
|
||||
import { APP_DIR } from '@/config/constants';
|
||||
|
||||
type MigrationParams = {
|
||||
postgresHost: string;
|
||||
@ -13,7 +13,7 @@ type MigrationParams = {
|
||||
};
|
||||
|
||||
export const runPostgresMigrations = async (params: MigrationParams) => {
|
||||
const assetsFolder = path.join(ROOT_FOLDER, 'assets');
|
||||
const assetsFolder = path.join(APP_DIR, 'assets');
|
||||
|
||||
const { postgresHost, postgresDatabase, postgresUsername, postgresPassword, postgresPort } = params;
|
||||
|
||||
|
@ -6,7 +6,7 @@ class SocketManager {
|
||||
private io: Server | null = null;
|
||||
|
||||
init() {
|
||||
const io = new Server(3001, { cors: { origin: '*' }, path: '/worker/socket.io' });
|
||||
const io = new Server(5001, { cors: { origin: '*' }, path: '/worker/socket.io' });
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
socket.on('disconnect', () => {});
|
||||
|
@ -8,7 +8,7 @@ import { envMapToString, envStringToMap, settingsSchema } from '@runtipi/shared'
|
||||
import { execAsync, pathExists } from '@runtipi/shared/node';
|
||||
import { logger } from '../logger/logger';
|
||||
import { getRepoHash } from '../../services/repo/repo.helpers';
|
||||
import { ROOT_FOLDER } from '@/config/constants';
|
||||
import { APP_DATA_DIR, APP_DIR, DATA_DIR } from '@/config/constants';
|
||||
|
||||
type EnvKeys =
|
||||
| 'APPS_REPO_ID'
|
||||
@ -23,7 +23,7 @@ type EnvKeys =
|
||||
| 'NGINX_PORT'
|
||||
| 'NGINX_PORT_SSL'
|
||||
| 'DOMAIN'
|
||||
| 'STORAGE_PATH'
|
||||
| 'APP_DATA_DIR'
|
||||
| 'POSTGRES_PORT'
|
||||
| 'POSTGRES_HOST'
|
||||
| 'POSTGRES_DBNAME'
|
||||
@ -48,7 +48,7 @@ const DEFAULT_REPO_URL = 'https://github.com/runtipi/runtipi-appstore';
|
||||
* Reads and returns the generated seed
|
||||
*/
|
||||
const getSeed = async () => {
|
||||
const seedFilePath = path.join(ROOT_FOLDER, 'state', 'seed');
|
||||
const seedFilePath = path.join(DATA_DIR, 'state', 'seed');
|
||||
|
||||
if (!(await pathExists(seedFilePath))) {
|
||||
throw new Error('Seed file not found');
|
||||
@ -75,11 +75,11 @@ const deriveEntropy = async (entropy: string) => {
|
||||
* Generates a random seed if it does not exist yet
|
||||
*/
|
||||
const generateSeed = async () => {
|
||||
if (!(await pathExists(path.join(ROOT_FOLDER, 'state', 'seed')))) {
|
||||
if (!(await pathExists(path.join(DATA_DIR, 'state', 'seed')))) {
|
||||
const randomBytes = crypto.randomBytes(32);
|
||||
const seed = randomBytes.toString('hex');
|
||||
|
||||
await fs.promises.writeFile(path.join(ROOT_FOLDER, 'state', 'seed'), seed);
|
||||
await fs.promises.writeFile(path.join(DATA_DIR, 'state', 'seed'), seed);
|
||||
}
|
||||
};
|
||||
|
||||
@ -99,9 +99,9 @@ const getArchitecture = () => {
|
||||
* Generates a valid .env file from the settings.json file
|
||||
*/
|
||||
export const generateSystemEnvFile = async () => {
|
||||
await fs.promises.mkdir(path.join(ROOT_FOLDER, 'state'), { recursive: true });
|
||||
const settingsFilePath = path.join(ROOT_FOLDER, 'state', 'settings.json');
|
||||
const envFilePath = path.join(ROOT_FOLDER, '.env');
|
||||
await fs.promises.mkdir(path.join(DATA_DIR, 'state'), { recursive: true });
|
||||
const settingsFilePath = path.join(DATA_DIR, 'state', 'settings.json');
|
||||
const envFilePath = path.join(DATA_DIR, '.env');
|
||||
|
||||
if (!(await pathExists(envFilePath))) {
|
||||
await fs.promises.writeFile(envFilePath, '');
|
||||
@ -155,11 +155,11 @@ export const generateSystemEnvFile = async () => {
|
||||
envMap.set('JWT_SECRET', jwtSecret);
|
||||
envMap.set('DOMAIN', data.domain || envMap.get('DOMAIN') || 'example.com');
|
||||
envMap.set('STORAGE_PATH', data.storagePath || envMap.get('STORAGE_PATH') || rootFolderHost);
|
||||
envMap.set('POSTGRES_HOST', 'tipi-db');
|
||||
envMap.set('POSTGRES_HOST', 'runtipi-db');
|
||||
envMap.set('POSTGRES_DBNAME', 'tipi');
|
||||
envMap.set('POSTGRES_USERNAME', 'tipi');
|
||||
envMap.set('POSTGRES_PORT', String(5432));
|
||||
envMap.set('REDIS_HOST', 'tipi-redis');
|
||||
envMap.set('REDIS_HOST', 'runtipi-redis');
|
||||
envMap.set('DEMO_MODE', typeof data.demoMode === 'boolean' || typeof data.demoMode === 'string' ? String(data.demoMode) : envMap.get('DEMO_MODE') || 'false');
|
||||
envMap.set('GUEST_DASHBOARD', typeof data.guestDashboard === 'boolean' || typeof data.guestDashboard === 'string' ? String(data.guestDashboard) : envMap.get('GUEST_DASHBOARD') || 'false');
|
||||
envMap.set('LOCAL_DOMAIN', data.localDomain || envMap.get('LOCAL_DOMAIN') || 'tipi.lan');
|
||||
@ -183,56 +183,56 @@ export const generateSystemEnvFile = async () => {
|
||||
*/
|
||||
export const copySystemFiles = async (envMap: Map<EnvKeys, string>) => {
|
||||
// Remove old unused files
|
||||
if (await pathExists(path.join(ROOT_FOLDER, 'scripts'))) {
|
||||
if (await pathExists(path.join(DATA_DIR, 'scripts'))) {
|
||||
logger.info('Removing old scripts folder');
|
||||
await fs.promises.rmdir(path.join(ROOT_FOLDER, 'scripts'), { recursive: true });
|
||||
await fs.promises.rmdir(path.join(DATA_DIR, 'scripts'), { recursive: true });
|
||||
}
|
||||
|
||||
const assetsFolder = path.join(ROOT_FOLDER, 'assets');
|
||||
const assetsFolder = path.join(APP_DIR, 'assets');
|
||||
|
||||
// Copy traefik folder from assets
|
||||
logger.info('Creating traefik folders');
|
||||
await fs.promises.mkdir(path.join(ROOT_FOLDER, 'traefik', 'dynamic'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(ROOT_FOLDER, 'traefik', 'shared'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(ROOT_FOLDER, 'traefik', 'tls'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(DATA_DIR, 'traefik', 'dynamic'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(DATA_DIR, 'traefik', 'shared'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(DATA_DIR, 'traefik', 'tls'), { recursive: true });
|
||||
|
||||
if (envMap.get('PERSIST_TRAEFIK_CONFIG') === 'true') {
|
||||
logger.warn('Skipping the copy of traefik files because persistTraefikConfig is set to true');
|
||||
} else {
|
||||
logger.info('Copying traefik files');
|
||||
await fs.promises.copyFile(path.join(assetsFolder, 'traefik', 'traefik.yml'), path.join(ROOT_FOLDER, 'traefik', 'traefik.yml'));
|
||||
await fs.promises.copyFile(path.join(assetsFolder, 'traefik', 'dynamic', 'dynamic.yml'), path.join(ROOT_FOLDER, 'traefik', 'dynamic', 'dynamic.yml'));
|
||||
await fs.promises.copyFile(path.join(assetsFolder, 'traefik', 'traefik.yml'), path.join(DATA_DIR, 'traefik', 'traefik.yml'));
|
||||
await fs.promises.copyFile(path.join(assetsFolder, 'traefik', 'dynamic', 'dynamic.yml'), path.join(DATA_DIR, 'traefik', 'dynamic', 'dynamic.yml'));
|
||||
}
|
||||
|
||||
// Create base folders
|
||||
logger.info('Creating base folders');
|
||||
await fs.promises.mkdir(path.join(ROOT_FOLDER, 'apps'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(ROOT_FOLDER, 'app-data'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(ROOT_FOLDER, 'state'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(ROOT_FOLDER, 'repos'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(DATA_DIR, 'apps'), { recursive: true });
|
||||
await fs.promises.mkdir(APP_DATA_DIR, { recursive: true });
|
||||
await fs.promises.mkdir(path.join(DATA_DIR, 'state'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(DATA_DIR, 'repos'), { recursive: true });
|
||||
|
||||
// Create media folders
|
||||
logger.info('Creating media folders');
|
||||
await fs.promises.mkdir(path.join(ROOT_FOLDER, 'media', 'torrents', 'watch'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(ROOT_FOLDER, 'media', 'torrents', 'complete'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(ROOT_FOLDER, 'media', 'torrents', 'incomplete'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(DATA_DIR, 'media', 'torrents', 'watch'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(DATA_DIR, 'media', 'torrents', 'complete'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(DATA_DIR, 'media', 'torrents', 'incomplete'), { recursive: true });
|
||||
|
||||
await fs.promises.mkdir(path.join(ROOT_FOLDER, 'media', 'usenet', 'watch'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(ROOT_FOLDER, 'media', 'usenet', 'complete'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(ROOT_FOLDER, 'media', 'usenet', 'incomplete'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(DATA_DIR, 'media', 'usenet', 'watch'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(DATA_DIR, 'media', 'usenet', 'complete'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(DATA_DIR, 'media', 'usenet', 'incomplete'), { recursive: true });
|
||||
|
||||
await fs.promises.mkdir(path.join(ROOT_FOLDER, 'media', 'downloads', 'watch'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(ROOT_FOLDER, 'media', 'downloads', 'complete'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(ROOT_FOLDER, 'media', 'downloads', 'incomplete'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(DATA_DIR, 'media', 'downloads', 'watch'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(DATA_DIR, 'media', 'downloads', 'complete'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(DATA_DIR, 'media', 'downloads', 'incomplete'), { recursive: true });
|
||||
|
||||
await fs.promises.mkdir(path.join(ROOT_FOLDER, 'media', 'data', 'books'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(ROOT_FOLDER, 'media', 'data', 'comics'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(ROOT_FOLDER, 'media', 'data', 'movies'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(ROOT_FOLDER, 'media', 'data', 'music'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(ROOT_FOLDER, 'media', 'data', 'tv'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(ROOT_FOLDER, 'media', 'data', 'podcasts'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(ROOT_FOLDER, 'media', 'data', 'images'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(ROOT_FOLDER, 'media', 'data', 'roms'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(DATA_DIR, 'media', 'data', 'books'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(DATA_DIR, 'media', 'data', 'comics'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(DATA_DIR, 'media', 'data', 'movies'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(DATA_DIR, 'media', 'data', 'music'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(DATA_DIR, 'media', 'data', 'tv'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(DATA_DIR, 'media', 'data', 'podcasts'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(DATA_DIR, 'media', 'data', 'images'), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(DATA_DIR, 'media', 'data', 'roms'), { recursive: true });
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -9,9 +9,9 @@ import { copyDataDir, generateEnvFile } from './app.helpers';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { compose } from '@/lib/docker';
|
||||
import { getEnv } from '@/lib/environment';
|
||||
import { ROOT_FOLDER, STORAGE_FOLDER } from '@/config/constants';
|
||||
import { SocketManager } from '@/lib/socket/SocketManager';
|
||||
import { getDbClient } from '@/lib/db';
|
||||
import { APP_DATA_DIR, DATA_DIR } from '@/config/constants';
|
||||
|
||||
export class AppExecutors {
|
||||
private readonly logger;
|
||||
@ -38,10 +38,10 @@ export class AppExecutors {
|
||||
private getAppPaths = (appId: string) => {
|
||||
const { appsRepoId } = getEnv();
|
||||
|
||||
const appDataDirPath = path.join(STORAGE_FOLDER, 'app-data', appId);
|
||||
const appDirPath = path.join(ROOT_FOLDER, 'apps', appId);
|
||||
const appDataDirPath = path.join(APP_DATA_DIR, appId);
|
||||
const appDirPath = path.join(DATA_DIR, 'apps', appId);
|
||||
const configJsonPath = path.join(appDirPath, 'config.json');
|
||||
const repoPath = path.join(ROOT_FOLDER, 'repos', appsRepoId, 'apps', appId);
|
||||
const repoPath = path.join(DATA_DIR, 'repos', appsRepoId, 'apps', appId);
|
||||
|
||||
return { appDataDirPath, appDirPath, configJsonPath, repoPath };
|
||||
};
|
||||
@ -53,7 +53,7 @@ export class AppExecutors {
|
||||
*/
|
||||
private ensureAppDir = async (appId: string) => {
|
||||
const { appDirPath, appDataDirPath, repoPath } = this.getAppPaths(appId);
|
||||
const dockerFilePath = path.join(ROOT_FOLDER, 'apps', appId, 'docker-compose.yml');
|
||||
const dockerFilePath = path.join(DATA_DIR, 'apps', appId, 'docker-compose.yml');
|
||||
|
||||
if (!(await pathExists(dockerFilePath))) {
|
||||
// delete eventual app folder if exists
|
||||
@ -104,7 +104,7 @@ export class AppExecutors {
|
||||
const { appDirPath, repoPath, appDataDirPath } = this.getAppPaths(appId);
|
||||
|
||||
// Check if app exists in repo
|
||||
const apps = await fs.promises.readdir(path.join(ROOT_FOLDER, 'repos', appsRepoId, 'apps'));
|
||||
const apps = await fs.promises.readdir(path.join(DATA_DIR, 'repos', appsRepoId, 'apps'));
|
||||
|
||||
if (!apps.includes(appId)) {
|
||||
this.logger.error(`App ${appId} not found in repo ${appsRepoId}`);
|
||||
|
@ -5,7 +5,7 @@ import { appInfoSchema, envMapToString, envStringToMap } from '@runtipi/shared';
|
||||
import { pathExists, execAsync } from '@runtipi/shared/node';
|
||||
import { generateVapidKeys, getAppEnvMap } from './env.helpers';
|
||||
import { getEnv } from '@/lib/environment';
|
||||
import { ROOT_FOLDER, STORAGE_FOLDER } from '@/config/constants';
|
||||
import { APP_DATA_DIR, DATA_DIR } from '@/config/constants';
|
||||
|
||||
/**
|
||||
* This function generates a random string of the provided length by using the SHA-256 hash algorithm.
|
||||
@ -17,7 +17,7 @@ import { ROOT_FOLDER, STORAGE_FOLDER } from '@/config/constants';
|
||||
*/
|
||||
const getEntropy = async (name: string, length: number) => {
|
||||
const hash = crypto.createHash('sha256');
|
||||
const seed = await fs.promises.readFile(path.join(ROOT_FOLDER, 'state', 'seed'));
|
||||
const seed = await fs.promises.readFile(path.join(DATA_DIR, 'state', 'seed'));
|
||||
|
||||
hash.update(name + seed.toString());
|
||||
return hash.digest('hex').substring(0, length);
|
||||
@ -38,14 +38,14 @@ const getEntropy = async (name: string, length: number) => {
|
||||
export const generateEnvFile = async (appId: string, config: Record<string, unknown>) => {
|
||||
const { internalIp, storagePath, rootFolderHost } = getEnv();
|
||||
|
||||
const configFile = await fs.promises.readFile(path.join(ROOT_FOLDER, 'apps', appId, 'config.json'));
|
||||
const configFile = await fs.promises.readFile(path.join(DATA_DIR, 'apps', appId, 'config.json'));
|
||||
const parsedConfig = appInfoSchema.safeParse(JSON.parse(configFile.toString()));
|
||||
|
||||
if (!parsedConfig.success) {
|
||||
throw new Error(`App ${appId} has invalid config.json file`);
|
||||
}
|
||||
|
||||
const baseEnvFile = await fs.promises.readFile(path.join(ROOT_FOLDER, '.env'));
|
||||
const baseEnvFile = await fs.promises.readFile(path.join(DATA_DIR, '.env'));
|
||||
const envMap = envStringToMap(baseEnvFile.toString());
|
||||
|
||||
// Default always present env variables
|
||||
@ -101,12 +101,12 @@ export const generateEnvFile = async (appId: string, config: Record<string, unkn
|
||||
}
|
||||
|
||||
// Create app-data folder if it doesn't exist
|
||||
const appDataDirectoryExists = await fs.promises.stat(path.join(STORAGE_FOLDER, 'app-data', appId)).catch(() => false);
|
||||
const appDataDirectoryExists = await fs.promises.stat(path.join(APP_DATA_DIR, appId)).catch(() => false);
|
||||
if (!appDataDirectoryExists) {
|
||||
await fs.promises.mkdir(path.join(STORAGE_FOLDER, 'app-data', appId), { recursive: true });
|
||||
await fs.promises.mkdir(path.join(APP_DATA_DIR, appId), { recursive: true });
|
||||
}
|
||||
|
||||
await fs.promises.writeFile(path.join(STORAGE_FOLDER, 'app-data', appId, 'app.env'), envMapToString(envMap));
|
||||
await fs.promises.writeFile(path.join(APP_DATA_DIR, appId, 'app.env'), envMapToString(envMap));
|
||||
};
|
||||
|
||||
/**
|
||||
@ -136,35 +136,35 @@ export const copyDataDir = async (id: string) => {
|
||||
const envMap = await getAppEnvMap(id);
|
||||
|
||||
// return if app does not have a data directory
|
||||
if (!(await pathExists(`${ROOT_FOLDER}/apps/${id}/data`))) {
|
||||
if (!(await pathExists(`${DATA_DIR}/apps/${id}/data`))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create app-data folder if it doesn't exist
|
||||
if (!(await pathExists(`${STORAGE_FOLDER}/app-data/${id}/data`))) {
|
||||
await fs.promises.mkdir(`${STORAGE_FOLDER}/app-data/${id}/data`, { recursive: true });
|
||||
if (!(await pathExists(`${APP_DATA_DIR}/${id}/data`))) {
|
||||
await fs.promises.mkdir(`${APP_DATA_DIR}/${id}/data`, { recursive: true });
|
||||
}
|
||||
|
||||
const dataDir = await fs.promises.readdir(`${ROOT_FOLDER}/apps/${id}/data`);
|
||||
const dataDir = await fs.promises.readdir(`${DATA_DIR}/apps/${id}/data`);
|
||||
|
||||
const processFile = async (file: string) => {
|
||||
if (file.endsWith('.template')) {
|
||||
const template = await fs.promises.readFile(`${ROOT_FOLDER}/apps/${id}/data/${file}`, 'utf-8');
|
||||
const template = await fs.promises.readFile(`${DATA_DIR}/apps/${id}/data/${file}`, 'utf-8');
|
||||
const renderedTemplate = renderTemplate(template, envMap);
|
||||
|
||||
await fs.promises.writeFile(`${STORAGE_FOLDER}/app-data/${id}/data/${file.replace('.template', '')}`, renderedTemplate);
|
||||
await fs.promises.writeFile(`${APP_DATA_DIR}/${id}/data/${file.replace('.template', '')}`, renderedTemplate);
|
||||
} else {
|
||||
await fs.promises.copyFile(`${ROOT_FOLDER}/apps/${id}/data/${file}`, `${STORAGE_FOLDER}/app-data/${id}/data/${file}`);
|
||||
await fs.promises.copyFile(`${DATA_DIR}/apps/${id}/data/${file}`, `/app-data/${id}/data/${file}`);
|
||||
}
|
||||
};
|
||||
|
||||
const processDir = async (p: string) => {
|
||||
await fs.promises.mkdir(`${STORAGE_FOLDER}/app-data/${id}/data/${p}`, { recursive: true });
|
||||
const files = await fs.promises.readdir(`${ROOT_FOLDER}/apps/${id}/data/${p}`);
|
||||
await fs.promises.mkdir(`${APP_DATA_DIR}/${id}/data/${p}`, { recursive: true });
|
||||
const files = await fs.promises.readdir(`${DATA_DIR}/apps/${id}/data/${p}`);
|
||||
|
||||
await Promise.all(
|
||||
files.map(async (file) => {
|
||||
const fullPath = `${ROOT_FOLDER}/apps/${id}/data/${p}/${file}`;
|
||||
const fullPath = `${DATA_DIR}/apps/${id}/data/${p}/${file}`;
|
||||
|
||||
if ((await fs.promises.lstat(fullPath)).isDirectory()) {
|
||||
await processDir(`${p}/${file}`);
|
||||
@ -177,7 +177,7 @@ export const copyDataDir = async (id: string) => {
|
||||
|
||||
await Promise.all(
|
||||
dataDir.map(async (file) => {
|
||||
const fullPath = `${ROOT_FOLDER}/apps/${id}/data/${file}`;
|
||||
const fullPath = `${DATA_DIR}/apps/${id}/data/${file}`;
|
||||
|
||||
if ((await fs.promises.lstat(fullPath)).isDirectory()) {
|
||||
await processDir(file);
|
||||
@ -188,7 +188,7 @@ export const copyDataDir = async (id: string) => {
|
||||
);
|
||||
|
||||
// Remove any .gitkeep files from the app-data folder at any level
|
||||
if (await pathExists(`${STORAGE_FOLDER}/app-data/${id}/data`)) {
|
||||
await execAsync(`find ${STORAGE_FOLDER}/app-data/${id}/data -name .gitkeep -delete`).catch(() => {});
|
||||
if (await pathExists(`${APP_DATA_DIR}/${id}/data`)) {
|
||||
await execAsync(`find ${APP_DATA_DIR}/${id}/data -name .gitkeep -delete`).catch(() => {});
|
||||
}
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import webpush from 'web-push';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { STORAGE_FOLDER } from '@/config/constants';
|
||||
import { APP_DATA_DIR } from '@/config/constants';
|
||||
|
||||
/**
|
||||
* This function reads the env file for the app with the provided id and returns a Map containing the key-value pairs of the environment variables.
|
||||
@ -11,7 +11,7 @@ import { STORAGE_FOLDER } from '@/config/constants';
|
||||
*/
|
||||
export const getAppEnvMap = async (appId: string) => {
|
||||
try {
|
||||
const envFile = await fs.promises.readFile(path.join(STORAGE_FOLDER, 'app-data', appId, 'app.env'));
|
||||
const envFile = await fs.promises.readFile(path.join(APP_DATA_DIR, appId, 'app.env'));
|
||||
const envVars = envFile.toString().split('\n');
|
||||
const envVarsMap = new Map<string, string>();
|
||||
|
||||
|
@ -3,6 +3,7 @@ import { execAsync, pathExists } from '@runtipi/shared/node';
|
||||
import * as Sentry from '@sentry/node';
|
||||
import { getRepoHash, getRepoBaseUrlAndBranch } from './repo.helpers';
|
||||
import { logger } from '@/lib/logger';
|
||||
import { DATA_DIR } from '@/config/constants';
|
||||
|
||||
export class RepoExecutors {
|
||||
private readonly logger;
|
||||
@ -36,7 +37,7 @@ export class RepoExecutors {
|
||||
// We may have a potential branch computed in the hash (see getRepoBaseUrlAndBranch)
|
||||
// so we do it here before splitting the url into repoUrl and branch
|
||||
const repoHash = getRepoHash(url);
|
||||
const repoPath = path.join('/app', 'repos', repoHash);
|
||||
const repoPath = path.join(DATA_DIR, 'repos', repoHash);
|
||||
|
||||
if (await pathExists(repoPath)) {
|
||||
this.logger.info(`Repo ${url} already exists`);
|
||||
@ -75,7 +76,7 @@ export class RepoExecutors {
|
||||
public pullRepo = async (repoUrl: string) => {
|
||||
try {
|
||||
const repoHash = getRepoHash(repoUrl);
|
||||
const repoPath = path.join('/app', 'repos', repoHash);
|
||||
const repoPath = path.join(DATA_DIR, 'repos', repoHash);
|
||||
|
||||
if (!(await pathExists(repoPath))) {
|
||||
this.logger.info(`Repo ${repoUrl} does not exist`);
|
||||
|
@ -29,13 +29,13 @@ export class SystemExecutors {
|
||||
const memResult = { total: 0, used: 0, available: 0 };
|
||||
|
||||
try {
|
||||
const memInfo = await fs.promises.readFile('/host/proc/meminfo');
|
||||
const memInfo = await fs.promises.readFile('/mnt/host/proc/meminfo');
|
||||
|
||||
memResult.total = Number(memInfo.toString().match(/MemTotal:\s+(\d+)/)?.[1] ?? 0) * 1024;
|
||||
memResult.available = Number(memInfo.toString().match(/MemAvailable:\s+(\d+)/)?.[1] ?? 0) * 1024;
|
||||
memResult.used = memResult.total - memResult.available;
|
||||
} catch (e) {
|
||||
this.logger.error(`Unable to read /host/proc/meminfo: ${e}`);
|
||||
this.logger.error(`Unable to read /mnt/host/proc/meminfo: ${e}`);
|
||||
}
|
||||
|
||||
const [disk0] = await si.fsSize();
|
||||
|
@ -16,7 +16,7 @@ export type SettingsFormValues = {
|
||||
internalIp?: string;
|
||||
appsRepoUrl?: string;
|
||||
domain?: string;
|
||||
storagePath?: string;
|
||||
appDataDirPath?: string;
|
||||
localDomain?: string;
|
||||
guestDashboard?: boolean;
|
||||
allowAutoThemes?: boolean;
|
||||
@ -246,18 +246,18 @@ export const SettingsForm = (props: IProps) => {
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<Input
|
||||
{...register('storagePath')}
|
||||
{...register('appDataDirPath')}
|
||||
label={
|
||||
<>
|
||||
{t('SETTINGS_GENERAL_STORAGE_PATH')}
|
||||
{t('SETTINGS_GENERAL_APP_DATA_DIR')}
|
||||
<Tooltip className="tooltip" anchorSelect=".storage-path-hint">
|
||||
{t('SETTINGS_GENERAL_STORAGE_PATH_HINT')}
|
||||
{t('SETTINGS_GENERAL_APP_DATA_DIR_HINT')}
|
||||
</Tooltip>
|
||||
<span className={clsx('ms-1 form-help storage-path-hint')}>?</span>
|
||||
</>
|
||||
}
|
||||
error={errors.storagePath?.message}
|
||||
placeholder={t('SETTINGS_GENERAL_STORAGE_PATH')}
|
||||
error={errors.appDataDirPath?.message}
|
||||
placeholder={t('SETTINGS_GENERAL_APP_DATA_DIR')}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
|
@ -3,6 +3,7 @@ import { TipiConfig } from '@/server/core/TipiConfig/TipiConfig';
|
||||
import { pathExists } from '@runtipi/shared/node';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { APP_DIR, DATA_DIR } from 'src/config';
|
||||
|
||||
export async function GET(request: Request) {
|
||||
try {
|
||||
@ -13,17 +14,10 @@ export async function GET(request: Request) {
|
||||
return new Response('Not found', { status: 404 });
|
||||
}
|
||||
|
||||
const defaultFilePath = path.join(TipiConfig.getConfig().rootFolder, 'apps', id, 'metadata', 'logo.jpg');
|
||||
const appRepoFilePath = path.join(
|
||||
TipiConfig.getConfig().rootFolder,
|
||||
'repos',
|
||||
TipiConfig.getConfig().appsRepoId,
|
||||
'apps',
|
||||
id,
|
||||
'metadata',
|
||||
'logo.jpg',
|
||||
);
|
||||
let filePath = path.join('/app', 'public', 'app-not-found.jpg');
|
||||
const defaultFilePath = path.join(DATA_DIR, 'apps', id, 'metadata', 'logo.jpg');
|
||||
const appRepoFilePath = path.join(DATA_DIR, 'repos', TipiConfig.getConfig().appsRepoId, 'apps', id, 'metadata', 'logo.jpg');
|
||||
|
||||
let filePath = path.join(APP_DIR, 'public', 'app-not-found.jpg');
|
||||
|
||||
if (await pathExists(defaultFilePath)) {
|
||||
filePath = defaultFilePath;
|
||||
|
@ -1,14 +1,14 @@
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
import { getUserFromCookie } from '@/server/common/session.helpers';
|
||||
import { TipiConfig } from '@/server/core/TipiConfig/TipiConfig';
|
||||
import fs from 'fs-extra';
|
||||
import { APP_DIR } from 'src/config';
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
const user = await getUserFromCookie();
|
||||
|
||||
if (user?.operator) {
|
||||
const filePath = `${TipiConfig.getConfig().rootFolder}/traefik/tls/cert.pem`;
|
||||
const filePath = `${APP_DIR}/traefik/tls/cert.pem`;
|
||||
|
||||
if (await fs.pathExists(filePath)) {
|
||||
const file = await fs.promises.readFile(filePath);
|
||||
|
@ -7,7 +7,7 @@ export async function fetchSystemStatus() {
|
||||
try {
|
||||
const { jwtSecret } = TipiConfig.getConfig();
|
||||
const token = jwt.sign({ skill: 'issue' }, jwtSecret);
|
||||
const response = await fetch('http://tipi-worker:3000/worker-api/system-status', {
|
||||
const response = await fetch('http://localhost:5000/worker-api/system-status', {
|
||||
method: 'GET',
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
});
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "Local domain",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "Domain name used for accessing apps in your local network. Your apps will be accessible at app-name.local-domain.",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "Settings updated. Restart your instance to apply new settings.",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "Storage path",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "Storage path",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Update settings",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "This will update your settings.json file. Make sure you know what you are doing before updating these values.",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "Settings",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "Local domain",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "Domain name used for accessing apps in your local network. Your apps will be accessible at app-name.local-domain.",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "Settings updated. Restart your instance to apply new settings.",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "Storage path",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "Storage path",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Update settings",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "This will update your settings.json file. Make sure you know what you are doing before updating these values.",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "Settings",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "Domini local",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "Nom de domini utilitzat per accedir a les aplicacions a la teva xarxa local. Les aplicacions seran accessibles a nom-app.domini-local.",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "Configuració actualitzada. Reinicia la teva instància per aplicar la nova configuració.",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "Ruta de l'emmagatzematge",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "Ruta de l'emmagatzematge",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Update settings",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "Això actualitzarà el teu fitxer settings.json. Assegura't de saber el que estàs fent abans d'actualitzar aquests valors.",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "Configuració",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "Local domain",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "Domain name used for accessing apps in your local network. Your apps will be accessible at app-name.local-domain.",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "Settings updated. Restart your instance to apply new settings.",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "Storage path",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "Storage path",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Update settings",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "This will update your settings.json file. Make sure you know what you are doing before updating these values.",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "Settings",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "Local domain",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "Domain name used for accessing apps in your local network. Your apps will be accessible at app-name.local-domain.",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "Settings updated. Restart your instance to apply new settings.",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "Storage path",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "Storage path",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Update settings",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "This will update your settings.json file. Make sure you know what you are doing before updating these values.",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "Settings",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "Local domain",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "Domain name used for accessing apps in your local network. Your apps will be accessible at app-name.local-domain.",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "Einstellungen aktualisiert. Starten Sie Ihre Instanz neu, um die Einstellungen zu übernehmen.",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "Speicherpfad",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "Speicherpfad",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Einstellungen aktualisieren",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "Dadurch wird Ihre Datei settings.json aktualisiert. Stellen Sie sicher, dass Sie wissen, was Sie tun, bevor Sie diese Werte aktualisieren.",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "Einstellungen",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "Όνομα τοπικού τομέα",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "Όνομα τομέα που χρησιμοποιείται για την πρόσβαση εφαρμογών στο τοπικό σας δίκτυο. Οι εφαρμογές σας θα είναι προσβάσιμες στο app-name.local-domain.",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "Οι ρυθμίσεις ενημερώθηκαν. Επανεκκινήστε για να εφαρμόσετε νέες ρυθμίσεις.",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "Διαδρομή αποθήκευσης",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "Διαδρομή αποθήκευσης",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Ενημέρωση ρυθμίσεων",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "Αυτό θα ενημερώσει το αρχείο settings.json σας. Βεβαιωθείτε ότι γνωρίζετε τι κάνετε πριν από την ενημέρωση αυτών των τιμών.",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "Ρυθμίσεις",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "Local domain",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "Domain name used for accessing apps in your local network. Your apps will be accessible at app-name.local-domain.",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "Settings updated. Restart your instance to apply new settings.",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "Storage path",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "Storage path",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Update settings",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "This will update your settings.json file. Make sure you know what you are doing before updating these values.",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "Settings",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "Local domain",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "Domain name used for accessing apps in your local network. Your apps will be accessible at app-name.local-domain.",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "Settings updated. Restart your instance to apply new settings.",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "Storage path",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "Storage path",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Update settings",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "This will update your settings.json file. Make sure you know what you are doing before updating these values.",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "Settings",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "Nombre de dominio local",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "Nombre de dominio utilizado para acceder a las aplicaciones en tu red local. Tus aplicaciones serán accesibles en nombre-aplicación.dominio-local.",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "Ajustes actualizados. Reinicia tu instancia para aplicar los nuevos ajustes.",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "Ruta de almacenamiento",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Ruta al directorio de almacenamiento. Asegúrate de que sea una ruta absoluta y de que exista.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "Ruta de almacenamiento",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Ruta al directorio de almacenamiento. Asegúrate de que sea una ruta absoluta y de que exista.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Actualizar ajustes",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "Esto actualizará el archivo settings.json. Asegúrate de saber lo que estás haciendo antes de actualizar estos valores.",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "Configuración",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "Local domain",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "Domain name used for accessing apps in your local network. Your apps will be accessible at app-name.local-domain.",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "Settings updated. Restart your instance to apply new settings.",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "Storage path",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "Storage path",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Update settings",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "This will update your settings.json file. Make sure you know what you are doing before updating these values.",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "Settings",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "Domaine local",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "Nom de domaine utilisé pour accéder aux applications dans votre réseau local. Vos applications seront accessibles sur app-name.local-domain.",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "Paramètres mis à jour. Redémarrez votre instance pour appliquer de nouveaux paramètres.",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "Répertoire de stockage",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Chemin vers le répertoire de stockage. Assurez-vous qu'il s'agit d'un chemin absolu et qu'il existe.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "Répertoire de stockage",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Chemin vers le répertoire de stockage. Assurez-vous qu'il s'agit d'un chemin absolu et qu'il existe.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Paramètres de mise à jour",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "Cela va mettre à jour votre fichier settings.json. Assurez-vous de savoir ce que vous faites avant de mettre à jour ces valeurs.",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "Paramètres",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "Local domain",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "Domain name used for accessing apps in your local network. Your apps will be accessible at app-name.local-domain.",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "Settings updated. Restart your instance to apply new settings.",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "Storage path",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "Storage path",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Update settings",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "This will update your settings.json file. Make sure you know what you are doing before updating these values.",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "Settings",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "Helyi domain",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "A helyi hálózat alkalmazásaihoz való hozzáféréshez használt domain név. Az alkalmazások az app-name.local-domain oldalon érhetők el.",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "A beállítások frissítve. Indítsa újra a példányt az új beállítások alkalmazásához.",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "A tárhely elérési útja",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "A tárolási könyvtár elérési útvonala. Győződjön meg róla, hogy abszolút elérési útvonalról van szó, és hogy létezik.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "A tárhely elérési útja",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "A tárolási könyvtár elérési útvonala. Győződjön meg róla, hogy abszolút elérési útvonalról van szó, és hogy létezik.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Beállítások frissítése",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "Ez frissíti a settings.json fájlt. Mielőtt frissítené ezeket az értékeket, győződjön meg arról, hogy tudja, mit csinál.",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "Beállítások",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "Nome di dominio locale",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "Nome di dominio utilizzato per accedere alle app nella tua rete locale. Le tue app saranno accessibili all'indirizzo nome-app.nome-di-dominio-locale.",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "Impostazioni aggiornate. Riavvia la tua istanza per applicare le nuove impostazioni.",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "Percorso di archiviazione",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "Percorso di archiviazione",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Update settings",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "Questo aggiornerà il tuo file settings.json. Assicurati di sapere cosa stai facendo prima di aggiornare questi valori.",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "Impostazioni",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "ローカルドメイン",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "ローカルネットワーク内のアプリにアクセスするために使用されるドメイン名。あなたのアプリはapp-name.local-domainからアクセスできます。",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "設定が更新されました。新しい設定を適用するにはインスタンスを再起動してください。",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "ストレージの場所",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "ストレージの場所",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Update settings",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "これにより、settings.json ファイルが更新されます。これらの値を更新する前に何をしているかを確認してください。",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "設定",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "Local domain",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "Domain name used for accessing apps in your local network. Your apps will be accessible at app-name.local-domain.",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "Settings updated. Restart your instance to apply new settings.",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "Storage path",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "Storage path",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Update settings",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "This will update your settings.json file. Make sure you know what you are doing before updating these values.",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "Settings",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "Local domain",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "Domain name used for accessing apps in your local network. Your apps will be accessible at app-name.local-domain.",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "Settings updated. Restart your instance to apply new settings.",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "Storage path",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "Storage path",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Update settings",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "This will update your settings.json file. Make sure you know what you are doing before updating these values.",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "Settings",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "Local domain",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "Domain name used for accessing apps in your local network. Your apps will be accessible at app-name.local-domain.",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "Settings updated. Restart your instance to apply new settings.",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "Storage path",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "Storage path",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Update settings",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "This will update your settings.json file. Make sure you know what you are doing before updating these values.",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "Settings",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "Local domain",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "Domain name used for accessing apps in your local network. Your apps will be accessible at app-name.local-domain.",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "Ustawienia zaktualizowane. Uruchom ponownie instancję, aby zastosować nowe ustawienia.",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "Ścieżka przechowywania",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "Ścieżka przechowywania",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Update settings",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "Spowoduje to aktualizację pliku settings.json. Upewnij się, że wiesz, co robisz przed aktualizacją tych wartości.",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "Ustawienia",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "Local domain",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "Domain name used for accessing apps in your local network. Your apps will be accessible at app-name.local-domain.",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "Settings updated. Restart your instance to apply new settings.",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "Storage path",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "Storage path",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Update settings",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "This will update your settings.json file. Make sure you know what you are doing before updating these values.",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "Settings",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "Domínio local",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "Nome de domínio usado para acessar aplicativos na sua rede local. Seus aplicativos estarão acessíveis em nome de app.local-domain.",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "Configurações atualizadas. Reinicie sua instância para aplicar novas configurações.",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "Caminho de armazenamento",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "Caminho de armazenamento",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Atualizar as configurações",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "Isto irá atualizar seu arquivo settings.json. Certifique-se de saber o que está fazendo antes de atualizar esses valores.",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "Configurações",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "Local domain",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "Domain name used for accessing apps in your local network. Your apps will be accessible at app-name.local-domain.",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "Setări actualizate. Reporniți instanța pentru a aplica noile setări.",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "Calea de stocare",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "Calea de stocare",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Actualizează Setări",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "Acest lucru va actualiza fișierul settings.json. Asigură-te că știi ce faci înainte de a actualiza aceste valori.",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "Setări",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "Локальный домен",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "Доменное имя, используемое для доступа к приложениям в вашей локальной сети. Ваши приложения будут доступны по адресу app-name.local-domain.",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "Настройки обновлены. Перезапустите сервер Tipi для применения новых настроек.",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "Путь хранилища",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Путь к каталогу хранения. Убедитесь, что это абсолютный путь и что он существует.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "Путь хранилища",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Путь к каталогу хранения. Убедитесь, что это абсолютный путь и что он существует.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Обновить настройки",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "Это обновит файл settings.json. Убедитесь, что вы знаете, что делаете перед обновлением этих значений.",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "Настройки",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "Local domain",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "Domain name used for accessing apps in your local network. Your apps will be accessible at app-name.local-domain.",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "Settings updated. Restart your instance to apply new settings.",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "Storage path",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "Storage path",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Update settings",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "This will update your settings.json file. Make sure you know what you are doing before updating these values.",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "Settings",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "Lokal domän",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "Domännamn som används för att komma åt appar i ditt lokala nätverk. Apparna kommer att vara tillgängliga på app-name.local-domain.",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "Inställningar uppdaterade. Starta om din instans för att tillämpa nya inställningar.",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "Lagring sökväg",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Sökväg till lagringskatalogen. Se till att den är en absolut sökväg och att den finns.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "Lagring sökväg",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Sökväg till lagringskatalogen. Se till att den är en absolut sökväg och att den finns.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Uppdatera inställningar",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "Detta kommer att uppdatera din settings.json fil. Se till att du vet vad du gör innan du uppdaterar dessa värden.",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "Inställningar",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "Yerel alan adı",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "Yerel ağınızdaki uygulamalara erişmek için kullanılan alan adı. Uygulamalarınıza app-name.local-domain adresinden erişilebilir.",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "Ayarlar güncellendi. Yeni ayarları uygulamak için sunucunuzu yeniden başlatın.",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "Depolama yolu",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "Depolama yolu",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Ayarları güncelle",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "Bu, settings.json dosyanızı güncelleyecektir. Bu değerleri güncellemeden önce ne yaptığınızı bildiğinizden emin olun.",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "Ayarlar",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "Local domain",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "Domain name used for accessing apps in your local network. Your apps will be accessible at app-name.local-domain.",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "Settings updated. Restart your instance to apply new settings.",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "Storage path",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "Storage path",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Update settings",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "This will update your settings.json file. Make sure you know what you are doing before updating these values.",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "Settings",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "Tên miền nội bộ",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "Tên miền để truy cập ứng dụng từ mạng nội bộ của bạn. Ứng dụng có thể được truy cập tại app-name.local-domain.",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "Cài đặt đã được cập nhật. Khởi động lại máy chủ của bạn để áp dụng cài đặt mới.",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "Đường dẫn lưu trữ dữ liệu",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "Đường dẫn lưu trữ dữ liệu",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Cập nhật cài đặt",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "Điều này sẽ thay đổi file setting.json của bạn. Hãy chắc chắn rằng bạn hiểu rõ mình đang làm gì trước khi thay đổi các giá trị này.",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "Cài đặt",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "本地域名",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "访问本地网络应用所使用的域名。您的应用将可以在app-name.local-domain访问。",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "设置已更新。重新启动您的实例以应用新设置。",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "保存路径",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "保存路径",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Update settings",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "这将更新您的 settings.json 文件。在更新这些值之前,请确保您知道您正在做什么。",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "设置",
|
||||
|
@ -237,8 +237,8 @@
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN": "本地域名",
|
||||
"SETTINGS_GENERAL_LOCAL_DOMAIN_HINT": "訪問本地網絡應用所使用的域名。您的應用將可以在app-name.local-domain訪問。",
|
||||
"SETTINGS_GENERAL_SETTINGS_UPDATED": "設置更新。 重新啟動您的實例以應用新設置。",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH": "儲存路徑",
|
||||
"SETTINGS_GENERAL_STORAGE_PATH_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR": "儲存路徑",
|
||||
"SETTINGS_GENERAL_APP_DATA_DIR_HINT": "Path to the storage directory. Make sure it is an absolute path and that it exists.",
|
||||
"SETTINGS_GENERAL_SUBMIT": "Update settings",
|
||||
"SETTINGS_GENERAL_SUBTITLE": "這將更新您的 settings.json 文件。 在更新這些值之前,請確保您知道自己在做什麼。",
|
||||
"SETTINGS_GENERAL_TAB_TITLE": "設定",
|
||||
|
3
src/config/constants.ts
Normal file
3
src/config/constants.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export const APP_DIR = process.env.DASHBOARD_APP_DIR || '/dashboard';
|
||||
export const DATA_DIR = '/data';
|
||||
export const APP_DATA_DIR = '/app-data';
|
1
src/config/index.ts
Normal file
1
src/config/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './constants';
|
@ -1,5 +1,6 @@
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { DATA_DIR } from 'src/config';
|
||||
import { createLogger, format, transports } from 'winston';
|
||||
|
||||
const { align, printf, timestamp, combine, colorize } = format;
|
||||
@ -22,7 +23,7 @@ const combinedLogFormatDev = combine(
|
||||
);
|
||||
|
||||
const productionLogger = () => {
|
||||
const logsFolder = '/app/logs';
|
||||
const logsFolder = `${DATA_DIR}/logs`;
|
||||
try {
|
||||
if (!fs.existsSync(logsFolder)) {
|
||||
fs.mkdirSync(logsFolder);
|
||||
|
@ -24,7 +24,7 @@ describe('Test: getConfig', () => {
|
||||
domain: faker.lorem.word(),
|
||||
};
|
||||
const MockFiles = {
|
||||
'/runtipi/state/settings.json': JSON.stringify(settingsJson),
|
||||
'/data/state/settings.json': JSON.stringify(settingsJson),
|
||||
};
|
||||
// @ts-expect-error - We are mocking fs
|
||||
fs.__createMockFiles(MockFiles);
|
||||
@ -78,7 +78,7 @@ describe('Test: setConfig', () => {
|
||||
expect(config).toBeDefined();
|
||||
expect(config.appsRepoUrl).toBe(randomWord);
|
||||
|
||||
const settingsJson = readJsonFile('/runtipi/state/settings.json') as { [key: string]: string };
|
||||
const settingsJson = readJsonFile('/data/state/settings.json') as { [key: string]: string };
|
||||
|
||||
expect(settingsJson).toBeDefined();
|
||||
expect(settingsJson.appsRepoUrl).toBe(randomWord);
|
||||
@ -91,7 +91,7 @@ describe('Test: getSettings', () => {
|
||||
const fakeSettings = {
|
||||
appsRepoUrl: faker.internet.url(),
|
||||
};
|
||||
const MockFiles = { '/runtipi/state/settings.json': JSON.stringify(fakeSettings) };
|
||||
const MockFiles = { '/data/state/settings.json': JSON.stringify(fakeSettings) };
|
||||
// @ts-expect-error - We are mocking fs
|
||||
fs.__createMockFiles(MockFiles);
|
||||
|
||||
@ -106,7 +106,7 @@ describe('Test: getSettings', () => {
|
||||
it('It should return current config if settings.json has any invalid value', () => {
|
||||
// arrange
|
||||
const tipiConf = new TipiConfigClass(0);
|
||||
const MockFiles = { '/runtipi/state/settings.json': JSON.stringify({ appsRepoUrl: 10 }) };
|
||||
const MockFiles = { '/data/state/settings.json': JSON.stringify({ appsRepoUrl: 10 }) };
|
||||
// @ts-expect-error - We are mocking fs
|
||||
fs.__createMockFiles(MockFiles);
|
||||
|
||||
@ -129,7 +129,7 @@ describe('Test: setSettings', () => {
|
||||
|
||||
// act
|
||||
await new TipiConfigClass(0).setSettings(fakeSettings);
|
||||
const settingsJson = readJsonFile('/runtipi/state/settings.json') as { [key: string]: string };
|
||||
const settingsJson = readJsonFile('/data/state/settings.json') as { [key: string]: string };
|
||||
|
||||
// assert
|
||||
expect(settingsJson).toBeDefined();
|
||||
@ -142,7 +142,7 @@ describe('Test: setSettings', () => {
|
||||
|
||||
// act
|
||||
new TipiConfigClass(0).setSettings(fakeSettings as object);
|
||||
const settingsJson = (readJsonFile('/runtipi/state/settings.json') || {}) as { [key: string]: string };
|
||||
const settingsJson = (readJsonFile('/data/state/settings.json') || {}) as { [key: string]: string };
|
||||
|
||||
// assert
|
||||
expect(settingsJson).toBeDefined();
|
||||
|
@ -3,6 +3,7 @@ import { envSchema, envStringToMap, settingsSchema } from '@runtipi/shared';
|
||||
import fs from 'fs-extra';
|
||||
import nextConfig from 'next/config';
|
||||
import * as Sentry from '@sentry/nextjs';
|
||||
import { DATA_DIR } from 'src/config';
|
||||
import { readJsonFile } from '../../common/fs.helpers';
|
||||
import { Logger } from '../Logger';
|
||||
|
||||
@ -34,7 +35,7 @@ export class TipiConfigClass {
|
||||
private genConfig() {
|
||||
let envFile = '';
|
||||
try {
|
||||
envFile = fs.readFileSync('/runtipi/.env').toString();
|
||||
envFile = fs.readFileSync(`${DATA_DIR}/.env`).toString();
|
||||
} catch (e) {
|
||||
Sentry.captureException(e);
|
||||
Logger.error('❌ .env file not found');
|
||||
@ -53,7 +54,6 @@ export class TipiConfigClass {
|
||||
redisPassword: conf.REDIS_PASSWORD,
|
||||
NODE_ENV: process.env.NODE_ENV || 'production',
|
||||
architecture: conf.ARCHITECTURE || 'amd64',
|
||||
rootFolder: '/runtipi',
|
||||
internalIp: conf.INTERNAL_IP,
|
||||
version: conf.TIPI_VERSION,
|
||||
jwtSecret: conf.JWT_SECRET,
|
||||
@ -62,7 +62,7 @@ export class TipiConfigClass {
|
||||
domain: conf.DOMAIN,
|
||||
localDomain: conf.LOCAL_DOMAIN,
|
||||
dnsIp: conf.DNS_IP || '9.9.9.9',
|
||||
storagePath: conf.STORAGE_PATH,
|
||||
appDataDirPath: conf.APP_DATA_DIR,
|
||||
demoMode: conf.DEMO_MODE,
|
||||
guestDashboard: conf.GUEST_DASHBOARD,
|
||||
allowErrorMonitoring: conf.ALLOW_ERROR_MONITORING,
|
||||
@ -89,7 +89,7 @@ export class TipiConfigClass {
|
||||
if (this.fileConfigCache && now - this.cacheTime < this.cacheTimeout) {
|
||||
fileConfig = this.fileConfigCache;
|
||||
} else {
|
||||
const rawFileConfig = readJsonFile('/runtipi/state/settings.json') || {};
|
||||
const rawFileConfig = readJsonFile(`${DATA_DIR}/state/settings.json`) || {};
|
||||
const parsedFileConfig = settingsSchema.safeParse(rawFileConfig);
|
||||
|
||||
if (parsedFileConfig.success) {
|
||||
@ -137,13 +137,13 @@ export class TipiConfigClass {
|
||||
this.config = envSchema.parse(newConf);
|
||||
|
||||
if (writeFile) {
|
||||
const currentJsonConf = readJsonFile('/runtipi/state/settings.json') || {};
|
||||
const currentJsonConf = readJsonFile(`${DATA_DIR}/state/settings.json`) || {};
|
||||
const parsedConf = envSchema.partial().parse(currentJsonConf);
|
||||
|
||||
parsedConf[key] = value;
|
||||
const parsed = envSchema.partial().parse(parsedConf);
|
||||
|
||||
await fs.promises.writeFile('/runtipi/state/settings.json', JSON.stringify(parsed));
|
||||
await fs.promises.writeFile(`${DATA_DIR}/state/settings.json`, JSON.stringify(parsed));
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,7 +159,7 @@ export class TipiConfigClass {
|
||||
return;
|
||||
}
|
||||
|
||||
await fs.promises.writeFile('/runtipi/state/settings.json', JSON.stringify(parsed.data));
|
||||
await fs.promises.writeFile(`${DATA_DIR}/state/settings.json`, JSON.stringify(parsed.data));
|
||||
|
||||
// Reset cache
|
||||
this.cacheTime = 0;
|
||||
|
@ -5,6 +5,7 @@ import { fileExists, readdirSync, readFile, readJsonFile } from '../../common/fs
|
||||
import { TipiConfig } from '../../core/TipiConfig';
|
||||
import { Logger } from '../../core/Logger';
|
||||
import { notEmpty } from '../../common/typescript.helpers';
|
||||
import { DATA_DIR } from 'src/config';
|
||||
|
||||
/**
|
||||
* This function checks the requirements for the app with the provided name.
|
||||
@ -17,7 +18,7 @@ import { notEmpty } from '../../common/typescript.helpers';
|
||||
* @throws Will throw an error if the app has an invalid config.json file or if the current system architecture is not supported by the app.
|
||||
*/
|
||||
export const checkAppRequirements = (appName: string) => {
|
||||
const configFile = readJsonFile(`/runtipi/repos/${TipiConfig.getConfig().appsRepoId}/apps/${appName}/config.json`);
|
||||
const configFile = readJsonFile(`${DATA_DIR}/repos/${TipiConfig.getConfig().appsRepoId}/apps/${appName}/config.json`);
|
||||
const parsedConfig = appInfoSchema.safeParse(configFile);
|
||||
|
||||
if (!parsedConfig.success) {
|
||||
@ -37,12 +38,12 @@ export const checkAppRequirements = (appName: string) => {
|
||||
If the config.json file is invalid, it logs an error message.
|
||||
*/
|
||||
export const getAvailableApps = async () => {
|
||||
if (!(await pathExists(`/runtipi/repos/${TipiConfig.getConfig().appsRepoId}/apps`))) {
|
||||
if (!(await pathExists(`${DATA_DIR}/repos/${TipiConfig.getConfig().appsRepoId}/apps`))) {
|
||||
Logger.error(`Apps repo ${TipiConfig.getConfig().appsRepoId} not found. Make sure your repo is configured correctly.`);
|
||||
return [];
|
||||
}
|
||||
|
||||
const appsDir = readdirSync(`/runtipi/repos/${TipiConfig.getConfig().appsRepoId}/apps`);
|
||||
const appsDir = readdirSync(`${DATA_DIR}/repos/${TipiConfig.getConfig().appsRepoId}/apps`);
|
||||
|
||||
const skippedFiles = ['__tests__', 'docker-compose.common.yml', 'schema.json', '.DS_Store'];
|
||||
|
||||
@ -50,14 +51,14 @@ export const getAvailableApps = async () => {
|
||||
.map((app) => {
|
||||
if (skippedFiles.includes(app)) return null;
|
||||
|
||||
const configFile = readJsonFile(`/runtipi/repos/${TipiConfig.getConfig().appsRepoId}/apps/${app}/config.json`);
|
||||
const configFile = readJsonFile(`${DATA_DIR}/repos/${TipiConfig.getConfig().appsRepoId}/apps/${app}/config.json`);
|
||||
const parsedConfig = appInfoSchema.safeParse(configFile);
|
||||
|
||||
if (!parsedConfig.success) {
|
||||
Logger.error(`App ${JSON.stringify(app)} has invalid config.json`);
|
||||
Logger.error(JSON.stringify(parsedConfig.error, null, 2));
|
||||
} else if (parsedConfig.data.available) {
|
||||
const description = readFile(`/runtipi/repos/${TipiConfig.getConfig().appsRepoId}/apps/${parsedConfig.data.id}/metadata/description.md`);
|
||||
const description = readFile(`${DATA_DIR}/repos/${TipiConfig.getConfig().appsRepoId}/apps/${parsedConfig.data.id}/metadata/description.md`);
|
||||
return { ...parsedConfig.data, description };
|
||||
}
|
||||
|
||||
@ -77,7 +78,7 @@ export const getAvailableApps = async () => {
|
||||
* @param {string} id - The app id.
|
||||
*/
|
||||
export const getUpdateInfo = (id: string) => {
|
||||
const repoConfig = readJsonFile(`/runtipi/repos/${TipiConfig.getConfig().appsRepoId}/apps/${id}/config.json`);
|
||||
const repoConfig = readJsonFile(`${DATA_DIR}/repos/${TipiConfig.getConfig().appsRepoId}/apps/${id}/config.json`);
|
||||
const parsedConfig = appInfoSchema.safeParse(repoConfig);
|
||||
|
||||
if (parsedConfig.success) {
|
||||
@ -105,22 +106,22 @@ export const getAppInfo = (id: string, status?: App['status']) => {
|
||||
// Check if app is installed
|
||||
const installed = typeof status !== 'undefined' && status !== 'missing';
|
||||
|
||||
if (installed && fileExists(`/runtipi/apps/${id}/config.json`)) {
|
||||
const configFile = readJsonFile(`/runtipi/apps/${id}/config.json`);
|
||||
if (installed && fileExists(`${DATA_DIR}/apps/${id}/config.json`)) {
|
||||
const configFile = readJsonFile(`${DATA_DIR}/apps/${id}/config.json`);
|
||||
const parsedConfig = appInfoSchema.safeParse(configFile);
|
||||
|
||||
if (parsedConfig.success && parsedConfig.data.available) {
|
||||
const description = readFile(`/runtipi/apps/${id}/metadata/description.md`);
|
||||
const description = readFile(`${DATA_DIR}/apps/${id}/metadata/description.md`);
|
||||
return { ...parsedConfig.data, description };
|
||||
}
|
||||
}
|
||||
|
||||
if (fileExists(`/runtipi/repos/${TipiConfig.getConfig().appsRepoId}/apps/${id}/config.json`)) {
|
||||
const configFile = readJsonFile(`/runtipi/repos/${TipiConfig.getConfig().appsRepoId}/apps/${id}/config.json`);
|
||||
if (fileExists(`/data/repos/${TipiConfig.getConfig().appsRepoId}/apps/${id}/config.json`)) {
|
||||
const configFile = readJsonFile(`${DATA_DIR}/repos/${TipiConfig.getConfig().appsRepoId}/apps/${id}/config.json`);
|
||||
const parsedConfig = appInfoSchema.safeParse(configFile);
|
||||
|
||||
if (parsedConfig.success && parsedConfig.data.available) {
|
||||
const description = readFile(`/runtipi/repos/${TipiConfig.getConfig().appsRepoId}/apps/${id}/metadata/description.md`);
|
||||
const description = readFile(`${DATA_DIR}/repos/${TipiConfig.getConfig().appsRepoId}/apps/${id}/metadata/description.md`);
|
||||
return { ...parsedConfig.data, description };
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import { tipiCache } from '@/server/core/TipiCache/TipiCache';
|
||||
import { TipiConfig } from '../../core/TipiConfig';
|
||||
import { fileExists, unlinkFile } from '../../common/fs.helpers';
|
||||
import { decrypt, encrypt } from '../../utils/encryption';
|
||||
import { DATA_DIR } from 'src/config';
|
||||
|
||||
type UsernamePasswordInput = {
|
||||
username: string;
|
||||
@ -294,7 +295,7 @@ export class AuthServiceClass {
|
||||
|
||||
await this.queries.updateUser(user.id, { password: hash, totpEnabled: false, totpSecret: null });
|
||||
|
||||
await unlinkFile(`/runtipi/state/password-change-request`);
|
||||
await unlinkFile(`${DATA_DIR}/state/password-change-request`);
|
||||
|
||||
return { email: user.username };
|
||||
};
|
||||
@ -306,7 +307,7 @@ export class AuthServiceClass {
|
||||
* @returns {boolean} - A boolean indicating if there is a password change request or not
|
||||
*/
|
||||
public static checkPasswordChangeRequest = () => {
|
||||
if (fileExists(`/runtipi/state/password-change-request`)) {
|
||||
if (fileExists(`${DATA_DIR}/state/password-change-request`)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -321,8 +322,8 @@ export class AuthServiceClass {
|
||||
* @throws {Error} - If the file cannot be removed
|
||||
*/
|
||||
public static cancelPasswordChangeRequest = async () => {
|
||||
if (fileExists(`/runtipi/state/password-change-request`)) {
|
||||
await unlinkFile(`/runtipi/state/password-change-request`);
|
||||
if (fileExists(`${DATA_DIR}/state/password-change-request`)) {
|
||||
await unlinkFile(`${DATA_DIR}/state/password-change-request`);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -4,6 +4,7 @@ import { tipiCache } from '@/server/core/TipiCache';
|
||||
import { fileExists } from '../../common/fs.helpers';
|
||||
import { Logger } from '../../core/Logger';
|
||||
import { TipiConfig } from '../../core/TipiConfig';
|
||||
import { DATA_DIR } from 'src/config';
|
||||
|
||||
export class SystemServiceClass {
|
||||
/**
|
||||
@ -42,12 +43,12 @@ export class SystemServiceClass {
|
||||
};
|
||||
|
||||
public static hasSeenWelcome = async () => {
|
||||
return fileExists(`/runtipi/state/seen-welcome`);
|
||||
return fileExists(`${DATA_DIR}/state/seen-welcome`);
|
||||
};
|
||||
|
||||
public static markSeenWelcome = async () => {
|
||||
// Create file state/seen-welcome
|
||||
await promises.writeFile(`/runtipi/state/seen-welcome`, '');
|
||||
await promises.writeFile(`${DATA_DIR}/state/seen-welcome`, '');
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import { eq } from 'drizzle-orm';
|
||||
import { AppInfo, Architecture, appInfoSchema, APP_CATEGORIES } from '@runtipi/shared';
|
||||
import { TestDatabase } from './test-utils';
|
||||
import { appTable, AppStatus, App, NewApp } from '../db/schema';
|
||||
import { APP_DATA_DIR, DATA_DIR } from 'src/config';
|
||||
|
||||
interface IProps {
|
||||
installed?: boolean;
|
||||
@ -34,10 +35,10 @@ const createAppConfig = (props?: Partial<AppInfo>) => {
|
||||
});
|
||||
|
||||
const mockFiles: Record<string, string | string[]> = {};
|
||||
mockFiles['/runtipi/.env'] = 'TEST=test';
|
||||
mockFiles[`/runtipi/repos/repo-id/apps/${appInfo.id}/config.json`] = JSON.stringify(appInfoSchema.parse(appInfo));
|
||||
mockFiles[`/runtipi/repos/repo-id/apps/${appInfo.id}/docker-compose.yml`] = 'compose';
|
||||
mockFiles[`/runtipi/repos/repo-id/apps/${appInfo.id}/metadata/description.md`] = 'md desc';
|
||||
mockFiles['/data/.env'] = 'TEST=test';
|
||||
mockFiles[`/data/repos/repo-id/apps/${appInfo.id}/config.json`] = JSON.stringify(appInfoSchema.parse(appInfo));
|
||||
mockFiles[`/data/repos/repo-id/apps/${appInfo.id}/docker-compose.yml`] = 'compose';
|
||||
mockFiles[`/data/repos/repo-id/apps/${appInfo.id}/metadata/description.md`] = 'md desc';
|
||||
|
||||
// @ts-expect-error - custom mock method
|
||||
fs.__applyMockFiles(mockFiles);
|
||||
@ -101,10 +102,10 @@ const createApp = async (props: IProps, database: TestDatabase) => {
|
||||
}
|
||||
|
||||
const mockFiles: Record<string, string | string[]> = {};
|
||||
mockFiles['/runtipi/.env'] = 'TEST=test';
|
||||
mockFiles[`/runtipi/repos/repo-id/apps/${appInfo.id}/config.json`] = JSON.stringify(appInfoSchema.parse(appInfo));
|
||||
mockFiles[`/runtipi/repos/repo-id/apps/${appInfo.id}/docker-compose.yml`] = 'compose';
|
||||
mockFiles[`/runtipi/repos/repo-id/apps/${appInfo.id}/metadata/description.md`] = 'md desc';
|
||||
mockFiles[`${DATA_DIR}/.env`] = 'TEST=test';
|
||||
mockFiles[`${DATA_DIR}/repos/repo-id/apps/${appInfo.id}/config.json`] = JSON.stringify(appInfoSchema.parse(appInfo));
|
||||
mockFiles[`${DATA_DIR}/repos/repo-id/apps/${appInfo.id}/docker-compose.yml`] = 'compose';
|
||||
mockFiles[`${DATA_DIR}/repos/repo-id/apps/${appInfo.id}/metadata/description.md`] = 'md desc';
|
||||
|
||||
let appEntity: App = {} as App;
|
||||
if (installed) {
|
||||
@ -122,9 +123,9 @@ const createApp = async (props: IProps, database: TestDatabase) => {
|
||||
|
||||
// eslint-disable-next-line prefer-destructuring
|
||||
appEntity = insertedApp[0] as App;
|
||||
mockFiles[`/app/storage/app-data/${appInfo.id}/app.env`] = 'TEST=test\nAPP_PORT=3000\nTEST_FIELD=test';
|
||||
mockFiles[`/runtipi/apps/${appInfo.id}/config.json`] = JSON.stringify(appInfo);
|
||||
mockFiles[`/runtipi/apps/${appInfo.id}/metadata/description.md`] = 'md desc';
|
||||
mockFiles[`${APP_DATA_DIR}/${appInfo.id}/app.env`] = 'TEST=test\nAPP_PORT=3000\nTEST_FIELD=test';
|
||||
mockFiles[`${DATA_DIR}/apps/${appInfo.id}/config.json`] = JSON.stringify(appInfo);
|
||||
mockFiles[`${DATA_DIR}/apps/${appInfo.id}/metadata/description.md`] = 'md desc';
|
||||
}
|
||||
|
||||
// @ts-expect-error - custom mock method
|
||||
@ -146,12 +147,12 @@ const insertApp = async (data: Partial<NewApp>, appInfo: AppInfo, database: Test
|
||||
|
||||
const mockFiles: Record<string, string | string[]> = {};
|
||||
if (data.status !== 'missing') {
|
||||
mockFiles[`/app/storage/app-data/${values.id}/app.env`] = `TEST=test\nAPP_PORT=3000\n${Object.entries(data.config || {})
|
||||
mockFiles[`app-data/${values.id}/app.env`] = `TEST=test\nAPP_PORT=3000\n${Object.entries(data.config || {})
|
||||
.map(([key, value]) => `${key}=${value}`)
|
||||
.join('\n')}`;
|
||||
mockFiles[`/runtipi/apps/${values.id}/config.json`] = JSON.stringify(appInfo);
|
||||
mockFiles[`/runtipi/apps/${values.id}/metadata/description.md`] = 'md desc';
|
||||
mockFiles[`/runtipi/apps/${values.id}/docker-compose.yml`] = 'compose';
|
||||
mockFiles[`${DATA_DIR}/apps/${values.id}/config.json`] = JSON.stringify(appInfo);
|
||||
mockFiles[`${DATA_DIR}/apps/${values.id}/metadata/description.md`] = 'md desc';
|
||||
mockFiles[`${DATA_DIR}/apps/${values.id}/docker-compose.yml`] = 'compose';
|
||||
}
|
||||
|
||||
// @ts-expect-error - custom mock method
|
||||
|
17
start.dev.sh
Normal file
17
start.dev.sh
Normal file
@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Start worker
|
||||
pm2 start pnpm --name worker -- --filter @runtipi/worker -r dev
|
||||
|
||||
# Wait for http://localhost:5000/healthcheck to return OK
|
||||
while true; do
|
||||
if [[ "$(curl -s http://localhost:5000/worker-api/healthcheck)" == "OK" ]]; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
pm2 start npm --name dashboard -- run dev
|
||||
|
||||
# Log apps realtime
|
||||
pm2 logs --raw
|
20
start.prod.sh
Normal file
20
start.prod.sh
Normal file
@ -0,0 +1,20 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Start worker
|
||||
cd worker/
|
||||
pm2 start index.js --ignore-watch="/worker" --name worker -- start
|
||||
|
||||
# Wait for http://localhost:5000/healthcheck to return OK with a maximum of 5 retries
|
||||
while true; do
|
||||
if [[ "$(curl -s http://localhost:5000/worker-api/healthcheck)" == "OK" ]]; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Start apps
|
||||
cd /dashboard
|
||||
pm2 start npm --ignore-watch="/dashboard" --name dashboard -- run start
|
||||
|
||||
# Log apps realtime
|
||||
pm2 logs
|
Loading…
Reference in New Issue
Block a user