Automate Your WebCalendar Deployment in AWS with Terraform

For applications like WebCalendar, it’s important to have a reliable and scalable infrastructure to support them. One way to achieve this is by using Infrastructure as Code (IAC) to provision and manage your infrastructure. IAC allows you to automate the provisioning and management of your infrastructure, reducing the risk of errors and saving time. In this post, we will be discussing how to use Terraform to install aWebCalendar on an AWS EC2 instance using Amazon Linux 2. We will also discuss the benefits of IAC and compare Terraform to AWS Cloud Formation.

Why Infrastructure As Code (IAC) and Terraform?

Let’s first discuss the concept of IAC and its benefits. IAC enables you to manage and provision your infrastructure using code, making it easier to version control (GitOps), automate, and repeat your infrastructure. This approach can save time and reduce the risk of errors, making it an essential tool for businesses that rely on web applications.

AWS Cloud Formation and Terraform are both popular Infrastructure as Code (IAC) tools that allow you to automate the provisioning and management of your infrastructure. While both tools have their own benefits, they have different approaches to IAC.

Cloud Formation uses a “push” approach, where it pushes the desired state of the infrastructure to the cloud environment. This approach makes it easier to manage complex infrastructures, and it integrates well with other AWS services. Additionally, Cloud Formation templates can be created and executed via the AWS Console, making it easier for less technical users to use the service.

Terraform, on the other hand, uses a “pull” approach, where it reads the current state of the infrastructure and makes changes to bring it in line with the desired state. This approach makes it easier to manage multiple cloud environments, including AWS, Google Cloud, and Microsoft Azure. Additionally, Terraform is written in HashiCorp Configuration Language (HCL), making it easier for developers to understand and write Terraform code.

Both AWS Cloud Formation and Terraform have their own benefits, and the choice between them will depend on your specific needs. If you are solely focused on AWS, Cloud Formation may be the better option, while Terraform may be a better choice if you need to manage multiple cloud environments. For this post, we are using Terraform but you could achieve a similar result with Cloud Formation.

Installing WebCalendar

Now, let’s move on to installing WebCalendar using Terraform. To start, you will need to create an AWS account and set up the necessary credentials to access the AWS API. This is typically done by running aws configure on the command line. Once you have your credentials configured, you can start creating your Terraform code. Typically all files related to a small project are contained in a single directory, and the primary file is named main.tf. We will start our main.tf file by specifying we want to using Amazon Linux 2. When setting up an EC2 instance, you need to specify the AMI (Amazon Machine Image). The AMI is essentially the base operating system (much like a CD with Red Hat from 20 years ago). Here is the Terraform that will select Amazon Linux (which is the start of our main.tf file):

# data "aws_ami" "amazon_linux_2" {
  most_recent = true
  owners = ["amazon"]
  filter {
    name = "name"
    values = ["amzn2-ami-hvm*"]
  }
}

# Create the EC2 instance
resource "aws_instance" "webcalendar" {
  ami           = data.aws_ami.amazon_linux_2.id
  instance_type = "t2.micro"
  associate_public_ip_address = true
  tags = {
    Name = "webcalendar"
  }
  security_groups = [aws_security_group.allow_ssh_http_sg.name]

  user_data = "${file("setup_ec2.sh")}"
}

You may have noticed we have referenced some new things above. We are specifying an external setup script setup_ec2.sh. This script will be run once on setup of the EC2 instance. This script replaces this web-based install wizard you typically see in WebCalendar. But, it also installs and configures Apache, PHP, and SQLite. And, it sets up a cronjob to send out reminders (once you have email configured in the WebCalendar System Settings.) Here is what that file looks like:

#!/bin/bash
# Bash to setup the AWS EC2 instance (Amazon Linux 2) for using with WebCalendar
# using a SQLite v3 database.
# Currently, WebCalendar is installed from the master branch on Github.com
# so any changes you make to local WebCalendar files will not be included.
sudo yum update -y

sudo amazon-linux-extras install -y php7.2
sudo yum install -y httpd

sudo systemctl start httpd
sudo systemctl enable httpd
sudo usermod -a -G apache ec2-user

