<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Orlando Bayo's musings.]]></title><description><![CDATA[Thoughts, stories and ideas.]]></description><link>https://orlandobayo.com/blog/</link><image><url>https://orlandobayo.com/blog/favicon.png</url><title>Orlando Bayo&apos;s musings.</title><link>https://orlandobayo.com/blog/</link></image><generator>Ghost 3.41</generator><lastBuildDate>Mon, 24 Apr 2023 16:25:48 GMT</lastBuildDate><atom:link href="https://orlandobayo.com/blog/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Secure RDS Access through SSH tunneling over AWS SSM in terraform]]></title><description><![CDATA[How I set up secure AWS RDS access using SSM and EC2 instance profile with terraform]]></description><link>https://orlandobayo.com/blog/secure-ads-access-ssm-terraform/</link><guid isPermaLink="false">6027ff40b45bfe2bcf76904f</guid><category><![CDATA[terraform]]></category><category><![CDATA[hashicorp]]></category><category><![CDATA[aws]]></category><category><![CDATA[rds]]></category><dc:creator><![CDATA[Orlando Bayo Adeyemi]]></dc:creator><pubDate>Thu, 18 Feb 2021 14:51:37 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1504418682362-6ad6257cb2fb?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=MXwxMTc3M3wwfDF8c2VhcmNofDc3fHxzeWRuZXl8ZW58MHx8fA&amp;ixlib=rb-1.2.1&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1504418682362-6ad6257cb2fb?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MXwxMTc3M3wwfDF8c2VhcmNofDc3fHxzeWRuZXl8ZW58MHx8fA&ixlib=rb-1.2.1&q=80&w=2000" alt="Secure RDS Access through SSH tunneling over AWS SSM in terraform"><p>This post is what I wish I read before recently working on replacing the SSH access to the bastion in my terraform project with SSM &amp; EC2 Instance Connect.</p><h2 id="motivation">Motivation</h2><blockquote>I'm a bit paranoid about security. Not as paranoid as Amelia who's a cybersecurity consultant but paranoid nonetheless.</blockquote><blockquote>I didn't always care about security until I got burnt.</blockquote><blockquote>7 years ago, while working for an early-stage startup, I woke up one day to find an email from Digital Ocean about a compromised server being shut down by their team.</blockquote><blockquote>An attacker had identified an insecure port and gained access to the server. They had integrated it with a botnet, connecting it to a C&amp;C server.</blockquote><blockquote>I was new to managing servers then, DigitalOcean didn't have their firewall feature and I didn't even know about firewalls anyway.</blockquote><blockquote>I was determined to protect our servers from future attacks so I spent the next few days learning about security, IP-tables, firewalls, and general server hardening.</blockquote><p>I've come a long way from my self-taught server hardening crash course but I still have a strong drive for security and that's what has driven this exercise.</p><h2 id="my-use-case">My use case</h2><p>I had a terraform configuration with:</p><ul><li>An ECS cluster in a private subnet behind an ALB.</li><li>A Postgres RDS instance in a private subnet.</li><li>A bastion in a public subnet.</li></ul><p>With this configuration, I would connect to the database, and EC2 instances using SSH tunneling via the bastion. </p><h3 id="the-networking-module">The networking module</h3><pre><code class="language-HCL">module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~&gt; 2.69.0"

  name = "staff-advocacy-vpc"
  cidr = var.vpc_cidr
  azs  = var.availability_zones

  # ALB &amp; Bastion
  public_subnets = var.public_subnets_cidr

  # ECS CLUSTER
  private_subnets = var.private_subnets_cidr

  # RDS CLUSTER
  database_subnets = var.database_subnets_cidr

  # DNS
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Terraform   = "true"
    Environment = var.environment
  }
}</code></pre><h3 id="the-database-module">The database module</h3><pre><code class="language-HCL">locals {
  create_test_resources = true
}

module "db" {
  source  = "terraform-aws-modules/rds/aws"
  version = "2.20.0"

  vpc_security_group_ids = [module.postgres_security_group.this_security_group_id]
  create_db_subnet_group = false
  db_subnet_group_name   = local.create_test_resources ? var.subnet_group_name : ""

  username       = var.database_username
  password       = var.database_password
  port           = var.database_port
  identifier     = var.identifier
  name           = var.database_name
  engine         = "postgres"
  engine_version = var.database_engine_version

  create_db_option_group    = false
  create_db_parameter_group = false

  allocated_storage  = db_storage
  instance_class     = var.db_instance_class
  maintenance_window = var.db_maintenance_window
  backup_window      = var.db_backup_window

  tags = {
    Terraform   = "true"
    Environment = var.environment
  }
}

module "postgres_security_group" {
  source  = "terraform-aws-modules/security-group/aws//modules/postgresql"
  version = "~&gt; 3.0"

  name   = "${var.identifier}-sg"
  vpc_id = var.vpc_id
  
  # using computed_* here to get around count issues.
  ingress_cidr_blocks = var.vpc_cidr_block
  computed_ingress_cidr_blocks = var.vpc_cidr_block
  number_of_computed_ingress_cidr_blocks = 1

  ingress_rules       = ["postgresql-tcp"]

  egress_cidr_blocks = ["0.0.0.0/0"]
  egress_rules       = ["http-80-tcp", "https-443-tcp"]

  tags = {
    Terraform   = "true"
    Name = "${var.environment}-rds-sg"
    Environment = var.environment
  }
}</code></pre><h3 id="the-bastion-module">The bastion module</h3><pre><code class="language-HCL">module "bastion" {
  source  = "terraform-aws-modules/ec2-instance/aws"
  version = "2.16.0"

  ami                         = "ami-0e2e14798f7a300a1"
  name                        = var.name
  associate_public_ip_address = true
  instance_type               = "t2.small"
  vpc_security_group_ids      = [module.bastion_security_group.this_security_group_id]
  subnet_ids                  = var.vpc_public_subnets
  key_name                    = var.bastion_key_name
}

module "bastion_security_group" {
  source  = "terraform-aws-modules/security-group/aws"
  version = "3.1.0"

  name   = "${var.name}-sg"
  vpc_id = var.vpc_id

  ingress_cidr_blocks = ["0.0.0.0/0"]
  ingress_rules       = ["ssh-tcp"]

  egress_cidr_blocks = ["0.0.0.0/0"]
  egress_rules       = ["postgresql-tcp", "http-80-tcp", "https-443-tcp"]
}</code></pre><h3 id="fargate-cluster">Fargate cluster</h3><p>The full cluster module includes a lot more than a cluster, and alb resource definition but all of this is irrelevant here.</p><pre><code class="language-HCL">module "ecs_cluster" {
  source = "terraform-aws-modules/ecs/aws"

  name               = "${var.name}-${var.environment}"
  container_insights = true

  capacity_providers = ["FARGATE", "FARGATE_SPOT"]

  default_capacity_provider_strategy = [{
    capacity_provider = "FARGATE"
    weight            = "1"
  }]

  tags = {
    Environment = var.environment
  }
}

