Browse Source

working bin

main
pvincent 2 weeks ago
parent
commit
b001cf1290
  1. 86
      lib/bashrc_watcher.sh
  2. 60
      lib/common.bash
  3. 39
      lib/setup-common.bash
  4. 93
      lib/setup-prod-normal-user.bash
  5. 283
      setup-prod.bash
  6. 27
      yamal.d/2025_05_09_060105.bash

86
lib/bashrc_watcher.sh

@ -0,0 +1,86 @@
function sed_replace {
REGEX=$1
STRING=$2
FILE=$3
if ! grep -Eq "$REGEX" "$FILE"; then
builtin echo -e "$STRING" >>"$FILE"
echo 'appended'
else
sed -Ei "s|$REGEX|$STRING|g" "$FILE"
echo 'replaced'
fi
}
function bashrc_count_lines {
wc -l "$HOME/.bashrc" | cut -d' ' -f1
}
function bashrc_watch_start {
if [[ -z "${BASHRC_LINES+set}" ]]; then
BASHRC_LINES=$(bashrc_count_lines)
else
BASHRC_EVOLVED=true
fi
}
function bashrc_watch_end {
bashrc_lines=$(bashrc_count_lines)
if [[ "$BASHRC_LINES" -lt "$bashrc_lines" || -n "${BASHRC_EVOLVED+set}" ]]; then
echo
echo '*****************************'
echo '* BASHRC has evolved! *'
echo '* please synchronize: *'
echo '* *'
echo "* source ~/.bashrc *"
echo '* *'
echo '*****************************'
echo
fi
}
function _bashrc_env_with_prefix {
arg1="$1"
arg2="$2"
prefix="$3"
if printenv | grep -q "$arg1"; then
real_value=$(eval "echo \$$arg1")
echo "success for $arg1 = $real_value"
else
case "$prefix" in
export)
session_line="export ${arg1}=${arg2}"
regex="$session_line"
;;
eval)
session_line="eval \"\$($arg2)\""
regex="eval \"\\\$\\($arg2\\)\""
;;
*) echo "unknown prefix $prefix" && exit 10 ;;
esac
sed_replace "$regex" "$session_line" "$HOME/.bashrc" &>/dev/null
eval "$session_line"
set +e
trap - EXIT
eval "BASHRC_LINES=$BASHRC_LINES $0"
exit $?
fi
}
function bashrc_export {
_bashrc_env_with_prefix "$1" "$2" 'export'
}
function bashrc_eval {
_bashrc_env_with_prefix "$1" "$2" 'eval'
}
function on_exit() {
rv=$?
# [[ $rv -eq 0 ]] && bashrc_watch_end
bashrc_watch_end
exit $rv
}
bashrc_watch_start
trap "on_exit" EXIT

60
lib/common.bash

@ -0,0 +1,60 @@
# library functions
function confirm {
read -p "$1 ([y]es or [N]o): "
case $(echo $REPLY | tr '[A-Z]' '[a-z]') in
y | yes) echo "yes" ;;
*) echo "no" ;;
esac
}
function load_app_name {
[[ -z "${PROJECT_DIR:-}" ]] && echo '`PROJECT_DIR` variable should be initialized first!' && exit 4
local application_ruby="$PROJECT_DIR/config/application.rb"
[[ ! -f $application_ruby ]] && echo "ERROR: $application_ruby not found!" && exit 20
APP_NAME=$(grep ^module $application_ruby | cut -d' ' -f2)
APP_NAME=${APP_NAME,,} # downcase
}
function load_database_settings {
local rails_cmd="
ActiveRecord::Base.configurations.configurations
# .find{it.env_name == '$RAILS_ENV'}
.find{_1.env_name == '$RAILS_ENV'}
.configuration_hash
.each do |k,v|
puts \"#{k}\\t#{v}\"
end
"
local db_settings=$(rails runner "$rails_cmd")
ADAPTER=$(echo -e "$db_settings" | grep adapter | cut -f2)
DB_NAME=$(echo -e "$db_settings" | grep database | cut -f2)
DB_USER=$(echo -e "$db_settings" | grep user | cut -f2)
DB_PASSWORD=$(echo -e "$db_settings" | grep password | cut -f2)
}
function load_storage_settings {
STORAGE_DIR=$(rails runner 'puts ActiveStorage::Blob.service.root')
}
function load_db_version {
DB_VERSION=$(grep -E "define\(version:" "$PROJECT_DIR/db/schema.rb" | cut -d' ' -f2 | cut -d')' -f1)
}
function load_yamal_d_extra_scripts {
load_app_name
load_db_version
echo "APP_NAME=$APP_NAME"
echo "DB_VERSION=$DB_VERSION"
if [[ -f $PROJECT_DIR/bin/yamal.d/$DB_VERSION.bash ]]; then
echo '------------------------'
echo "YAMAL_D extra script found"
source $PROJECT_DIR/bin/yamal.d/$DB_VERSION.bash
echo '------------------------'
fi
}
## MAIN
load_yamal_d_extra_scripts

