Prechádzať zdrojové kódy

Move system-api in a docker comntainer

Nicolas Meienberger 3 rokov pred
rodič
commit
4e03ca01f1

+ 21 - 2
.github/workflows/build-images.yml

@@ -25,10 +25,29 @@ jobs:
           username: ${{ secrets.DOCKERHUB_USERNAME }}
           password: ${{ secrets.DOCKERHUB_TOKEN }}
       -
-        name: Build and push
+        name: Get tag from VERSION file
+        id: meta
+        run: |
+          VERSION=$(cat VERSION)
+          TAG=${VERSION}
+          echo "::set-output name=tag::${TAG}"
+      -
+        name: Build and push dashboard
         uses: docker/build-push-action@v2
         with:
           context: ./packages/dashboard
           platforms: linux/amd64,linux/arm64
           push: true
-          tags: meienberger/tipi-dashboard:latest
+          tags: meienberger/tipi-dashboard:latest,meienberger/tipi-dashboard:${{ steps.meta.outputs.TAG }}
+          cache-from: type=registry,ref=meienberger/tipi-dashboard:latest
+          cache-to: type=inline
+      -
+        name: Build and push api
+        uses: docker/build-push-action@v2
+        with:
+          context: ./packages/system-api
+          platforms: linux/amd64,linux/arm64
+          push: true
+          tags: meienberger/tipi-api:latest,meienberger/tipi-api:${{ steps.meta.outputs.TAG }}
+          cache-from: type=registry,ref=meienberger/tipi-api:latest
+          cache-to: type=inline

+ 33 - 0
.github/workflows/verify-release.yml

@@ -0,0 +1,33 @@
+name: Verify release
+
+on:
+  pull_request:
+    branches:
+      - master
+
+jobs:
+  verify:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v3
+        with:
+          fetch-depth: 2
+
+      - uses: technote-space/get-diff-action@v6
+        with:
+          FILES: |
+            VERSION
+
+      - name: Ensure env.MATCHED_FILES has VERSION in it
+        id: check-version
+        run: |
+          if [[ -z "${{ env.MATCHED_FILES }}" ]]; then
+            echo "::error::VERSION not modified"
+            exit 1
+          fi
+          if [[ ! "${{ env.MATCHED_FILES }}" =~ VERSION ]]; then
+            echo "::error::VERSION not modified"
+            exit 1
+          fi
+          

+ 1 - 0
VERSION

@@ -0,0 +1 @@
+0.1.2

+ 0 - 2
ansible/host_vars/tipi.yml

@@ -1,6 +1,4 @@
 packages:
   - jq
-  - ufw
   - coreutils
-  - git
   - docker

+ 0 - 6
ansible/start.yml

@@ -1,6 +0,0 @@
----
-- hosts: tipi
-  become: yes
-
-  tasks:
-    - import_tasks: ./tasks/common/packages.yml

+ 6 - 40
ansible/tasks/common/essential.yml

@@ -3,12 +3,6 @@
     update_cache: yes
     upgrade: yes
 
-- name: Install node 16
-  shell: curl -fsSL https://deb.nodesource.com/setup_16.x | bash -
-
-- name: Install node
-  shell: apt-get install -y nodejs
-
 - name: Install essential packages
   package:
     name: "{{ packages }}"
@@ -26,37 +20,9 @@
     line: "{{ username }} ALL=(ALL) NOPASSWD: ALL"
     validate: "/usr/sbin/visudo -cf %s"
 
-- name: Restart SSH daemon
-  service:
-    name: sshd
-    state: restarted
-
-- name: Allow SSH in UFW
-  community.general.ufw:
-    rule: allow
-    port: 22
-    proto: tcp
-
-- name: Allow port 80 in UFW
-  community.general.ufw:
-    rule: allow
-    port: 80
-    proto: tcp
-
-- name: Allow port 443 in UFW
-  community.general.ufw:
-    rule: allow
-    port: 443
-    proto: tcp
-
-- name: Allow ports for apps
-  community.general.ufw:
-    rule: allow
-    port: 3000:3001
-    proto: tcp
-
-- name: Enable ufw daemon
-  service:
-    name: ufw
-    state: started
-    enabled: yes
+- name: Create cron every minute running system-info.sh
+  cron:
+    name: "system-info"
+    user: "{{ username }}"
+    minute: "*/1"
+    command: "{{ playbook_dir }}/../scripts/system-info.sh"

