Automating Deployment for Laravel Using Deployer and GitHub Actions

Overview

In this note, we outline how to automate the deployment process for a Laravel project using Deployer and GitHub Actions. The setup covers deploying to both staging and production environments, with different workflows for each environment. We also integrate necessary post-deployment tasks such as running migrations and restarting queues using Supervisor.

Prerequisites

Before setting up this automation, ensure the following:

  • Deployer is installed in your Laravel project (composer require deployer/deployer --dev).
  • The deploy.php configuration file is created in the root of your project.
  • SSH keys are set up correctly between GitHub Actions and your server for secure deployment.

Deployment Setup

Step 1: Creating the deploy.php File

The deploy.php file is essential for configuring Deployer. It defines the repository, shared directories, writable directories, and host settings for both staging and production environments. Below is an example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?php
namespace Deployer;

require 'recipe/laravel.php';

// Project repository
set('repository', '[email protected]:your-username/your-repo.git');

// Shared files/dirs between deploys
add('shared_files', ['.env']);
add('shared_dirs', ['node_modules', 'vendor']);

// Writable dirs by web server
add('writable_dirs', []);

// Hosts
host('staging')
->set('hostname','IP')
->set('remote_user', 'deployer')
->set('branch', 'staging')
->set('deploy_path', '/var/www/staging');

host('production')
->set('hostname','IP')
->set('remote_user', 'deployer')
->set('branch', 'main')
->set('deploy_path', '/var/www/production');

// Tasks
desc('Restart PHP-FPM service');
task('php-fpm:restart', function () {
run('sudo systemctl restart php7.4-fpm');
});

desc('Restart supervisor service');
task('supervisor:restart', function () {
run('sudo supervisorctl reread');
run('sudo supervisorctl update');
run('sudo supervisorctl restart all');
});

// Hooks
after('deploy:symlink', 'php-fpm:restart');
after('deploy:symlink', 'artisan:migrate');
after('deploy:symlink', 'supervisor:restart');

Step 2: GitHub Actions Workflow

Staging Workflow (staging-deploy.yml)

The following is an example of a GitHub Actions workflow file for deploying to the staging environment.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
name: Deploy to Staging

on:
push:
branches:
- staging

jobs:
deploy:
name: Deploy to Staging
runs-on: ubuntu-latest

steps:
- name: Checkout Code
uses: actions/checkout@v4

- name: Install PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.1'

- name: Install Composer Dependencies
run: composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader

- name: Deploy to Staging
uses: deployphp/action@v1
with:
private-key: ${{ secrets.SSH_KEY }}
dep: deploy staging

Production Workflow (production-deploy.yml)

The production workflow is similar but triggers on pushes to the main branch.

Step 3: Testing and Troubleshooting

Common Errors

  1. No host selected: This error occurs when Deployer cannot find the host based on the command selector. Make sure you pass the correct host (either staging or production) when deploying.

    1
    ./vendor/bin/dep deploy staging
  2. Call to undefined method: This error may appear if you’re using the wrong Deployer version or method. In our case, ensure you’re following the Deployer 7.x documentation.

Step 4: Post-Deployment Tasks

  1. Running Migrations: We use the artisan:migrate command to ensure that migrations are run automatically after deployment.

  2. Restarting Queues: If changes to the queue system are deployed, it’s essential to restart the queue workers. We achieve this by running artisan:queue:restart after deployment.

  3. Restarting Supervisor: If you’re managing workers using Supervisor, the deployment process includes commands to restart Supervisor with supervisorctl.


Summary

By combining Deployer and GitHub Actions, we’ve automated the deployment process for a Laravel project across both staging and production environments. This approach ensures the project is deployed and migrations are run automatically, and queue workers are restarted when necessary. The process is both scalable and adaptable for future projects.

Sources

  1. Deployer Documentation: Hosts in Deployer 7.x
    https://deployer.org/docs/7.x/hosts

  2. Deployer GitHub Action
    https://github.com/deployphp/action

  3. Deployer Official Documentation: Version 7.x
    https://deployer.org/docs/7.x/

  4. GitHub Actions Documentation: Workflow Syntax
    https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions

Masterclass Notes on Radio Advertising

Introduction