resource "aws_lb" "main" {
  name               = "${var.name}-alb-${var.environment}"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.alb.id]
  subnets            = var.vpc_public_subnets

  enable_deletion_protection = false

  tags = {
    Name        = "${var.name}-alb-${var.environment}"
    Environment = var.environment
  }
}

resource "aws_lb" "main" {
  name               = "${var.name}-alb-${var.environment}"
  internal           = false
  load_balancer_type = "application"
  security_groups    = [aws_security_group.alb.id]
  subnets            = var.vpc_public_subnets

  enable_deletion_protection = false

  tags = {
    Name        = "${var.name}-alb-${var.environment}"
    Environment = var.environment
  }
}

resource "aws_alb_target_group" "main" {
  name        = "${var.name}-tg-${var.environment}"
  port        = 80
  protocol    = "HTTP"
  vpc_id      = var.vpc_id
  target_type = "ip"

  health_check {
    healthy_threshold   = "3"
    interval            = "30"
    protocol            = "HTTP"
    matcher             = "200"
    timeout             = "3"
    path                = var.health_check_path
    unhealthy_threshold = "2"
  }

  tags = {
    Name        = "${var.name}-tg-${var.environment}"
    Environment = var.environment
  }
}

# Redirect to https listener
resource "aws_alb_listener" "http" {
  load_balancer_arn = aws_lb.main.id
  port              = 80
  protocol          = "HTTP"

  default_action {
    type = "redirect"

    redirect {
      port        = 443
      protocol    = "HTTPS"
      status_code = "HTTP_301"
    }
  }
}

# Redirect traffic to target group
resource "aws_alb_listener" "https" {
  load_balancer_arn = aws_lb.main.id
  port              = 443
  protocol          = "HTTPS"

  ssl_policy        = "ELBSecurityPolicy-2016-08"
  certificate_arn   = var.alb_tls_cert_arn

  default_action {
    target_group_arn = aws_alb_target_group.main.id
    type             = "forward"
  }
}</code></pre><p>To connect to my database, I use local ssh port forwarding:</p><pre><code class="language-bash">
# Run in the foreground
ssh -N ubuntu@&lt;bastion_ip&gt; -L 8888: rds-db.weoweio.ap-southeast-2.rds.amazonaws.com:5432

# Run in the background
ssh -Nf ubuntu@&lt;bastion_ip&gt; -L 9999: rds-db.weoweio.ap-southeast-2.rds.amazonaws.com:5432
</code></pre><p>This setup has always worked, and with <a href="https://www.fail2ban.org/wiki/index.php/Main_Page">fail2ban</a>/<a href="https://aws.amazon.com/guardduty/">Guardduty</a> and IP whitelist via security groups, it works great.</p><h2 id="challenges-with-this-configuration">Challenges with this configuration</h2><ul><li>I have to keep OpenSSH updated to mitigate emerging attacks that target vulnerabilities in outdated versions.</li><li>I have to manage ssh keys. ssh key management is hard, especially with large teams.</li><li>Auditing ssh sessions is a painful task. </li><li>Keeping public IP/DNS increases my attack surface. </li></ul><h2 id="the-solution">The solution</h2><h3 id="ec2-instance-connect">EC2 Instance Connect</h3><p>When I read about EC2 Instance Connect in 2019 as a way to connect to an ec2 instance with temporary SSH keys, I was excited. </p><p>EC2 Instance Connect lets me manage SSH access via IAM. </p><p>It works by allowing a user to send temporary public keys to the EC2 instance using a CLI, the user then has 60seconds to authenticate using the private key. </p><p>So instead of using long-term SSH keys that live on the bastion, I can use temporary/disposable keys that are automagically discarded after 60seconds.</p><!--kg-card-begin: html--><div style="width:100%;height:0;padding-bottom:56%;position:relative;"><iframe src="https://giphy.com/embed/3o7bu8LNPDbmXMhAys" width="100%" height="100%" style="position:absolute" frameborder="0" class="giphy-embed" allowfullscreen></iframe></div><p><a href="https://giphy.com/gifs/kimmyschmidt-3o7bu8LNPDbmXMhAys"></a></p><!--kg-card-end: html--><h3 id="ssm">SSM</h3><p>Using EC2 Instance Connect helps eliminate most of the problems listed above except the last one.</p><p>Using the AWS systems manager <code>StartSSHSession</code> document, I'm able to take the security a notch higher by eliminating the public DNS on the bastion.</p><!--kg-card-begin: html--><span id="how-to-do-this-in-terraform-anchor"></span><!--kg-card-end: html--><h3 id="how-to-do-this-in-terraform">How to do this in terraform</h3><!--kg-card-begin: markdown--><p>To do this in terraform, I have to :</p>
<ul>
<li>Configure IAM permissions for the bastion — Add an IAM instance profile that includes 2 policies.
<ul>
<li>arn:aws:iam::aws:policy/EC2InstanceConnect</li>
<li>arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore</li>
</ul>
</li>
<li>Install EC2 instance connect on the bastion or update my AMI. I found that EC2 instance connect comes pre-installed with certain AMIs (Amazon Linux &gt; 2 2.0.20190618 and Ubuntu &gt; 20.04 ).</li>
<li>Install <a href="https://github.com/aws/aws-ec2-instance-connect-cli"><code>ec2_instance_connect CLI</code></a>  on my local environment.</li>
<li>Install SSM plugin for the AWS CLI to my local environment. <a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-working-with-install-plugin.html">Reference here</a></li>
</ul>
<p>And some cleanup steps:</p>
<ul>
<li>Remove the SSH key pair on the bastion.</li>
<li>Remove the SSH ingress rules from my bastion security group.</li>
<li>Move the bastion from the public subnet to a private subnet.</li>
</ul>
<!--kg-card-end: markdown--><h3 id="the-changes-to-the-bastion-module">The changes to the bastion module</h3><!--kg-card-begin: html--><pre><code>
module "bastion" {
  source  = "terraform-aws-modules/ec2-instance/aws"
  version = "2.16.0"

  <strong>ami                 = "ami-0e2e14798f7a300a1"</strong>
  name                        = var.name
  associate_public_ip_address = true
  instance_type               = "t2.small"
  vpc_security_group_ids      = [module.bastion_security_group.this_security_group_id]
  
  # Move the bastion into a private subnet
  <strong>subnet_ids                  = var.vpc_private_subnets</strong>
  
  # remove the redundant ssh key pair
  <strong><span style="text-decoration:line-through">key_name                    = var.bastion_key_name</span></strong>
}