39
lib/setup-common.bash

@ -0,0 +1,39 @@
## library functions
function install_pacapt {
if [[ $DISTRO == 'arch' ]]; then
PACMAN_CMD=$(which pacman)
echo 'pacman natively detected!'
else
if [[ ! -f $HOME/.local/bin/pacman ]]; then
[[ $DISTRO == 'debian' ]] && apt install -y apt-utils
mkdir -p $HOME/.local/bin
curl -sLo $HOME/.local/bin/pacman https://github.com/icy/pacapt/raw/ng/pacapt
chmod 755 $HOME/.local/bin/pacman
echo "pacapt/pacman ($PACMAN_CMD) successfully installed!"
fi
PACMAN_CMD="$HOME/.local/bin/pacman"
fi
}
function install_idem_packages {
if ! pacman -Qi $1 &>/dev/null; then
sudo $HOME/.local/bin/pacman -S --noconfirm $1 >/dev/null
echo "$2 installed successfully"
else
echo "$2 already installed!"
fi
}
function install_packages {
install_idem_packages "${DISTRO_PACKAGES[_]}" "generic packages"
[ -v "DISTRO_PACKAGES[${DISTRO}]" ] && install_idem_packages "${DISTRO_PACKAGES[${DISTRO}]}" "distro specific packages: [$DISTRO]"
true
}
function postgres_newdb {
if ! (sudo -u postgres -- psql --csv -tc "SELECT 1 as found FROM pg_roles WHERE rolname = '$DB_USER'" | grep -q ^1$); then
sudo -iu postgres psql -c "CREATE USER $DB_USER WITH PASSWORD '$DB_USER'"
sudo -iu postgres psql -c "ALTER USER $DB_USER WITH SUPERUSER"
fi
}

93
lib/setup-prod-normal-user.bash

