From 675b141fc1b033c26b015e5202e617092f1a07d0 Mon Sep 17 00:00:00 2001 From: Boaz Sender Date: Mon, 24 Mar 2025 11:17:30 -0400 Subject: [PATCH] working radio station --- deploy/LICENSE.md | 22 ++++ deploy/README.md | 181 +++++++++++++++++++++++++++++++ deploy/deploy.yml | 26 +++++ deploy/inventory.example.yml | 40 +++++++ deploy/inventory.yml | 40 +++++++ deploy/lockdown.yml | 40 +++++++ deploy/provision.yml | 114 +++++++++++++++++++ deploy/templates/nginx.conf | 22 ++++ deploy/templates/start.sh | 3 + deploy/templates/systemd.service | 14 +++ readme.md | 95 +++++++++++++++- 11 files changed, 596 insertions(+), 1 deletion(-) create mode 100644 deploy/LICENSE.md create mode 100644 deploy/README.md create mode 100644 deploy/deploy.yml create mode 100644 deploy/inventory.example.yml create mode 100644 deploy/inventory.yml create mode 100644 deploy/lockdown.yml create mode 100644 deploy/provision.yml create mode 100644 deploy/templates/nginx.conf create mode 100644 deploy/templates/start.sh create mode 100644 deploy/templates/systemd.service diff --git a/deploy/LICENSE.md b/deploy/LICENSE.md new file mode 100644 index 0000000..8ed8d95 --- /dev/null +++ b/deploy/LICENSE.md @@ -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. diff --git a/deploy/README.md b/deploy/README.md new file mode 100644 index 0000000..9e582e4 --- /dev/null +++ b/deploy/README.md @@ -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" + }, +``` diff --git a/deploy/deploy.yml b/deploy/deploy.yml new file mode 100644 index 0000000..3f420ae --- /dev/null +++ b/deploy/deploy.yml @@ -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 }}" diff --git a/deploy/inventory.example.yml b/deploy/inventory.example.yml new file mode 100644 index 0000000..9021935 --- /dev/null +++ b/deploy/inventory.example.yml @@ -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 }}/ diff --git a/deploy/inventory.yml b/deploy/inventory.yml new file mode 100644 index 0000000..9021935 --- /dev/null +++ b/deploy/inventory.yml @@ -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 }}/ diff --git a/deploy/lockdown.yml b/deploy/lockdown.yml new file mode 100644 index 0000000..da9d300 --- /dev/null +++ b/deploy/lockdown.yml @@ -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 diff --git a/deploy/provision.yml b/deploy/provision.yml new file mode 100644 index 0000000..87b49e0 --- /dev/null +++ b/deploy/provision.yml @@ -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 diff --git a/deploy/templates/nginx.conf b/deploy/templates/nginx.conf new file mode 100644 index 0000000..8472481 --- /dev/null +++ b/deploy/templates/nginx.conf @@ -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; + } +} \ No newline at end of file diff --git a/deploy/templates/start.sh b/deploy/templates/start.sh new file mode 100644 index 0000000..7bbd22a --- /dev/null +++ b/deploy/templates/start.sh @@ -0,0 +1,3 @@ +#!/bin/bash +. /home/deploy/.nvm/nvm.sh +npm start diff --git a/deploy/templates/systemd.service b/deploy/templates/systemd.service new file mode 100644 index 0000000..ed501a9 --- /dev/null +++ b/deploy/templates/systemd.service @@ -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 diff --git a/readme.md b/readme.md index 1d36b1e..cbcf681 100644 --- a/readme.md +++ b/readme.md @@ -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 -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)" 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 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