Companies buy reach instead of a number of spots. This reach represents the number of listeners at a given moment, such as during rush hour. The number of spots does not matter if the reach is low. You have gross reach, average contact frequency (GCF), and Net reach. Net reach refers to the number of unique listeners, while gross reach does not distinguish between them. The GCF measures how often a listener hears a spot.

Example Calculation

$Gross = 2.583.958$
$GCF = 4$
$Net = \frac{Gross}{GCF} = 645.989,50 \approx 645.990 \text{ Unique listeners}$


Understanding GRP (Gross Rating Point)

Radio stations use Gross Rating Point (GRP), which works with target groups and percentages. A GRP of 5 means that 5% of the target group was reached. This can be filtered by age, gender, country, etc., with age being the most common filter. GRP adds up, so Net listeners do not matter. For example, $2 \times 5%$ GRP simply becomes 10% GRP.

Important Considerations

  • Average Contact Frequency: Always pay attention to the GCF.
  • Target Groups: Some age groups are more difficult to reach.
  • Campaign Duration: Longer campaigns may be necessary for less popular stations.

Data Sources: National Listening Survey (NLO)

The National Listening Survey (NLO) provides data on the number of listeners to major radio stations. NLO participants maintain a diary every fifteen minutes over two months, noting which station they listen to.

Key Statistics

  • 15.000.000 listeners monthly to large radio stations.
  • Market share is recorded as a percentage of listeners aged ten years or older.

Choosing the Right Station

To advertise effectively, assess the most cost-efficient way to reach your target group. Many advertisers mistakenly choose stations based on assumptions rather than data.


Campaign Pricing Factors

Basic Annual Price (BJP)

The BJP represents how much you will pay per year for a basic number of spots. Discounts may apply for bulk spot purchases.

Spot Length

Radio stations base prices on a standard spot length of 20 seconds. Prices adjust in increments of 5 seconds for shorter or longer spots.

Monthly Rate Example

$BJP = 200 \text{ euros}$
$\text{Spot length} = 35 \text{ seconds}$
$Index = 175 \text{ euros}$
$\text{Monthly rate} = 175 \times 200 = 35.000 \text{ euros per month}$


Advertiser Packages

Advertiser packages offer different conditions such as priority in scarcity situations, and how quickly reach can be used (distribution).

Example: GRP Target

Suppose you want a GRP of 3.5 million people in a month on NPO Radio 2. With distribution, you can limit the campaign to stop once that GRP is reached, avoiding unnecessary extra costs.


Time Slots and Indexing

Time slots indicate when something is broadcast during the day (e.g., 6-24, 9-14). Some companies calculate these as a separate index.

Target groups also determine the price. Factors such as demand, supply, and size influence the cost.

Market Index

Radio stations use a market index to balance supply and demand. Prices may fluctuate based on demand and availability.

Monthly Index

The monthly index tracks how much spot time costs each month. For example, advertising costs may rise during holiday months due to increased demand.


Additional Options

Some packages include additional options, or they may be billed separately:

  • Preferred Positions: Your preference for spot position (e.g., last, first).
  • Roadblocks: A group of channels plays a spot simultaneously.

GRP Price Calculation

The GRP price is calculated by multiplying the following factors:

$\text{GRP price} = \text{BJP} \times \text{spot length index} \times \text{package index} \times \text{time slot index} \times \text{target group index} \times \text{market index} \times \text{month index}$


Conclusion

All radio stations use fixed costs per spot rather than GRPs, derived from second rates. For companies that prefer not to delve deeply into GRP mechanics, second rates may be the best choice for maximizing reach.

A small project: Short.moe

A small project: Short.moe

I’ve been recently working on a project with the domain short.moe creating a serverless URL shortening service.

Short.moe is a free URL shortener service that allows you to easily shorten long URLs into shorter, more manageable links. Built with NextJS 14, Clerk, Prisma, and PostgreSQL, hosted on serverless on Vercel, Short.moe is designed to be both easy to use and user-friendly.

Key Features

Easy URL Shortening

With Short.moe, you can shorten URLs without the need to create an account. When shortening a URL without an account, the slug/alias (the unique part of the shortened URL) will be randomly generated using the nanoid package.

Account-Based Customization