# Download and install WebCalendar (git master branch latest)
cd /tmp
wget https://github.com/craigk5n/webcalendar/archive/refs/heads/master.zip
unzip master.zip
cd webcalendar-master
sudo mv /tmp/webcalendar-master/* /var/www/html
rm -rf /tmp/webcalendar-master

# Update permissions
sudo chown -R ec2-user:apache /var/www
sudo chmod 2775 /var/www
sudo chmod 2775 /var/www && find /var/www -type d -exec sudo chmod 2775 {} \;
sudo find /var/www -type f -exec sudo chmod 0664 {} \;

# Setup a file for SQLite
cd /opt
sudo mkdir webcalendar-sqlite
# Must be 777 or SQLite will fail on writes
sudo chmod 777 webcalendar-sqlite
cd webcalendar-sqlite

# Configure WebCalendar for SQLite v3
# We leave out the install password setting by setting it a junk value.
# To update this install, you'll need to hand-edit settings.php and remove
# the install_password setting.  We don't want to omit it here since a
# malicious user could then access the install pages.
f=/var/www/html/includes/settings.php
echo '<?php' > $f
echo '/* updated via terraform */' >> $f
echo 'install_password: remove-this-line-to-update-this-webcalendar-install'
echo 'db_cachedir: /tmp' >> $f
echo 'db_database: /opt/webcalendar-sqlite/webcalendar.sqlite3' >> $f
echo 'db_host: /opt/webcalendar-sqlite/webcalendar.sqlite3' >> $f
echo 'db_login: root' >> $f
echo 'db_password: none' >> $f
echo 'db_persistent: false' >> $f
echo 'db_type: sqlite3' >> $f
echo 'readonly: false' >> $f
echo 'single_user: false' >> $f
echo 'use_http_auth: false' >> $f
echo 'user_inc: user.php' >> $f
echo 'mode: dev' >> $f
echo '?>' >> $f
chmod 666 $f

# Populate the Sqlite v3 database (create tables, add admin user)
cd /var/www/html/tools
php populate_sqlite3.php -f /opt/webcalendar-sqlite/webcalendar.sqlite3

# Update the SERVER_URL setting with the public IP
ip=`dig +short myip.opendns.com @resolver1.opendns.com`
sqlite3 /opt/webcalendar-sqlite/webcalendar.sqlite3<<EOS
  INSERT INTO  webcal_config (cal_setting, cal_value) VALUES ('SERVER_URL', 'http://$ip/');
EOS

# Setup our cron job to send reminders and to resync any remote calendars.
# Note: this won't work until email is configured in System Settings
echo '0 * * * * cd /var/www/html/webcalendar/tools/; /usr/bin/php send_reminders.php; /usr/bin/php reload_remotes.php' | sudo crontab -u ec2-user -

exit 0

And finally, you may have noticed in our main.tf file that we referenced a security group. In AWS, the security group is essentially a firewall rule. We need to allow HTTP traffic on port 80 to access our website. For convenience, let’s place this in a security.tf file in the same directory. The security group below will allow HTTP traffic from anywhere on port 80 and SSH traffic from anywhere on port 22. Note that you would still need to setup an EC2 key-pair to use SSH and access it from your machine. I prefer to just use the AWS Console to access SSH (no key-pair required).

//security.tf
resource "aws_security_group" "allow_ssh_http_sg" {
  tags = {
    Name = "allow_ssh_http"
  }
  ingress {
    description = "ssh"
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  ingress {
    description = "http"
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

And finally, let’s go back to our main.tf file. We need to specify that we want a public IP address and that we’d like to see that IP address in our console when everything is setup.

# creating Elastic IP for EC2
resource "aws_eip" "eip" {
  instance = aws_instance.webcalendar.id

}

output "IP" {
  value = aws_eip.eip.public_ip
}

So, that’s it. We’ve created three files (main.tf, security.tf and setup_ec2.sh). Now, we have to run Terraform to do our work. First, make sure you are in the same directory as our new files. Then, we must run terraform init once to set things up. You won’t need to do that again (even if you change your files in this directory). Run terraform plan to validate your Terraform files. If you’ve setup everything correctly, you should see 200 or so lines of output without errors. Near the end you should see something like this:

Plan: 3 to add, 0 to change, 0 to destroy.

If so, you have created valid Terraform files and you are ready to setup your resources in AWS. Run terraform apply from the command line (say “yes” when prompted), and Terraform will set everything up. You should see the IP address output. Wait a couple of minutes for everything to get setup, then enter that address in your browser. Be sure to use “http:” rather than nothing (which defaults to https) or “https”. You should get a WebCalendar login page. The default admin user is “admin” with password “admin”. (Be sure to login and change the password!)

What’s Next?

Maybe it’s easier to say what we did not do:

  • Setup a cron job to backup your SQLite database (which is just a file) somewhere like a AWS S3 bucket.
  • Setup HTTPS which requires a valid certificate and a valid domain name (not just an IP address).
  • Use MySQL rather than SQLite. You could set this up on the same EC2 to save money. For production quality and resiliency, you’d probably want to use Amazon’s RDS offering.
  • Use docker images in Fargate rather than an EC2 instance. You can run WebCalendar in docker… with limitation. You currently cannot run two or more instances of WebCalendar since we still store some info (user-uploaded category icons) in the local file system. And, note that we don’t currently build docker images as part of the WebCalendar release process. Ideally, we would do that so you could pull the docker image into Fargate automatically. You’d also need away to send out reminders which are currently setup as a cron job on the EC2 host.

Leave a Reply

Your email address will not be published. Required fields are marked *