commit
a2feb47d77
20 changed files with 15882 additions and 702 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,4 +1,5 @@
|
|||
.env
|
||||
.env*
|
||||
nginx/*
|
||||
letsencrypt/*
|
||||
app-data/*
|
||||
|
|
71
README.md
71
README.md
|
@ -1,7 +1,68 @@
|
|||

|
||||

|
||||

|
||||

|
||||

|
||||
# ⛺️ Tipi — A personal homeserver for everyone
|
||||
[](https://github.com/meienberger/runtipi/blob/master/LICENSE)
|
||||
[](https://github.com/meienberger/runtipi/releases)
|
||||

|
||||

|
||||
|
||||

|
||||
> ⚠️ Tipi is still at an early stage of development and issues are to be expected. Feel free to open an issue or pull request if you find a bug.
|
||||
|
||||
Tipi is a personal homeserver orchestrator. It is running docker containers under the hood and provides a simple web interface to manage them. Every service comes with an opinionated configuration in order to remove the need for manual configuration and network setup.
|
||||
|
||||
## Apps available
|
||||
- [Anonaddy](https://github.com/anonaddy/anonaddy) - Anonymous email forwarding
|
||||
- [Filebrowser](https://github.com/filebrowser/filebrowser) - Web File Browser
|
||||
- [Freshrss](https://github.com/FreshRSS/FreshRSS) - A free, self-hostable RSS aggregator
|
||||
- [Invidious](https://github.com/iv-org/invidious) - An alternative front-end to YouTube
|
||||
- [Jackett](https://github.com/Jackett/Jackett) - API Support for your favorite torrent trackers
|
||||
- [Jellyfin](https://github.com/jellyfin/jellyfin) - A media server for your home collection
|
||||
- [Joplin](https://github.com/laurent22/joplin) - Privacy focused note-taking app
|
||||
- [n8n](https://github.com/n8n-io/n8n) - Workflow Automation Tool
|
||||
- [Nextcloud](https://github.com/nextcloud/server) - A safe home for all your data
|
||||
- [Pihole](https://github.com/pi-hole/pi-hole) - A black hole for Internet advertisements
|
||||
- [Radarr](https://github.com/Radarr/Radarr) - Movie collection manager for Usenet and BitTorrent users.
|
||||
- [Sonarr](https://github.com/Sonarr/Sonarr) - TV show manager for Usenet and BitTorrent
|
||||
- [Syncthing](https://github.com/syncthing/syncthing) - Continuous File Synchronization
|
||||
- [Tailscale](https://github.com/tailscale/tailscale) - The easiest, most secure way to use WireGuard and 2FA.
|
||||
- [Transmission](https://github.com/transmission/transmission) - Fast, easy, and free BitTorrent client
|
||||
- [Wireguard Easy](https://github.com/WeeJeWel/wg-easy) - WireGuard VPN + Web-based Admin UI
|
||||
## 🛠 Installation
|
||||
### Installation Requirements
|
||||
- Ubuntu 18.04 LTS or higher (or Debian 10)
|
||||
|
||||
Make sure your User ID is `1000` (verify it by running `id -u`) and ensure that your account is [correctly permissioned to use docker](https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user).
|
||||
|
||||
### Step 1. Download Tipi
|
||||
Run this in an empty directory where you want to install Tipi.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/meienberger/runtipi.git
|
||||
```
|
||||
|
||||
### Step 2. Run Tipi
|
||||
cd into the downloaded directory and run the start script.
|
||||
|
||||
```bash
|
||||
cd runtipi && sudo ./scripts/start.sh
|
||||
```
|
||||
|
||||
The script will prompt you the ip address of the dashboard once configured.
|
||||
|
||||
To stop Tipi, run the stop script.
|
||||
|
||||
```bash
|
||||
sudo ./scripts/stop.sh
|
||||
```
|
||||
|
||||
## ❤️ Contributing
|
||||
|
||||
Tipi is made to be very easy to plug in new apps. We welcome and appreciate new contributions.
|
||||
|
||||
If you want to support a new app or feature, you can:
|
||||
- Fork the repository and create a new branch for your changes.
|
||||
- Create a pull request.
|
||||
|
||||
## 📜 License
|
||||
[](https://github.com/meienberger/runtipi/blob/master/LICENSE)
|
||||
|
||||
Tipi is licensed under the GNU General Public License v3.0. TL;DR — You may copy, distribute and modify the software as long as you track changes/dates in source files. Any modifications to or software including (via compiler) GPL-licensed code must also be made available under the GPL along with build & install instructions.
|
||||
|
|
|
@ -11,6 +11,7 @@ packages:
|
|||
- npm
|
||||
|
||||
username: nicolas
|
||||
jwt_secret: test
|
||||
|
||||
### ZSH Settings
|
||||
zsh_theme: "powerlevel10k/powerlevel10k"
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
dest: "{{ playbook_dir }}/../system-api/.env"
|
||||
content: |
|
||||
ROOT_FOLDER={{ playbook_dir }}/../
|
||||
JWT_SECRET={{ jwt_secret }}
|
||||
|
||||
- name: Install packages based on package.json.
|
||||
community.general.npm:
|
||||
|
|
0
apps/invidious/data/postgres/.gitkeep
Normal file
0
apps/invidious/data/postgres/.gitkeep
Normal file
0
apps/invidious/data/sql/.gitkeep
Normal file
0
apps/invidious/data/sql/.gitkeep
Normal file
2
dashboard/.dockerignore
Normal file
2
dashboard/.dockerignore
Normal file
|
@ -0,0 +1,2 @@
|
|||
node_modules/
|
||||
.next/
|
|
@ -1,4 +1,4 @@
|
|||
FROM node:latest
|
||||
FROM node:18
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
|
|
@ -19,9 +19,9 @@
|
|||
"framer-motion": "^6",
|
||||
"immer": "^9.0.12",
|
||||
"js-cookie": "^3.0.1",
|
||||
"next": "12.1.4",
|
||||
"react": "18.0.0",
|
||||
"react-dom": "18.0.0",
|
||||
"next": "12.1.6",
|
||||
"react": "18.1.0",
|
||||
"react-dom": "18.1.0",
|
||||
"react-final-form": "^6.5.9",
|
||||
"react-icons": "^4.3.1",
|
||||
"swr": "^1.3.0",
|
||||
|
@ -31,9 +31,9 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@types/js-cookie": "^3.0.2",
|
||||
"@types/node": "17.0.23",
|
||||
"@types/react": "17.0.43",
|
||||
"@types/react-dom": "17.0.14",
|
||||
"@types/node": "17.0.31",
|
||||
"@types/react": "18.0.8",
|
||||
"@types/react-dom": "18.0.3",
|
||||
"@types/validator": "^13.7.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.18.0",
|
||||
"autoprefixer": "^10.4.4",
|
||||
|
@ -42,6 +42,6 @@
|
|||
"eslint-config-next": "12.1.4",
|
||||
"postcss": "^8.4.12",
|
||||
"tailwindcss": "^3.0.23",
|
||||
"typescript": "4.6.3"
|
||||
"typescript": "4.6.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import MenuDrawer from './MenuDrawer';
|
|||
interface IProps {
|
||||
loading?: boolean;
|
||||
breadcrumbs?: { name: string; href: string; current?: boolean }[];
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const Layout: React.FC<IProps> = ({ children, loading, breadcrumbs }) => {
|
||||
|
|
|
@ -4,6 +4,7 @@ import React from 'react';
|
|||
interface IProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const MenuDrawer: React.FC<IProps> = ({ children, isOpen, onClose }) => {
|
||||
|
|
|
@ -4,6 +4,7 @@ import React from 'react';
|
|||
interface IProps {
|
||||
title: string;
|
||||
description: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const AuthFormLayout: React.FC<IProps> = ({ children, title, description }) => {
|
||||
|
|
|
@ -4,7 +4,11 @@ import { useAuthStore } from '../../../state/authStore';
|
|||
import Login from './Login';
|
||||
import Onboarding from './Onboarding';
|
||||
|
||||
const AuthWrapper: React.FC = ({ children }) => {
|
||||
interface IProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const AuthWrapper: React.FC<IProps> = ({ children }) => {
|
||||
const [initialLoad, setInitialLoad] = useState(true);
|
||||
const { configured, user, me, fetchConfigured } = useAuthStore();
|
||||
|
||||
|
|
1104
dashboard/yarn.lock
1104
dashboard/yarn.lock
File diff suppressed because it is too large
Load diff
|
@ -22,6 +22,12 @@ if ! command -v ansible-playbook > /dev/null; then
|
|||
sudo pip3 install ansible
|
||||
fi
|
||||
|
||||
# Create seed file with cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1
|
||||
if [[ ! -f "${ROOT_FOLDER}/state/seed" ]]; then
|
||||
echo "Generating seed..."
|
||||
cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1 > "${ROOT_FOLDER}/state/seed"
|
||||
fi
|
||||
|
||||
ansible-playbook ansible/setup.yml -i ansible/hosts -K
|
||||
|
||||
# echo "Configuring permissions..."
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
#!/usr/bin/env bash
|
||||
set -e # Exit immediately if a command exits with a non-zero status.
|
||||
|
||||
# Get field from json file
|
||||
function get_json_field() {
|
||||
local json_file="$1"
|
||||
local field="$2"
|
||||
|
||||
echo $(jq -r ".${field}" "${json_file}")
|
||||
}
|
||||
|
||||
# use greadlink instead of readlink on osx
|
||||
if [[ "$(uname)" == "Darwin" ]]; then
|
||||
readlink=greadlink
|
||||
|
@ -21,6 +13,31 @@ STATE_FOLDER="${ROOT_FOLDER}/state"
|
|||
INTERNAL_IP="$(hostname -I | awk '{print $1}')"
|
||||
DNS_IP=9.9.9.9
|
||||
|
||||
# Get field from json file
|
||||
function get_json_field() {
|
||||
local json_file="$1"
|
||||
local field="$2"
|
||||
|
||||
echo $(jq -r ".${field}" "${json_file}")
|
||||
}
|
||||
|
||||
# Deterministically derives 128 bits of cryptographically secure entropy
|
||||
function derive_entropy() {
|
||||
SEED_FILE="${STATE_FOLDER}/seed"
|
||||
identifier="${1}"
|
||||
tipi_seed=$(cat "${SEED_FILE}") || true
|
||||
|
||||
if [[ -z "$tipi_seed" ]] || [[ -z "$identifier" ]]; then
|
||||
>&2 echo "Missing derivation parameter, this is unsafe, exiting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# We need `sed 's/^.* //'` to trim the "(stdin)= " prefix from some versions of openssl
|
||||
printf "%s" "${identifier}" | openssl dgst -sha256 -hmac "${tipi_seed}" | sed 's/^.* //'
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Get dns ip if pihole is installed
|
||||
str=$(get_json_field ${STATE_FOLDER}/apps.json installed)
|
||||
|
||||
|
@ -47,7 +64,12 @@ fi
|
|||
|
||||
# Copy the app state if it isn't here
|
||||
if [[ ! -f "${STATE_FOLDER}/apps.json" ]]; then
|
||||
cp "${ROOT_FOLDER}/templates/apps-sample.json" "${STATE_FOLDER}/apps.json"
|
||||
cp "${ROOT_FOLDER}/templates/apps-sample.json" "${STATE_FOLDER}/apps.json" && chown -R "1000:1000" "${STATE_FOLDER}/users.json"
|
||||
fi
|
||||
|
||||
# Copy the user state if it isn't here
|
||||
if [[ ! -f "${STATE_FOLDER}/users.json" ]]; then
|
||||
cp "${ROOT_FOLDER}/templates/users-sample.json" "${STATE_FOLDER}/users.json" && chown -R "1000:1000" "${STATE_FOLDER}/users.json"
|
||||
fi
|
||||
|
||||
export DOCKER_CLIENT_TIMEOUT=240
|
||||
|
@ -56,25 +78,36 @@ export COMPOSE_HTTP_TIMEOUT=240
|
|||
echo "Generating config files..."
|
||||
# Remove current .env file
|
||||
[[ -f "${ROOT_FOLDER}/.env" ]] && rm -f "${ROOT_FOLDER}/.env"
|
||||
[[ -f "${ROOT_FOLDER}/system-api/.env" ]] && rm -f "${ROOT_FOLDER}/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"
|
||||
|
||||
# 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"
|
||||
|
||||
for template in "${ENV_FILE}"; do
|
||||
JWT_SECRET=$(derive_entropy "jwt")
|
||||
|
||||
echo $JWT_SECRET
|
||||
|
||||
for template in "${ENV_FILE}" "${ENV_FILE_SYSTEM_API}"; 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}"
|
||||
sed -i "s/<pgid>/${PGID}/g" "${template}"
|
||||
sed -i "s/<tz>/${TZ}/g" "${template}"
|
||||
sed -i "s/<root_folder>/${ROOT_FOLDER}/g" "${template}"
|
||||
sed -i "s/<jwt_secret>/${JWT_SECRET}/g" "${template}"
|
||||
done
|
||||
|
||||
mv -f "$ENV_FILE" "$ROOT_FOLDER/.env"
|
||||
mv -f "$ENV_FILE_SYSTEM_API" "$ROOT_FOLDER/system-api/.env"
|
||||
|
||||
ansible-playbook ansible/start.yml -i ansible/hosts -K
|
||||
|
||||
|
@ -84,8 +117,6 @@ docker-compose --env-file "${ROOT_FOLDER}/.env" up --detach --remove-orphans --b
|
|||
exit 1
|
||||
}
|
||||
|
||||
|
||||
|
||||
str=$(get_json_field ${STATE_FOLDER}/apps.json installed)
|
||||
apps_to_start=($str)
|
||||
|
||||
|
|
15306
system-api/package-lock.json
generated
15306
system-api/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -4,11 +4,12 @@ interface IConfig {
|
|||
NODE_ENV: string;
|
||||
ROOT_FOLDER: string;
|
||||
JWT_SECRET: string;
|
||||
CLIENT_URLS: string[];
|
||||
}
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const { NODE_ENV = 'development', ROOT_FOLDER = '', JWT_SECRET = '' } = process.env;
|
||||
const { NODE_ENV = 'development', ROOT_FOLDER = '', JWT_SECRET = '', INTERNAL_IP = '' } = process.env;
|
||||
|
||||
const missing = [];
|
||||
|
||||
|
@ -22,6 +23,7 @@ const config: IConfig = {
|
|||
NODE_ENV,
|
||||
ROOT_FOLDER,
|
||||
JWT_SECRET,
|
||||
CLIENT_URLS: ['locahost:3000', `${INTERNAL_IP}`, `${INTERNAL_IP}:3000`],
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
|
@ -10,8 +10,7 @@ import systemRoutes from './modules/system/system.routes';
|
|||
import authRoutes from './modules/auth/auth.routes';
|
||||
import { tradeTokenForUser } from './modules/auth/auth.helpers';
|
||||
import cookieParser from 'cookie-parser';
|
||||
|
||||
// suExec.init();
|
||||
import config from './config';
|
||||
|
||||
const app = express();
|
||||
const port = 3001;
|
||||
|
@ -24,7 +23,7 @@ if (isProd) {
|
|||
app.use(helmet());
|
||||
}
|
||||
|
||||
app.use(cors());
|
||||
app.use(cors({ credentials: true, origin: config.CLIENT_URLS }));
|
||||
|
||||
// Get user from token
|
||||
app.use((req, res, next) => {
|
||||
|
|
3
templates/env-api-sample
Normal file
3
templates/env-api-sample
Normal file
|
@ -0,0 +1,3 @@
|
|||
ROOT_FOLDER=<root_folder>
|
||||
JWT_SECRET=<jwt_secret>
|
||||
INTERNAL_IP=<internal_ip>
|
Loading…
Add table
Reference in a new issue