module "bastion_security_group" {
  source  = "terraform-aws-modules/security-group/aws"
  version = "3.1.0"

  name   = "${var.name}-sg"
  vpc_id = var.vpc_id

  # remove redundant ssh ingress rule
  <strong><span style="text-decoration:line-through">ingress_cidr_blocks = ["0.0.0.0/0"]</span></strong>
  <strong><span style="text-decoration:line-through">ingress_rules       = ["ssh-tcp"]</span></strong>

  egress_cidr_blocks = ["0.0.0.0/0"]
  egress_rules       = ["postgresql-tcp", "http-80-tcp", "https-443-tcp"]
}

<strong>
module ec2_connect_role_policy {
  source  = "terraform-aws-modules/iam/aws//modules/iam-assumable-role"
  version = "~> 3.7.0"

  role_name               = "${var.name}-ec2-connect-role"
  role_requires_mfa       = false
  create_role             = true
  create_instance_profile = true

  trusted_role_services   = ["ec2.amazonaws.com"]
  custom_role_policy_arns = ["arn:aws:iam::aws:policy/EC2InstanceConnect", "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"]
}
</strong>
</code></pre><!--kg-card-end: html--><h2 id="ssh-tunneling">SSH Tunneling</h2><p>To set up the ssh tunnel I:</p><ul><li>Generate a temporary ssh key.</li><li>Use ec2-instance-connect to upload the key to the bastion</li><li>SSH to the server over session manager using SSH ProxyCommand and the AWS CLI SSM plugin </li></ul><pre><code class="language-BASH"># Generate a temporary ssh key. 
ssh-keygen -t rsa -f /ssh_key -N ''

# Use ec2-instance-connect to upload the key to the bastion
aws ec2-instance-connect send-ssh-public-key --instance-id &lt;instance_id&gt; --instance-os-user &lt;os_user&gt; --availability-zone &lt;az&gt; --ssh-public-key file:///ssh_key.pub

#ssh to the server over session manager using the aws cli SSM plugin
ssh &lt;os_user&gt;@&lt;instance_id&gt; -i /ssh_key -Nf \
  -L 9999:&lt;rds_endpoint&gt; \
  -o "StrictHostKeyChecking=no" \
  -o "UserKnownHostsFile=/dev/null" \
  -o ProxyCommand="aws ssm start-session --target %h --document AWS-StartSSHSession --parameters portNumber=%p --region=&lt;region&gt;"</code></pre><p></p><p>My primary concern here is rds access. I haven't had the need to ssh into a fargate managed container in the ECS cluster but I imagine it would work the same way.</p>]]></content:encoded></item><item><title><![CDATA[Monorepo testing using jest projects]]></title><description><![CDATA[<!--kg-card-begin: html--><a style="background-color:black;color:white;text-decoration:none;padding:4px 6px;font-family:-apple-system, BlinkMacSystemFont, &quot;San Francisco&quot;, &quot;Helvetica Neue&quot;, Helvetica, Ubuntu, Roboto, Noto, &quot;Segoe UI&quot;, Arial, sans-serif;font-size:12px;font-weight:bold;line-height:1.2;display:inline-block;border-radius:3px" href="https://unsplash.com/@nevenkrcmarek?utm_medium=referral&amp;utm_campaign=photographer-credit&amp;utm_content=creditBadge" target="_blank" rel="noopener noreferrer" title="Download free do whatever you want high-resolution photos from Neven Krcmarek"><span style="display:inline-block;padding:2px 3px"><svg xmlns="http://www.w3.org/2000/svg" style="height:12px;width:auto;position:relative;vertical-align:middle;top:-1px;fill:white" viewbox="0 0 32 32"><title>unsplash-logo</title><path d="M20.8 18.1c0 2.7-2.2 4.8-4.8 4.8s-4.8-2.1-4.8-4.8c0-2.7 2.2-4.8 4.8-4.8 2.7.1 4.8 2.2 4.8 4.8zm11.2-7.4v14.9c0 2.3-1.9 4.3-4.3 4.3h-23.4c-2.4 0-4.3-1.9-4.3-4.3v-15c0-2.3 1.9-4.3 4.3-4.3h3.7l.8-2.3c.4-1.1 1.7-2 2.9-2h8.6c1.2 0 2.5.9 2.9 2l.8 2.4h3.7c2.4 0 4.3 1.9 4.3 4.3zm-8.6 7.5c0-4.1-3.3-7.5-7.5-7.5-4.1 0-7.5 3.4-7.5 7.5s3.3 7.5 7.5 7.5c4.2-.1 7.5-3.4 7.5-7.5z"/></svg></span><span style="display:inline-block;padding:2px 3px">Neven Krcmarek</span></a><!--kg-card-end: html--><!--kg-card-begin: markdown--><p>Recently, I’ve worked on setting up testing for a mono-repo at <a href="https://www.assignar.com">Assignar</a>.<br>