+ 0 - 40
ansible/tasks/common/packages.yml

@@ -1,40 +0,0 @@
-- name: Install "pm2" package globally.
-  community.general.npm:
-    name: pm2
-    global: yes
-
-- name: Install "pnpm" package globally.
-  community.general.npm:
-    name: pnpm
-    global: yes
-
-- name: Run pm2 first time
-  shell: pm2 list
-
-- name: Enable pm2 as a service
-  shell: sudo env PATH=$PATH:/usr/local/bin pm2 startup -u {{ username }}
-
-- name: Install dependencies
-  shell: cd {{ playbook_dir }} && pnpm install
-
-- name: Clean packages
-  shell: cd {{ playbook_dir }} && pnpm -r clean
-
-- name: Build packages
-  become_user: "{{ username }}"
-  shell: cd {{ playbook_dir }} && pnpm -r build-prod
-
-- name: Check if app is already running
-  become_user: "{{ username }}"
-  shell: pm2 status system-api
-  register: pm2_result
-
-- name: Start app
-  become_user: "{{ username }}"
-  shell: cd {{ playbook_dir }}/../packages/system-api && pm2 start npm --name "system-api" -- start
-  when: pm2_result.stdout.find("online") == -1
-
-- name: Reload app
-  become_user: "{{ username }}"
-  shell: pm2 reload system-api
-  when: pm2_result.stdout.find("online") != -1

+ 7 - 1
ansible/tasks/common/teardown.yml

@@ -1,9 +1,15 @@
+- name: Check if pm2 is installed
+  stat:
+    path: /usr/local/bin/pm2
+  register: pm2_status
+
 - name: Check if app is already running
   become_user: "{{ username }}"
   shell: pm2 list
   register: pm2_result
+  when: pm2_status.stat.exists
 
 - name: Stop app
   become_user: "{{ username }}"
   shell: pm2 stop "system-api"
-  when: pm2_result.stdout.find("system-api") != -1
+  when: pm2_status.stat.exists && pm2_result.stdout.find("system-api") != -1

+ 6 - 2
docker-compose.yml

@@ -18,7 +18,7 @@ services:
 
   
   api:
-    image: meienberger/tipi-api:latest
+    image: meienberger/tipi-api:${TIPI_VERSION}
     container_name: api
     ports:
       - 3001:3001
@@ -28,18 +28,22 @@ services:
       - ${PWD}:/tipi
     environment:
       - INTERNAL_IP=${INTERNAL_IP}
+      - TIPI_VERSION=${TIPI_VERSION}
+      - JWT_SECRET=${JWT_SECRET}
     networks:
       tipi_main_network:
         ipv4_address: 10.21.21.3
 
   dashboard:
-    image: meienberger/tipi-dashboard:latest
+    image: meienberger/tipi-dashboard:${TIPI_VERSION}
     container_name: dashboard
     ports:
       - 3000:3000
     networks:
       tipi_main_network:
         ipv4_address: 10.21.21.4
+    environment:
+      - INTERNAL_IP=${INTERNAL_IP}
     labels:
       traefik.enable: true
       traefik.http.routers.dashboard.rule: PathPrefix("/") # Host(`tipi.local`) &&

+ 1 - 2
package.json

