working radio station

This commit is contained in:
Boaz Sender 2025-03-24 11:17:30 -04:00
parent 37cdda870e
commit 675b141fc1
11 changed files with 596 additions and 1 deletions

22
deploy/LICENSE.md Normal file
View file

@ -0,0 +1,22 @@
MIT License
Copyright (c) Remix Software Inc. 2021
Copyright (c) Shopify Inc. 2022-2023
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

181
deploy/README.md Normal file
View file

@ -0,0 +1,181 @@
# DIY Deploy
This project includes ansible scripts for locking down and provisioning a node.js server, and deploying something you can `npm start` to that server. That server can be bare metal, or a Virtual Private Server (VPS). These scripts make it so you never have to shell into your server by hand. You can copy or submodule these scripts into your project, which we detail in the setup steps below.
## Create or cat ssh keys
If you already have an ssh key pair, `cat ~/.ssh/id_rsa.pub` or `~/.ssh/id_ed25519.pub` depending on how long ago you did this for the first time.
If you don't have a key pair yet, then generate one:
```sh
ssh-keygen -t ed25519 -C "you@example.com"
```
Hit enter three times to save the key pair in the default location and not use a passphrase.
## Get a server
## Install Ansible
Next let's install `ansible`:
### On a mac
[Install homebrew](https://docs.brew.sh/Installation) and then:
```sh
brew update && brew install ansible
```
### On Linux
```sh
sudo apt-add-repository ppa:ansible/ansible && sudo apt update && sudo apt install ansible
```
### On Windows
Follow the [Ansible Windows Installation Instructions](https://docs.ansible.com/ansible/latest/os_guide/windows_setup.html)
## Provision a new server
If you don't have one yet, try a droplet on [digital ocean](https://digitalocean.com/). Something with 1gb of memory should be enough to start you off. Point a domain at that droplet, and replace `privacy-stack-template.com` with that domain in the following instructions. An IP address will work as well.
### Manually copy or submodule this project into your existing node app
If you want to use a submodule:
```sh
cd /path/to/myproject && git submodule add https://github.com/bocoup/deploy.git && git submodule init
```
### Copy an update inventory.example.yml
These ansible playbooks uses the variables in an inventory to know what server to deploy to, what domain to use, what version of node to install, and if you have a root ssh password, what password to use.
First copy the example inventory file:
```sh
cp deploy/inventory.example.yml inventory.yml
```
Then change the values in that file to match your project:
```yml
# Our production server.
# Copy this whole block if you'd like to add a staging server
Production:
# The IP address of your server.
# Add a second one if you'd like to deploy twice.
# You can add as many as you want.
hosts: an.ip.add.ress
vars:
# Used for your app's domain name
domain: example.com
# Used for your certbot email, note you'll be agreeing to the ToS.
email: you@example.com
# Pick your node version
nodejs_version: 20
# If you have an SSH key on your server for the root user, you don't need this.
ansible_ssh_pass: "secret"
# List of files and folders to copy to the server on deploy.
# Change this to be the files your node app needs to run.
# Example set up for a remix.run indie stack app.
deploy_files:
- src: ../prisma/migrations
dest: /home/{{ domain }}/prisma/
- src: ../prisma/schema.prisma
dest: /home/{{ domain }}/prisma/schema.prisma
- src: ../build/
dest: /home/{{ domain }}/build
- src: ../public/
dest: /home/{{ domain }}/public
- src: ../.env
dest: /home/{{ domain }}/
- src: ../.npmrc
dest: /home/{{ domain }}/
- src: ../package.json
dest: /home/{{ domain }}/
- src: ../package-lock.json
dest: /home/{{ domain }}/
- src: ../LICENSE.md
dest: /home/{{ domain }}/
- src: ../README.md
dest: /home/{{ domain }}/
```
Now you're ready to use ansible in your project.
### Lock down the server
First we'll lock down the server. If you have a ssh key already on the server, you can run:
```sh
ansible-playbook -i inventory.yml deploy/lockdown.yml
```
If you have a root user and password, you need to disable host key checking as part of your command:
```sh
export ANSIBLE_HOST_KEY_CHECKING=false && ansible-playbook -i deployment/inventory.yml deploy/lockdown.yml
```
In either case this script uses ansible to:
- Add a new user named deploy
- Add deploy user to the sudoers
- Deploy your SSH Key
- Disable Password Authentication
- Disable Root Login
- restart ssh
### Provision the server
Now let's configure the server:
```sh
ansible-playbook -i inventory.yml deploy/provision.yml
```
This script uses ansible to:
- Update apt repo and cache
- Upgrade all packages
- Check if a reboot is needed
- Reboot the server if kernel updated
- Install system packages with apt (curl, gnupg, ufw, nginx, and python3-certbot-nginx)
- Enable ufw firewall
- Enable SSH and nginx in UFW
- Create directory for the app
- Copy nginx conf to server and symlink it
- Create ssl certificate with certbot
- Copy systemd service to server
- Copy systemd friendly start script to server
- Reload and enable systemd service
- Restart nginx
- Install nvm and use it to install node and npm
## Deploy the software
Next up let's build and deploy your project!
```sh
npm run build && ansible-playbook -i inventory.yml deploy/deploy.yml
```
This script uses ansible to:
- Copy the app to the server (prisma migrations and schema, build files, public files, .env, .npmrc, package.json, package-lock.json, LICENSE.md, and README.md)
- Install npm deps
- Run migrations
- Start the app with systemd
## Make package.json commands
We also recommend that you make a deploy command to alias ansible for convenience:
```json
"scripts": {
"deploy": "npm run build && ansible-playbook -i inventory.yml deploy/deploy.yml"
},
```

26
deploy/deploy.yml Normal file
View file

@ -0,0 +1,26 @@
- name: Deploy the app
hosts: all
remote_user: deploy
tasks:
- name: Copy app files to server
synchronize:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
loop: "{{ deploy_files }}"
- name: Install npm deps
shell:
cmd: "source /home/deploy/.nvm/nvm.sh && nvm exec default npm install"
chdir: /home/{{ domain }}
args:
executable: /bin/bash
- name: Run migrations
shell:
cmd: "source /home/deploy/.nvm/nvm.sh && nvm exec default npx prisma migrate deploy && nvm exec default npx prisma generate"
chdir: /home/{{ domain }}
args:
executable: /bin/bash
- name: Start the app with systemd
shell: "sudo systemctl restart {{ domain }}"

View file

@ -0,0 +1,40 @@
# Our production server.
# Copy this whole block if you'd like to add a staging server
Production:
# The IP address of your server.
# Add a second one if you'd like to deploy twice.
# You can add as many as you want.
hosts: an.ip.add.ress
vars:
# Used for your app's domain name
domain: example.com
# Used for your certbot email, note you'll be agreeing to the ToS.
email: you@example.com
# Pick your node version
nodejs_version: 20
# If you have an SSH key on your server for the root user, you don't need this
ansible_ssh_pass: "secret"
# List of files and folders to copy to the server on deploy.
# Change this to be the files your node app needs to run.
# Example set up for a remix.run indie stack app.
deploy_files:
- src: ../prisma/migrations
dest: /home/{{ domain }}/prisma/
- src: ../prisma/schema.prisma
dest: /home/{{ domain }}/prisma/schema.prisma
- src: ../build/
dest: /home/{{ domain }}/build
- src: ../public/
dest: /home/{{ domain }}/public
- src: ../.env
dest: /home/{{ domain }}/
- src: ../.npmrc
dest: /home/{{ domain }}/
- src: ../package.json
dest: /home/{{ domain }}/
- src: ../package-lock.json
dest: /home/{{ domain }}/
- src: ../LICENSE.md
dest: /home/{{ domain }}/
- src: ../README.md
dest: /home/{{ domain }}/

40
deploy/inventory.yml Normal file
View file

@ -0,0 +1,40 @@
# Our production server.
# Copy this whole block if you'd like to add a staging server
Production:
# The IP address of your server.
# Add a second one if you'd like to deploy twice.
# You can add as many as you want.
hosts: an.ip.add.ress
vars:
# Used for your app's domain name
domain: example.com
# Used for your certbot email, note you'll be agreeing to the ToS.
email: you@example.com
# Pick your node version
nodejs_version: 20
# If you have an SSH key on your server for the root user, you don't need this
ansible_ssh_pass: "secret"
# List of files and folders to copy to the server on deploy.
# Change this to be the files your node app needs to run.
# Example set up for a remix.run indie stack app.
deploy_files:
- src: ../prisma/migrations
dest: /home/{{ domain }}/prisma/
- src: ../prisma/schema.prisma
dest: /home/{{ domain }}/prisma/schema.prisma
- src: ../build/
dest: /home/{{ domain }}/build
- src: ../public/
dest: /home/{{ domain }}/public
- src: ../.env
dest: /home/{{ domain }}/
- src: ../.npmrc
dest: /home/{{ domain }}/
- src: ../package.json
dest: /home/{{ domain }}/
- src: ../package-lock.json
dest: /home/{{ domain }}/
- src: ../LICENSE.md
dest: /home/{{ domain }}/
- src: ../README.md
dest: /home/{{ domain }}/

40
deploy/lockdown.yml Normal file
View file

@ -0,0 +1,40 @@
- name: Add deploy user and disable root user
hosts: all
vars:
remote_user: root
tasks:
- name: Add a new user named deploy
user: name=deploy
- name: Add deploy user to the sudoers
copy:
dest: "/etc/sudoers.d/deploy"
content: "deploy ALL=(ALL) NOPASSWD: ALL"
- name: Deploy your SSH Key
authorized_key: user=deploy
key="{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
state=present
- name: Disable Password Authentication
lineinfile: dest=/etc/ssh/sshd_config
regexp='^PasswordAuthentication'
line="PasswordAuthentication no"
state=present
backup=yes
notify:
- restart ssh
- name: Disable Root Login
lineinfile: dest=/etc/ssh/sshd_config
regexp='^PermitRootLogin'
line="PermitRootLogin no"
state=present
backup=yes
notify:
- restart ssh
handlers:
- name: restart ssh
service: name=ssh
state=restarted

114
deploy/provision.yml Normal file
View file

@ -0,0 +1,114 @@
- name: Update and upgrade apt packages
hosts: all
remote_user: deploy
become: yes
tasks:
- name: Update apt repo and cache
apt:
update_cache: yes
force_apt_get: yes
cache_valid_time: 3600
- name: Upgrade all packages
apt:
upgrade: dist
force_apt_get: yes
- name: Check if a reboot is needed
register: reboot_required_file
stat:
path: /var/run/reboot-required
- name: Reboot the server if kernel updated
reboot:
msg: "Reboot initiated by Ansible for kernel updates"
connect_timeout: 5
reboot_timeout: 300
pre_reboot_delay: 0
post_reboot_delay: 30
test_command: uptime
when: reboot_required_file.stat.exists
- name: Install packages
hosts: all
remote_user: deploy
become: true
tasks:
- name: Install system packages with apt
register: updatesys
apt:
update_cache: yes
name:
- curl
- gnupg
- ufw
- nginx
- python3-certbot-nginx
state: present
- name: Enable ufw firewall
community.general.ufw:
state: enabled
- community.general.ufw:
rule: allow
name: OpenSSH
- community.general.ufw:
rule: allow
name: "Nginx Full"
- name: Create directory for the app
file: path=/home/{{domain}}
state=directory
owner=deploy
group=deploy
- name: Copy nginx conf to server
template: src=./templates/nginx.conf
dest=/etc/nginx/sites-available/{{ domain }}.conf
- name: Create symlink to new nginx conf
file: src=/etc/nginx/sites-available/{{ domain }}.conf
dest=/etc/nginx/sites-enabled/{{ domain }}.conf
state=link
- name: Create ssl certificate with certbot
shell: "sudo certbot --nginx -d {{ domain }} --agree-tos --email {{ email }} --non-interactive"
notify: Restart nginx
- name: Copy systemd service to server
template: src=./templates/systemd.service
dest=/lib/systemd/system/{{ domain }}.service
- name: Copy systemd friendly start script to server
template:
src: ./templates/start.sh
dest: /home/{{ domain }}/start.sh
mode: +x
- name: Reload and enable systemd service
shell: "sudo systemctl daemon-reload && sudo systemctl enable --now {{ domain }} && sudo systemctl start {{ domain }}"
handlers:
- name: Restart nginx
service:
name: nginx
state: restarted
- name: Install node and the app
hosts: all
remote_user: deploy
tasks:
- name: Install nvm
shell: >
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
args:
creates: "{{ ansible_env.HOME }}/.nvm/nvm.sh"
- name: Install node and set version
shell: >
source ~/.nvm/nvm.sh && nvm install {{ nodejs_version }} && nvm use {{ nodejs_version }}
args:
executable: /bin/bash

View file

@ -0,0 +1,22 @@
server {
server_name www.{{ domain }};
rewrite ^(.*) http://{{ domain }}$1 permanent;
}
server {
listen 80;
listen [::]:80;
server_name {{ domain }};
access_log /var/log/nginx/{{ domain }}.log;
error_log /var/log/nginx/{{ domain }}-error.log error;
location / {
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $http_host;
proxy_pass http://127.0.0.1:3000;
proxy_redirect off;
client_max_body_size 10M;
}
}

View file

@ -0,0 +1,3 @@
#!/bin/bash
. /home/deploy/.nvm/nvm.sh
npm start

View file

@ -0,0 +1,14 @@
[Unit]
Description={{ domain }}
After=network.target
[Service]
Environment=NODE_ENV=production
Type=simple
User=root
WorkingDirectory=/home/{{ domain }}
ExecStart=/home/{{ domain }}/start.sh
Restart=on-failure
[Install]
WantedBy=multi-user.target

View file

@ -9,7 +9,7 @@ arecord -l
arecord -f cd -D plughw:0,0 | ffmpeg -i - -acodec libmp3lame -ab 128k -ac 1 -f rtp rtp://127.0.0.1:3000 arecord -f cd -D plughw:0,0 | ffmpeg -i - -acodec libmp3lame -ab 128k -ac 1 -f rtp rtp://127.0.0.1:3000
sudo apt install git nginx vim sudo apt install git nginx vim
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
https://www.digitalocean.com/community/tutorials/how-to-set-up-a-video-streaming-server-using-nginx-rtmp-on-ubuntu-20-04 https://www.digitalocean.com/community/tutorials/how-to-set-up-a-video-streaming-server-using-nginx-rtmp-on-ubuntu-20-04
@ -17,4 +17,97 @@ https://www.digitalocean.com/community/tutorials/how-to-set-up-a-video-streaming
sudo apt install libnginx-mod-rtmp sudo apt install libnginx-mod-rtmp
sudo vim /etc/nginx/nginx.conf sudo vim /etc/nginx/nginx.conf
```
rtmp {
server {
listen 1935;
chunk_size 4096;
allow publish 127.0.0.1;
deny publish all;
application live {
live on;
record off;
hls on;
hls_path /var/www/html/portal/hls;
hls_fragment 3;
hls_playlist_length 60;
dash on;
dash_path /var/www/html/portal/dash;
}
}
}
```
sudo nano /etc/nginx/sites-available/portal.conf
```
server {
listen 8080;
server_name localhost;
# rtmp stat
location /stat {
rtmp_stat all;
rtmp_stat_stylesheet stat.xsl;
}
location /stat.xsl {
root /var/www/html/portal;
}
# rtmp control
location /control {
rtmp_control all;
}
}
server {
listen 80;
location / {
add_header Access-Control-Allow-Origin *;
root /var/www/html/portal;
}
}
types {
application/dash+xml mpd;
}
```
sudo ln -s /etc/nginx/sites-available/portal.conf /etc/nginx/sites-enabled/portal.conf
sudo chown -R $USER:$USER /var/www/html/portal
sudo chmod -R 775 /var/www/html/portal
sudo mkdir /var/www/html/portal
sudo systemctl reload nginx
sudo apt install ffmpeg
nano ~/portal.sh
arecord -f cd -D plughw:3,0 | ffmpeg -i - -acodec aac -ab 128k -ac 1 -f flv rtmp://localhost/live/portal
chmod u+x ~/portal.sh
sudo nano /lib/systemd/system/portal.service
```
[Unit]
Description=portal
After=network.target
[Service]
Environment=NODE_ENV=production
Type=simple
User=grace
WorkingDirectory=/home/grace/
ExecStart=/home/grace/portal.sh
Restart=on-failure
[Install]
WantedBy=multi-user.target
```
sudo systemctl daemon-reload && sudo systemctl enable --now portal && sudo systemctl start portal