For users who create an account, Short.moe offers the ability to set custom slugs. This means you can create memorable, aliased links that are easy to share and recall. Clerk handles authentication, making the process of signing up and managing your account straightforward. On the technical side, this was very easy to do.

In Short

Short.moe aims to be an easy option for anyone looking to shorten URLs quickly and easily, whether with or without a personalized alias/slug. Thank you for reading this short post.

Unleashing the Power of CloudFlare's AI Workers

Introduction

CloudFlare’s AI Workers allows developers to interact serverlessly with CloudFlare’s AI models, providing a simple way to integrate AI into your applications.

CloudFlare’s AI Workers Overview

CloudFlare’s AI Workers bring the capabilities of Artificial Intelligence (AI) directly into the realm of serverless computing.

Now, developers can use the power of AI models through CloudFlare’s infrastructure, opening new possibilities for their applications. The AI Workers come with a REST API, allowing for easy integration and communication with CloudFlare’s AI models. These include LLMs, Stable Diffusion, HuggingFace and more.

Python Example: Interacting with CloudFlare’s AI

This Python example that shows how to interact with CloudFlare’s AI models. This script utilizes CloudFlare’s REST API to run an AI model named “@cf/meta/llama-2-7b-chat-int8.”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import requests

API_BASE_URL = "https://api.cloudflare.com/client/v4/accounts/ACCOUNT_ID/ai/run/"
headers = {"Authorization": "Bearer {API_TOKEN}"}

def run(model, inputs):
input = { "messages": inputs }
response = requests.post(f"{API_BASE_URL}{model}", headers=headers, json=input)
return response.json()

inputs = [
{ "role": "system", "content": "You are an AI" },
{ "role": "user", "content": "Write a short story about a llama that goes on a journey to find an orange cloud"}
]
output = run("@cf/meta/llama-2-7b-chat-int8", inputs)
print(output)

JavaScript Example: Deployment with CloudFlare’s AI

For developers looking to integrate CloudFlare’s AI into their web applications (e.g. customer service chat bots or SaaS), the following JavaScript example showcases the deployment of an AI-powered function using CloudFlare Workers:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { Ai } from './vendor/@cloudflare/ai.js';

export default {
async fetch(request, env) {
const tasks = [];
const ai = new Ai(env.AI);

// Prompt - simple completion-style input
let simple = {
prompt: 'Tell me a joke about CloudFlare'
};
let response = await ai.run('@cf/meta/llama-2-7b-chat-int8', simple);
tasks.push({ inputs: simple, response });

// Messages - chat-style input
let chat = {
messages: [
{ role: 'system', content: 'You are a helpful assistant.' },
{ role: 'user', content: 'Who won the world series in 2020?' }
]
};
response = await ai.run('@cf/meta/llama-2-7b-chat-int8', chat);
tasks.push({ inputs: chat, response });

return Response.json(tasks);
}
};

Proof of Concept AI Worker

To see CloudFlare’s AI Workers in action, check out my AI Chat App. It’s a proof of concept that uses CloudFlare’s AI, providing an eye into the capabilities and potential applications. Try it here: ai.cvyl.me.

Exploring the Decentralized Web with IPFS

Introduction

I recently delved into the world of IPFS (InterPlanetary File System), a protocol designed to improve the way we store and share content on the internet. In this journey, I discovered how IPFS operates compared to centralized servers, its cryptography in blockchain technology, and its potential to provide a decentralized solution.

Understanding IPFS

How IPFS Works

IPFS operates on a decentralized network where files are not stored on a central server but are distributed across a peer-to-peer network. Each file is given a unique hash, and retrieving the file involves querying the network for nodes that have that specific hash. Files on IPFS have a hash that changes if the file has been tampered with, providing a mechanism to prevent and spot malicious data.

IPFS Diagram

This diagram shows the decentralized nature of IPFS and how files are distributed across nodes in the network. Each node contains a unique hash, ensuring data integrity and preventing a single point of failure.

Comparison with Centralized Systems

Let’s compare IPFS with traditional centralized systems:

Aspect Centralized IPFS
Storage Central Servers with Nodes Peer-to-Peer Network
Reliability Redundant Nodes Redundancy and Resilience
Censorship Resistance Vulnerable Resistant and Robust

Centralized servers do use nodes to decrease a single point of failure, but IPFS takes it a step further by decentralizing the entire network.