We use jest for testing, and although jest added a [multi-project runner] over a year ago, I couldn’t find any guides on it which made the implementation a bit of a pain.</p>
<p>This</p>]]></description><link>https://orlandobayo.com/blog/monorepo-testing-using-jest/</link><guid isPermaLink="false">6027ef54b45bfe2bcf769007</guid><category><![CDATA[monorepo]]></category><category><![CDATA[jest]]></category><category><![CDATA[test]]></category><category><![CDATA[babel]]></category><category><![CDATA[babel-jest]]></category><category><![CDATA[typescript]]></category><dc:creator><![CDATA[Orlando Bayo Adeyemi]]></dc:creator><pubDate>Thu, 15 Nov 2018 00:52:59 GMT</pubDate><media:content url="https://orlandobayo.com/blog/content/images/2018/11/neven-krcmarek-1147315-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: html--><a style="background-color:black;color:white;text-decoration:none;padding:4px 6px;font-family:-apple-system, BlinkMacSystemFont, &quot;San Francisco&quot;, &quot;Helvetica Neue&quot;, Helvetica, Ubuntu, Roboto, Noto, &quot;Segoe UI&quot;, Arial, sans-serif;font-size:12px;font-weight:bold;line-height:1.2;display:inline-block;border-radius:3px" href="https://unsplash.com/@nevenkrcmarek?utm_medium=referral&amp;utm_campaign=photographer-credit&amp;utm_content=creditBadge" target="_blank" rel="noopener noreferrer" title="Download free do whatever you want high-resolution photos from Neven Krcmarek"><span style="display:inline-block;padding:2px 3px"><svg xmlns="http://www.w3.org/2000/svg" style="height:12px;width:auto;position:relative;vertical-align:middle;top:-1px;fill:white" viewbox="0 0 32 32"><title>unsplash-logo</title><path d="M20.8 18.1c0 2.7-2.2 4.8-4.8 4.8s-4.8-2.1-4.8-4.8c0-2.7 2.2-4.8 4.8-4.8 2.7.1 4.8 2.2 4.8 4.8zm11.2-7.4v14.9c0 2.3-1.9 4.3-4.3 4.3h-23.4c-2.4 0-4.3-1.9-4.3-4.3v-15c0-2.3 1.9-4.3 4.3-4.3h3.7l.8-2.3c.4-1.1 1.7-2 2.9-2h8.6c1.2 0 2.5.9 2.9 2l.8 2.4h3.7c2.4 0 4.3 1.9 4.3 4.3zm-8.6 7.5c0-4.1-3.3-7.5-7.5-7.5-4.1 0-7.5 3.4-7.5 7.5s3.3 7.5 7.5 7.5c4.2-.1 7.5-3.4 7.5-7.5z"/></svg></span><span style="display:inline-block;padding:2px 3px">Neven Krcmarek</span></a><!--kg-card-end: html--><!--kg-card-begin: markdown--><img src="https://orlandobayo.com/blog/content/images/2018/11/neven-krcmarek-1147315-unsplash.jpg" alt="Monorepo testing using jest projects"><p>Recently, I’ve worked on setting up testing for a mono-repo at <a href="https://www.assignar.com">Assignar</a>.<br>
We use jest for testing, and although jest added a [multi-project runner] over a year ago, I couldn’t find any guides on it which made the implementation a bit of a pain.</p>
<p>This post is about my experience with the implementation.</p>
<h2 id="theproblem">The Problem.</h2>
<p><a href="https://en.wikipedia.org/wiki/Monorepo">Monorepos</a> are a source control pattern where all of the source code is kept in a single repository. This pattern has it's challenges. For testing, they include:</p>
<ul>
<li>Management. Testing can become unmanageable in a monorepo as there are many smaller projects that have their own setup and configuration.</li>
<li>Running tests. Having to <code>cd</code> into every directory and run tests for each project is time consuming, and you miss out on unified coverage report.</li>
</ul>
<h2 id="thesolution">The solution</h2>
<p><a href="https://jestjs.io/docs/en/configuration#projects-array-string-projectconfig">Jest projects</a> helps you manage and run tests for project in a mono-repo using a single instance of jest.</p>
<h3 id="goals">Goals</h3>
<ul>
<li>Run tests globally from the root.</li>
<li>Run tests from individual project directories.</li>
<li>Let developers manage their own jest, babel, and typescript configurations, i.e. shared common configuration in the root, and have custom configuration in each package.</li>
</ul>
<h3 id="projectstructure">Project Structure</h3>
<pre><code> ├──assignar   
 │   ├── packages
 │   │   ├── packageA
 │   │   │   ├── jest.config.js - jest config file for packageA (extends base.config.base.js)
 │   │   │   ├── .babelrc - used by `babel-jest` to transpile ts in packageA
 │   │   ├── packageB
 │   │   │   ├── jest.config.js - jest config file for packageB (extends base.config.base.js)
 │   │   │   ├── .babelrc - used by `babel-jest` to transpile ts in packageA
 │   │   │── packageC
 │   │   │   ├── jest.config.js - jest config file for packageC (extends base.config.base.js)
 │   │   │   ├── .babelrc - used by `babel-jest` to transpile ts in packageA
 │   ├── jest.config.base.js - (base jest config, imported by other packages)
 │   ├── babel.config.js - (used to instruct babel to use `.babelrc` files within the each package root directory`
 └── └── jest.config.js - config used to configure projects and run all tests 
</code></pre>
<h3 id="rootconfiguration">Root Configuration</h3>
<p>The root of the app contains:</p>
<ul>
<li>jest.config.js</li>
<li>jest.config.base.js</li>
<li>babel.config.js</li>
</ul>
<p><strong>jest.config.js</strong><br>
To setup jest projects for the directory structure above, I specify a <a href="https://en.wikipedia.org/wiki/Glob_(programming)">path glob</a>.</p>
<pre><code class="language-javascript">// jest.config.js
module.exports = {
...,
projects: [ '&lt;rootDir&gt;/packages/*/jest.config.js']
}
</code></pre>
<p>I end up with a file that looks like this.</p>
<pre><code class="language-javascript">const baseConfig = require('./jest.config.base')

module.exports = {
    ...baseConfig,
    projects: [
            '&lt;rootDir&gt;/packages/*/jest.config.js',
    ],
    coverageDirectory: '&lt;rootDir&gt;/coverage/',
    collectCoverageFrom: [
        '&lt;rootDir&gt;/packages/*/src/**/*.{ts,tsx}',
    ],
    testURL: 'http://localhost/',
    moduleNameMapper: {
        '.json$': 'identity-obj-proxy',
    },
    moduleDirectories: [
        'node_modules',
    ],
    snapshotSerializers: [
        'enzyme-to-json/serializer',
    ],
}
</code></pre>
<p><strong>jest.config.base.js</strong><br>
The base config <code>./jest.config.base.js</code> contains configuration that's shared across the monorepo.</p>
<p><strong>babel.config.js</strong><br>
We use babel-jest for ts transpilation, and so I had to figure out a way to let babel use the <code>babelrc</code> files in individual project directories. I was shocked to find that this doesn't work out of the box.<br>
After a bit of digging, I found <a href="https://github.com/facebook/jest/issues/6053">this comment</a> on github that explains that you have to specify <code>babelRcRoots: &quot;packages/*&quot;</code> in <a href="https://babeljs.io/docs/en/next/config-files#project-wide-configuration"><code>babel.config.js</code></a>(<em>which is a new non-heirarchical config file that loads from the root directory</em>) to instruct babel to use the configurations in subdirectories.<br>
There's more information on that config file in the <a href="https://babeljs.io/docs/en/next/config-files#project-wide-configuration">babel docs</a></p>
<h3 id="projectconfiguration">Project Configuration</h3>
<p>Based on my monorepo's  <code>jest.config.js</code> configuration above, jest looks for <code>jest.config.js</code> files in my <code>packages</code> directory and sets up the parent directories of those files as projects.</p>
<p><strong>Root Directory</strong><br>
I set the root directory of all project configurations as the root directory of the mono-repo.i.e. <code>{rootDir: '../..'}</code>.</p>
<p>As a result I have to specify absolute path all over my configuration instead of relative path. e.g.</p>
<pre><code> {
 setupTestFrameworkScriptFile:rootDir&gt;/packages/${packageName}/jest/setupJest.ts
 }
</code></pre>
<p><strong>Naming</strong><br>
Jest provides a <code>displayName</code> option, which is shown nexts to the tests in the terminal.</p>
<p><strong>Module Paths</strong><br>
I ran into ts path issues while running the tests. Some typescript project configurations specify a baseUrl of <code>./src</code>, and typescript tries to resolve modules using <code>monorepoRootDir/src</code>.</p>
<p>Jest has a <a href="https://jestjs.io/docs/en/configuration#modulepaths-array-string">modulePaths</a> option to let you provide an array of absolute paths to search when resolving modules. This solved the above issue.</p>
<p>Each package ends up with a configuration like this.</p>
<pre><code class="language-javascript">'use strict'
const baseConfig = require('../../jest.config.base')

const packageName = require('./package.json').name.split('@assignar/').pop()

module.exports = {
    ...baseConfig,
    roots: [
        `&lt;rootDir&gt;/packages/${packageName}`,
    ],
    collectCoverageFrom: [
        'src/**/*.{ts,tsx}',
    ],
    setupFiles: [
        `&lt;rootDir&gt;/packages/${packageName}/jest/polyfills.ts`,
        `&lt;rootDir&gt;/packages/${packageName}/jest/setupEnzyme.ts`,
    ],
    setupTestFrameworkScriptFile: `&lt;rootDir&gt;/packages/${packageName}/jest/setupJest.ts`,
    testRegex: `(packages/${packageName}/.*/__tests__/.*|\\.(test|spec))\\.tsx?$`,
    testURL: 'http://localhost/',
    moduleNameMapper: {
        '.json$': 'identity-obj-proxy',
        'lodash-es': 'lodash',
    },
    moduleDirectories: [
        'node_modules',
    ],
    modulePaths: [
        `&lt;rootDir&gt;/packages/${packageName}/src/`,
    ],
    snapshotSerializers: [
        'enzyme-to-json/serializer',
    ],
    name: packageName,
    displayName: packageName,
    rootDir: '../..',
}
</code></pre>
<h1 id="alternatives">Alternatives</h1>
<p><a href="https://github.com/lerna/lerna">Lerna</a> is a tool that optimizes the workflow around managing multi-package repositories.<br>
<a href="https://github.com/lerna/lerna">Lerna</a> has a test command you can run.  <code>lerna run test</code> runs a script that goes through each package and runs the test script declared in package.json. This is very slow, might lose syntax highlighting, and use up too much resources as it spawns tests in seperate processes. Also it doesn't solve any of the problems we faced, so it wasn't a viable option.</p>
<!--kg-card-end: markdown--><hr><!--kg-card-begin: markdown--><p>This has worked out well, as we now able to run pre-commit hooks using lerna's <a href="https://jestjs.io/docs/en/cli#onlychanged">--onlyChanged option</a>, and we have one coverage report for all projects across the mono-repo.</p>
<p>One challenge is that I've been unable to use jest's <a href="https://jestjs.io/docs/en/cli#findrelatedtests-spaceseparatedlistofsourcefiles">--findRelatedTests</a> option, which would be greate for CI.<br>
I hope to figure that out soon.</p>
<!--kg-card-end: markdown--><!--kg-card-begin: html--><h2>One last thing</h2>


If you've found this post helpful, consider supporting the blog using the button below.

<script type="text/javascript" src="https://cdnjs.buymeacoffee.com/1.0.0/button.prod.min.js" data-name="bmc-button" data-slug="orlandobayo" data-color="#5F7FFF" data-emoji="🍺" data-font="Poppins" data-text="Buy me a beer" data-outline-color="#000000" data-font-color="#ffffff" data-coffee-color="#FFDD00"></script><!--kg-card-end: html-->]]></content:encoded></item><item><title><![CDATA[Deploying to Microsoft Azure with Terraform]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Last year, I fell in love with <a href="http://www.terraform.io/">Terraform</a> and I used it with AWS and Digital ocean with really great results.</p>
<p>I recently began to explore Azure and I faced a couple of challenges getting to use Terraform with Azure, so I thought I'd write an article about it.</p>
<p>In</p>]]></description><link>https://orlandobayo.com/blog/deploying-to-microsoft-azure-with-terraform/</link><guid isPermaLink="false">6027ef54b45bfe2bcf769005</guid><category><![CDATA[terraform]]></category><category><![CDATA[hashicorp]]></category><category><![CDATA[microsoft]]></category><category><![CDATA[azure]]></category><category><![CDATA[ansible]]></category><dc:creator><![CDATA[Orlando Bayo Adeyemi]]></dc:creator><pubDate>Sat, 18 Feb 2017 15:36:00 GMT</pubDate><media:content url="https://orlandobayo.com/blog/content/images/2018/10/terrazure--1-.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://orlandobayo.com/blog/content/images/2018/10/terrazure--1-.jpg" alt="Deploying to Microsoft Azure with Terraform"><p>Last year, I fell in love with <a href="http://www.terraform.io/">Terraform</a> and I used it with AWS and Digital ocean with really great results.</p>
<p>I recently began to explore Azure and I faced a couple of challenges getting to use Terraform with Azure, so I thought I'd write an article about it.</p>
<p>In this article, I will talk about Terraform and show the steps I followed, and challenges faced while setting up virtual machines in a Virtual network and all the supporting infrastructure (subnets, resource groups, security groups, etc).</p>
<h4 id="background">Background</h4>
<p><a href="https://www.terraform.io/">HashiCorp's Terraform</a> is my preferred tool for orchestration. Although I still use <a href="https://www.ansible.com/">Ansible</a> for configuration management, I prefer to use Terraform for orchestration.</p>
<p>Configuration management tools install and manage software on a machine that already exists. Terraform is not a configuration management tool, and it allows existing tooling to focus on their strengths: bootstrapping and initializing resources.</p>
<p>With the availability of <a href="https://www.docker.com/">docker</a> and <a href="https://www.packer.io/">packer</a>, there's little need for configuration management, so I have found my need for ansible has reduced significantly.</p>
<h4 id="whyiuseterraform">Why I use terraform</h4>
<p>I could probably write a blog post about how great terraform is, but I'll just list 2 reasons here.</p>
<ul>
<li><strong>Declarative code</strong> : Terraform lets you use a declarative style to write your code, so you just write your desired end state, and leave it to decide the order in which your declared resources need to be created.</li>
<li><strong>Client-side architecture</strong> : Terraform connects to cloud providers directly through their APIs, so you don't need to setup or run any special softwares on your servers.</li>
</ul>
<h4 id="requirements">Requirements</h4>
<ul>
<li>Terraform</li>
<li>Azure-CLI</li>
</ul>
<h4 id="installingterraform">Installing Terraform</h4>
<p>To install Terraform, you need to download the appropriate package, unzip the package, and add the executable file to your <strong>PATH</strong>.</p>
<pre><code class="language-bash"># step 1. Download the package
wget https://releases.hashicorp.com/terraform/0.8.7/terraform_0.8.7_linux_amd64.zip
# step 2. Unzip the package
unzip terraform_0.8.7_linux_amd64.zip
#move the execuatable to directory in PATH 
sudo mv terraform /usr/bin/
</code></pre>
<p>At this point, terraform should be added to your path. You can confirm this by running <code>terraform --version</code> which should show your installed version <code>0.8.7</code> in my case.</p>
<h4 id="installingazurecli">Installing Azure CLI</h4>
<pre><code class="language-bash">pip install --user --upgrade azure-cli
</code></pre>
<p>If you don't have python or pip installed and would rather not install them, you can read <a href="https://github.com/Azure/azure-cli">here</a> for alternative installation methods.</p>
<h4 id="connectingtoazuresubscription">Connecting to Azure subscription</h4>
<p>Every account on Azure is part of a subscription. To connect to azure services, Terraform needs to be able to provision resources unto your Azure subscription for you.</p>
<p>Azure Active Directory manages access to subscriptions. It is an Identity and Access management solution that provides a set of capabilities to manage users and groups.<br>
I'm still wrapping my head around the idea of Azure Active Directory so you can join me to read about it <a href="https://docs.microsoft.com/en-us/azure/active-directory/active-directory-whatis">here</a></p>
<p>To connect Terraform to azure, you need the following credentials:</p>
<ul>
<li>A subscription ID</li>
<li>A client ID</li>
<li>A client secret</li>
<li>A Tenant ID</li>
</ul>
<p>HashiCorp and Microsoft have produced scripts to help with obtaining these <a href="https://github.com/mitchellh/packer/blob/master/contrib/azure-setup.sh">here</a> however the script continually failed for me, so here's how I went about obtaining these credentials.</p>
<p><strong>Login</strong></p>
<pre><code># first step, run the login command follow the steps 
$ az login

#you should see an output like below.
[
  {
    &quot;cloudName&quot;: &quot;AzureCloud&quot;,
    &quot;id&quot;: &quot;ads238932-2323-209239-SUSI-02ksjdk&quot;,
    &quot;isDefault&quot;: true,
    &quot;name&quot;: &quot;BizSpark&quot;,
    &quot;state&quot;: &quot;Enabled&quot;,
    &quot;tenantId&quot;: &quot;fo90dfoi-23-232ji-a233-c2389jkk2389832&quot;,
    &quot;user&quot;: {
      &quot;name&quot;: &quot;email@outlook.com&quot;,
      &quot;type&quot;: &quot;user&quot;
    }
  }
]
#save the tenantId and id. The id here is your subscription ID
</code></pre>
<p>Now we have the subscription ID and tenant ID, next we find the client secret and client ID.</p>
<p><strong>Set subscription ID</strong></p>
<pre><code class="language-bash">#replace SUBSCRIPTION_ID with the id above or set it as the SUBSCRIPTION_ID environmental variable.

az account set --subscription=&quot;${SUBSCRIPTION_ID}&quot;
</code></pre>
<p><strong>Create role for subscription</strong></p>
<p>Three things need to be done here:</p>
<ul>
<li>Create Azure active directory application</li>
<li>Create Azure service principal</li>
<li>Assign a contributor role</li>
</ul>
<pre><code class="language-bash">#Create a service principal, configure its access to Azure resources and assign Contributor role.

az ad sp create-for-rbac --role=&quot;Contributor&quot; --scopes=&quot;/subscriptions/${SUBSCRIPTION_ID}&quot;

# This should return JSON with the appId and password

{
  &quot;appId&quot;: &quot;12332skdu-we32-23df-se43-wew23243223&quot;,
  &quot;displayName&quot;: &quot;azure-cli-2017-02-10-02-18-22&quot;,
  &quot;name&quot;: &quot;http://azure-cli-2017-02-10-02-18-22&quot;,
  &quot;password&quot;: &quot;jsiue981289-12-12er-we2344-23ksjksd&quot;,
  &quot;tenant&quot;: &quot;fo90dfoi-23-232ji-a233-c2389jkk2389832&quot;
} 
</code></pre>
<p>The <code>appId</code> returned above is the <code>client ID</code>, and <code>password</code> is the <code>client secret</code> we need to use with terraform.</p>
<p>You can confirm the credentials by running the login command on the cli using the app ID, password and tenant ID obtained above.</p>
<pre><code class="language-bash">az login --service-principal -u [APP ID] -p [PASSWORD] --tenant [TENANT ID]
</code></pre>
<h4 id="terraformscript">Terraform script</h4>
<p>After obtaining credentials we can move on to create the terraform script. In this part, I'll create an empty resource group and a virtual network with 3 subnets.</p>
<p>I'll create two files; one for variables and one to create resources. I like to keep my variables in a different file, so they can be referenced in my script.</p>
<p>First I'll create a file to save my variables, I'll call this <strong>variables.tf</strong></p>
<pre><code class="language-javascript">#variables.tf
variable &quot;tenant_id&quot; {
	default = &quot;o90dfoi-23-232ji-a233-c2389jkk2389832&quot;
}

variable &quot;client_id&quot; {
	default=&quot;12332skdu-we32-23df-se43-wew23243223&quot;
}

variable &quot;client_secret&quot; {
	default=&quot;jsiue981289-12-12er-we2344-23ksjksd&quot;
}

variable &quot;subscription_id&quot; {
	default=&quot;ads238932-2323-209239-SUSI-02ksjdk&quot;
}
</code></pre>
<p>Next I'll create a file called <strong>main.tf</strong> with code to create my resources.</p>
<pre><code class="language-javascript">#main.tf
# Configure the Microsoft Azure Provider
provider &quot;azurerm&quot; {
  subscription_id = &quot;${var.subscription_id}&quot;
  client_id       = &quot;${var.client_id}&quot;
  client_secret   = &quot;${var.client_secret}&quot;
  tenant_id       = &quot;${var.tenant_id}&quot;
}

# Create a resource group
resource &quot;azurerm_resource_group&quot; &quot;development&quot; {
    name     = &quot;development&quot;
    location = &quot;North Europe&quot;
}

# Create a virtual network in the web_servers resource group
resource &quot;azurerm_virtual_network&quot; &quot;network&quot; {
  name                = &quot;developmentNetwork&quot;
  address_space       = [&quot;192.168.0.0/16&quot;]
  location            = &quot;${azurerm_resource_group.production.location}&quot;
  resource_group_name = &quot;${azurerm_resource_group.production.name}&quot;

  subnet {
    name           = &quot;subnet1&quot;
    address_prefix = &quot;192.168.1.0/24&quot;
  }

  subnet {
    name           = &quot;subnet2&quot;
    address_prefix = &quot;192.168.2.0/24&quot;
  }

  subnet {
    name           = &quot;subnet3&quot;
    address_prefix = &quot;192.168.3.0/24&quot;
  }
}

</code></pre>
<h4 id="runningthescript">Running the script</h4>
<p>I'll run <code>terraform plan</code> to see what changes will be made. If you're new to terraform. The plan command helps you catch bugs and verify changes before deployment.</p>
<pre><code class="language-bash">$ terraform apply

Refreshing Terraform state in-memory prior to plan...

+ azurerm_resource_group.development
    location: &quot;northeurope&quot;
    name:     &quot;development&quot;
    tags.%:   &quot;&lt;computed&gt;&quot;

+ azurerm_virtual_network.network
    address_space.#:                  &quot;1&quot;
    address_space.0:                  &quot;192.168.0.0/16&quot;
    location:                         &quot;northeurope&quot;
    name:                             &quot;developmentNetwork&quot;
    resource_group_name:              &quot;development&quot;
    subnet.#:                         &quot;3&quot;
    subnet.1008457452.address_prefix: &quot;192.168.3.0/24&quot;
    subnet.1008457452.name:           &quot;subnet3&quot;
    subnet.1008457452.security_group: &quot;&quot;
    subnet.1321000609.address_prefix: &quot;192.168.2.0/24&quot;
    subnet.1321000609.name:           &quot;subnet2&quot;
    subnet.1321000609.security_group: &quot;&quot;
    subnet.3646277238.address_prefix: &quot;192.168.1.0/24&quot;
    subnet.3646277238.name:           &quot;subnet1&quot;
    subnet.3646277238.security_group: &quot;&quot;
    tags.%:                           &quot;&lt;computed&gt;&quot;


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

</code></pre>
<p>Everything looks okay, I'll run <code>terraform apply</code> to deploy these resources to azure.</p>
<pre><code class="language-bash">$ terraform apply

azurerm_resource_group.development: Creating...
  location: &quot;&quot; =&gt; &quot;northeurope&quot;
  name:     &quot;&quot; =&gt; &quot;development&quot;
  tags.%:   &quot;&quot; =&gt; &quot;&lt;computed&gt;&quot;
azurerm_resource_group.development: Creation complete
azurerm_virtual_network.network: Creating...
  address_space.#:                  &quot;&quot; =&gt; &quot;1&quot;
  address_space.0:                  &quot;&quot; =&gt; &quot;192.168.0.0/16&quot;
  location:                         &quot;&quot; =&gt; &quot;northeurope&quot;
  name:                             &quot;&quot; =&gt; &quot;developmentNetwork&quot;
  resource_group_name:              &quot;&quot; =&gt; &quot;development&quot;
  subnet.#:                         &quot;&quot; =&gt; &quot;3&quot;
  subnet.1472110187.address_prefix: &quot;&quot; =&gt; &quot;192.168.1.0/24&quot;
  subnet.1472110187.name:           &quot;&quot; =&gt; &quot;subnet1&quot;
  subnet.1472110187.security_group: &quot;&quot; =&gt; &quot;&quot;
  subnet.2796830261.address_prefix: &quot;&quot; =&gt; &quot;192.168.2.0/24&quot;
  subnet.2796830261.name:           &quot;&quot; =&gt; &quot;subnet2&quot;
  subnet.2796830261.security_group: &quot;&quot; =&gt; &quot;&quot;
  subnet.4132282879.address_prefix: &quot;&quot; =&gt; &quot;192.168.3.0/24&quot;
  subnet.4132282879.name:           &quot;&quot; =&gt; &quot;subnet3&quot;
  subnet.4132282879.security_group: &quot;&quot; =&gt; &quot;&quot;
  tags.%:                           &quot;&quot; =&gt; &quot;&lt;computed&gt;&quot;
azurerm_virtual_network.network: Creation complete

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
</code></pre>
<h4 id="conclusion">Conclusion</h4>
<p>I decided to make this into a 2-part series. In the next part, I'll create a network with subnets, network interfaces, storage blocks,  virtual machines and storage disks.</p>
<p>If you've faced any other challenges or have any other comments, please let me know in the comments.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Ionic-social sharing android application]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Although I have worked with Angularjs for a while, I never got to explore Ionic until very recently.</p>
<p>In this post, I write about my first experience with setting up Ionic and building a simple application with it.</p>
<p>I found a client who wanted to hire me to work on</p>]]></description><link>https://orlandobayo.com/blog/ionic-social-sharing-android-application/</link><guid isPermaLink="false">6027ef54b45bfe2bcf769004</guid><category><![CDATA[socialsharing plugin]]></category><category><![CDATA[angularjs]]></category><category><![CDATA[node.js]]></category><category><![CDATA[android]]></category><dc:creator><![CDATA[Orlando Bayo Adeyemi]]></dc:creator><pubDate>Tue, 05 Aug 2014 23:48:00 GMT</pubDate><media:content url="https://orlandobayo.com/blog/content/images/2018/10/sharemobile_screenshot-1.jpg" medium="image"/><content:encoded><![CDATA[<!--kg-card-begin: markdown--><img src="https://orlandobayo.com/blog/content/images/2018/10/sharemobile_screenshot-1.jpg" alt="Ionic-social sharing android application"><p>Although I have worked with Angularjs for a while, I never got to explore Ionic until very recently.</p>
<p>In this post, I write about my first experience with setting up Ionic and building a simple application with it.</p>
<p>I found a client who wanted to hire me to work on an android project that was built using Angularjs/Ionic and cordova. .</p>
<p>I was asked to create a very basic android application using Ionic/Angularjs. The features are:</p>
<ul>
<li>share text from my app to another app.</li>
<li>take a screenshot of my screen, save it and share it.</li>
</ul>
<h2 id="step1installation">Step1 : Installation</h2>
<p>You need to have <code>node.js</code> installed.</p>
<ul>
<li>
<p>Install cordova	<code>$ npm install -g cordova</code></p>
</li>
<li>
<p>Install ionic, set platform, and run. You can replace android with ios here</p>
</li>
</ul>
<script src="https://gist.github.com/dhatawesomedude/1731e6b05430b23e3514.js"></script>
<p>After instlalling cordova. if you use a windows machine, you'll have to set up <a href="http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html">Java JDK</a>, <a href="http://ant.apache.org/manual/install.html">Apache Ant</a> and <a href="http://developer.android.com/sdk/index.html">Android SDK</a>. Download these and refer to the <a href="http://docs.phonegap.com/en/2.2.0/guide_getting-started_android_index.md.html">phonegap guide</a> for help with PATH setup.</p>
<p>I installed <a href="https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin">this plugin</a> to use android's sharing feature.<br>
Installing via CLI is easy. Just run<br>
<code>$ cordova plugin add https://github.com/EddyVerbruggen/SocialSharing-PhoneGap-Plugin.git</code></p>
<h2 id="step2settingupangular">Step2: Setting Up Angular</h2>
<p>For my Angular setup first. I created a module, a controller(in a sperate module), and defined functions to handle my screenshots which will be applied to my views.</p>
<ul>
<li>Controller</li>
</ul>
<script src="https://gist.github.com/dhatawesomedude/1e70bfce687d4917ba1a.js"></script>
<ul>
<li>Main module. Here i included <code>ionic</code> and <code>MainCtrl</code> as dependencies.</li>
</ul>
<script src="https://gist.github.com/dhatawesomedude/4ac1113d7a2d02413d24.js"></script>
<ul>
<li>Main View<br>
My main view is really simple with a header-bar and two buttons that call <code>toShare()</code> and <code>takeScreenshot()</code> when clicked.</li>
</ul>
<script src="https://gist.github.com/dhatawesomedude/4e1d5a404bf6e45e26ef.js"></script>
<p>To run the aapp. Connect your android phone, and run <code>ionic run android</code><br>
<img src="https://orlandobayo.com/blog/content/images/2018/10/sharemobile_screenshot.jpg" alt="Ionic-social sharing android application"></p>
<h2 id="testing">Testing</h2>
<p>Head on to <a href="https://github.com/dhatawesomedude/sharemobile">github</a> and download the code to tweak or test it.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Welcome to Ghost]]></title><description><![CDATA[<!--kg-card-begin: markdown--><p>Blogging isn't really my thing, but everytime I find myself stuck while writing code 😓 and I can't find any useful resources online I keep thinking I should share what I learn for someone else who gets stuck like I did to learn from.</p>
<p>I am currently working on some MEAN</p>]]></description><link>https://orlandobayo.com/blog/welcome-to-ghost/</link><guid isPermaLink="false">6027ef54b45bfe2bcf769002</guid><dc:creator><![CDATA[Orlando Bayo Adeyemi]]></dc:creator><pubDate>Fri, 11 Jul 2014 05:32:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>Blogging isn't really my thing, but everytime I find myself stuck while writing code 😓 and I can't find any useful resources online I keep thinking I should share what I learn for someone else who gets stuck like I did to learn from.</p>
<p>I am currently working on some MEAN stack web projects as well as Android mobile development projects. I will be sharing my progress and challenges from time to time.</p>
<p>And of course, every other code-unrelated issues I have on my mind. 😆</p>
<p>If you have any requests or need help with any of my tutorials, just let me know. ✌</p>
<!--kg-card-end: markdown-->]]></content:encoded></item><item><title><![CDATA[Deploy Ghost Blog to a subdirectory using Nignx on Ubuntu Server]]></title><description><![CDATA[Learn how to deploy ghost unto your server to a different subdirectory]]></description><link>https://orlandobayo.com/blog/deploy-ghost-blog-to-a-subdirectory-using-nignx-on-ubuntu-server/</link><guid isPermaLink="false">6027ef54b45bfe2bcf769003</guid><category><![CDATA[ghost]]></category><category><![CDATA[nginx]]></category><category><![CDATA[forever]]></category><category><![CDATA[ubuntu]]></category><category><![CDATA[mandrill]]></category><category><![CDATA[node.js]]></category><dc:creator><![CDATA[Orlando Bayo Adeyemi]]></dc:creator><pubDate>Thu, 12 Jun 2014 00:23:00 GMT</pubDate><content:encoded><![CDATA[<!--kg-card-begin: markdown--><p>I am sharing how I deployed ghost onto my server with a differeent subdirectory.</p>
<h2 id="step1installnodejs"><strong>STEP 1: Install Node.js</strong></h2>
<p>If you already have Node.js installed you can skip this part.</p>
<script src="https://gist.github.com/dhatawesomedude/c416b474f0523d1f2aeb.js"></script>
<h2 id="step2installghost"><strong>STEP 2: Install Ghost</strong></h2>
<p>Here I opened  the directory where the source code for my site lies and install ghost using npm.</p>
<script src="https://gist.github.com/dhatawesomedude/3e70a8d51387ee4c9c74.js"></script>
<h2 id="step3configureghost"><strong>Step 3: Configure Ghost</strong></h2>
<p>To begin I copied over the example config file and then edited it.</p>
<script src="https://gist.github.com/dhatawesomedude/55df909f847e2e0fa68b.js"></script>
<p>Under the production section of the config file.</p>
<ul>
<li>I Changed the default url to include your subdirectory</li>
</ul>
<script src="https://gist.github.com/dhatawesomedude/c1d3d6e6c979dc25166e.js"></script>
<ul>
<li>I Changed the host</li>
</ul>
<script src="https://gist.github.com/dhatawesomedude/4ee29184e2456d89df7e.js"></script>
<ul>
<li>I Changed the mailer. To continue this part, I had to go over to <a href="https://orlandobayo.com/blog/deploy-ghost-blog-to-a-subdirectory-using-nignx-on-ubuntu-server/www.mandrill.com">Mandrill</a> and sign up for an account. Mandrill will be used to send mail for the blog.</li>
</ul>
<script src="https://gist.github.com/dhatawesomedude/3cc8bd13ead077f6f78a.js"></script>
<h2 id="step4createghostuser"><strong>Step 4: Create ghost User</strong></h2>
<p>Here I created a new user and run the test to see that Ghost is running.</p>
<script src="https://gist.github.com/dhatawesomedude/b41ea3d044f75efd2fec.js"></script>
<p>If you see the message &quot;Ghost is running.&quot; you have successfully installed ghost. Press CTRL+C to exit the process.</p>
<h2 id="step5runghostasbackgroundprocess"><strong>Step 5: Run ghost as background process</strong></h2>
<p>I now have ghost running but I need to keep the terminal open to have it run. My goal is to have it run in the background, and I will use forever to acheive this. Forever is a CLI tool for ensuring that a given script runs continously. You can look up the documentation <a href="https://github.com/nodejitsu/forever">here</a>.<br>
Install forever and start a process with the index.js file.<br>
<em>Run this code from within the ghost directory</em></p>
<script src="https://gist.github.com/dhatawesomedude/f283db16bc0fbb85095b.js"></script>
<h2 id="step6nginxconfiguration"><strong>Step 6 : Nginx configuration</strong></h2>
<p>I had to  find the Nginx config file and edit it to set the /blog to proxy to the port that ghost sits on; default : 2368.</p>
<script src="https://gist.github.com/dhatawesomedude/efba8c8e07e64669cf1d.js"></script>
<p>If you cannot find your nginx config file, checkout the <a href="http://nginx.com/resources/admin-guide/web-server/">nginx documentation</a> for other possible locations.</p>
<p>After I found the file, I edited the file like this</p>
<script src="https://gist.github.com/dhatawesomedude/ffddec87115587ee3548.js"></script>
<p>Next. restarted Nginx and my blog was live.<br>
<code>sudo /etc/init.d/nginx/ restart</code> or <code>service nginx restart</code></p>
<p>Now when you go to yoursite.com/blog you should have your ghost blog there.</p>
<!--kg-card-end: markdown-->]]></content:encoded></item></channel></rss>