Merge pull request #221 from meienberger/chore/cleanup

Chore/cleanup
This commit is contained in:
Nicolas Meienberger 2022-10-06 18:39:34 +02:00 committed by GitHub
commit 3c10575b46
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 98 additions and 522 deletions

View file

@ -1,4 +1,4 @@
FROM node:18-alpine3.16 AS build
FROM node:18 AS build
RUN npm install node-gyp -g
@ -19,33 +19,25 @@ COPY ./packages/dashboard /dashboard
RUN npm run build
FROM node:18-alpine3.16 as app
FROM alpine:3.16.0 as app
WORKDIR /
RUN apt-get update
# Install docker
RUN apt-get install -y ca-certificates curl gnupg lsb-release jq
RUN mkdir -p /etc/apt/keyrings
RUN curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
RUN echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list >/dev/null
RUN apt-get update
RUN apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# # Install dependencies
RUN apk --no-cache add nodejs npm
RUN apk --no-cache add g++
RUN apk --no-cache add make
RUN apk --no-cache add python3
# Install node
RUN curl -fsSL https://deb.nodesource.com/setup_16.x | bash -
RUN apt-get install -y nodejs
# Install dependencies
RUN npm install node-gyp -g
WORKDIR /api
COPY ./packages/system-api/package*.json /api/
RUN npm install --production
RUN npm install --omit=dev
WORKDIR /dashboard
COPY ./packages/dashboard/package*.json /dashboard/
RUN npm install --production
RUN npm install --omit=dev
COPY --from=build /api/dist /api/dist
COPY --from=build /dashboard/.next /dashboard/.next

View file

@ -2,6 +2,7 @@ FROM node:18-alpine3.16
WORKDIR /
RUN apk --no-cache add g++ make
RUN npm install node-gyp -g
WORKDIR /api

View file

