start.sh 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. #!/usr/bin/env bash
  2. set -o errexit
  3. set -o nounset
  4. set -o pipefail
  5. if [[ "${TRACE-0}" == "1" ]]; then
  6. set -o xtrace
  7. fi
  8. source "${BASH_SOURCE%/*}/common.sh"
  9. ROOT_FOLDER="${PWD}"
  10. # Cleanup and ensure environment
  11. ensure_linux
  12. ensure_pwd
  13. ensure_root
  14. clean_logs
  15. ### --------------------------------
  16. ### Pre-configuration
  17. ### --------------------------------
  18. "${ROOT_FOLDER}/scripts/configure.sh"
  19. STATE_FOLDER="${ROOT_FOLDER}/state"
  20. # Create seed file with cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1
  21. if [[ ! -f "${STATE_FOLDER}/seed" ]]; then
  22. echo "Generating seed..."
  23. if ! tr </dev/urandom -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1 >"${STATE_FOLDER}/seed"; then
  24. echo "Created seed file..."
  25. fi
  26. fi
  27. ### --------------------------------
  28. ### General variables
  29. ### --------------------------------
  30. DEFAULT_TZ="Etc\/UTC"
  31. TZ="$(timedatectl | grep "Time zone" | awk '{print $3}' | sed 's/\//\\\//g')"
  32. if [[ -z "$TZ" ]]; then
  33. TZ="$DEFAULT_TZ"
  34. fi
  35. NGINX_PORT=80
  36. NGINX_PORT_SSL=443
  37. DOMAIN=tipi.localhost
  38. SED_ROOT_FOLDER="$(echo "$ROOT_FOLDER" | sed 's/\//\\\//g')"
  39. DNS_IP="9.9.9.9" # Default to Quad9 DNS
  40. ARCHITECTURE="$(uname -m | tr '[:upper:]' '[:lower:]')"
  41. apps_repository="https://github.com/meienberger/runtipi-appstore"
  42. REPO_ID="$("${ROOT_FOLDER}"/scripts/git.sh get_hash ${apps_repository})"
  43. APPS_REPOSITORY_ESCAPED="$(echo ${apps_repository} | sed 's/\//\\\//g')"
  44. JWT_SECRET=$(derive_entropy "jwt")
  45. POSTGRES_PASSWORD=$(derive_entropy "postgres")
  46. POSTGRES_USERNAME=tipi
  47. POSTGRES_DBNAME=tipi
  48. POSTGRES_PORT=5432
  49. POSTGRES_HOST=tipi-db
  50. TIPI_VERSION=$(get_json_field "${ROOT_FOLDER}/package.json" version)
  51. storage_path="${ROOT_FOLDER}"
  52. STORAGE_PATH_ESCAPED="$(echo "${storage_path}" | sed 's/\//\\\//g')"
  53. REDIS_HOST=tipi-redis
  54. DEMO_MODE=false
  55. INTERNAL_IP=
  56. if [[ "$ARCHITECTURE" == "aarch64" ]] || [[ "$ARCHITECTURE" == "armv8"* ]]; then
  57. ARCHITECTURE="arm64"
  58. elif [[ "$ARCHITECTURE" == "x86_64" ]]; then
  59. ARCHITECTURE="amd64"
  60. fi
  61. # If none of the above conditions are met, the architecture is not supported
  62. if [[ "$ARCHITECTURE" != "arm64" ]] && [[ "$ARCHITECTURE" != "amd64" ]]; then
  63. echo "Architecture ${ARCHITECTURE} not supported if you think this is a mistake, please open an issue on GitHub."
  64. exit 1
  65. fi
  66. ### --------------------------------
  67. ### CLI arguments
  68. ### --------------------------------
  69. while [ -n "${1-}" ]; do
  70. case "$1" in
  71. --rc) rc="true" ;;
  72. --ci) ci="true" ;;
  73. --demo) DEMO_MODE=true ;;
  74. --port)
  75. port="${2-}"
  76. if [[ "${port}" =~ ^[0-9]+$ ]]; then
  77. NGINX_PORT="${port}"
  78. else
  79. echo "--port must be a number"
  80. exit 1
  81. fi
  82. shift
  83. ;;
  84. --ssl-port)
  85. ssl_port="${2-}"
  86. if [[ "${ssl_port}" =~ ^[0-9]+$ ]]; then
  87. NGINX_PORT_SSL="${ssl_port}"
  88. else
  89. echo "--ssl-port must be a number"
  90. exit 1
  91. fi
  92. shift
  93. ;;
  94. --domain)
  95. domain="${2-}"
  96. if [[ "${domain}" =~ ^[a-zA-Z0-9.-]+$ ]]; then
  97. DOMAIN="${domain}"
  98. else
  99. echo "--domain must be a valid domain"
  100. exit 1
  101. fi
  102. shift
  103. ;;
  104. --listen-ip)
  105. listen_ip="${2-}"
  106. if [[ "${listen_ip}" =~ ^[a-fA-F0-9.:]+$ ]]; then
  107. INTERNAL_IP="${listen_ip}"
  108. else
  109. echo "--listen-ip must be a valid IP address"
  110. exit 1
  111. fi
  112. shift
  113. ;;
  114. --)
  115. shift # The double dash makes them parameters
  116. break
  117. ;;
  118. *) echo "Option $1 not recognized" && exit 1 ;;
  119. esac
  120. shift
  121. done
  122. if [[ -z "${INTERNAL_IP:-}" ]]; then
  123. network_interface="$(ip route | grep default | awk '{print $5}' | uniq)"
  124. network_interface_count=$(echo "$network_interface" | wc -l)
  125. if [[ "$network_interface_count" -eq 0 ]]; then
  126. echo "No network interface found!"
  127. exit 1
  128. elif [[ "$network_interface_count" -gt 1 ]]; then
  129. echo "Found multiple network interfaces. Please select one of the following interfaces:"
  130. echo "$network_interface"
  131. while true; do
  132. read -rp "> " USER_NETWORK_INTERFACE
  133. if echo "$network_interface" | grep -x "$USER_NETWORK_INTERFACE"; then
  134. network_interface="$USER_NETWORK_INTERFACE"
  135. break
  136. else
  137. echo "Please select one of the interfaces above. (CTRL+C to abort)"
  138. fi
  139. done
  140. fi
  141. INTERNAL_IP="$(ip addr show "${network_interface}" | grep "inet " | awk '{print $2}' | cut -d/ -f1)"
  142. internal_ip_count=$(echo "$INTERNAL_IP" | wc -l)
  143. if [[ "$internal_ip_count" -eq 0 ]]; then
  144. echo "No IP address found for network interface ${network_interface}! Set the IP address manually with --listen-ip or with the listenIp field in settings.json."
  145. exit 1
  146. elif [[ "$internal_ip_count" -gt 1 ]]; then
  147. echo "Found multiple IP addresses for network interface ${network_interface}. Please select one of the following IP addresses:"
  148. echo "$INTERNAL_IP"
  149. while true; do
  150. read -rp "> " USER_INTERNAL_IP
  151. if echo "$INTERNAL_IP" | grep -x "$USER_INTERNAL_IP"; then
  152. INTERNAL_IP="$USER_INTERNAL_IP"
  153. break
  154. else
  155. echo "Please select one of the IP addresses above. (CTRL+C to abort)"
  156. fi
  157. done
  158. fi
  159. fi
  160. # If port is not 80 and domain is not tipi.localhost, we exit
  161. if [[ "${NGINX_PORT}" != "80" ]] && [[ "${DOMAIN}" != "tipi.localhost" ]]; then
  162. echo "Using a custom domain with a custom port is not supported"
  163. exit 1
  164. fi
  165. ### --------------------------------
  166. ### Watcher and system-info
  167. ### --------------------------------
  168. echo "Running system-info.sh..."
  169. "${ROOT_FOLDER}/scripts/system-info.sh"
  170. kill_watcher
  171. "${ROOT_FOLDER}/scripts/watcher.sh" &
  172. ### --------------------------------
  173. ### settings.json overrides
  174. ### --------------------------------
  175. echo "Generating config files..."
  176. # Override vars with values from settings.json
  177. if [[ -f "${STATE_FOLDER}/settings.json" ]]; then
  178. # If dnsIp is set in settings.json, use it
  179. if [[ "$(get_json_field "${STATE_FOLDER}/settings.json" dnsIp)" != "null" ]]; then
  180. DNS_IP=$(get_json_field "${STATE_FOLDER}/settings.json" dnsIp)
  181. fi
  182. # If domain is set in settings.json, use it
  183. if [[ "$(get_json_field "${STATE_FOLDER}/settings.json" domain)" != "null" ]]; then
  184. DOMAIN=$(get_json_field "${STATE_FOLDER}/settings.json" domain)
  185. fi
  186. # If appsRepoUrl is set in settings.json, use it
  187. if [[ "$(get_json_field "${STATE_FOLDER}/settings.json" appsRepoUrl)" != "null" ]]; then
  188. apps_repository=$(get_json_field "${STATE_FOLDER}/settings.json" appsRepoUrl)
  189. APPS_REPOSITORY_ESCAPED="$(echo "${apps_repository}" | sed 's/\//\\\//g')"
  190. REPO_ID="$("${ROOT_FOLDER}"/scripts/git.sh get_hash "${apps_repository}")"
  191. fi
  192. # If port is set in settings.json, use it
  193. if [[ "$(get_json_field "${STATE_FOLDER}/settings.json" port)" != "null" ]]; then
  194. NGINX_PORT=$(get_json_field "${STATE_FOLDER}/settings.json" port)
  195. fi
  196. # If sslPort is set in settings.json, use it
  197. if [[ "$(get_json_field "${STATE_FOLDER}/settings.json" sslPort)" != "null" ]]; then
  198. NGINX_PORT_SSL=$(get_json_field "${STATE_FOLDER}/settings.json" sslPort)
  199. fi
  200. # If listenIp is set in settings.json, use it
  201. if [[ "$(get_json_field "${STATE_FOLDER}/settings.json" listenIp)" != "null" ]]; then
  202. INTERNAL_IP=$(get_json_field "${STATE_FOLDER}/settings.json" listenIp)
  203. fi
  204. # If storagePath is set in settings.json, use it
  205. storage_path_settings=$(get_json_field "${STATE_FOLDER}/settings.json" storagePath)
  206. if [[ "${storage_path_settings}" != "null" && "${storage_path_settings}" != "" ]]; then
  207. storage_path="${storage_path_settings}"
  208. STORAGE_PATH_ESCAPED="$(echo "${storage_path}" | sed 's/\//\\\//g')"
  209. fi
  210. fi
  211. new_values="DOMAIN=${DOMAIN}\nDNS_IP=${DNS_IP}\nAPPS_REPOSITORY=${APPS_REPOSITORY_ESCAPED}\nREPO_ID=${REPO_ID}\nNGINX_PORT=${NGINX_PORT}\nNGINX_PORT_SSL=${NGINX_PORT_SSL}\nINTERNAL_IP=${INTERNAL_IP}\nSTORAGE_PATH=${STORAGE_PATH_ESCAPED}\nTZ=${TZ}\nJWT_SECRET=${JWT_SECRET}\nROOT_FOLDER=${SED_ROOT_FOLDER}\nTIPI_VERSION=${TIPI_VERSION}\nARCHITECTURE=${ARCHITECTURE}"
  212. write_log "Final values: \n${new_values}"
  213. ### --------------------------------
  214. ### env file generation
  215. ### --------------------------------
  216. ENV_FILE=$(mktemp)
  217. [[ -f "${ROOT_FOLDER}/.env" ]] && rm -f "${ROOT_FOLDER}/.env"
  218. [[ -f "$ROOT_FOLDER/templates/env-sample" ]] && cp "$ROOT_FOLDER/templates/env-sample" "$ENV_FILE"
  219. # Function below is modified from Umbrel
  220. # Required Notice: Copyright
  221. # Umbrel (https://umbrel.com)
  222. for template in ${ENV_FILE}; do
  223. sed -i "s/<dns_ip>/${DNS_IP}/g" "${template}"
  224. sed -i "s/<internal_ip>/${INTERNAL_IP}/g" "${template}"
  225. sed -i "s/<tz>/${TZ}/g" "${template}"
  226. sed -i "s/<jwt_secret>/${JWT_SECRET}/g" "${template}"
  227. sed -i "s/<root_folder>/${SED_ROOT_FOLDER}/g" "${template}"
  228. sed -i "s/<tipi_version>/${TIPI_VERSION}/g" "${template}"
  229. sed -i "s/<architecture>/${ARCHITECTURE}/g" "${template}"
  230. sed -i "s/<nginx_port>/${NGINX_PORT}/g" "${template}"
  231. sed -i "s/<nginx_port_ssl>/${NGINX_PORT_SSL}/g" "${template}"
  232. sed -i "s/<postgres_password>/${POSTGRES_PASSWORD}/g" "${template}"
  233. sed -i "s/<postgres_username>/${POSTGRES_USERNAME}/g" "${template}"
  234. sed -i "s/<postgres_dbname>/${POSTGRES_DBNAME}/g" "${template}"
  235. sed -i "s/<postgres_port>/${POSTGRES_PORT}/g" "${template}"
  236. sed -i "s/<postgres_host>/${POSTGRES_HOST}/g" "${template}"
  237. sed -i "s/<apps_repo_id>/${REPO_ID}/g" "${template}"
  238. sed -i "s/<apps_repo_url>/${APPS_REPOSITORY_ESCAPED}/g" "${template}"
  239. sed -i "s/<domain>/${DOMAIN}/g" "${template}"
  240. sed -i "s/<storage_path>/${STORAGE_PATH_ESCAPED}/g" "${template}"
  241. sed -i "s/<redis_host>/${REDIS_HOST}/g" "${template}"
  242. sed -i "s/<demo_mode>/${DEMO_MODE}/g" "${template}"
  243. done
  244. mv -f "$ENV_FILE" "$ROOT_FOLDER/.env"
  245. ### --------------------------------
  246. ### Start the project
  247. ### --------------------------------
  248. if [[ ! "${ci-false}" == "true" ]]; then
  249. if [[ "${rc-false}" == "true" ]]; then
  250. docker compose -f docker-compose.rc.yml --env-file "${ROOT_FOLDER}/.env" pull
  251. # Run docker compose
  252. docker compose -f docker-compose.rc.yml --env-file "${ROOT_FOLDER}/.env" up --detach --remove-orphans --build || {
  253. echo "Failed to start containers"
  254. exit 1
  255. }
  256. else
  257. docker compose --env-file "${ROOT_FOLDER}/.env" pull
  258. # Run docker compose
  259. docker compose --env-file "${ROOT_FOLDER}/.env" up --detach --remove-orphans --build || {
  260. echo "Failed to start containers"
  261. exit 1
  262. }
  263. fi
  264. fi
  265. echo "Tipi is now running"
  266. echo ""
  267. cat <<"EOF"
  268. _,.
  269. ,` -.)
  270. '( _/'-\\-.
  271. /,|`--._,-^| ,
  272. \_| |`-._/|| ,'|
  273. | `-, / | / /
  274. | || | / /
  275. `r-._||/ __ / /
  276. __,-<_ )`-/ `./ /
  277. ' \ `---' \ / /
  278. | |./ /
  279. / // /
  280. \_/' \ |/ /
  281. | | _,^-'/ /
  282. | , `` (\/ /_
  283. \,.->._ \X-=/^
  284. ( / `-._//^`
  285. `Y-.____(__}
  286. | {__)
  287. ()`
  288. EOF
  289. port_display=""
  290. if [[ $NGINX_PORT != "80" ]]; then
  291. port_display=":${NGINX_PORT}"
  292. fi
  293. echo ""
  294. echo "Visit http://${INTERNAL_IP}${port_display}/ to view the dashboard"
  295. echo ""