mirror of https://github.com/interlegis/sapl.git
30 changed files with 424 additions and 303 deletions
@ -1,151 +0,0 @@ |
|||||
#!/usr/bin/env bash |
|
||||
|
|
||||
create_env() { |
|
||||
echo "[ENV FILE] creating .env file..." |
|
||||
# check if file exists |
|
||||
if [ -f "/var/interlegis/sapl/data/secret.key" ]; then |
|
||||
KEY=`cat /var/interlegis/sapl/data/secret.key` |
|
||||
else |
|
||||
KEY=`python3 genkey.py` |
|
||||
echo $KEY > data/secret.key |
|
||||
fi |
|
||||
|
|
||||
FILENAME="/var/interlegis/sapl/sapl/.env" |
|
||||
|
|
||||
if [ -z "${DATABASE_URL:-}" ]; then |
|
||||
DATABASE_URL="postgresql://sapl:sapl@sapldb:5432/sapl" |
|
||||
fi |
|
||||
|
|
||||
# ALWAYS replace the content of .env variable |
|
||||
# If want to conditionally create only if absent then use IF below |
|
||||
# if [ ! -f $FILENAME ]; then |
|
||||
|
|
||||
touch $FILENAME |
|
||||
|
|
||||
# explicitly use '>' to erase any previous content |
|
||||
echo "SECRET_KEY="$KEY > $FILENAME |
|
||||
# now only appends |
|
||||
echo "DATABASE_URL = "$DATABASE_URL >> $FILENAME |
|
||||
echo "DEBUG = ""${DEBUG-False}" >> $FILENAME |
|
||||
echo "EMAIL_USE_TLS = ""${USE_TLS-True}" >> $FILENAME |
|
||||
echo "EMAIL_PORT = ""${EMAIL_PORT-587}" >> $FILENAME |
|
||||
echo "EMAIL_HOST = ""${EMAIL_HOST-''}" >> $FILENAME |
|
||||
echo "EMAIL_HOST_USER = ""${EMAIL_HOST_USER-''}" >> $FILENAME |
|
||||
echo "EMAIL_HOST_PASSWORD = ""${EMAIL_HOST_PASSWORD-''}" >> $FILENAME |
|
||||
echo "EMAIL_SEND_USER = ""${EMAIL_HOST_USER-''}" >> $FILENAME |
|
||||
echo "DEFAULT_FROM_EMAIL = ""${EMAIL_HOST_USER-''}" >> $FILENAME |
|
||||
echo "SERVER_EMAIL = ""${EMAIL_HOST_USER-''}" >> $FILENAME |
|
||||
echo "USE_SOLR = ""${USE_SOLR-False}" >> $FILENAME |
|
||||
echo "SOLR_COLLECTION = ""${SOLR_COLLECTION-sapl}" >> $FILENAME |
|
||||
echo "SOLR_URL = ""${SOLR_URL-http://localhost:8983}" >> $FILENAME |
|
||||
echo "IS_ZK_EMBEDDED = ""${IS_ZK_EMBEDDED-False}" >> $FILENAME |
|
||||
echo "ENABLE_SAPN = ""${ENABLE_SAPN-False}" >> $FILENAME |
|
||||
|
|
||||
echo "[ENV FILE] done." |
|
||||
} |
|
||||
|
|
||||
create_env |
|
||||
|
|
||||
/bin/bash wait-for-pg.sh $DATABASE_URL |
|
||||
|
|
||||
### |
|
||||
### This is required for compability with newer versions of psycopg2 lib |
|
||||
### |
|
||||
echo "Setting database timezone to UTC" |
|
||||
psql $DATABASE_URL -c 'SET TIME ZONE UTC;' |
|
||||
|
|
||||
yes yes | python3 manage.py migrate |
|
||||
|
|
||||
|
|
||||
## SOLR |
|
||||
USE_SOLR="${USE_SOLR:=False}" |
|
||||
SOLR_URL="${SOLR_URL:=http://admin:solr@localhost:8983}" |
|
||||
SOLR_COLLECTION="${SOLR_COLLECTION:=sapl}" |
|
||||
NUM_SHARDS=${NUM_SHARDS:=1} |
|
||||
RF=${RF:=1} |
|
||||
MAX_SHARDS_PER_NODE=${MAX_SHARDS_PER_NODE:=1} |
|
||||
IS_ZK_EMBEDDED="${IS_ZK_EMBEDDED:=False}" |
|
||||
|
|
||||
if [ "${USE_SOLR-False}" == "True" ] || [ "${USE_SOLR-False}" == "true" ]; then |
|
||||
|
|
||||
echo "Solr configurations" |
|
||||
echo "===================" |
|
||||
echo "URL: $SOLR_URL" |
|
||||
echo "COLLECTION: $SOLR_COLLECTION" |
|
||||
echo "NUM_SHARDS: $NUM_SHARDS" |
|
||||
echo "REPLICATION FACTOR: $RF" |
|
||||
echo "MAX SHARDS PER NODE: $MAX_SHARDS_PER_NODE" |
|
||||
echo "ASSUME ZK EMBEDDED: $IS_ZK_EMBEDDED" |
|
||||
echo "=========================================" |
|
||||
|
|
||||
echo "running Solr script" |
|
||||
/bin/bash wait-for-solr.sh $SOLR_URL |
|
||||
CHECK_SOLR_RETURN=$? |
|
||||
|
|
||||
if [ $CHECK_SOLR_RETURN == 1 ]; then |
|
||||
echo "Connecting to Solr..." |
|
||||
|
|
||||
|
|
||||
if [ "${IS_ZK_EMBEDDED-False}" == "True" ] || [ "${IS_ZK_EMBEDDED-False}" == "true" ]; then |
|
||||
ZK_EMBEDDED="--embedded_zk" |
|
||||
echo "Assuming embedded ZooKeeper instalation..." |
|
||||
fi |
|
||||
|
|
||||
python3 solr_cli.py -u $SOLR_URL -c $SOLR_COLLECTION -s $NUM_SHARDS -rf $RF -ms $MAX_SHARDS_PER_NODE $ZK_EMBEDDED & |
|
||||
# Enable SOLR switch on, creating if it doesn't exist on database |
|
||||
./manage.py waffle_switch SOLR_SWITCH on --create |
|
||||
else |
|
||||
echo "Solr is offline, not possible to connect." |
|
||||
# Disable Solr switch off, creating if it doesn't exist on database |
|
||||
./manage.py waffle_switch SOLR_SWITCH off --create |
|
||||
fi |
|
||||
|
|
||||
else |
|
||||
echo "Solr support is not initialized." |
|
||||
# Disable Solr switch off, creating if it doesn't exist on database |
|
||||
./manage.py waffle_switch SOLR_SWITCH off --create |
|
||||
fi |
|
||||
|
|
||||
## Enable/Disable SAPN |
|
||||
if [ "${ENABLE_SAPN-False}" == "True" ] || [ "${ENABLE_SAPN-False}" == "true" ]; then |
|
||||
echo "Enabling SAPN" |
|
||||
./manage.py waffle_switch SAPLN_SWITCH on --create |
|
||||
else |
|
||||
echo "Enabling SAPL" |
|
||||
./manage.py waffle_switch SAPLN_SWITCH off --create |
|
||||
fi |
|
||||
|
|
||||
|
|
||||
echo "Creating admin user..." |
|
||||
|
|
||||
user_created=$(python3 create_admin.py 2>&1) |
|
||||
|
|
||||
echo $user_created |
|
||||
|
|
||||
cmd=$(echo $user_created | grep 'ADMIN_USER_EXISTS') |
|
||||
user_exists=$? |
|
||||
|
|
||||
cmd=$(echo $user_created | grep 'MISSING_ADMIN_PASSWORD') |
|
||||
lack_pwd=$? |
|
||||
|
|
||||
if [ $user_exists -eq 0 ]; then |
|
||||
echo "[SUPERUSER CREATION] User admin already exists. Not creating" |
|
||||
fi |
|
||||
|
|
||||
if [ $lack_pwd -eq 0 ]; then |
|
||||
echo "[SUPERUSER] Environment variable $ADMIN_PASSWORD for superuser admin was not set. Leaving container" |
|
||||
# return -1 |
|
||||
fi |
|
||||
|
|
||||
|
|
||||
echo "-------------------------------------" |
|
||||
echo "| ███████╗ █████╗ ██████╗ ██╗ |" |
|
||||
echo "| ██╔════╝██╔══██╗██╔══██╗██║ |" |
|
||||
echo "| ███████╗███████║██████╔╝██║ |" |
|
||||
echo "| ╚════██║██╔══██║██╔═══╝ ██║ |" |
|
||||
echo "| ███████║██║ ██║██║ ███████╗ |" |
|
||||
echo "| ╚══════╝╚═╝ ╚═╝╚═╝ ╚══════╝ |" |
|
||||
echo "-------------------------------------" |
|
||||
|
|
||||
gunicorn -c gunicorn.conf.py & |
|
||||
/usr/sbin/nginx -g "daemon off;" |
|
@ -0,0 +1,290 @@ |
|||||
|
#!/usr/bin/env bash |
||||
|
set -Eeuo pipefail |
||||
|
IFS=$'\n\t' |
||||
|
|
||||
|
DATA_DIR="/var/interlegis/sapl/data" |
||||
|
APP_DIR="/var/interlegis/sapl/sapl" |
||||
|
ENV_FILE="$APP_DIR/.env" |
||||
|
SECRET_FILE="$DATA_DIR/secret.key" |
||||
|
|
||||
|
mkdir -p "$DATA_DIR" "$APP_DIR" |
||||
|
|
||||
|
log() { printf '[%s] %s\n' "$(date -Is)" "$*"; } |
||||
|
err() { printf '[%s] ERROR: %s\n' "$(date -Is)" "$*" >&2; } |
||||
|
|
||||
|
cleanup() { jobs -p | xargs -r kill 2>/dev/null || true; } |
||||
|
trap cleanup TERM INT EXIT |
||||
|
|
||||
|
# --- new function --- |
||||
|
configure_pg_timezone() { |
||||
|
: "${DATABASE_URL:=postgresql://sapl:sapl@sapldb:5432/sapl}" |
||||
|
: "${DB_TIMEZONE:=America/Sao_Paulo}" |
||||
|
: "${DB_NAME:=}" |
||||
|
: "${DB_ROLE:=}" |
||||
|
|
||||
|
log "Checking database/role timezone defaults…" |
||||
|
/bin/bash wait-for-pg.sh "$DATABASE_URL" |
||||
|
|
||||
|
# Detect DB and role if not provided |
||||
|
if [[ -z "$DB_NAME" ]]; then |
||||
|
DB_NAME="$(psql "$DATABASE_URL" -At -v ON_ERROR_STOP=1 -c 'select current_database();')" |
||||
|
fi |
||||
|
if [[ -z "$DB_ROLE" ]]; then |
||||
|
DB_ROLE="$(psql "$DATABASE_URL" -At -v ON_ERROR_STOP=1 -c 'select current_user;')" |
||||
|
fi |
||||
|
|
||||
|
# What is the effective timezone for this DB/role right now? |
||||
|
current_tz="$(psql "$DATABASE_URL" -At -v ON_ERROR_STOP=1 -c 'show time zone;')" |
||||
|
current_tz_lower="${current_tz,,}" |
||||
|
|
||||
|
# Consider these as already UTC |
||||
|
if [[ "$current_tz_lower" == "utc" || "$current_tz_lower" == "etc/utc" ]]; then |
||||
|
log "Timezone already UTC for DB='$DB_NAME' ROLE='$DB_ROLE' (SHOW TIME ZONE => $current_tz). Skipping ALTERs." |
||||
|
return |
||||
|
fi |
||||
|
|
||||
|
log "Timezone is '$current_tz' (not UTC). Applying persistent defaults…" |
||||
|
|
||||
|
# Persist at database level (requires DB owner or superuser) |
||||
|
if psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -q \ |
||||
|
-c "ALTER DATABASE \"$DB_NAME\" SET timezone TO '$DB_TIMEZONE';"; then |
||||
|
log "ALTER DATABASE \"$DB_NAME\" SET timezone TO '$DB_TIMEZONE' applied." |
||||
|
else |
||||
|
err "ALTER DATABASE \"$DB_NAME\" failed. Need DB owner or superuser." |
||||
|
exit 1 |
||||
|
fi |
||||
|
|
||||
|
# Persist at role level (requires superuser) |
||||
|
if psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -q \ |
||||
|
-c "ALTER ROLE \"$DB_ROLE\" SET timezone TO '$DB_TIMEZONE';"; then |
||||
|
log "ALTER ROLE \"$DB_ROLE\" SET timezone TO '$DB_TIMEZONE' applied." |
||||
|
else |
||||
|
err "ALTER ROLE \"$DB_ROLE\" failed. Need superuser privileges." |
||||
|
exit 1 |
||||
|
fi |
||||
|
|
||||
|
# Re-check (new session shows the new default) |
||||
|
verify_tz="$(psql "$DATABASE_URL" -At -v ON_ERROR_STOP=1 -c 'show time zone;')" |
||||
|
log "SHOW TIME ZONE now => $verify_tz (new sessions will inherit the defaults)." |
||||
|
} |
||||
|
|
||||
|
|
||||
|
create_secret() { |
||||
|
if [[ -f "$SECRET_FILE" ]]; then |
||||
|
SECRET_KEY="$(<"$SECRET_FILE")" |
||||
|
else |
||||
|
log "Generating SECRET_KEY..." |
||||
|
SECRET_KEY="$(python3 genkey.py)" |
||||
|
umask 177 |
||||
|
printf '%s\n' "$SECRET_KEY" > "$SECRET_FILE" |
||||
|
chmod 600 "$SECRET_FILE" |
||||
|
fi |
||||
|
export SECRET_KEY |
||||
|
} |
||||
|
|
||||
|
write_env_file() { |
||||
|
: "${DATABASE_URL:=postgresql://sapl:sapl@sapldb:5432/sapl}" |
||||
|
: "${DEBUG:=False}" |
||||
|
: "${EMAIL_USE_TLS:=True}" |
||||
|
: "${EMAIL_PORT:=587}" |
||||
|
: "${EMAIL_HOST:=}" |
||||
|
: "${EMAIL_HOST_USER:=}" |
||||
|
: "${EMAIL_HOST_PASSWORD:=}" |
||||
|
: "${DEFAULT_FROM_EMAIL:=$EMAIL_HOST_USER}" |
||||
|
: "${SERVER_EMAIL:=$EMAIL_HOST_USER}" |
||||
|
: "${USE_SOLR:=False}" |
||||
|
: "${SOLR_COLLECTION:=sapl}" |
||||
|
: "${SOLR_URL:=http://localhost:8983}" |
||||
|
: "${IS_ZK_EMBEDDED:=False}" |
||||
|
: "${NUM_SHARDS:=1}" |
||||
|
: "${RF:=1}" |
||||
|
: "${MAX_SHARDS_PER_NODE:=1}" |
||||
|
: "${ENABLE_SAPN:=False}" |
||||
|
|
||||
|
tmp="$(mktemp)" |
||||
|
{ |
||||
|
printf 'SECRET_KEY=%s\n' "$SECRET_KEY" |
||||
|
printf 'DATABASE_URL=%s\n' "$DATABASE_URL" |
||||
|
printf 'DEBUG=%s\n' "$DEBUG" |
||||
|
printf 'EMAIL_USE_TLS=%s\n' "$EMAIL_USE_TLS" |
||||
|
printf 'EMAIL_PORT=%s\n' "$EMAIL_PORT" |
||||
|
printf 'EMAIL_HOST=%s\n' "$EMAIL_HOST" |
||||
|
printf 'EMAIL_HOST_USER=%s\n' "$EMAIL_HOST_USER" |
||||
|
printf 'EMAIL_HOST_PASSWORD=%s\n' "$EMAIL_HOST_PASSWORD" |
||||
|
printf 'EMAIL_SEND_USER=%s\n' "$EMAIL_HOST_USER" |
||||
|
printf 'DEFAULT_FROM_EMAIL=%s\n' "$DEFAULT_FROM_EMAIL" |
||||
|
printf 'SERVER_EMAIL=%s\n' "$SERVER_EMAIL" |
||||
|
printf 'USE_SOLR=%s\n' "$USE_SOLR" |
||||
|
printf 'SOLR_COLLECTION=%s\n' "$SOLR_COLLECTION" |
||||
|
printf 'SOLR_URL=%s\n' "$SOLR_URL" |
||||
|
printf 'IS_ZK_EMBEDDED=%s\n' "$IS_ZK_EMBEDDED" |
||||
|
printf 'NUM_SHARDS=%s\n' "$NUM_SHARDS" |
||||
|
printf 'RF=%s\n' "$RF" |
||||
|
printf 'MAX_SHARDS_PER_NODE=%s\n' "$MAX_SHARDS_PER_NODE" |
||||
|
printf 'ENABLE_SAPN=%s\n' "$ENABLE_SAPN" |
||||
|
} > "$tmp" |
||||
|
|
||||
|
chmod 600 "$tmp" |
||||
|
mv -f "$tmp" "$ENV_FILE" |
||||
|
log "[ENV] wrote $ENV_FILE" |
||||
|
} |
||||
|
|
||||
|
wait_for_pg() { |
||||
|
: "${DATABASE_URL:=postgresql://sapl:sapl@sapldb:5432/sapl}" |
||||
|
log "Waiting for Postgres..." |
||||
|
/bin/bash wait-for-pg.sh "$DATABASE_URL" |
||||
|
} |
||||
|
|
||||
|
migrate_db() { |
||||
|
log "Running Django migrations..." |
||||
|
python3 manage.py migrate --noinput |
||||
|
} |
||||
|
|
||||
|
# In start.sh (near your other helpers) |
||||
|
configure_solr() { |
||||
|
# respect envs, with sane defaults |
||||
|
local USE="${USE_SOLR:-False}" |
||||
|
local URL="${SOLR_URL:-http://admin:solr@localhost:8983}" |
||||
|
local COL="${SOLR_COLLECTION:-sapl}" |
||||
|
local SHARDS="${NUM_SHARDS:-1}" |
||||
|
local RF="${RF:-1}" |
||||
|
local MS="${MAX_SHARDS_PER_NODE:-1}" |
||||
|
local IS_ZK="${IS_ZK_EMBEDDED:-False}" |
||||
|
|
||||
|
# total wait time before we give up (seconds) |
||||
|
local WAIT_TIMEOUT="${SOLR_WAIT_TIMEOUT:-30}" |
||||
|
# per probe max seconds |
||||
|
local PROBE_TIMEOUT="${SOLR_PROBE_TIMEOUT:-3}" |
||||
|
# sleep between probes |
||||
|
local SLEEP_SECS="${SOLR_WAIT_INTERVAL:-2}" |
||||
|
|
||||
|
# feature flag OFF by default unless we confirm Solr |
||||
|
./manage.py waffle_switch SOLR_SWITCH off --create || true |
||||
|
|
||||
|
# Fast exit if disabled |
||||
|
if [[ "${USE,,}" != "true" ]]; then |
||||
|
echo "[SOLR] USE_SOLR=$USE → skipping Solr initialization." |
||||
|
return 0 |
||||
|
fi |
||||
|
|
||||
|
echo "[SOLR] Best-effort wait (<= ${WAIT_TIMEOUT}s): $URL, collection=$COL" |
||||
|
|
||||
|
local deadline=$((SECONDS + WAIT_TIMEOUT)) |
||||
|
while (( SECONDS < deadline )); do |
||||
|
# Try a cheap SolrCloud endpoint; swap for /solr/admin/info/system if you prefer |
||||
|
if curl -fsS --max-time "${PROBE_TIMEOUT}" \ |
||||
|
"${URL%/}/solr/admin/collections?action=LIST" >/dev/null; then |
||||
|
echo "[SOLR] Reachable. Kicking off background configuration…" |
||||
|
|
||||
|
# optional flag if ZK is embedded |
||||
|
local ZK_FLAG="" |
||||
|
if [[ "${IS_ZK,,}" == "true" ]]; then |
||||
|
ZK_FLAG="--embedded_zk" |
||||
|
fi |
||||
|
|
||||
|
( |
||||
|
set -Eeuo pipefail |
||||
|
python3 solr_cli.py \ |
||||
|
-u "$URL" -c "$COL" -s "$SHARDS" -rf "$RF" -ms "$MS" $ZK_FLAG |
||||
|
./manage.py waffle_switch SOLR_SWITCH on --create |
||||
|
echo "[SOLR] Configuration done, SOLR_SWITCH=on." |
||||
|
) >/var/log/sapl/solr_init.log 2>&1 & disown |
||||
|
|
||||
|
return 0 |
||||
|
fi |
||||
|
sleep "${SLEEP_SECS}" |
||||
|
done |
||||
|
|
||||
|
echo "[SOLR] Not reachable within ${WAIT_TIMEOUT}s. Proceeding without Solr (SOLR_SWITCH=off)." |
||||
|
return 0 |
||||
|
} |
||||
|
|
||||
|
configure_sapn() { |
||||
|
if [[ "${ENABLE_SAPN,,}" == "true" ]]; then |
||||
|
log "Enabling SAPN" |
||||
|
python3 manage.py waffle_switch SAPN_SWITCH on --create |
||||
|
else |
||||
|
log "Disabling SAPN" |
||||
|
python3 manage.py waffle_switch SAPN_SWITCH off --create |
||||
|
fi |
||||
|
} |
||||
|
|
||||
|
create_admin() { |
||||
|
log "Creating admin user..." |
||||
|
out="$(python3 create_admin.py 2>&1 || true)" |
||||
|
printf '%s\n' "$out" |
||||
|
|
||||
|
if grep -q 'MISSING_ADMIN_PASSWORD' <<<"$out"; then |
||||
|
err "[SUPERUSER] ADMIN_PASSWORD not set. Exiting." |
||||
|
exit 1 |
||||
|
fi |
||||
|
} |
||||
|
|
||||
|
fix_logging_and_socket_perms() { |
||||
|
local APP_DIR="/var/interlegis/sapl" |
||||
|
local LOG_FILE="$APP_DIR/sapl.log" |
||||
|
|
||||
|
# dirs |
||||
|
mkdir -p "$APP_DIR/run" |
||||
|
chown -R root:nginx "$APP_DIR" |
||||
|
chmod 2775 "$APP_DIR" "$APP_DIR/run" |
||||
|
chmod -R g+rwX "$APP_DIR" |
||||
|
|
||||
|
# new files/sockets → 660 |
||||
|
umask 0007 |
||||
|
|
||||
|
# ensure log file is owned by sapl and writable |
||||
|
install -Dm0660 /dev/null "$LOG_FILE" |
||||
|
chown sapl:nginx "$LOG_FILE" |
||||
|
|
||||
|
# stale socket cleanup (if any) |
||||
|
rm -f "$APP_DIR/run/gunicorn.sock" 2>/dev/null || true |
||||
|
} |
||||
|
|
||||
|
setup_cache_dir() { |
||||
|
# if you later move cache under /var/interlegis/sapl/cache, this line can read an env var |
||||
|
local CACHE_DIR="${DJANGO_CACHE_DIR:-/var/tmp/django_cache}" |
||||
|
|
||||
|
mkdir -p "$CACHE_DIR" |
||||
|
chown -R sapl:nginx "$CACHE_DIR" |
||||
|
chmod -R 2775 "$CACHE_DIR" |
||||
|
find "$CACHE_DIR" -type d -exec chmod g+s {} + |
||||
|
|
||||
|
# keep your global umask; 0007 ensures new files are rw for owner+group |
||||
|
umask 0007 |
||||
|
} |
||||
|
|
||||
|
start_services() { |
||||
|
log "Starting gunicorn..." |
||||
|
gunicorn -c gunicorn.conf.py & |
||||
|
log "Starting nginx..." |
||||
|
exec /usr/sbin/nginx -g "daemon off;" |
||||
|
} |
||||
|
|
||||
|
main() { |
||||
|
create_secret |
||||
|
write_env_file |
||||
|
wait_for_pg |
||||
|
configure_pg_timezone |
||||
|
migrate_db |
||||
|
configure_solr || true |
||||
|
configure_sapn |
||||
|
create_admin |
||||
|
setup_cache_dir |
||||
|
fix_logging_and_socket_perms |
||||
|
|
||||
|
cat <<'BANNER' |
||||
|
------------------------------------- |
||||
|
| ███████╗ █████╗ ██████╗ ██╗ | |
||||
|
| ██╔════╝██╔══██╗██╔══██╗██║ | |
||||
|
| ███████╗███████║██████╔╝██║ | |
||||
|
| ╚════██║██╔══██║██╔═══╝ ██║ | |
||||
|
| ███████║██║ ██║██║ ███████╗ | |
||||
|
| ╚══════╝╚═╝ ╚═╝╚═╝ ╚══════╝ | |
||||
|
------------------------------------- |
||||
|
BANNER |
||||
|
|
||||
|
start_services |
||||
|
} |
||||
|
|
||||
|
main "$@" |
Loading…
Reference in new issue