start.sh 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  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)"
  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. TIPI_VERSION=$(get_json_field "${ROOT_FOLDER}/package.json" version)
  47. storage_path="${ROOT_FOLDER}"
  48. STORAGE_PATH_ESCAPED="$(echo "${storage_path}" | sed 's/\//\\\//g')"
  49. REDIS_HOST=tipi-redis
  50. INTERNAL_IP=
  51. if [[ "$ARCHITECTURE" == "aarch64" ]]; then
  52. ARCHITECTURE="arm64"
  53. elif [[ "$ARCHITECTURE" == "armv7"* || "$ARCHITECTURE" == "armv8"* ]]; then
  54. ARCHITECTURE="arm"
  55. elif [[ "$ARCHITECTURE" == "x86_64" ]]; then
  56. ARCHITECTURE="amd64"
  57. fi
  58. # If none of the above conditions are met, the architecture is not supported
  59. if [[ "$ARCHITECTURE" != "arm64" ]] && [[ "$ARCHITECTURE" != "arm" ]] && [[ "$ARCHITECTURE" != "amd64" ]]; then
  60. echo "Architecture ${ARCHITECTURE} not supported!"
  61. exit 1
  62. fi
  63. ### --------------------------------
  64. ### CLI arguments
  65. ### --------------------------------
  66. while [ -n "${1-}" ]; do
  67. case "$1" in
  68. --rc) rc="true" ;;
  69. --ci) ci="true" ;;
  70. --port)
  71. port="${2-}"
  72. if [[ "${port}" =~ ^[0-9]+$ ]]; then
  73. NGINX_PORT="${port}"
  74. else
  75. echo "--port must be a number"
  76. exit 1
  77. fi
  78. shift
  79. ;;
  80. --ssl-port)
  81. ssl_port="${2-}"
  82. if [[ "${ssl_port}" =~ ^[0-9]+$ ]]; then
  83. NGINX_PORT_SSL="${ssl_port}"
  84. else
  85. echo "--ssl-port must be a number"
  86. exit 1
  87. fi
  88. shift
  89. ;;
  90. --domain)
  91. domain="${2-}"
  92. if [[ "${domain}" =~ ^[a-zA-Z0-9.-]+$ ]]; then
  93. DOMAIN="${domain}"
  94. else
  95. echo "--domain must be a valid domain"
  96. exit 1
  97. fi
  98. shift
  99. ;;
  100. --listen-ip)
  101. listen_ip="${2-}"
  102. if [[ "${listen_ip}" =~ ^[a-fA-F0-9.:]+$ ]]; then
  103. INTERNAL_IP="${listen_ip}"
  104. else
  105. echo "--listen-ip must be a valid IP address"
  106. exit 1
  107. fi
  108. shift
  109. ;;
  110. --)
  111. shift # The double dash makes them parameters
  112. break
  113. ;;
  114. *) echo "Option $1 not recognized" && exit 1 ;;
  115. esac
  116. shift
  117. done
  118. if [[ -z "${INTERNAL_IP:-}" ]]; then
  119. network_interface="$(ip route | grep default | awk '{print $5}' | uniq)"
  120. network_interface_count=$(echo "$network_interface" | wc -l)
  121. if [[ "$network_interface_count" -eq 0 ]]; then
  122. echo "No network interface found!"
  123. exit 1
  124. elif [[ "$network_interface_count" -gt 1 ]]; then
  125. echo "Found multiple network interfaces. Please select one of the following interfaces:"
  126. echo "$network_interface"
  127. while true; do
  128. read -rp "> " USER_NETWORK_INTERFACE
  129. if echo "$network_interface" | grep -x "$USER_NETWORK_INTERFACE"; then
  130. network_interface="$USER_NETWORK_INTERFACE"
  131. break
  132. else
  133. echo "Please select one of the interfaces above. (CTRL+C to abort)"
  134. fi
  135. done
  136. fi
  137. INTERNAL_IP="$(ip addr show "${network_interface}" | grep "inet " | awk '{print $2}' | cut -d/ -f1)"
  138. internal_ip_count=$(echo "$INTERNAL_IP" | wc -l)
  139. if [[ "$internal_ip_count" -eq 0 ]]; then
  140. 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."
  141. exit 1
  142. elif [[ "$internal_ip_count" -gt 1 ]]; then
  143. echo "Found multiple IP addresses for network interface ${network_interface}. Please select one of the following IP addresses:"
  144. echo "$INTERNAL_IP"
  145. while true; do
  146. read -rp "> " USER_INTERNAL_IP
  147. if echo "$INTERNAL_IP" | grep -x "$USER_INTERNAL_IP"; then
  148. INTERNAL_IP="$USER_INTERNAL_IP"
  149. break
  150. else
  151. echo "Please select one of the IP addresses above. (CTRL+C to abort)"
  152. fi
  153. done
  154. fi
  155. fi
  156. # If port is not 80 and domain is not tipi.localhost, we exit
  157. if [[ "${NGINX_PORT}" != "80" ]] && [[ "${DOMAIN}" != "tipi.localhost" ]]; then
  158. echo "Using a custom domain with a custom port is not supported"
  159. exit 1
  160. fi
  161. ### --------------------------------
  162. ### Watcher and system-info
  163. ### --------------------------------
  164. echo "Running system-info.sh..."
  165. "${ROOT_FOLDER}/scripts/system-info.sh"
  166. kill_watcher
  167. "${ROOT_FOLDER}/scripts/watcher.sh" &
  168. ### --------------------------------
  169. ### settings.json overrides
  170. ### --------------------------------
  171. echo "Generating config files..."
  172. # Override vars with values from settings.json
  173. if [[ -f "${STATE_FOLDER}/settings.json" ]]; then
  174. # If dnsIp is set in settings.json, use it
  175. if [[ "$(get_json_field "${STATE_FOLDER}/settings.json" dnsIp)" != "null" ]]; then
  176. DNS_IP=$(get_json_field "${STATE_FOLDER}/settings.json" dnsIp)
  177. fi
  178. # If domain is set in settings.json, use it
  179. if [[ "$(get_json_field "${STATE_FOLDER}/settings.json" domain)" != "null" ]]; then
  180. DOMAIN=$(get_json_field "${STATE_FOLDER}/settings.json" domain)
  181. fi
  182. # If appsRepoUrl is set in settings.json, use it
  183. if [[ "$(get_json_field "${STATE_FOLDER}/settings.json" appsRepoUrl)" != "null" ]]; then
  184. apps_repository=$(get_json_field "${STATE_FOLDER}/settings.json" appsRepoUrl)
  185. APPS_REPOSITORY_ESCAPED="$(echo "${apps_repository}" | sed 's/\//\\\//g')"
  186. REPO_ID="$("${ROOT_FOLDER}"/scripts/git.sh get_hash "${apps_repository}")"
  187. fi
  188. # If port is set in settings.json, use it
  189. if [[ "$(get_json_field "${STATE_FOLDER}/settings.json" port)" != "null" ]]; then
  190. NGINX_PORT=$(get_json_field "${STATE_FOLDER}/settings.json" port)
  191. fi
  192. # If sslPort is set in settings.json, use it
  193. if [[ "$(get_json_field "${STATE_FOLDER}/settings.json" sslPort)" != "null" ]]; then
  194. NGINX_PORT_SSL=$(get_json_field "${STATE_FOLDER}/settings.json" sslPort)
  195. fi
  196. # If listenIp is set in settings.json, use it
  197. if [[ "$(get_json_field "${STATE_FOLDER}/settings.json" listenIp)" != "null" ]]; then
  198. INTERNAL_IP=$(get_json_field "${STATE_FOLDER}/settings.json" listenIp)
  199. fi
  200. # If storagePath is set in settings.json, use it
  201. if [[ "$(get_json_field "${STATE_FOLDER}/settings.json" storagePath)" != "null" ]]; then
  202. storage_path="$(get_json_field "${STATE_FOLDER}/settings.json" storagePath)"
  203. STORAGE_PATH_ESCAPED="$(echo "${storage_path}" | sed 's/\//\\\//g')"
  204. fi
  205. fi
  206. 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}"
  207. write_log "Final values: \n${new_values}"
  208. ### --------------------------------
  209. ### env file generation
  210. ### --------------------------------
  211. ENV_FILE=$(mktemp)
  212. [[ -f "${ROOT_FOLDER}/.env" ]] && rm -f "${ROOT_FOLDER}/.env"
  213. [[ -f "$ROOT_FOLDER/templates/env-sample" ]] && cp "$ROOT_FOLDER/templates/env-sample" "$ENV_FILE"
  214. for template in ${ENV_FILE}; do
  215. sed -i "s/<dns_ip>/${DNS_IP}/g" "${template}"
  216. sed -i "s/<internal_ip>/${INTERNAL_IP}/g" "${template}"
  217. sed -i "s/<tz>/${TZ}/g" "${template}"
  218. sed -i "s/<jwt_secret>/${JWT_SECRET}/g" "${template}"
  219. sed -i "s/<root_folder>/${SED_ROOT_FOLDER}/g" "${template}"
  220. sed -i "s/<tipi_version>/${TIPI_VERSION}/g" "${template}"
  221. sed -i "s/<architecture>/${ARCHITECTURE}/g" "${template}"
  222. sed -i "s/<nginx_port>/${NGINX_PORT}/g" "${template}"
  223. sed -i "s/<nginx_port_ssl>/${NGINX_PORT_SSL}/g" "${template}"
  224. sed -i "s/<postgres_password>/${POSTGRES_PASSWORD}/g" "${template}"
  225. sed -i "s/<apps_repo_id>/${REPO_ID}/g" "${template}"
  226. sed -i "s/<apps_repo_url>/${APPS_REPOSITORY_ESCAPED}/g" "${template}"
  227. sed -i "s/<domain>/${DOMAIN}/g" "${template}"
  228. sed -i "s/<storage_path>/${STORAGE_PATH_ESCAPED}/g" "${template}"
  229. sed -i "s/<redis_host>/${REDIS_HOST}/g" "${template}"
  230. done
  231. mv -f "$ENV_FILE" "$ROOT_FOLDER/.env"
  232. ### --------------------------------
  233. ### Start the project
  234. ### --------------------------------
  235. if [[ ! "${ci-false}" == "true" ]]; then
  236. if [[ "${rc-false}" == "true" ]]; then
  237. docker compose -f docker-compose.rc.yml --env-file "${ROOT_FOLDER}/.env" pull
  238. # Run docker compose
  239. docker compose -f docker-compose.rc.yml --env-file "${ROOT_FOLDER}/.env" up --detach --remove-orphans --build || {
  240. echo "Failed to start containers"
  241. exit 1
  242. }
  243. else
  244. docker compose --env-file "${ROOT_FOLDER}/.env" pull
  245. # Run docker compose
  246. docker compose --env-file "${ROOT_FOLDER}/.env" up --detach --remove-orphans --build || {
  247. echo "Failed to start containers"
  248. exit 1
  249. }
  250. fi
  251. fi
  252. echo "Tipi is now running"
  253. echo ""
  254. cat <<"EOF"
  255. _,.
  256. ,` -.)
  257. '( _/'-\\-.
  258. /,|`--._,-^| ,
  259. \_| |`-._/|| ,'|
  260. | `-, / | / /
  261. | || | / /
  262. `r-._||/ __ / /
  263. __,-<_ )`-/ `./ /
  264. ' \ `---' \ / /
  265. | |./ /
  266. / // /
  267. \_/' \ |/ /
  268. | | _,^-'/ /
  269. | , `` (\/ /_
  270. \,.->._ \X-=/^
  271. ( / `-._//^`
  272. `Y-.____(__}
  273. | {__)
  274. ()`
  275. EOF
  276. port_display=""
  277. if [[ $NGINX_PORT != "80" ]]; then
  278. port_display=":${NGINX_PORT}"
  279. fi
  280. echo ""
  281. echo "Visit http://${INTERNAL_IP}${port_display}/ to view the dashboard"
  282. echo ""