@ -0,0 +1,93 @@
## CONSTANTS
REPO_URL=$1
REPO_BRANCH=$2
FORCE=${3:-false}
RAILS_ENV=production
KEY_LENGTH=10
## FUNCTIONS
function install_mise {
if [[ ! -f $HOME/.local/bin/mise ]]; then
echo -n 'installing mise...'
curl -s https://mise.run | sh 2>&1 >/dev/null
echo OK
fi
if [[ ! -f $HOME/.bashrc ]] || ! grep -q '.local/bin/mise activate bash' $HOME/.bashrc; then
echo "eval \"\$($HOME/.local/bin/mise activate bash)\"" >>$HOME/.bashrc
echo "cd /opt/$USER" >>$HOME/.bashrc
source $HOME/.bashrc
mise version
fi
if [[ ! -f $HOME/.config/mise/config.toml ]] || ! grep -q 'RAILS_ENV' $HOME/.config/mise/config.toml; then
mise self-update -y 2>&1 >/dev/null
mise settings add idiomatic_version_file_enable_tools ruby
mise set --global RAILS_ENV=production
mise set --global SECRET_KEY_BASE=$(head /dev/urandom | tr -dc A-Za-z0-9 | head -c $KEY_LENGTH)
echo mise install successfully for production with SECRET_KEY_BASE
fi
}
function pull_repo {
if [[ ! -d .git ]]; then
git config --global init.defaultBranch $REPO_BRANCH
git init
git remote add origin $REPO_URL
fi
if git fetch origin $REPO_BRANCH --dry-run --verbose 2>&1 | grep -q " =.*\-> origin\/$REPO_BRANCH"; then
set +Eue
$FORCE || exit 100 # means no change
set -Eue
fi
git config pull.rebase true
git pull origin $REPO_BRANCH
git checkout -q $REPO_BRANCH
}
function install_rails {
mise install
mise exec -- bundle check >/dev/null || mise exec -- bundle install --quiet
mise exec -- rails db:prepare
mise exec -- rails assets:precompile
}
function assert_params {
[[ $# -ge 2 ]] || (echo '2 params required: REPO_URL REPO_BRANCH [FORCE]' && exit 1)
}
function link_to_config {
for file in /etc/$USER/*; do
local destination=$(basename $file)
case "$destination" in
\*)
echo "WARNING: no configuration file provided, you may copy from ./config/*_sample.yml to location: /etc/$USER"
;;
service.conf)
echo "environment (service.conf) converted to .env.production"
ln -sf $(realpath $file) .env.production
;;
*.yml | *.yaml)
echo "yaml file: $(realpath $file) -> config"
ln -sf $(realpath $file) config/
;;
esac
done
}
## MAIN
set -Eue
assert_params $*
mkdir -p /opt/$USER
cd /opt/$USER
pull_repo
link_to_config
install_mise
install_rails

283
setup-prod.bash

@ -0,0 +1,283 @@
#!/usr/bin/env bash
## CONSTANTS
REPO_BRANCH=main
YAMAL_DIR=/home/yamal
YAMAL_CONFIG=$YAMAL_DIR/config
RAILS_BASE_PORT=900
FORCE=false
DEBUG=''
PACMAN_CMD='pacman'
SESSION_RESTART=false
OS_RELEASE=/etc/os-release
DISTRO=$(test -f "$OS_RELEASE" && (grep -s ^ID "$OS_RELEASE" | cut -d= -f2) || echo unknown_distro)
# TODO: project introspection...
declare -A DISTRO_PACKAGES
DISTRO_PACKAGES[_]="postgresql shfmt"
DISTRO_PACKAGES[debian]="build-essential libssl-dev libyaml-dev zlib1g-dev libgmp-dev libpq-dev libvips42 poppler-utils redis-server"
DISTRO_PACKAGES[arch]="base-devel openssl libyaml zlib gmp libvips poppler valkey"
## FUNCTIONS
function usage {
echo "$(dirname $0)/$(basename $0) --repo-url|-r <URL> [--repo-name|-n <NAME>] [--project|-p <NAME>] [--branch|-b <BRANCH>] [--force|-f] [--debug|-d]"
}
function assert_requirements {
[[ ! $SHELL =~ /bash$ ]] && (echo 'bash is mandatory!' && exit 1)
command -v curl >/dev/null || (echo 'curl is mandatory!' && exit 2)
true
}
function parse_options {
while [[ $# -gt 0 ]]; do
case "$1" in
--repo-url | -r)
shift 1
[[ -z ${1:-} || $1 =~ ^- ]] && usage && exit 1
REPO_URL=$1
;;
--repo-name | -n)
shift 1
[[ -z ${1:-} || $1 =~ ^- ]] && usage && exit 1
REPO_NAME=$1
;;
--project | -p)
shift 1
[[ -z ${1:-} || $1 =~ ^- ]] && usage && exit 1
PROJECT_NAME=$1
;;
--branch | -b)
shift 1
[[ -z ${1:-} || $1 =~ ^- ]] && usage && exit 1
REPO_BRANCH=$1
;;
--origin | -o)
shift 1
[[ -z ${1:-} || $1 =~ ^- ]] && usage && exit 1
ORIGIN=$1
;;
--force | -f)
FORCE=true
;;
--debug | -d)
DEBUG='bash -xl'
;;
--help | -h)
usage
exit 0
;;
*)
echo "Unknown option: $1"
usage
exit 2
;;
esac
shift 1 # Move to the next argument
done
}
function assert_repo_url {
if [[ -z ${REPO_URL:-} ]]; then
>&2 echo -e "ERROR3: REPO_URL undefined!\n\tplease either use --repo-url or -r\n"
usage
exit 3
fi
}
function create_user {
if ! id -u $PROJECT_NAME >/dev/null 2>&1; then
[[ -d /opt/$PROJECT_NAME ]] &&
echo -e "ERROR\n special opt folder not empty: /opt/$PROJECT_NAME\nplease specify unique project name with argument --project|-p" &&
exit 20
useradd -G ssh,yamal -rm --home-dir $YAMAL_DIR/projects/$PROJECT_NAME --shell /bin/bash $PROJECT_NAME
echo "user $PROJECT_NAME successfully created!"
fi
if [[ ! -d /opt/$PROJECT_NAME ]]; then
mkdir -p /opt/$PROJECT_NAME
chown $PROJECT_NAME:$PROJECT_NAME /opt/$PROJECT_NAME
fi
if [[ ! -d /etc/$PROJECT_NAME ]]; then
mkdir -p /etc/$PROJECT_NAME
echo "special folder: /etc/$PROJECT_NAME successfully created!"
fi
chown -R $PROJECT_NAME:$PROJECT_NAME /etc/$PROJECT_NAME
}
function set_project_name {
[[ -z ${REPO_NAME:-} ]] && REPO_NAME=$(echo $REPO_URL | rev | cut -d/ -f1 | rev |cut -d. -f1) # get last url part minus '.git'
DB_USER=$REPO_NAME
[[ -z ${PROJECT_NAME:-} ]] && PROJECT_NAME=$REPO_NAME
true
}
function define_rails_app {
if [[ -f $YAMAL_CONFIG ]]; then
local index=$(grep "^rails_app_.*=$PROJECT_NAME\$" $YAMAL_CONFIG | cut -d '=' -f1 | cut -d '_' -f3)
[[ -z $index ]] && index=$(grep "rails_app_count=" $YAMAL_CONFIG | cut -d '=' -f2)
fi
RAILS_APP=${index:-0}
}
function inc_rails_app_count {
if [[ -f $YAMAL_CONFIG ]]; then
if ! grep -q "^rails_app_.*=$PROJECT_NAME\$" $YAMAL_CONFIG; then
echo "rails_app_$RAILS_APP=$PROJECT_NAME" >>$YAMAL_CONFIG
RAILS_APP=$(($RAILS_APP + 1))
sed -i "s/^rails_app_count.*/rails_app_count=$RAILS_APP/" $YAMAL_CONFIG
echo "new rails app succesfully registered!"
fi
else
mkdir -p $(dirname $YAMAL_CONFIG)
echo "rails_app_count=1" >$YAMAL_CONFIG
echo "rails_app_$RAILS_APP=$PROJECT_NAME" >>$YAMAL_CONFIG
fi
}
function install_systemd_service {
local service="/etc/systemd/system/$PROJECT_NAME.service"
local rails_port=$(($RAILS_BASE_PORT + $RAILS_APP))
if [[ ! -f $service ]]; then
cat <<EOF >$service
[Unit]
Description=$PROJECT_NAME
After=network.target
[Service]
Type=simple
User=$PROJECT_NAME
SyslogIdentifier=$PROJECT_NAME
AmbientCapabilities=CAP_NET_BIND_SERVICE
PermissionsStartOnly=true
WorkingDirectory=/opt/$PROJECT_NAME
ExecStart=$YAMAL_DIR/projects/$PROJECT_NAME/.local/bin/mise exec -- rails server --port $rails_port
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable $PROJECT_NAME
fi
systemctl restart $PROJECT_NAME
systemctl is-active $PROJECT_NAME >/dev/null && echo "service $PROJECT_NAME running on port: $rails_port"
}
function launch_normal_user_setup {
# clean temporary files in case mise install ruby fails!
rm /tmp/mise-ruby-build -rf
echo "launch setup as normal user: $PROJECT_NAME"
local cmd="${DEBUG:-bash -l} -- $(realpath $(dirname "$0"))/lib/setup-prod-normal-user.bash $REPO_URL $REPO_BRANCH $FORCE"
if sudo -iu $PROJECT_NAME $cmd; then
: #ok
else
local exit_code=$?
[[ $exit_code == 100 ]] && echo 'up-to-date: no change!' && exit 0
echo "an error $exit_code has occured during 'setup-prod-normal-user'" && exit $exit_code
fi
}
function enhance_mise_user_from_origin {
[[ -z ${ORIGIN:-} ]] && return
if [[ -d $ORIGIN/.local/share/mise/installs/ruby ]] && [[ ! -f /home/$PROJECT_NAME/.local/bin/mise ]]; then
echo -n "copy mise ruby from origin=$ORIGIN..."
mkdir -p /home/$PROJECT_NAME/.local/share/mise/installs/ruby
cp -r $ORIGIN/.local/share/mise/installs/ruby /home/$PROJECT_NAME/.local/share/mise/installs/
chown -R $PROJECT_NAME:$PROJECT_NAME /home/$PROJECT_NAME/.local
echo OK
fi
}
function install_pacapt {
if [[ ! -f $HOME/.local/bin/pacman ]]; then
mkdir -p $HOME/.local/bin
curl -sLo $HOME/.local/bin/pacman https://github.com/icy/pacapt/raw/ng/pacapt
chmod 755 $HOME/.local/bin/pacman
else
if [[ $DISTRO == 'arch' ]]; then
echo 'pacman natively detected!'
else
PACMAN_CMD="$HOME/.local/bin/pacman"
# echo "pacapt/pacman ($PACMAN_CMD) already installed!"
fi
fi
}
function install_idem_packages {
if ! pacman -Qi $1 &>/dev/null; then
sudo $HOME/.local/bin/pacman -S --noconfirm $1 >/dev/null
echo "$2 installed successfully"
else
echo "$2 already installed!"
fi
}
function install_packages {
install_idem_packages "${DISTRO_PACKAGES[_]}" "generic packages"
[ -v "DISTRO_PACKAGES[${DISTRO}]" ] && install_idem_packages "${DISTRO_PACKAGES[${DISTRO}]}" "distro specific packages: [$DISTRO]"
true
}
function assert_etc_config_for_user {
local config_dir="/etc/$PROJECT_NAME"
if [[ -d $config_dir ]]; then
chown $PROJECT_NAME $config_dir
chmod -R 750 $config_dir
else
echo -e "-----\nERROR\n-----\nplease provide configuration file from location:\n $config_dir"
false
fi
}
function postgres_newdb {
if ! (sudo -u postgres -- psql --csv -tc "SELECT 1 as found FROM pg_roles WHERE rolname = '$DB_USER'" | grep -q ^1$); then
echo "creating postgresql user: $DB_USER"
sudo -iu postgres psql -c "CREATE USER $DB_USER WITH PASSWORD '$DB_USER'"
sudo -iu postgres psql -c "ALTER USER $DB_USER WITH SUPERUSER"
fi
}
function postgres_global_encoding_utf8 {
if ! sudo -iu postgres -- psql -c '\l template1' | grep -q UTF8; then
echo 'defining postgresql template1 with default UTF8 encoding'
sudo -iu postgres -- psql <<EOF
UPDATE pg_database SET datistemplate = FALSE WHERE datname = 'template1';
DROP DATABASE template1;
CREATE DATABASE template1 WITH TEMPLATE = template0 ENCODING = 'UTF8';
UPDATE pg_database SET datistemplate = TRUE WHERE datname = 'template1';
VACUUM FREEZE;
EOF
fi
}
## MAIN
assert_requirements
parse_options $*
[[ $(id -u) != 0 ]] && exec sudo -- $DEBUG $(dirname "$0")/$(basename "$0") --origin $HOME $*
set -Eue
assert_repo_url
set_project_name
install_pacapt
install_packages
define_rails_app
# echo "$PROJECT_NAME:$REPO_BRANCH:$RAILS_APP:$ORIGIN"
create_user
assert_etc_config_for_user
# enhance_mise_user_from_origin # FIXME: not working yet!
postgres_global_encoding_utf8
postgres_newdb
launch_normal_user_setup
install_systemd_service
inc_rails_app_count

27
yamal.d/2025_05_09_060105.bash

@ -0,0 +1,27 @@
[[ $RAILS_ENV != 'production' ]] && return
function export_config {
mkdir -p $EXPORT_TEMP_DIR/config
if [[ -d /etc/$APP_NAME ]]; then
echo "found deprecated: /etc/$APP_NAME"
cp /etc/$APP_NAME/* $EXPORT_TEMP_DIR/config
else
cp /etc/$USER/* $EXPORT_TEMP_DIR/config
fi
rm -f $EXPORT_TEMP_DIR/config/database.yml
}
function import_config {
cp $IMPORT_TEMP_DIR/config/*.yml /etc/$USER
for i in /etc/$USER/*.yml; do ln -sf $i $PROJECT_DIR/config/; done
local service_conf="/etc/$USER/service.conf"
if [[ -f $service_conf ]]; then
ln -sf $service_conf $PROJECT_DIR/.env.production
if grep -q ^SECRET_KEY_BASE= $service_conf; then
local secret=$(grep ^SECRET_KEY_BASE= $service_conf | cut -d= -f2)
mise set --global SECRET_KEY_BASE=$secret
echo "SECRET_KEY_BASE stored via mise environment!"
fi
fi
}
Loading…
Cancel
Save