feat(update front-end to support a custom domain api): n

This commit is contained in:
Nicolas Meienberger 2022-09-04 12:31:57 +00:00 committed by Nicolas Meienberger
parent b483a284bd
commit 97bb074ae5
25 changed files with 257 additions and 81 deletions

View file

@ -92,6 +92,15 @@ To stop Tipi, run the stop script.
sudo ./scripts/stop.sh
```
## 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.
```bash
sudo ./scripts/start.sh --domain mydomain.com
```
A Let's Encrypt certificate will be generated and installed automatically. Make sure to have port 443 open on your firewall and that your domain has an **A** record pointing to your server IP.
## ❤️ Contributing
Tipi is made to be very easy to plug in new apps. We welcome and appreciate new contributions.

View file

@ -14,6 +14,7 @@ services:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ${PWD}/traefik:/root/.config
- ${PWD}/traefik/shared:/shared
- ${PWD}/traefik/letsencrypt:/letsencrypt
networks:
- tipi_main_network
@ -67,8 +68,26 @@ services:
POSTGRES_HOST: tipi-db
APPS_REPO_ID: ${APPS_REPO_ID}
APPS_REPO_URL: ${APPS_REPO_URL}
DOMAIN: ${DOMAIN}
networks:
- tipi_main_network
labels:
traefik.enable: true
# Web
traefik.http.routers.api.rule: PathPrefix(`/api`)
traefik.http.routers.api.service: api
traefik.http.routers.api.entrypoints: web
traefik.http.routers.api.middlewares: api-stripprefix
traefik.http.services.api.loadbalancer.server.port: 3001
# Websecure
traefik.http.routers.api-secure.rule: (Host(`${DOMAIN}`) && PathPrefix(`/api`))
traefik.http.routers.api-secure.entrypoints: websecure
traefik.http.routers.api-secure.service: api-secure
traefik.http.routers.api-secure.tls.certresolver: myresolver
traefik.http.routers.api-secure.middlewares: api-stripprefix
traefik.http.services.api-secure.loadbalancer.server.port: 3001
# Middlewares
traefik.http.middlewares.api-stripprefix.stripprefix.prefixes: /api
dashboard:
build:
@ -82,10 +101,40 @@ services:
- tipi_main_network
environment:
- INTERNAL_IP=${INTERNAL_IP}
- DOMAIN=${DOMAIN}
volumes:
- ${PWD}/packages/dashboard/src:/dashboard/src
# - /dashboard/node_modules
# - /dashboard/.next
labels:
traefik.enable: true
traefik.http.routers.dashboard-redirect.rule: PathPrefix("/")
traefik.http.routers.dashboard-redirect.entrypoints: web
traefik.http.routers.dashboard-redirect.middlewares: redirect-middleware
traefik.http.routers.dashboard-redirect.service: dashboard
traefik.http.services.dashboard-redirect.loadbalancer.server.port: 3000
traefik.http.routers.dashboard-redirect-secure.rule: Host(`${DOMAIN}`) && PathPrefix(`/`)
traefik.http.routers.dashboard-redirect-secure.entrypoints: websecure
traefik.http.routers.dashboard-redirect-secure.middlewares: redirect-middleware
traefik.http.routers.dashboard-redirect-secure.service: dashboard
traefik.http.routers.dashboard-redirect-secure.tls.certresolver: myresolver
traefik.http.services.dashboard-redirect-secure.loadbalancer.server.port: 3000
# Web
traefik.http.routers.dashboard.rule: PathPrefix("/dashboard")
traefik.http.routers.dashboard.service: dashboard
traefik.http.routers.dashboard.entrypoints: web
traefik.http.services.dashboard.loadbalancer.server.port: 3000
# Websecure
traefik.http.routers.dashboard-secure.rule: Host(`${DOMAIN}`) && PathPrefix(`/dashboard`)
traefik.http.routers.dashboard-secure.service: dashboard-secure
traefik.http.routers.dashboard-secure.entrypoints: websecure
traefik.http.routers.dashboard-secure.tls.certresolver: myresolver
traefik.http.services.dashboard-secure.loadbalancer.server.port: 3000
# Middlewares
traefik.http.middlewares.redirect-middleware.redirectregex.regex: .*
traefik.http.middlewares.redirect-middleware.redirectregex.replacement: /dashboard
networks:
tipi_main_network:

View file

@ -8,6 +8,7 @@ services:
ports:
- ${NGINX_PORT-80}:80
- ${NGINX_PORT_SSL-443}:443
- 8080:8080
command: --providers.docker
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
@ -63,10 +64,28 @@ services:
NODE_ENV: production
APPS_REPO_ID: ${APPS_REPO_ID}
APPS_REPO_URL: ${APPS_REPO_URL}
DOMAIN: ${DOMAIN}
dns:
- ${DNS_IP}
networks:
- tipi_main_network
labels:
traefik.enable: true
# Web
traefik.http.routers.api.rule: PathPrefix(`/api`)
traefik.http.routers.api.service: api
traefik.http.routers.api.entrypoints: web
traefik.http.routers.api.middlewares: api-stripprefix
traefik.http.services.api.loadbalancer.server.port: 3001
# Websecure
traefik.http.routers.api-secure.rule: (Host(`${DOMAIN}`) && PathPrefix(`/api`))
traefik.http.routers.api-secure.entrypoints: websecure
traefik.http.routers.api-secure.service: api-secure
traefik.http.routers.api-secure.tls.certresolver: myresolver
traefik.http.routers.api-secure.middlewares: api-stripprefix
traefik.http.services.api-secure.loadbalancer.server.port: 3001
# Middlewares
traefik.http.middlewares.api-stripprefix.stripprefix.prefixes: /api
dashboard:
image: meienberger/runtipi:rc-${TIPI_VERSION}
@ -79,12 +98,36 @@ services:
environment:
INTERNAL_IP: ${INTERNAL_IP}
NODE_ENV: production
DOMAIN: ${DOMAIN}
labels:
traefik.enable: true
traefik.http.routers.dashboard.rule: PathPrefix("/") # Host(`tipi.local`) &&
traefik.http.routers.dashboard.entrypoints: webinsecure
traefik.http.routers.dashboard-redirect.rule: PathPrefix("/")
traefik.http.routers.dashboard-redirect.entrypoints: web
traefik.http.routers.dashboard-redirect.middlewares: redirect-middleware
traefik.http.routers.dashboard-redirect.service: dashboard
traefik.http.services.dashboard-redirect.loadbalancer.server.port: 3000
traefik.http.routers.dashboard-redirect-secure.rule: Host(`${DOMAIN}`) && PathPrefix(`/`)
traefik.http.routers.dashboard-redirect-secure.entrypoints: websecure
traefik.http.routers.dashboard-redirect-secure.middlewares: redirect-middleware
traefik.http.routers.dashboard-redirect-secure.service: dashboard
traefik.http.routers.dashboard-redirect-secure.tls.certresolver: myresolver
traefik.http.services.dashboard-redirect-secure.loadbalancer.server.port: 3000
# Web
traefik.http.routers.dashboard.rule: PathPrefix("/dashboard")
traefik.http.routers.dashboard.service: dashboard
traefik.http.routers.dashboard.entrypoints: web
traefik.http.services.dashboard.loadbalancer.server.port: 3000
# Websecure
traefik.http.routers.dashboard-secure.rule: Host(`${DOMAIN}`) && PathPrefix(`/dashboard`)
traefik.http.routers.dashboard-secure.service: dashboard-secure
traefik.http.routers.dashboard-secure.entrypoints: websecure
traefik.http.routers.dashboard-secure.tls.certresolver: myresolver
traefik.http.services.dashboard-secure.loadbalancer.server.port: 3000
# Middlewares
traefik.http.middlewares.redirect-middleware.redirectregex.regex: .*
traefik.http.middlewares.redirect-middleware.redirectregex.replacement: /dashboard
networks:
tipi_main_network:

View file

@ -64,10 +64,28 @@ services:
NODE_ENV: production
APPS_REPO_ID: ${APPS_REPO_ID}
APPS_REPO_URL: ${APPS_REPO_URL}
DOMAIN: ${DOMAIN}
dns:
- ${DNS_IP}
networks:
- tipi_main_network
labels:
traefik.enable: true
# Web
traefik.http.routers.api.rule: PathPrefix(`/api`)
traefik.http.routers.api.service: api
traefik.http.routers.api.entrypoints: web
traefik.http.routers.api.middlewares: api-stripprefix
traefik.http.services.api.loadbalancer.server.port: 3001
# Websecure
traefik.http.routers.api-secure.rule: (Host(`${DOMAIN}`) && PathPrefix(`/api`))
traefik.http.routers.api-secure.entrypoints: websecure
traefik.http.routers.api-secure.service: api-secure
traefik.http.routers.api-secure.tls.certresolver: myresolver
traefik.http.routers.api-secure.middlewares: api-stripprefix
traefik.http.services.api-secure.loadbalancer.server.port: 3001
# Middlewares
traefik.http.middlewares.api-stripprefix.stripprefix.prefixes: /api
dashboard:
image: meienberger/runtipi:${TIPI_VERSION}
@ -81,12 +99,36 @@ services:
environment:
INTERNAL_IP: ${INTERNAL_IP}
NODE_ENV: production
DOMAIN: ${DOMAIN}
labels:
traefik.enable: true
traefik.http.routers.dashboard.rule: PathPrefix("/") # Host(`tipi.local`) &&
traefik.http.routers.dashboard.entrypoints: webinsecure
traefik.http.routers.dashboard-redirect.rule: PathPrefix("/")
traefik.http.routers.dashboard-redirect.entrypoints: web
traefik.http.routers.dashboard-redirect.middlewares: redirect-middleware
traefik.http.routers.dashboard-redirect.service: dashboard
traefik.http.services.dashboard-redirect.loadbalancer.server.port: 3000
traefik.http.routers.dashboard-redirect-secure.rule: Host(`${DOMAIN}`) && PathPrefix(`/`)
traefik.http.routers.dashboard-redirect-secure.entrypoints: websecure
traefik.http.routers.dashboard-redirect-secure.middlewares: redirect-middleware
traefik.http.routers.dashboard-redirect-secure.service: dashboard
traefik.http.routers.dashboard-redirect-secure.tls.certresolver: myresolver
traefik.http.services.dashboard-redirect-secure.loadbalancer.server.port: 3000
# Web
traefik.http.routers.dashboard.rule: PathPrefix("/dashboard")
traefik.http.routers.dashboard.service: dashboard
traefik.http.routers.dashboard.entrypoints: web
traefik.http.services.dashboard.loadbalancer.server.port: 3000
# Websecure
traefik.http.routers.dashboard-secure.rule: Host(`${DOMAIN}`) && PathPrefix(`/dashboard`)
traefik.http.routers.dashboard-secure.service: dashboard-secure
traefik.http.routers.dashboard-secure.entrypoints: websecure
traefik.http.routers.dashboard-secure.tls.certresolver: myresolver
traefik.http.services.dashboard-secure.loadbalancer.server.port: 3000
# Middlewares
traefik.http.middlewares.redirect-middleware.redirectregex.regex: .*
traefik.http.middlewares.redirect-middleware.redirectregex.replacement: /dashboard
networks:
tipi_main_network:

View file

@ -1,5 +1,5 @@
/** @type {import('next').NextConfig} */
const { NODE_ENV, INTERNAL_IP } = process.env;
const { INTERNAL_IP, DOMAIN } = process.env;
const nextConfig = {
webpackDevMiddleware: (config) => {
@ -12,7 +12,9 @@ const nextConfig = {
reactStrictMode: true,
env: {
INTERNAL_IP: INTERNAL_IP,
NEXT_PUBLIC_DOMAIN: DOMAIN,
},
basePath: '/dashboard',
};
module.exports = nextConfig;

View file

@ -2,8 +2,8 @@ import React from 'react';
import { useSytemStore } from '../../state/systemStore';
const AppLogo: React.FC<{ id: string; size?: number; className?: string; alt?: string }> = ({ id, size = 80, className = '', alt = '' }) => {
const { internalIp } = useSytemStore();
const logoUrl = `http://${internalIp}:3001/apps/${id}/metadata/logo.jpg`;
const { baseUrl } = useSytemStore();
const logoUrl = `${baseUrl}/apps/${id}/metadata/logo.jpg`;
return (
<div aria-label={alt} className={`drop-shadow ${className}`} style={{ width: size, height: size }}>

View file

@ -2,6 +2,7 @@ import React from 'react';
import Link from 'next/link';
import { Flex } from '@chakra-ui/react';
import { FiMenu } from 'react-icons/fi';
import { getUrl } from '../../core/helpers/url-helpers';
interface IProps {
onClickMenu: () => void;
@ -16,7 +17,7 @@ const Header: React.FC<IProps> = ({ onClickMenu }) => {
</div>
<Flex justifyContent="center" flex="1">
<Link href="/" passHref>
<img src="/tipi.png" alt="Tipi Logo" width={30} height={30} />
<img src={getUrl('tipi.png')} alt="Tipi Logo" width={30} height={30} />
</Link>
</Flex>
</Flex>

View file

@ -9,6 +9,7 @@ import clsx from 'clsx';
import { useRouter } from 'next/router';
import { IconType } from 'react-icons';
import { useLogoutMutation, useVersionQuery } from '../../generated/graphql';
import { getUrl } from '../../core/helpers/url-helpers';
const SideMenu: React.FC = () => {
const router = useRouter();
@ -45,7 +46,7 @@ const SideMenu: React.FC = () => {
return (
<Box className="flex-1 flex flex-col p-0 md:p-4">
<img className="self-center mb-5 logo mt-0 md:mt-5" src="/tipi.png" width={512} height={512} />
<img className="self-center mb-5 logo mt-0 md:mt-5" src={getUrl('tipi.png')} width={512} height={512} />
<List spacing={3} className="pt-5">
{renderMenuItem('Dashboard', '', AiOutlineDashboard)}
{renderMenuItem('My Apps', 'apps', AiOutlineAppstore)}

View file

@ -12,7 +12,7 @@ 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 BASE_URL = getState().baseUrl;
const response = await axios.request<T & { error?: string }>({
method,

View file

@ -1,8 +1,8 @@
import { ApolloClient, from, InMemoryCache } from '@apollo/client';
import links from './links';
export const createApolloClient = async (ip: string): Promise<ApolloClient<any>> => {
const additiveLink = from([links.errorLink, links.httpLink(ip)]);
export const createApolloClient = async (url: string): Promise<ApolloClient<any>> => {
const additiveLink = from([links.errorLink, links.httpLink(url)]);
return new ApolloClient({
link: additiveLink,

View file

@ -1,9 +1,10 @@
import { HttpLink } from '@apollo/client';
const httpLink = (ip: string) =>
new HttpLink({
uri: `http://${ip}:3001/graphql`,
const httpLink = (url: string) => {
return new HttpLink({
uri: `${url}/graphql`,
credentials: 'include',
});
};
export default httpLink;

View file

@ -3,10 +3,9 @@ import axios from 'axios';
import { useSytemStore } from '../state/systemStore';
const fetcher: BareFetcher<any> = (url: string) => {
const { getState } = useSytemStore;
const BASE_URL = `http://${getState().internalIp}:3001`;
const { baseUrl } = useSytemStore.getState();
return axios.get(url, { baseURL: BASE_URL, withCredentials: true }).then((res) => res.data);
return axios.get(url, { baseURL: baseUrl, withCredentials: true }).then((res) => res.data);
};
export default fetcher;

View file

@ -0,0 +1,10 @@
export const getUrl = (url: string) => {
const domain = process.env.NEXT_PUBLIC_DOMAIN;
let prefix = '';
if (domain !== 'tipi.localhost') {
prefix = 'dashboard';
}
return `/${prefix}/${url}`;
};

View file

@ -4,6 +4,7 @@ import axios from 'axios';
import useSWR, { BareFetcher } from 'swr';
import { createApolloClient } from '../core/apollo/client';
import { useSytemStore } from '../state/systemStore';
import { getUrl } from '../core/helpers/url-helpers';
interface IReturnProps {
client?: ApolloClient<unknown>;
@ -11,18 +12,18 @@ interface IReturnProps {
}
const fetcher: BareFetcher<any> = (url: string) => {
return axios.get(url).then((res) => res.data);
return axios.get(getUrl(url)).then((res) => res.data);
};
export default function useCachedResources(): IReturnProps {
const { data } = useSWR('/api/ip', fetcher);
const { internalIp, setInternalIp } = useSytemStore();
const { data } = useSWR('api/ip', fetcher);
const { baseUrl, setBaseUrl, setInternalIp, setDomain } = useSytemStore();
const [isLoadingComplete, setLoadingComplete] = useState(false);
const [client, setClient] = useState<ApolloClient<unknown>>();
async function loadResourcesAndDataAsync(ip: string) {
async function loadResourcesAndDataAsync(url: string) {
try {
const restoredClient = await createApolloClient(ip);
const restoredClient = await createApolloClient(url);
setClient(restoredClient);
} catch (error) {
@ -34,16 +35,24 @@ export default function useCachedResources(): IReturnProps {
}
useEffect(() => {
if (data?.ip && !internalIp) {
setInternalIp(data.ip);
const { ip, domain } = data || {};
if (ip && !baseUrl) {
setInternalIp(ip);
setDomain(domain);
if (!domain || domain === 'tipi.localhost') {
setBaseUrl(`http://${ip}/api`);
} else {
setBaseUrl(`https://${domain}/api`);
}
}
}, [data?.ip, internalIp, setInternalIp]);
}, [baseUrl, data.ip, data.domain, setBaseUrl, data, setInternalIp, setDomain]);
useEffect(() => {
if (internalIp) {
loadResourcesAndDataAsync(internalIp);
if (baseUrl) {
loadResourcesAndDataAsync(baseUrl);
}
}, [internalIp]);
}, [baseUrl]);
return { client, isLoadingComplete };
}

View file

@ -45,7 +45,7 @@ const InstallForm: React.FC<IProps> = ({ formFields, onSubmit, initalValues, exp
render={({ input, meta }) => <FormInput className="mb-3" error={meta.error} isInvalid={meta.invalid && (meta.submitError || meta.submitFailed)} label="Domain name" {...input} />}
/>
<span className="text-sm">
Make sur the domain contains an <strong>A</strong> record pointing to your IP.
Make sure this exact domain contains an <strong>A</strong> record pointing to your IP.
</span>
</>
)}

View file

@ -1,5 +1,6 @@
import { Container, Flex, SlideFade, Text } from '@chakra-ui/react';
import React from 'react';
import { getUrl } from '../../../core/helpers/url-helpers';
interface IProps {
title: string;
@ -12,7 +13,7 @@ const AuthFormLayout: React.FC<IProps> = ({ children, title, description }) => {
<Container maxW="1250px">
<Flex flex={1} height="100vh" overflowY="hidden">
<SlideFade in className="flex flex-1 flex-col justify-center items-center" offsetY="20px">
<img className="self-center mb-5 logo" src="/tipi.png" width={512} height={512} />
<img className="self-center mb-5 logo" src={getUrl('tipi.png')} width={512} height={512} />
<Text className="text-xl md:text-2xl lg:text-5xl font-bold" size="3xl">
{title}
</Text>

View file

@ -2,16 +2,18 @@ import React from 'react';
import { Html, Head, Main, NextScript } from 'next/document';
import { ColorModeScript } from '@chakra-ui/react';
import { theme } from '../styles/theme';
import { getUrl } from '../core/helpers/url-helpers';
export default function MyDocument() {
return (
<Html lang="en">
<Head>
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="manifest" href="/site.webmanifest" />
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5" />
<title>Tipi - Dashboard</title>
<link rel="apple-touch-icon" sizes="180x180" href={getUrl('apple-touch-icon.png')} />
<link rel="icon" type="image/png" sizes="32x32" href={getUrl('favicon-32x32.png')} />
<link rel="icon" type="image/png" sizes="16x16" href={getUrl('favicon-16x16.png')} />
<link rel="manifest" href={getUrl('site.webmanifest')} />
<link rel="mask-icon" href={getUrl('safari-pinned-tab.svg')} color="#5bbad5" />
<meta name="msapplication-TileColor" content="#da532c" />
<meta name="theme-color" content="#ffffff" />
</Head>

View file

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

View file

@ -1,19 +0,0 @@
import create from 'zustand';
import api from '../core/api';
type AppsStore = {
internalIp: string;
fetchInternalIp: () => void;
};
export const useNetworkStore = create<AppsStore>((set) => ({
internalIp: '',
fetchInternalIp: async () => {
const response = await api.fetch<string>({
endpoint: '/network/internal-ip',
method: 'get',
});
set({ internalIp: response });
},
}));

View file

@ -1,11 +1,19 @@
import create from 'zustand';
type Store = {
baseUrl: string;
internalIp: string;
setInternalIp: (internalIp: string) => void;
domain: string;
setDomain: (domain: string) => void;
setBaseUrl: (url: string) => void;
setInternalIp: (ip: string) => void;
};
export const useSytemStore = create<Store>((set) => ({
baseUrl: '',
internalIp: '',
setInternalIp: (internalIp: string) => set((state) => ({ ...state, internalIp })),
domain: '',
setDomain: (domain: string) => set((state) => ({ ...state, domain })),
setBaseUrl: (url: string) => set((state) => ({ ...state, baseUrl: url })),
setInternalIp: (ip: string) => set((state) => ({ ...state, internalIp: ip })),
}));

View file

@ -34,6 +34,7 @@ const {
NGINX_PORT = '80',
APPS_REPO_ID = '',
APPS_REPO_URL = '',
DOMAIN = '',
} = process.env;
const config: IConfig = {
@ -45,7 +46,7 @@ const config: IConfig = {
NODE_ENV,
ROOT_FOLDER: '/tipi',
JWT_SECRET,
CLIENT_URLS: ['http://localhost:3000', `http://${INTERNAL_IP}`, `http://${INTERNAL_IP}:${NGINX_PORT}`, `http://${INTERNAL_IP}:3000`],
CLIENT_URLS: ['http://localhost:3000', `http://${INTERNAL_IP}`, `http://${INTERNAL_IP}:${NGINX_PORT}`, `http://${INTERNAL_IP}:3000`, `https://${DOMAIN}`],
VERSION: TIPI_VERSION,
ROOT_FOLDER_HOST,
APPS_REPO_ID,

View file

@ -18,27 +18,23 @@ import recover from './core/updates/recover-migrations';
import { cloneRepo, updateRepo } from './helpers/repo-helpers';
import startJobs from './core/jobs/jobs';
let corsOptions = __prod__
? {
credentials: true,
origin: function (origin: any, callback: any) {
// disallow requests with no origin
if (!origin) return callback(new Error('Not allowed by CORS'), false);
if (config.CLIENT_URLS.includes(origin)) {
return callback(null, true);
}
const message = "The CORS policy for this origin doesn't allow access from the particular origin.";
return callback(new Error(message), false);
},
let corsOptions = {
credentials: true,
origin: function (origin: any, callback: any) {
if (!__prod__) {
return callback(null, true);
}
: {
credential: true,
origin: function (origin: any, callback: any) {
return callback(null, true);
},
};
// disallow requests with no origin
if (!origin) return callback(new Error('Not allowed by CORS'), false);
if (config.CLIENT_URLS.includes(origin)) {
return callback(null, true);
}
const message = "The CORS policy for this origin doesn't allow access from the particular origin.";
return callback(new Error(message), false);
},
};
const main = async () => {
try {

View file

@ -10,6 +10,7 @@ fi
NGINX_PORT=80
PROXY_PORT=8080
DOMAIN=tipi.localhost
while [ -n "$1" ]; do # while loop starts
case "$1" in
@ -37,6 +38,17 @@ while [ -n "$1" ]; do # while loop starts
fi
shift
;;
--domain)
domain="$2"
if [[ "${domain}" =~ ^[a-zA-Z0-9.-]+$ ]]; then
DOMAIN="${domain}"
else
echo "--domain must be a valid domain"
exit 1
fi
shift
;;
--)
shift # The double dash makes them parameters
break
@ -58,6 +70,12 @@ if [[ "$(uname)" != "Linux" ]]; then
exit 1
fi
# If port is not 80 and domain is not tipi.localhost, we exit
if [[ "${NGINX_PORT}" != "80" ]] && [[ "${DOMAIN}" != "tipi.localhost" ]]; then
echo "Using a custom domain with a custom port is not supported"
exit 1
fi
ROOT_FOLDER="$($readlink -f $(dirname "${BASH_SOURCE[0]}")/..)"
STATE_FOLDER="${ROOT_FOLDER}/state"
SED_ROOT_FOLDER="$(echo $ROOT_FOLDER | sed 's/\//\\\//g')"
@ -154,6 +172,7 @@ for template in ${ENV_FILE}; do
sed -i "s/<postgres_password>/${POSTGRES_PASSWORD}/g" "${template}"
sed -i "s/<apps_repo_id>/${REPO_ID}/g" "${template}"
sed -i "s/<apps_repo_url>/${APPS_REPOSITORY_ESCAPED}/g" "${template}"
sed -i "s/<domain>/${DOMAIN}/g" "${template}"
done
mv -f "$ENV_FILE" "$ROOT_FOLDER/.env"

View file

@ -12,4 +12,5 @@ JWT_SECRET=<jwt_secret>
ROOT_FOLDER_HOST=<root_folder>
NGINX_PORT=<nginx_port>
PROXY_PORT=<proxy_port>
POSTGRES_PASSWORD=<postgres_password>
POSTGRES_PASSWORD=<postgres_password>
DOMAIN=<domain>

View file

@ -23,4 +23,4 @@ certificatesResolvers:
entryPoint: web
log:
level: DEBUG
level: ERROR