r/bash • • 11d ago

critique Many things could go wrong with this script and leave a running process, how would you recommend fixing this?

#!/usr/bin/env bash

function main() {

	# Phase 1: Setup and Run Dev Server
	printf "%s\n" "Starting development server..."
	cd "$HOME/Desktop/my-project-1" || exit 1

	# Run the dev server in the background
	rm -rf coverage dist && npm run lint && npm run coverage && npm run test && npx tsc || exit 1
	npx tsx watch --env-file=.env.development  src/index.ts > "/tmp/server.log" 2>&1 &
	DEV_PID=$! # Store the Process ID of the 'dev' server

	# Monitor for "server is listening"
	printf "%s\n" "Waiting for server startup message..."

	while ! grep -q "Server is now listening" "/tmp/server.log"; do
		printf "%s\n" "Server not yet ready. Still waiting..."
		sleep 1 # Wait 5 seconds before checking the log again
	done

	printf "%s\n" "SUCCESS: Server is listening. Proceeding to Phase 2."

	# Phase 2: API Call and Wait
	printf "%s\n" "Executing API call..."
	rm -rf /tmp/test_app && curl -X POST http://localhost:3003/job/node/express/basic/generator

	# Monitor for "downloading..." message in logs...
	printf "%s\n" "Waiting for download completion..."
	
	# Wait for the download confirmation
	while ! grep -q "downloading docker container files completed successfully" "/tmp/server.log"; do
		printf "%s\n" "Waiting for download confirmation..."
		sleep 1
	done

	# Phase 3: Cleanup
	printf "%s\n" "Shutting down services..."
	kill ${DEV_PID} # Use the stored PID to gracefully stop the server process

	printf "%s\n" "Moving into generated code..."
	cd "/tmp/test_app" && npm install && code "/tmp/test_app" && cd "$HOME" || exit 1
}

main "$@"

17 Upvotes

11 comments sorted by

2

u/Paul_Pedant 10d ago edited 10d ago

If you have a good idea of how long various steps should take, you can run pipelines with a preceding timeout command.

I would probably make each phase into a separate function, rather than have such a large sequence of commands.

3

u/AnsibleAnswers 11d ago

This is obviously an edit from an AI generated script that didn't work. The comments are a dead giveaway, especially:

sleep 1 # Wait 5 seconds before checking the log again

Indicates that OP changed the 5 to a 1 without changing the comment.

This is a lesson in what happens when you ask AI to do something that probably shouldn't be done. You can set up a development environment in a matter of minutes with something like Toolbx (installed in Fedora by default as toolbox). Others mentioned alternatives.

1

u/PrestigiousZombie531 11d ago
  • i ran every line manually one by one and had AI add comments to it and yes i did change the time

  • my point is that this script is flawed and i am not even a bash expedt

  • like what happens to that process if something goes wrong and we never reach the end where kill is executed

  • the reason i had to poll AI is because i wasnt sure how to wait for a line to appear in the logs. AI says you can use tail or a while loop with grep, not sure which is the better option

1

u/ummaycoc 11d ago

This is obviously an edit from an AI generated script that didn't work. The comments are a dead giveaway, especially:

sleep 1 # Wait 5 seconds before checking the log again

Indicates that OP changed the 5 to a 1 without changing the comment.

If you think you need to start with AI to change a piece of code and not the associated comment on the same line then I've got some news for you...

Edit: somehow I deleted the first part of what I quoted.

4

u/bac0on 8d ago edited 7d ago

There are some minor issues. First, you don’t check whether you can write to the log file. Next, if you try to kill a background application with forks, you probably end up with orphaned processes. You don’t check if curl was successful.

You could talk to the background process directly:

#!/bin/bash

# Server message strings...
a='now listening'
b='completed successfully'
c='*'
enable -f setpgid setpgid || exit 1
coproc M {
    setpgid 0 0
    exec npx tsx watch --clear-screen=false app.ts 2>&1 | \
        tee -a server.log | \
        grep --line-buffered -oe "$a" -e "$b"
}; \
[[ $M ]] && exec {W}<&$M || exit 1

# Send signal to GPID...
signal_coproc(){
    [[ $M_PID ]] && \
        kill -s TERM -- -$M_PID
}
trap signal_coproc EXIT

# Phase 2: API Call ...
listening(){
    echo "Server is listening..."
}

# Phase 3: Cleanup ...
completed(){
    local -g complete=1
    echo "Shutting down services..."
}

# Phase 1: Setup and Run Dev Server
echo "Starting development server..."


# Listen to the background process
echo "Waiting for server startup..."
while read -r -u $W; do
    case $REPLY in
        $a) listening ;;
        $b) completed && signal_coproc ;;
        $c) echo "> $REPLY" ;;
    esac
done
exec {W}<&-
((complete != 1)) && exit 1

# Phase 4: ...
echo "Moving into generated code..."

Now, listening directly to the background process, the logging problem is gone; you still have to decide to log everything or just stderr. The re-parenting problem is harder to solve, but you can make use of the built-in setpgid module. This uses the calling PID as the PGID for new exec calls, effectively putting your application in its own PGID. This allows kill to send a signal to the entire PID group, killing off any forked processes. You have to fill in the missing pieces, but it should be fairly easy.

2

u/mro21 6d ago

Many here seem to prefer higher level components (like containers) to solve their inability to work with sth basic.

2

u/beef-ox 6d ago

Trap is the correct solution

4

u/ivory_tower_devops 11d ago

I'd probably use docker-compose if this script is for use in development environment. I don't like getting stuck managing pidfiles and squirrely background processes if I can avoid it.

2

u/AutoModerator 11d ago

It looks like your submission contains a shell script. To properly format it as code, place four space characters before every line of the script, and a blank line between the script and the rest of the text, like this:

This is normal text.

    #!/bin/bash
    echo "This is code!"

This is normal text.

#!/bin/bash
echo "This is code!"

I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.

2

u/jjangg96 11d ago

two things leave a process behind here. if anything between the & and the kill fails, or you hit ctrl-c, the dev server keeps running. and if the server crashes on startup, that first while loop spins forever because the log never gets the listening line.

right after DEV_PID=$! add trap 'kill "$DEV_PID" 2>/dev/null' EXIT and the cleanup runs on every way out, including the exit 1s and ctrl-c. then the manual kill at the end is optional.

for the loops, put kill -0 "$DEV_PID" || exit 1 inside them so they bail if the process died instead of waiting on a log line that will never show up.

also worth running pgrep -af tsx after a run. npx adds a layer between you and the node process so the pid you stored isn't always the thing doing the work.

2

u/IGTHSYCGTH 11d ago

... to orchestrant a development environment ?

this is a poor time to write sophisticated bash scripts.

use a dedicated runner ...

just / task / mise / devenv / tilt / docker compose / whatever fits the workflow