commit
3c10575b46
19 changed files with 98 additions and 522 deletions
26
Dockerfile
26
Dockerfile
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
17
README.md
17
README.md
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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 };
|
|
@ -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;
|
|
@ -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 });
|
||||
}
|
||||
},
|
||||
}));
|
|
@ -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",
|
||||
|
|
|
@ -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, '');
|
||||
}
|
||||
|
|
|
@ -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
389
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue