# Exercise: A Project's Scripts

Give an animal shelter's website the scripts every project has: setup.sh checks the tools and makes a private .env, test.sh checks that every page has a title, and deploy.sh loads .env, runs the tests and copies the site with rsync; then keep .env out of Git, and let a check script tell you what's done.

Journeyman · Unix · Linux · Updated November 10, 2026

The Hillside Animal Shelter's website has a new volunteer every few months, and each one asks the same things. What do I install? How do I see the site? How do I know I didn't break a page? How does it go live, and where's the password for that? You'll answer all of them with the scripts every project has: `setup.sh` checks the tools and makes a private settings file, `test.sh` checks the pages, and `deploy.sh` puts the site live, but only after the tests pass and never with a token written in a script. A check script tells you what's done.

## Before you start

You need a terminal: the Terminal app on a Mac, or Ubuntu on Linux or in WSL on Windows. You also need `python3` (for `dev.sh`'s web server), `rsync` (for the deploy) and **bats**, a small program that runs checks written for bash. If `bats --version`, `python3 --version` or `rsync --version` doesn't print a version number yet, install what's missing:

```bash
sudo apt install bats python3 rsync      # Ubuntu, and Ubuntu in WSL
brew install bats-core python            # a Mac, with Homebrew
```

`sudo` asks for your password, the one you use to sign in; nothing shows on the screen while you type it.

## Get the exercise code

Open this lesson's GitHub repository, [github.com/ask-codey/exercise-bash-a-projects-scripts](https://github.com/ask-codey/exercise-bash-a-projects-scripts), also linked from the **Exercise** box beside the lesson. Click **Code**, then **Download ZIP**, and unzip it into your `terminal-practice` folder. Or clone it with Git. On Windows, put it inside Ubuntu, in your Linux home folder, not in a Windows folder under `/mnt/c`, so `chmod` works on the scripts.

The folder holds:

- `site/`: the shelter's website, the pages your scripts check and deploy.
- `scripts/dev.sh`: ready to use, and an example of the shape the other scripts follow.
- `scripts/setup.sh`, `scripts/test.sh` and `scripts/deploy.sh`: the three scripts you write. Each starts with a comment that says exactly what it must do.
- `.env.example`: the two settings `deploy.sh` needs, with no values. `.gitignore`: for you to finish.
- `check.bats`: the check. You don't need to change it.

## What to do

Open a terminal in the exercise folder, make the scripts runnable (*Your First Script*), and open the first one in nano:

```bash
chmod +x scripts/*.sh
nano scripts/setup.sh
```

1. **`scripts/setup.sh`** checks that `python3` and `rsync` are installed, and prints `missing: TOOL` for each one that isn't, checking both before it stops. With a tool missing, it stops with exit code 1 and makes no `.env`. Otherwise it copies `.env.example` to `.env` when there's no `.env` yet, never replacing one, makes it private, and prints `Ready.`
2. **`scripts/test.sh`** prints `No title: site/NAME.html` for every page without a `<title>`, and finishes with exit code 1. When every page has one, it prints `Every page has a title.` and finishes with 0.
3. **`scripts/deploy.sh`** stops with exit code 1 and a message when there's no `.env`. It loads `.env` so its values are exported, stops when `DEPLOY_TARGET` or `HOST_TOKEN` is empty, naming the empty one, and runs `test.sh`, stopping if it fails. Only then does it copy what's inside `site/` into the folder in `DEPLOY_TARGET` with `rsync`, so that folder matches `site/` exactly, and print `Deployed to` and the folder. It never prints the token.
4. **`.gitignore`** keeps `.env` out of Git, but not `.env.example`.

All three scripts work from any folder: run `../scripts/test.sh` from inside `site/`, and it checks the same pages.

Try each script as you go. To try `deploy.sh`, fill in `.env` with nano: `DEPLOY_TARGET=../shelter-live`, a folder beside the exercise, and a made-up `HOST_TOKEN` such as `hx-1234-practice`. Run `./scripts/deploy.sh`, and the site appears in `../shelter-live`. If you use Git, run `git status --short` too: `.env` shouldn't be on the list.

## Check your work

From the exercise folder:

```bash
bats check.bats
```

At first almost every check fails. That's expected: each job you finish turns its checks into a ✓. The check copies the project into a throwaway folder and runs your scripts there, with a `.env` of its own and a `live` folder in place of a server, so nothing in your folder changes and your own `.env` is never read. To pretend a tool is missing, it runs `setup.sh` with a PATH that leaves the tool out. A failing check says what to look at, for example *volunteer.html was deleted from site/, but it's still in the live folder after the next deploy.*

## Hints

- **Start from the top folder.** *The Scripts Every Project Has* shows the first line every project script needs, and the tool check with `command -v` that lists every missing tool before it stops.
- **Never replace a real .env.** `[[ ! -f .env ]]` is true only when there's no `.env` yet (*Checking Before You Act: File Tests*). `chmod 600` comes after, every time.
- **Exported, not only set.** *Secrets and .env Files* shows the three lines that load `.env` so the programs your script starts can see its values, and how to stop with a clear message when a value is empty.
- **Tests first, copy second.** `if ! ./scripts/test.sh; then ... fi` runs the tests and lets you stop when they fail (*Did It Work? Exit Codes, && and ||*).
- **Mind the slash.** *Backups That Work: rsync* shows what `site/` and `site` each copy, and what `--delete` does.

## When you're done

Every check passes:

```text
check.bats
 ✓ the project's files are here, the scripts are runnable, and rsync is installed
 ✓ 1) setup.sh makes .env from .env.example, makes it private and says Ready.
 ✓ 2) setup.sh keeps a .env that's already there
 ✓ 3) setup.sh lists every missing tool, stops with exit code 1 and makes no .env
 ✓ 4) setup.sh works from any folder
 ✓ 5) test.sh says every page has a title, with exit code 0
 ✓ 6) test.sh names every page without a title, with exit code 1
 ✓ 7) test.sh works from any folder
 ✓ 8) deploy.sh copies site/ to DEPLOY_TARGET with rsync, and never prints the token
 ✓ 9) deploy.sh exports .env's values to the programs it starts
 ✓ 10) deploy.sh makes DEPLOY_TARGET match site/: a deleted page goes too
 ✓ 11) deploy.sh stops with exit code 1, and copies nothing, when there's no .env
 ✓ 12) deploy.sh stops with exit code 1, and copies nothing, when a setting is empty
 ✓ 13) deploy.sh runs test.sh first, and copies nothing when it fails
 ✓ 14) .gitignore keeps .env out of Git, but not .env.example

15 tests, 0 failures
```

Now picture the next volunteer. Which two commands do they type before they can see the site, and which one file do they fill in before they can put it live?

## Submit your exercise

The exercise is how you finish this lesson. When your work is done, put it on GitHub in a repo of your own, then submit that repo's address here.

1. On GitHub, click **+**, then **New repository**. Name it (for example `exercise-bash-a-projects-scripts`), choose **Public**, and click **Create repository**. Leave out the README: your folder already has one.
2. In a terminal in your exercise folder (Ubuntu in WSL on Windows), send your work to your new repo. Your `.gitignore` keeps `.env` out of it. If you downloaded the ZIP:

   ```bash
   git init -b main
   git add .
   git commit -m "My solution"
   git remote add origin https://github.com/your-name/exercise-bash-a-projects-scripts.git
   git push -u origin main
   ```

   If you cloned the starter instead, point your copy at your own repo, then commit and push:

   ```bash
   git remote set-url origin https://github.com/your-name/exercise-bash-a-projects-scripts.git
   git add .
   git commit -m "My solution"
   git push -u origin main
   ```

3. Paste your repo's address, such as `https://github.com/your-name/exercise-bash-a-projects-scripts`, into **Submit your exercise** on this page. The site checks that the repo is public and holds your commits, and then the lesson is complete.

Changed something after you submitted? Push it, and submit again: your latest submission is the one that counts.

## So what

Three short scripts and one settings file turn "ask whoever set this up" into `./scripts/setup.sh`, `./scripts/dev.sh`, and a deploy that can't skip the tests or leak a key.

## Key terms

- [Project Script](https://www.ask-codey.com/glossary/project-script)
- [command -v](https://www.ask-codey.com/glossary/command-v)
- [.env File](https://www.ask-codey.com/glossary/env-file)
- .gitignore
- [Exit Code](https://www.ask-codey.com/glossary/exit-code)

## Related

- [The Scripts Every Project Has](https://www.ask-codey.com/lessons/bash-project-scripts)
- [Secrets and .env Files](https://www.ask-codey.com/lessons/bash-env-files-and-secrets)
- [Git Hooks: Scripts Git Runs for You](https://www.ask-codey.com/lessons/bash-git-hooks)
- [Running Commands on Another Computer](https://www.ask-codey.com/lessons/bash-remote-commands)

Source: https://www.ask-codey.com/lessons/exercise-bash-a-projects-scripts