IPFS in Cryptography and Blockchain Technology

IPFS integrates with blockchain technology, providing secure and transparent file storage. The cryptographic hashes used in IPFS ensure data integrity and authentication. Comparatively, HTTP 2.0 lacks this built-in security layer.

Let’s break down conventional HTTP 2.0 links and an example link from IPFS:

HTTP 2.0 Link:

1
https://www.example.com/path/to/content

IPFS Link:

1
ipfs://QmXyZaBc123/example/content

Components breakdown:

  • Protocol: HTTP vs IPFS
  • Address: Domain in HTTP, Hash in IPFS
  • Path: Traditional path in HTTP, Content hash in IPFS

IPFS and Censorship Resistance

In countries with strict censorship like China, IPFS offers a decentralized solution. Since the content is distributed across a peer-to-peer network, it becomes challenging for authorities to block access. Additionally, the cryptographic integrity checks make it harder to manipulate or inject malicious content.

Privacy and Decentralization

One of the significant advantages of IPFS is privacy. With your data distributed across the network, ISPs find it challenging to track your activities compared to centralized systems. This decentralization contributes to a more private and secure online experience.

Summary

IPFS is a protocol reshaping the internet. It’s a decentralized, peer-to-peer file system that ensures data integrity, resiliency, and resistance. By using cryptographic hashes and blockchain integration, IPFS provides a secure and transparent way to share and store and share content.

Suicidal ideation: Choosing faith and departure

Suicidal ideation: Choosing faith and departure

After crying for over two hours straight, I chose to write a bit on my blogging website. As I haven’t updated anything on here in a long time, sorry for that.

I cried because of my realization that I have every so often about my life and how unfortunate it really is. Today’s nonsensical reasoning was due to my dysphoria.

Taking Control of My Narrative

In the midst of these emotional struggles, I decided to take control of my own narrative. I’ve been navigating the challenging waters of gender dysphoria, seeking solace and understanding within a system that often feels like it’s failing me. My decision to start self-administered hormone replacement therapy (HRT) in December 2023 was a pivotal moment in my journey.

Despite reaching out to professionals, such as a gender clinic and an endocrinologist at UMCG on April 20th, 2023, the response has been disappointingly slow. Frustrated by the lengthy waiting times and lack of support, I chose not to be limited by the protocols that seemed to hinder my progress.

Learning to self-administer Estradiol Acetate injections weekly has been both empowering and challenging. My determination to embrace my authentic self propelled me to overcome the hurdles presented by a healthcare system that often leaves transgender individuals waiting in the wings.

My GP has been supportive in this journey, prescribing monthly blood tests to monitor my hormone levels. While unconventional, this approach has given me a sense of agency over my well-being.

A Glimmer of Hope

Acknowledging the risks I’ve taken, I am conscious of the potential consequences. Yet, for me, the urgency to transition at a young age outweighs the risks. The positive impact on my mental well-being since starting HRT has been notable. The reduction in suicidal ideation underscores the importance of timely access to gender-affirming care.

The struggle for acceptance and understanding in the face of a healthcare system that feels unresponsive has been daunting. Still, my determination to chart my course and embrace authenticity keeps me going. My story is ongoing, and every day is a step towards reclaiming my identity.

In sharing my journey, I hope to shed light on the challenges faced by many in the LGBTQ+ community and the broader conversation around mental health. It’s a raw, unfiltered account of my experiences, with the intent to foster understanding and empathy.

Remember, even in the darkest times, there can be a glimmer of hope, and I am trying to be the living proof of that.

Harmony Within Us Development Update

Hey HWU community,

I hope this post finds you well. I wanted to provide a quick update on the development of Harmony Within Us (HWU). As many of you know, our shared journey toward creating a supportive and inclusive space is incredibly important to me.

Due to a heightened focus on my mental health and overall well-being, I’ve made the decision to temporarily pause the development of HWU. It’s crucial for me to take this time to ensure that I can continue to contribute positively to our community.

I want to reassure you that this pause is not a goodbye or an end. It’s a necessary step to prioritize self-care, and I believe it will ultimately lead to a more robust and impactful HWU in the long run.