@@ -5,8 +5,7 @@
   "scripts": {
     "prepare": "husky install",
     "act:test-install": "act --container-architecture linux/amd64 -j test-install",
-    "act:docker": "act --container-architecture linux/amd64 --secret-file github.secrets -j docker",
-    "docker:build": "docker build ./packages/system-api/ -t meienberger/tipi-api:latest && docker build ./packages/dashboard/ -t meienberger/tipi-dashboard:latest"
+    "act:docker": "act --container-architecture linux/amd64 --secret-file github.secrets -j docker"
   },
   "dependencies": {
     "eslint": "^8.15.0",

+ 2 - 2
packages/dashboard/next.config.js

@@ -2,9 +2,9 @@
 const { NODE_ENV, INTERNAL_IP } = process.env;
 
 const nextConfig = {
-  reactStrictMode: false,
+  reactStrictMode: true,
   env: {
-    INTERNAL_IP: NODE_ENV === 'development' ? 'localhost' : INTERNAL_IP,
+    INTERNAL_IP: INTERNAL_IP,
   },
 };
 

+ 4 - 2
packages/dashboard/src/core/api.ts

@@ -1,6 +1,5 @@
 import axios, { Method } from 'axios';
-
-export const BASE_URL = 'http://localhost:3001';
+import { useSytemStore } from '../state/systemStore';
 
 interface IFetchParams {
   endpoint: string;
@@ -12,6 +11,9 @@ interface IFetchParams {
 const api = async <T = unknown>(fetchParams: IFetchParams): Promise<T> => {
   const { endpoint, method = 'GET', params, data } = fetchParams;
 
+  const { getState } = useSytemStore;
+  const BASE_URL = `http://${getState().internalIp}:3001`;
+
   const response = await axios.request<T & { error?: string }>({
     method,
     params,

+ 4 - 1
packages/dashboard/src/core/fetcher.ts

@@ -1,8 +1,11 @@
 import { BareFetcher } from 'swr';
 import axios from 'axios';
-import { BASE_URL } from './api';
+import { useSytemStore } from '../state/systemStore';
 
 const fetcher: BareFetcher<any> = (url: string) => {
+  const { getState } = useSytemStore;
+  const BASE_URL = `http://${getState().internalIp}:3001`;
+
   return axios.get(url, { baseURL: BASE_URL, withCredentials: true }).then((res) => res.data);
 };
 

+ 3 - 1
packages/dashboard/src/modules/Apps/containers/AppDetails.tsx

@@ -3,6 +3,7 @@ import React from 'react';
 import { FiExternalLink } from 'react-icons/fi';
 import { AppConfig } from '../../../core/types';
 import { useAppsStore } from '../../../state/appsStore';
+import { useSytemStore } from '../../../state/systemStore';
 import AppActions from '../components/AppActions';
 import InstallModal from '../components/InstallModal';
 import StopModal from '../components/StopModal';
@@ -21,6 +22,7 @@ const AppDetails: React.FC<IProps> = ({ app }) => {
   const updateDisclosure = useDisclosure();
 
   const { install, update, uninstall, stop, start, fetchApp } = useAppsStore();
+  const { internalIp } = useSytemStore();
 
   const handleError = (error: unknown) => {
     if (error instanceof Error) {
@@ -86,7 +88,7 @@ const AppDetails: React.FC<IProps> = ({ app }) => {
   };
 
   const handleOpen = () => {
-    window.open(`http://${process.env.INTERNAL_IP}:${app.port}`, '_blank');
+    window.open(`http://${internalIp}:${app.port}`, '_blank');
   };
 
   return (

+ 18 - 2
packages/dashboard/src/modules/Auth/containers/AuthWrapper.tsx

@@ -1,6 +1,9 @@
+import axios from 'axios';
 import React, { useEffect, useState } from 'react';
+import useSWR, { BareFetcher } from 'swr';
 import LoadingScreen from '../../../components/LoadingScreen';
 import { useAuthStore } from '../../../state/authStore';
+import { useSytemStore } from '../../../state/systemStore';
 import Login from './Login';
 import Onboarding from './Onboarding';
 
@@ -8,9 +11,16 @@ interface IProps {
   children: React.ReactNode;
 }
 
+const fetcher: BareFetcher<any> = (url: string) => {
+  return axios.get(url).then((res) => res.data);
+};
+
 const AuthWrapper: React.FC<IProps> = ({ children }) => {
   const [initialLoad, setInitialLoad] = useState(true);
   const { configured, user, me, fetchConfigured } = useAuthStore();
+  const { internalIp, setInternalIp } = useSytemStore();
+
+  const { data } = useSWR('/api/ip', fetcher);
 
   useEffect(() => {
     const fetchUser = async () => {
@@ -19,8 +29,14 @@ const AuthWrapper: React.FC<IProps> = ({ children }) => {
 
       setInitialLoad(false);
     };
-    if (!user) fetchUser();
-  }, [fetchConfigured, me, user]);
+    if (!user && internalIp) fetchUser();
+  }, [fetchConfigured, internalIp, me, user]);
+
+  useEffect(() => {
+    if (data?.ip && !internalIp) {
+      setInternalIp(data.ip);
+    }
+  }, [data?.ip, internalIp, setInternalIp]);
 
   if (initialLoad && !user) {
     return <LoadingScreen />;

+ 2 - 2
packages/dashboard/src/modules/Dashboard/containers/Dashboard.tsx

@@ -25,12 +25,12 @@ const Dashboard: React.FC = () => {
 
   // Convert bytes to GB
   const diskFree = Math.round(disk.available / 1024 / 1024 / 1024);
-  const diskSize = Math.round(disk.size / 1024 / 1024 / 1024);
+  const diskSize = Math.round(disk.total / 1024 / 1024 / 1024);
   const diskUsed = diskSize - diskFree;
   const percentUsed = Math.round((diskUsed / diskSize) * 100);
 
   const memoryTotal = Math.round(memory?.total / 1024 / 1024 / 1024);
-  const memoryFree = Math.round(memory?.free / 1024 / 1024 / 1024);
+  const memoryFree = Math.round(memory?.available / 1024 / 1024 / 1024);
   const percentUsedMemory = Math.round(((memoryTotal - memoryFree) / memoryTotal) * 100);
 
   return (

+ 5 - 0
packages/dashboard/src/pages/api/ip.tsx

@@ -0,0 +1,5 @@
+export default function handler(_: any, res: any) {
+  const { INTERNAL_IP } = process.env;
+
+  res.status(200).json({ ip: INTERNAL_IP });
+}

+ 6 - 7
packages/dashboard/src/pages/settings.tsx

@@ -3,26 +3,25 @@ import { Text } from '@chakra-ui/react';
 import useSWR from 'swr';
 import Layout from '../components/Layout';
 import fetcher from '../core/fetcher';
-import Package from '../../package.json';
 
 const Settings: NextPage = () => {
-  const { data: latestVersion } = useSWR<string>('/system/version/latest', fetcher);
+  const { data } = useSWR<{ current: string; latest: string }>('/system/version', fetcher);
 
-  const isLatest = latestVersion === `v${Package.version}`;
+  const isLatest = data?.latest === data?.current;
 
   const renderUpdate = () => {
     if (isLatest) {
       return (
         <Text fontSize="md" color="green.500">
-          Your Tipi install is up to date. Version {Package.version}
+          Your Tipi install is up to date. Version {data?.current}
         </Text>
       );
     }
 
     return (
       <Text fontSize="md">
-        You are not using the latest version of Tipi. There is a new version ({latestVersion}) available. Visit{' '}
-        <a className="text-blue-600" target="_blank" rel="noreferrer" href={`https://github.com/meienberger/runtipi/releases/${latestVersion}`}>
+        You are not using the latest version of Tipi. There is a new version ({data?.latest}) available. Visit{' '}
+        <a className="text-blue-600" target="_blank" rel="noreferrer" href={`https://github.com/meienberger/runtipi/releases/v${data?.latest}`}>
           Github
         </a>{' '}
         for update instructions.
@@ -31,7 +30,7 @@ const Settings: NextPage = () => {
   };
 
   return (
-    <Layout loading={!latestVersion}>
+    <Layout loading={!data}>
       <Text fontSize="3xl" className="font-bold">
         Settings
       </Text>

+ 8 - 4
packages/dashboard/src/state/systemStore.ts

@@ -3,17 +3,21 @@ import api from '../core/api';
 
 type Store = {
   cpuLoad: number;
-  disk: { size: number; used: number; available: number };
-  memory: { total: number; used: number; free: number };
+  internalIp: string;
+  disk: { total: number; used: number; available: number };
+  memory: { total: number; used: number; available: number };
   fetchDiskSpace: () => void;
   fetchCpuLoad: () => void;
   fetchMemoryLoad: () => void;
+  setInternalIp: (internalIp: string) => void;
 };
 
 export const useSytemStore = create<Store>((set) => ({
   cpuLoad: 0,
-  memory: { total: 0, used: 0, free: 0 },
-  disk: { size: 0, used: 0, available: 0 },
+  internalIp: '',
+  setInternalIp: (internalIp: string) => set((state) => ({ ...state, internalIp })),
+  memory: { total: 0, used: 0, available: 0 },
+  disk: { total: 0, used: 0, available: 0 },
   fetchDiskSpace: async () => {
     const response = await api.fetch<any>({
       endpoint: '/system/disk',

+ 4 - 2
packages/system-api/src/config/config.ts

@@ -5,17 +5,19 @@ interface IConfig {
   ROOT_FOLDER: string;
   JWT_SECRET: string;
   CLIENT_URLS: string[];
+  VERSION: string;
 }
 
 dotenv.config();
 
-const { NODE_ENV = 'development', JWT_SECRET = '' } = process.env;
+const { NODE_ENV = 'development', JWT_SECRET = '', INTERNAL_IP = '', TIPI_VERSION = '' } = process.env;
 
 const config: IConfig = {
   NODE_ENV,
   ROOT_FOLDER: '/tipi',
   JWT_SECRET,
-  CLIENT_URLS: ['http://locahost:3000', 'http://10.21.21.4', 'http://10.21.21.4:3000'],
+  CLIENT_URLS: ['http://localhost:3000', `http://${INTERNAL_IP}`, `http://${INTERNAL_IP}:3000`],
+  VERSION: TIPI_VERSION,
 };
 
 export default config;

+ 22 - 32
packages/system-api/src/modules/system/system.controller.ts

@@ -1,34 +1,42 @@
 import { Request, Response } from 'express';
-import si from 'systeminformation';
 import fetch from 'node-fetch';
+import config from '../../config';
 import TipiCache from '../../config/cache';
+import { readJsonFile } from '../fs/fs.helpers';
 
 type CpuData = {
   load: number;
 };
 
 type DiskData = {
-  size: number;
+  total: number;
   used: number;
   available: number;
 };
 
 type MemoryData = {
   total: number;
-  free: number;
+  available: number;
   used: number;
 };
 
+type SystemInfo = {
+  cpu: CpuData;
+  disk: DiskData;
+  memory: MemoryData;
+};
+
 /**
  *
  * @param req
  * @param res
  */
 const getCpuInfo = async (req: Request, res: Response<CpuData>) => {
-  //   const cpuInfo = await cpu.getCpuInfo();
-  const cpuLoad = await si.currentLoad();
+  const systemInfo: SystemInfo = readJsonFile('/state/system-info.json');
+
+  const cpu = systemInfo.cpu;
 
-  res.status(200).send({ load: cpuLoad.currentLoad });
+  res.status(200).send({ load: cpu.load });
 };
 
 /**
@@ -37,19 +45,9 @@ const getCpuInfo = async (req: Request, res: Response<CpuData>) => {
  * @param res
  */
 const getDiskInfo = async (req: Request, res: Response<DiskData>) => {
-  const disk = await si.fsSize();
+  const systemInfo: SystemInfo = readJsonFile('/state/system-info.json');
 
-  const rootDisk = disk.find((item) => item.mount === '/');
-
-  if (!rootDisk) {
-    throw new Error('Could not find root disk');
-  }
-
-  const result: DiskData = {
-    size: rootDisk.size,
-    used: rootDisk.used,
-    available: rootDisk.available,
-  };
+  const result: DiskData = systemInfo.disk;
 
   res.status(200).send(result);
 };
@@ -60,32 +58,24 @@ const getDiskInfo = async (req: Request, res: Response<DiskData>) => {
  * @param res
  */
 const getMemoryInfo = async (req: Request, res: Response<MemoryData>) => {
-  const memory = await si.mem();
+  const systemInfo: SystemInfo = readJsonFile('/state/system-info.json');
 
-  const result: MemoryData = {
-    total: memory.total,
-    free: memory.free,
-    used: memory.used,
-  };
+  const result: MemoryData = systemInfo.memory;
 
   res.status(200).json(result);
 };
 
-const getLatestVersion = async (req: Request, res: Response<string>) => {
+const getVersion = async (req: Request, res: Response<{ current: string; latest: string }>) => {
   let version = TipiCache.get<string>('latestVersion');
 
-  console.log('CACHED', version);
-
   if (!version) {
     const response = await fetch('https://api.github.com/repos/meienberger/runtipi/releases/latest');
     const json = (await response.json()) as { name: string };
     TipiCache.set('latestVersion', json.name);
-    version = json.name;
+    version = json.name.replace('v', '');
   }
 
-  console.log(version);
-
-  res.status(200).send(version);
+  res.status(200).send({ current: config.VERSION, latest: version });
 };
 
-export default { getCpuInfo, getDiskInfo, getMemoryInfo, getLatestVersion };
+export default { getCpuInfo, getDiskInfo, getMemoryInfo, getVersion };

+ 1 - 1
packages/system-api/src/modules/system/system.routes.ts

@@ -6,6 +6,6 @@ const router = Router();
 router.route('/cpu').get(SystemController.getCpuInfo);
 router.route('/disk').get(SystemController.getDiskInfo);
 router.route('/memory').get(SystemController.getMemoryInfo);
-router.route('/version/latest').get(SystemController.getLatestVersion);
+router.route('/version').get(SystemController.getVersion);
 
 export default router;

+ 16 - 1
packages/system-api/src/server.ts

@@ -23,7 +23,22 @@ if (isProd) {
   app.use(helmet());
 }
 
-app.use(cors({ credentials: true, origin: config.CLIENT_URLS }));
+app.use(
+  cors({
+    credentials: true,
+    origin: function (origin, callback) {
+      // allow requests with no origin
+      if (!origin) return callback(null, true);
+
+      if (config.CLIENT_URLS.indexOf(origin) === -1) {
+        var message = "The CORS policy for this origin doesn't allow access from the particular origin.";
+        return callback(new Error(message), false);
+      }
+
+      return callback(null, true);
+    },
+  }),
+);
 
 // Get user from token
 app.use((req, res, next) => {

+ 0 - 2
scripts/configure.sh

@@ -23,8 +23,6 @@ if ! command -v ansible-playbook > /dev/null; then
   sudo pip3 install ansible
 fi
 
-
-
 ansible-playbook ansible/setup.yml -i ansible/hosts -K -e username="$USERNAME"
 
 # echo "Configuring permissions..."

+ 7 - 8
scripts/start.sh

@@ -90,20 +90,14 @@ echo "Generating config files..."
 [[ -f "${ROOT_FOLDER}/packages/system-api/.env" ]] && rm -f "${ROOT_FOLDER}/packages/system-api/.env"
 
 # Store paths to intermediary config files
-ENV_FILE="$ROOT_FOLDER/templates/.env"
-ENV_FILE_SYSTEM_API="$ROOT_FOLDER/templates/.env-api"
-
-# Remove intermediary config files
-[[ -f "$ENV_FILE" ]] && rm -f "$ENV_FILE"
-[[ -f "$ENV_FILE_SYSTEM_API" ]] && rm -f "$ENV_FILE_SYSTEM_API"
+ENV_FILE=$(mktemp)
 
 # Copy template configs to intermediary configs
 [[ -f "$ROOT_FOLDER/templates/env-sample" ]] && cp "$ROOT_FOLDER/templates/env-sample" "$ENV_FILE"
-[[ -f "$ROOT_FOLDER/templates/env-api-sample" ]] && cp "$ROOT_FOLDER/templates/env-api-sample" "$ENV_FILE_SYSTEM_API"
 
 JWT_SECRET=$(derive_entropy "jwt")
 
-for template in "${ENV_FILE}" "${ENV_FILE_SYSTEM_API}"; do
+for template in "${ENV_FILE}"; do
   sed -i "s/<dns_ip>/${DNS_IP}/g" "${template}"
   sed -i "s/<internal_ip>/${INTERNAL_IP}/g" "${template}"
   sed -i "s/<puid>/${PUID}/g" "${template}"
@@ -111,11 +105,16 @@ for template in "${ENV_FILE}" "${ENV_FILE_SYSTEM_API}"; do
   sed -i "s/<tz>/${TZ}/g" "${template}"
   sed -i "s/<jwt_secret>/${JWT_SECRET}/g" "${template}"
   sed -i "s/<root_folder>/${SED_ROOT_FOLDER}/g" "${template}"
+  sed -i "s/<tipi_version>/$(cat "${ROOT_FOLDER}/VERSION")/g" "${template}"
 done
 
 mv -f "$ENV_FILE" "$ROOT_FOLDER/.env"
 mv -f "$ENV_FILE_SYSTEM_API" "$ROOT_FOLDER/packages/system-api/.env"
 
+# Run system-info.sh
+echo "Running system-info.sh..."
+bash "${ROOT_FOLDER}/scripts/system-info.sh"
+
 # ansible-playbook ansible/start.yml -i ansible/hosts -K -e username="$USERNAME"
 
 # Run docker-compose

+ 25 - 0
scripts/system-info.sh

@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+set -e  # Exit immediately if a command exits with a non-zero status.
+
+ROOT_FOLDER="$(readlink -f $(dirname "${BASH_SOURCE[0]}")/..)"
+STATE_FOLDER="${ROOT_FOLDER}/state"
+
+# Available disk space
+TOTAL_DISK_SPACE_BYTES=$(df -P -B 1 / | tail -n 1 | awk '{print $2}')
+AVAILABLE_DISK_SPACE_BYTES=$(df -P -B 1 / | tail -n 1 | awk '{print $4}')
+USED_DISK_SPACE_BYTES=$(($TOTAL_DISK_SPACE_BYTES - $AVAILABLE_DISK_SPACE_BYTES))
+
+# CPU info
+CPU_LOAD_PERCENTAGE=$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1}')
+
+# Memory info
+MEM_TOTAL_BYTES=$(cat /proc/meminfo | grep MemTotal | awk '{print $2}')
+MEM_FREE_BYTES=$(cat /proc/meminfo | grep MemFree | awk '{print $2}')
+MEM_USED_BYTES=$(($MEM_TOTAL_BYTES - $MEM_FREE_BYTES))
+
+# Create temporary json file
+TEMP_JSON_FILE=$(mktemp)
+echo '{ "cpu": { "load": '"${CPU_LOAD_PERCENTAGE}"' }, "memory": { "total": '"${MEM_TOTAL_BYTES}"' , "used": '"${MEM_USED_BYTES}"', "available": '"${MEM_FREE_BYTES}"' }, "disk": { "total": '"${TOTAL_DISK_SPACE_BYTES}"' , "used": '"${USED_DISK_SPACE_BYTES}"', "available": '"${AVAILABLE_DISK_SPACE_BYTES}"' } }' > "${TEMP_JSON_FILE}"
+
+# Write to state file
+echo "$(cat "${TEMP_JSON_FILE}")" > "${STATE_FOLDER}/system-info.json"

+ 0 - 4
templates/env-api-sample

@@ -1,4 +0,0 @@
-ROOT_FOLDER=<root_folder>
-JWT_SECRET=<jwt_secret>
-INTERNAL_IP=<internal_ip>
-ARCHITECTURE=<architecture>

+ 2 - 0
templates/env-sample

@@ -7,3 +7,5 @@ PGID=<pgid>
 INTERNAL_IP=<internal_ip>
 DNS_IP=<dns_ip>
 ARCHITECTURE=<architecture>
+TIPI_VERSION=<tipi_version>
+JWT_SECRET=<jwt_secret>