@ -94,6 +94,21 @@ To stop Tipi, run the stop script.
sudo ./scripts/stop.sh
```
### Custom settings
You can change the default settings by creating a `settings.json` file. The file should be located in the `state` directory. This file will make your changes persist across restarts. Example file:
```json
{
"dnsIp": "9.9.9.9", // DNS IP address
"domain": "mydomain.com", // Domain name to link to the dashboard
"port": 7000, // Change default http port 80
"sslPort": 7001, // Change default ssl port 443
"listenIp": "192.168.1.1", // Change default listen ip (advanced)
"storagePath": "/mnt/usb", // Change default storage path of app data
}
```
## Linking a domain to your dashboard
If you want to link a domain to your dashboard, you can do so by providing the `--domain` option in the start script.
@ -101,6 +116,8 @@ If you want to link a domain to your dashboard, you can do so by providing the `
sudo ./scripts/start.sh --domain mydomain.com
```
You can also specify it in the `settings.json` file as shown in the previous section.
A Let's Encrypt certificate will be generated and installed automatically. Make sure to have ports 80 and 443 open on your firewall and that your domain has an **A** record pointing to your server IP.
## ❤️ Contributing

View file

@ -1,13 +1,13 @@
{
"name": "runtipi",
"version": "0.6.1",
"version": "0.6.2",
"description": "A homeserver for everyone",
"scripts": {
"prepare": "husky install",
"commit": "git-cz",
"act:test-install": "act --container-architecture linux/amd64 -j test-install",
"act:docker": "act --container-architecture linux/amd64 --secret-file github.secrets -j build-images",
"start:dev": "./scripts/start-dev.sh",
"start:dev": "sudo ./scripts/start-dev.sh",
"start:rc": "docker-compose -f docker-compose.rc.yml --env-file .env up --build",
"start:prod": "docker-compose --env-file .env up --build",
"start:pg": "docker run --name test-db -p 5433:5432 -d --rm -e POSTGRES_PASSWORD=postgres postgres",

View file

@ -1,6 +1,6 @@
{
"name": "dashboard",
"version": "0.6.1",
"version": "0.6.2",
"private": true,
"scripts": {
"test": "jest --colors",
@ -17,14 +17,11 @@
"@emotion/react": "^11",
"@emotion/styled": "^11",
"@fontsource/open-sans": "^4.5.8",
"axios": "^0.26.1",
"clsx": "^1.1.1",
"final-form": "^4.20.6",
"framer-motion": "^6",
"graphql": "^15.8.0",
"graphql-tag": "^2.12.6",
"immer": "^9.0.12",
"js-cookie": "^3.0.1",
"next": "12.1.6",
"react": "18.1.0",
"react-dom": "18.1.0",
@ -36,7 +33,6 @@
"remark-gfm": "^3.0.1",
"remark-mdx": "^2.1.1",
"swr": "^1.3.0",
"systeminformation": "^5.11.9",
"tslib": "^2.4.0",
"validator": "^13.7.0",
"zustand": "^3.7.2"

View file

@ -10,6 +10,7 @@ import { useRouter } from 'next/router';
import { IconType } from 'react-icons';
import { useLogoutMutation, useVersionQuery } from '../../generated/graphql';
import { getUrl } from '../../core/helpers/url-helpers';
import { BsHeart } from 'react-icons/bs';
const SideMenu: React.FC = () => {
const router = useRouter();
@ -57,6 +58,12 @@ const SideMenu: React.FC = () => {
<Flex flex="1" />
<List>
<div className="mx-3">
<a href="https://github.com/meienberger/runtipi?sponsor=1" target="_blank" rel="noreferrer">
<ListItem className="cursor-pointer hover:font-bold flex items-center mb-4">
<BsHeart size={20} className="mr-3" />
<p className="flex-1 mb-1 text-md">Donate</p>
</ListItem>
</a>
<ListItem onClick={() => logout()} className="cursor-pointer hover:font-bold flex items-center mb-5">
<FiLogOut size={20} className="mr-3" />
<p className="flex-1">Log out</p>
@ -68,6 +75,7 @@ const SideMenu: React.FC = () => {
</ListItem>
</div>
</List>
<div className="pb-1 text-center text-sm text-gray-400 mt-5">Tipi version {Package.version}</div>
{!isLatest && (
<Badge className="self-center mt-1" colorScheme="green">

View file

@ -1,34 +0,0 @@
import axios, { Method } from 'axios';
import { useSystemStore } from '../state/systemStore';
interface IFetchParams {
endpoint: string;
method?: Method;
params?: JSON;
data?: Record<string, unknown>;
}
const api = async <T = unknown>(fetchParams: IFetchParams): Promise<T> => {
const { endpoint, method = 'GET', params, data } = fetchParams;
const { getState } = useSystemStore;
const BASE_URL = getState().baseUrl;
const response = await axios.request<T & { error?: string }>({
method,
params,
data,
url: `${BASE_URL}${endpoint}`,
withCredentials: true,
});
if (response.data.error) {
throw new Error(response.data.error);
}
if (response.data) return response.data;
throw new Error(`Network request error. status : ${response.status}`);
};
export default { fetch: api };

View file

@ -1,11 +0,0 @@
import { BareFetcher } from 'swr';
import axios from 'axios';
import { useSystemStore } from '../state/systemStore';
const fetcher: BareFetcher<any> = (url: string) => {
const { baseUrl } = useSystemStore.getState();
return axios.get(url, { baseURL: baseUrl, withCredentials: true }).then((res) => res.data);
};
export default fetcher;

View file

@ -1,74 +0,0 @@
import create from 'zustand';
import Cookies from 'js-cookie';
import api from '../core/api';
import { IUser } from '../core/types';
type AppsStore = {
user: IUser | null;
configured: boolean;
me: () => Promise<void>;
login: (email: string, password: string) => Promise<void>;
register: (email: string, password: string) => Promise<void>;
logout: () => void;
fetchConfigured: () => Promise<void>;
loading: boolean;
};
export const useAuthStore = create<AppsStore>((set) => ({
user: null,
configured: false,
loading: false,
me: async () => {
try {
set({ loading: true });
const response = await api.fetch<{ user: IUser | null }>({ endpoint: '/auth/me' });
set({ user: response.user, loading: false });
} catch (error) {
set({ loading: false, user: null });
}
},
login: async (email: string, password: string) => {
set({ loading: true });
try {
const response = await api.fetch<{ user: IUser }>({
endpoint: '/auth/login',
method: 'post',
data: { email, password },
});
set({ user: response.user, loading: false });
} catch (e) {
set({ loading: false });
throw e;
}
},
logout: async () => {
Cookies.remove('tipi_token');
set({ user: null, loading: false });
},
register: async (email: string, password: string) => {
set({ loading: true });
try {
const response = await api.fetch<{ user: IUser }>({
endpoint: '/auth/register',
method: 'post',
data: { email, password },
});
set({ user: response.user, loading: false });
} catch (e) {
set({ loading: false });
throw e;
}
},
fetchConfigured: async () => {
try {
const response = await api.fetch<{ configured: boolean }>({ endpoint: '/auth/configured' });
set({ configured: response.configured });
} catch (e) {
set({ configured: false });
}
},
}));

View file

@ -1,6 +1,6 @@
{
"name": "system-api",
"version": "0.6.1",
"version": "0.6.2",
"description": "",
"exports": "./dist/server.js",
"type": "module",
@ -30,8 +30,6 @@
"argon2": "^0.29.1",
"axios": "^0.26.1",
"class-validator": "^0.13.2",
"compression": "^1.7.4",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"dotenv": "^16.0.0",
"express": "^4.17.3",
@ -41,18 +39,13 @@
"graphql-type-json": "^0.3.2",
"http": "0.0.1-security",
"internal-ip": "^6.0.0",
"jsonwebtoken": "^8.5.1",
"mock-fs": "^5.1.2",
"node-cache": "^5.1.2",
"node-cron": "^3.0.1",
"node-port-scanner": "^3.0.1",
"p-iteration": "^1.1.8",
"pg": "^8.7.3",
"public-ip": "^5.0.0",
"reflect-metadata": "^0.1.13",
"semver": "^7.3.7",
"session-file-store": "^1.5.0",
"systeminformation": "^5.11.9",
"tcp-port-used": "^1.0.2",
"type-graphql": "^1.1.1",
"typeorm": "^0.3.6",
@ -64,15 +57,11 @@
"@faker-js/faker": "^7.3.0",
"@swc/cli": "^0.1.57",
"@swc/core": "^1.2.210",
"@types/compression": "^1.7.2",
"@types/cookie-parser": "^1.4.3",
"@types/cors": "^2.8.12",
"@types/express": "^4.17.13",
"@types/express-session": "^1.17.4",
"@types/fs-extra": "^9.0.13",
"@types/jest": "^27.5.0",
"@types/jsonwebtoken": "^8.5.8",
"@types/mock-fs": "^4.13.1",
"@types/node": "17.0.31",
"@types/node-cron": "^3.0.2",
"@types/pg": "^8.6.5",

View file

@ -69,7 +69,7 @@ class EventDispatcher {
return;
}
this.clearEvent(this.lock.id);
this.clearEvent(this.lock, status);
this.lock = null;
}
@ -165,10 +165,16 @@ class EventDispatcher {
* Clear event from queue
* @param id - Event id
*/
private clearEvent(id: string) {
this.queue = this.queue.filter((e) => e.id !== id);
if (fs.existsSync(`/app/logs/${id}.log`)) {
fs.unlinkSync(`/app/logs/${id}.log`);
private clearEvent(event: SystemEvent, status: EventStatusTypes = 'success') {
this.queue = this.queue.filter((e) => e.id !== event.id);
if (fs.existsSync(`/app/logs/${event.id}.log`)) {
const log = fs.readFileSync(`/app/logs/${event.id}.log`, 'utf8');
if (log && status === 'error') {
logger.error(`EventDispatcher: ${event.type} ${event.id} failed with error: ${log}`);
} else if (log) {
logger.info(`EventDispatcher: ${event.type} ${event.id} finished with message: ${log}`);
}
fs.unlinkSync(`/app/logs/${event.id}.log`);
}
fs.writeFileSync(WATCH_FILE, '');
}

View file

@ -154,10 +154,11 @@ describe('EventDispatcher - getEventStatus', () => {
describe('EventDispatcher - clearEvent', () => {
it('Should clear event', async () => {
const event = { id: '123', type: EventTypes.APP, args: [], creationDate: new Date() };
// @ts-ignore
eventDispatcher.queue = [{ id: '123', type: EventTypes.APP, args: [], creationDate: new Date() }];
eventDispatcher.queue = [event];
// @ts-ignore
eventDispatcher.clearEvent('123');
eventDispatcher.clearEvent(event);
// @ts-ignore
const queue = eventDispatcher.queue;

389
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -46,6 +46,7 @@ if [[ "$command" = "update" ]]; then
repo="$2"
repo_hash=$(get_hash "${repo}")
repo_dir="${ROOT_FOLDER}/repos/${repo_hash}"
git config --global --add safe.directory "${repo_dir}"
if [ ! -d "${repo_dir}" ]; then
write_log "Repo does not exist"
exit 1
@ -55,6 +56,7 @@ if [[ "$command" = "update" ]]; then
cd "${repo_dir}" || exit
if ! git pull origin master; then
cd "${ROOT_FOLDER}" || exit
write_log "Failed to update repo"
exit 1
fi

View file

@ -5,5 +5,7 @@ ROOT_FOLDER="${PWD}"
kill_watcher
"${ROOT_FOLDER}/scripts/watcher.sh" &
chmod -R a+rwx "${ROOT_FOLDER}/state/events"
chmod -R a+rwx "${ROOT_FOLDER}/state/system-info.json"
docker compose -f docker-compose.dev.yml --env-file "${ROOT_FOLDER}/.env.dev" up --build

View file

@ -201,6 +201,9 @@ mv -f "$ENV_FILE" "$ROOT_FOLDER/.env"
# Run system-info.sh
echo "Running system-info.sh..."
bash "${ROOT_FOLDER}/scripts/system-info.sh"
echo "Fixing permissions for events file"
chmod -R a+rwx "${ROOT_FOLDER}/state/events"
chmod -R a+rwx "${ROOT_FOLDER}/state/system-info.json"
## Don't run if config-only
if [[ ! $ci == "true" ]]; then

View file

@ -1,8 +1,12 @@
#!/usr/bin/env bash
set -e # Exit immediately if a command exits with a non-zero status.
ROOT_FOLDER="${PWD}"
STATE_FOLDER="${ROOT_FOLDER}/state"
# if not on linux exit
if [[ "$(uname)" != "Linux" ]]; then
echo '{"cpu": { "load": 0 },"memory": { "available": 0, "total": 0, "used": 0 },"disk": { "available": 0, "total": 0, "used": 0 }}' >"${STATE_FOLDER}/system-info.json"
exit 0
fi

View file

@ -1,9 +1,10 @@
#!/usr/bin/env bash
source "${BASH_SOURCE%/*}/common.sh"
ensure_pwd
ROOT_FOLDER="${PWD}"
if [ -z ${1+x} ]; then
command=""
else
@ -23,6 +24,7 @@ fi
# Update Tipi
if [[ "$command" = "update" ]]; then
scripts/stop.sh
git config --global --add safe.directory "${ROOT_FOLDER}"
git pull origin master
scripts/start.sh
exit

View file

@ -97,7 +97,6 @@ function select_command() {
return 0
}
check_running
write_log "Listening for events in ${WATCH_FILE}..."
clean_events
# Listen in for changes in the WATCH_FILE