I understand that many of you are excited about the project, and I truly appreciate your support and enthusiasm. Please don’t worry – Harmony Within Us is still very much alive, and I am committed to returning to development as soon as possible.

Thank you for your understanding and continued support. Let’s keep fostering harmony within ourselves and our community.

Stay tuned for more updates, and take care. <3

Gender Dysphoria Rant

So, here’s the deal. I never signed up for this whole MtF, gender dysphoria package. Like, seriously, who would willingly choose this rollercoaster of emotions? Not me.

Let’s rewind a bit. Imagine waking up one day and realizing your gender compass is pointing in the opposite direction. It’s like, “Hello dysphoria, my old friend. Can we not do this today?” But nope, life had other plans.

And here’s the kicker. It’s not a phase, it’s not a choice. It’s just me, dealing with this hand I’ve been dealt. I didn’t wake up one morning and think, “You know what would be fun? Let’s switch genders and see how that goes.” Nope, not a thing.

Let’s talk about societal expectations. I mean, come on. Why can’t we all just be who we are without the constant judgment and raised eyebrows? It’s exhausting. If got a euro every time someone said; “But why would you choose this?” I would be a millionaire.

If you ever wonder why I didn’t choose an ‘easier’ path, well, it wasn’t an option.

Sorry, rant over. Thank you for reading.

The Privacy Perils of Discord: A Closer Look

In the past 7 years, Discord has emerged as a popular platform for communication, particularly within gaming communities and social circles. However, beneath its user-friendly interface lies a complex web of privacy concerns that users should be aware of.

Discord’s Privacy Policy: A Critical Examination

Discord’s Privacy Policy (Discord Privacy Policy) outlines the platform’s practices regarding user data. While the document is comprehensive, the devil often lies in the details. People should pay attention to data collection, storage, and sharing policies to understand the extent of their digital footprint on the platform.

Government Requests and User Privacy

Discussions on Reddit (Reddit Post) have shed light on Discord’s stance when faced with government requests for user data. Users express concern towards the platform’s apparent lack of commitment to protecting user privacy in such situations. This raises questions about the balance between user rights and legal obligations.

Terms of Service: What Users Should Know

Terms of Service; Didn’t Read (TOS;DR) offers a summarization of Discord’s Terms of Service (TOS;DR - Discord). This website breaks down the legal jargon and highlights key points, making it easier for people to comprehend the implications of agreeing to Discord’s terms.

Third-Party Observations

In an era of digital transparency, independent observers and critics play a crucial role in scrutinizing online platforms. A critical article on Spyware (Spyware Article) provides a thought-provoking examination of Discord’s privacy-related issues.

The Impact of Political Stances on the Transgender Community in the Netherlands

The Impact of Political Stances on the Transgender Community in the Netherlands

In recent times, the rights and well-being of the LGBTQ+ community, especially transgender individuals, have become a focal point of political discourse in the Netherlands. Unfortunately, some political parties, like the Forum for Democracy (FvD), have been criticized for their stance on LGBTQ+ rights, contributing to an environment that can be harmful to the transgender community.

Unraveling the FvD’s Impact

The Forum for Democracy, through its policies and public statements, has raised concerns about its support for the rights and dignity of transgender individuals. This party’s positions, often rooted in traditionalist ideologies, can be detrimental to the progress made in securing equal rights for the LGBTQ+ community.

Propaganda and Misrepresentation

It is vital to address how political propaganda and nit-picking tactics are applied to cast transgender individuals in a negative light. By highlighting isolated incidents and promoting a skewed narrative, certain political entities contribute to the harmful stereotypes and biases of LGBTQ+ individuals.

A Call to Action

In the face of such challenges, it becomes imperative for the LGBTQ+ community and its allies to be informed and engaged. Platforms like RainbowVote (rainbowvote.nu) play a vital role in empowering individuals with the knowledge needed to make informed decisions during the elections.

Exposing Anti-LGBTQ+ Propaganda

Recent incidents, such as this FvD advertisement here, have come under scrutiny for promoting anti-LGBTQ+ sentiments. This Reddit post highlights the importance of vigilance and the need to counteract misleading narratives that can adversely affect the transgender community.

Conclusion

It is crucial to stand against policies and propaganda that undermine the rights and well-being of transgender individuals. Only together we can strive towards a more accepting and supportive society.