Skip to content

Commit

Permalink
production section
Browse files Browse the repository at this point in the history
  • Loading branch information
Pistonight committed Oct 27, 2024
1 parent 52ae534 commit abdd9c4
Show file tree
Hide file tree
Showing 10 changed files with 482 additions and 2 deletions.
17 changes: 17 additions & 0 deletions src/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,22 @@ const toolSidebar = [
},
]

const prodSidebar = [
{
text: "Production",
items: [
{ text: "Getting Started", link: "/prod/" },
{ text: "Security", link: "/prod/security" },
{ text: "Tools", link: "/prod/tools" },
{ text: "Nginx", link: "/prod/nginx" },
{ text: "Manage Sites", link: "/prod/sites" },
{ text: "Static Site", link: "/prod/static-site" },
{ text: "Docker", link: "/prod/docker" },
{ text: "System Upgrade", link: "/prod/upgrade" },
]
}
]

// https://vitepress.dev/reference/site-config
export default defineConfig({
title: "Pistonite VM",
Expand All @@ -138,6 +154,7 @@ export default defineConfig({
"/hyperv/": hypervSidebar,
"/arch/": archSidebar,
"/tool/": toolSidebar,
"/prod/": prodSidebar,
},

socialLinks: [
Expand Down
1 change: 1 addition & 0 deletions src/arch/user.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ PermitRootLogin no
Disallow password login (SSH key required). Search for `PasswordAuthentication`
```
PasswordAuthentication no
PermitEmptyPasswords no
```

Use only one internet protocol if you only need one of IPv4 or IPv6. Search for `AddressFamily`
Expand Down
121 changes: 121 additions & 0 deletions src/prod/docker.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Docker
Run a monolith website inside a docker container

## Enable Docker
```bash
sudo systemctl enable docker
sudo systemctl start docker
```

## Overview
For the example, assume the domain is `pistonite.org` and the subdomain/sitename
is `example`. Also, assume the docker image is `pistonite/example` and the container
will run on port `5001`

## Setup Tasks
Create tasks to manage sites with docker
```yaml
tasks:
# ... other tasks
docker-pull:
cmds:
- mkdir -p '/opt/pistonite/{{.SITE}}'
- touch '/opt/pistonite/{{.SITE}}/.env'
- docker pull '{{.IMAGE}}'
- docker tag '{{.IMAGE}}' '{{.SITE}}:deploy'

docker-run:
cmds:
- chown -R root:root '/opt/pistonite/{{.SITE}}'
- chmod -R 600 '/opt/pistonite/{{.SITE}}'
- docker container prune -f
- docker run -d --name '{{.SITE}}' -p '{{.PORT}}:80' --env-file '/opt/pistonite/{{.SITE}}/.env' '{{.SITE}}:deploy'
- docker image prune -a -f

```

## Add Site Commands
Create tasks to manage the site `example`
```yaml
tasks:
# ... other tasks
start:example:
desc: Start `example`
cmds:
- task: docker-run
vars:
SITE: example
PORT: 5001
deploy:example:
desc: Deploy `example`
cmds:
- task: docker-pull
vars:
IMAGE: pistonite/example:latest
SITE: example
- docker stop example
- task: start:example
```
## Environment Variables
Environment variables can be configured in the `/opt/pistonite/example/.env` file
```
FOO=BAR
MY_SECRET=SECRET
```
## Nginx Config
Create config file, replace `example` with the site name
```bash
sudo nvim /etc/nginx/sites-available/example.conf
```
Then paste in the following template and change as needed
```nginx
server {
listen 443 ssl;
server_name example.pistonite.org;
ssl_protocols TLSv1.2 TLSv1.3;
# Enable gzip if the web server doesn't have built-in compression
gzip on;
gzip_min_length 1000;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/gif image/jpeg image/png image/svg+xml image/tiff image/webp;
gzip_disable "msie6";
# Cross Origin Isolation?
#add_header Cross-Origin-Opener-Policy same-origin;
#add_header Cross-Origin-Embedder-Policy require-corp;
# Cache?
#add_header Cache-Control max-age=604800;
location / {
proxy_pass http://localhost:5001;
}
}
```

## Enable the Site
First run `deploy:example`, which will pull the image, but fail to stop the previous container because it doesn't exist
```bash
sudo task deploy:example
```
Then start the container and enable the site
```bash
sudo task start:example
sudo task enable -- example
sudo task restart
```

## Operations
To deploy newer image tagged `latest`
```bash
sudo task deploy:example
```
:::tip
This will pull the image, stop the old container, start a new container, and remove old container and images
:::
To restore the previous version, change the `IMAGE` in the deploy task, and re-run the deploy task
11 changes: 9 additions & 2 deletions src/prod/index.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
# Prod
TODO
# Production Environment

I use an Arch Linux server that runs nginx to serve static files and reverse proxy to some docker containers.

First, update the system and install `nvim`, so we can edit files.

```bash
sudo pacman -Syu nvim
```
96 changes: 96 additions & 0 deletions src/prod/nginx.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Nginx
Create basic config for `nginx`, without any sites yet.

## Create `www` user
First create a user that will own the nginx files, and set the permissions
so only that user can read/write the files.
```bash
sudo useradd --system -s /usr/bin/nologin www -U
sudo chown -R www:www /etc/nginx
sudo chmod -R 600 /etc/nginx
```

## Store Certificate
Assume you have a certificate file `foo.com.pem` and key file `foo.com.key`,
where `foo.com` is the site name.

Create the certificate file, and paste in the contents of the certificate and key.
```bash
sudo mkdir /etc/nginx/certs
sudo nvim /etc/nginx/certs/foo.com.pem
sudo nvim /etc/nginx/certs/foo.com.key
```
:::danger
Make sure to clear clipboard history/data after pasting in the key!
:::

Then, lock it down so only the `www` user can read it.
```bash
sudo chown -R www:www /etc/nginx/certs
sudo chmod -R 400 /etc/nginx/certs
```

## Base Config

Open the config file
```bash
sudo nvim /etc/nginx/nginx.conf
```
Paste in the following:
```nginx
user www;
worker_processes auto;
events {
multi_accept on;
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
# Set Certificate
ssl_certificate /etc/nginx/certs/pistonite.org.pem;
ssl_certificate_key /etc/nginx/certs/pistonite.org.key;
# Reuse SSL sessions
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
keepalive_timeout 70;
include sites-enabled/*;
# Block default requests
server {
listen 443 default_server;
server_name _;
return 444;
}
}
```

## Enable Service
Enable and start the `nginx` service.
```bash
sudo systemctl enable nginx
sudo systemctl start nginx
```
:::tip
If `nginx` fails to start, you can debug with `sudo nginx -t`
:::

## Firewall
Define firewall rules for port 80 and 443
for your desired traffic.

For example, since I use Cloudflare,
I will only allow the [IPs for Cloudflare](https://www.cloudflare.com/ips/)
and IP addresses for other necessary services
to access my server.
32 changes: 32 additions & 0 deletions src/prod/security.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Security

Have a production breach is bad so we want to have as much security as possible.

## Non-Root User
Follow [here](../arch/user.md#add-non-root-user) to create a non-root
user and add it to the `wheel` group.

## SSH
:::warning
Before preceeding, setup the key for SSH following [here](../arch/user.md#setting-up-ssh-keys),
so you don't get locked out of the machine! Sometimes you need to
set up the SSH key with the server provider.
:::

Login to the server with the non-root user and the SSH key, then,
follow [here](../arch/user.md#hardening-ssh-security) to edit the SSH config.
Restart the `sshd` service afterward.

## Firewall
You should use the server provider's service to configure firewall for the VM.

My firewall configuration is basically:
- Allow inbound TCP on port 22, from my IP
- Allow only inbound TCP on port 443 from Cloudflare IPs
- Allow outbound TCP/UDP to port 53 (for DNS) for all IPs
- Allow outbound TCP to port 80, 443 for all IPs
- Drop all other traffic
:::tip
The ports specified above for outbound rules are port on the remote machine
(not the port of my machine)
:::
42 changes: 42 additions & 0 deletions src/prod/sites.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Manage Sites

## Taskfile
Create a `Taskfile.yml` in your home directory.
```yaml
version: '3'

# Add environment variables
#env:
# FOO: BAR

tasks:
restart:
desc: Reset the permissions and restart the nginx service
cmds:
- chown -R www:www /etc/nginx
- chmod -R 600 /etc/nginx
- chmod -R 400 /etc/nginx/certs
- systemctl restart nginx
enable:
desc: Enable a site (-- SITE)
cmds:
- ln -s '/etc/nginx/sites-available/{{.CLI_ARGS}}.conf' '/etc/nginx/sites-enabled/{{.CLI_ARGS}}.conf'
disable:
desc: Disable a site (-- SITE)
cmds:
- unlink '/etc/nginx/sites-enabled/{{.CLI_ARGS}}.conf'
list:
desc: List all sites
cmds:
- ls /etc/nginx/sites-available
```
Then, lock it down so only the current user can read/write it
```bash
USER=$(whoami)
sudo chown $USER:$USER /home/$USER/Taskfile.yml
sudo chmod 600 /home/$USER/Taskfile.yml
```

Now, it should be (relatively) safe to store secrets in the `Taskfile.yml`.

Loading

0 comments on commit abdd9c4

Please sign in to comment.