All the implementations of this documentation is done on Ubuntu 22.04 LTS
## Discourse server setup
**[Before you start](#before-you-start)**
1. [Preparing your domain name](#1-preparing-your-domain-name)
2. [Setting up email](#2-setting-up-email)
**[Installation](#installation)**
3. [Create new cloud server](#3-create-new-cloud-server)
4. [Access new cloud server](#4-access-your-cloud-server)
5. [Install Discourse](#5-install-discourse)
6. [Edit Discourse configuration](#6-edit-discourse-configuration)
7. [Start Discourse](#7-start-discourse)
8. [Register new account and become admin](#8-register-new-account-and-become-admin)
9. [Post-install maintenance](#9-post-install-maintenance)
10. [(Optional) Add more Discourse features](#10-optional-add-more-discourse-features)
## Before you start
### 1. Preparing your domain name
> 🔔 Discourse will not work from an IP address, you must own a domain name such as `example.com` to proceed.
- Already own a domain name? Great. Select a subdomain such as `discourse.example.com` or `talk.example.com` or `forum.example.com` for your Discourse instance.
- No domain name? Get one! We can [recommend NameCheap](https://www.namecheap.com/domains/domain-name-search/), or there are many other [great domain name registrars](https://www.google.com/search?q=best+domain+name+registrars) to choose from.
- Your DNS controls should be accessible from the place where you purchased your domain name. This is where you will create a DNS [ `A` record](https://support.dnsimple.com/articles/a-record/) for the `discourse.example.com` hostname once you know the IP address of the cloud server where you are installing Discourse, as well as enter your [SPF and DKIM records](https://www.google.com/search?q=what+is+spf+dkim) for your email.
### 2. Setting Up Email
> ⚠️ **Email is CRITICAL for account creation and notifications in Discourse.** If you do not properly configure email before bootstrapping YOU WILL HAVE A BROKEN SITE!
> 💡 Email here refers to [Transactional Email](https://www.google.com/search?q=what+is+transactional+email) not the usual email service like Gmail, Outlook and/or Yahoo.
- No existing mail server? Check out our [**Recommended Email Providers for Discourse**][mailconfig].
- Already have a mail server? Great. Use your existing mail server credentials. (Free email services like Gmail/Outlook/Yahoo do not support transactional emails.)
- To ensure mail deliverability, you must add valid [SPF and DKIM records](https://www.google.com/search?q=what+is+spf+dkim) in your DNS. See your mail provider instructions for specifics.
- If you're having trouble getting emails to work, follow our [Email Troubleshooting Guide](https://meta.discourse.org/t/troubleshooting-email-on-a-new-discourse-install/16326)
## Installation
### 3. Create New Cloud Server
Create your new cloud server, for example [on DigitalOcean][do]:
- The default of **the current supported LTS release of Ubuntu Server** works fine. At minimum, a 64-bit Linux OS with a
modern kernel version is required.
- The default of **1 GB** RAM works fine for small Discourse communities. We recommend 2 GB RAM for larger communities.
- The default of **New York** is a good choice for most US and European audiences. Or select a region that is geographically closer to your audience.
- Enter your domain `discourse.example.com` as the Droplet name.
Create your new Droplet. You may receive an email with the root password, however, [you should set up SSH keys](https://www.google.com/search?q=digitalocean+ssh+keys), as they are more secure.
> ⚠️ Now you have created your cloud server! Go back to your DNS controls and use the IP address to set up an `A record` for your `discourse.example.com` hostname.
### 4. Access Your Cloud Server
Connect to your server via its IP address using SSH, or [Putty][put] on Windows:
ssh root@192.168.1.1
Either use the root password from the email DigitalOcean sent you when the server was set up, or have a valid SSH key configured on your local machine.
### 5. Install Discourse
Clone the [Official Discourse Docker Image][dd] into `/var/discourse`.
sudo -s
git clone https://github.com/discourse/discourse_docker.git /var/discourse
cd /var/discourse
chmod 700 containers
You will need to be root through the rest of the setup and bootstrap process.
### 6. Edit Discourse Configuration
Launch the setup tool at
./discourse-setup
Answer the following questions when prompted:
Hostname for your Discourse? [discourse.example.com]:
Email address for admin account(s)? [me@example.com,you@example.com]:
SMTP server address? [smtp.example.com]:
SMTP port? [587]:
SMTP user name? [user@example.com]:
SMTP password? [pa$$word]:
Let's Encrypt account email? (ENTER to skip) [me@example.com]:
Optional Maxmind License key () [xxxxxxxxxxxxxxxx]:
You'll get the SMTP details from your [email](#email) setup, be sure to complete that section.
Let's Encrypt account setup is to give you a free HTTPS certificate for your site, be sure to set that up if you want your site secure.
This will generate an `app.yml` configuration file on your behalf, and then kicks off bootstrap. Bootstrapping takes between **2-8 minutes** to set up your Discourse. If you need to change these settings after bootstrapping, you can run `./discourse-setup` again (it will re-use your previous values from the file) or edit `/containers/app.yml` manually with `nano` and then `./launcher rebuild app`, otherwise your changes will not take effect.
### 7. Start Discourse
Once bootstrapping is complete, your Discourse should be accessible in your web browser via the domain name `discourse.example.com` you entered earlier.
<img src="https://www.discourse.org/images/install/17/discourse-congrats.png" width="650">
### 8. Register New Account and Become Admin
Register a new admin account using one of the email addresses you entered before bootstrapping.
<img src="https://www.discourse.org/images/install/17/discourse-register.png" width="650">
<img src="https://www.discourse.org/images/install/17/discourse-activate.png" width="650">
(If you are unable to register your admin account, check the logs at `/var/discourse/shared/standalone/log/rails/production.log` and see our [Email Troubleshooting checklist](https://meta.discourse.org/t/troubleshooting-email-on-a-new-discourse-install/16326).)
After registering your admin account, the setup wizard will launch and guide you through basic configuration of your Discourse.
<img src="https://www.discourse.org/images/install/17/discourse-wizard-step-1.png" width="650">
After completing the setup wizard, you should see Staff topics and **READ ME FIRST: Admin Quick Start Guide**. This guide contains advice for further configuring and customizing your Discourse install.
<img src="https://www.discourse.org/images/install/17/discourse-homepage.png">
### 9. Post-Install Maintenance
- We strongly suggest you turn on automatic security updates for your OS. In Ubuntu use the `dpkg-reconfigure -plow unattended-upgrades` command. In CentOS/RHEL, use the [`yum-cron`](https://www.redhat.com/sysadmin/using-yum-cron) package.
- If you are using a password and not a SSH key, be sure to enforce a strong root password. In Ubuntu use the `apt install libpam-cracklib` package. We also recommend `fail2ban` which blocks any IP addresses for 10 minutes that attempt more than 3 password retries.
- **Ubuntu**: `apt install fail2ban`
- **CentOS/RHEL**: `sudo dnf install fail2ban`
- If you need or want a default firewall, [turn on ufw](https://meta.discourse.org/t/configure-a-firewall-for-discourse/20584) for Ubuntu or use `firewalld` for CentOS/RHEL.
> 💡 Discourse will send you an email notification when new versions of Discourse are released. Please stay current to get the latest features and security fixes.
To **upgrade Discourse to the latest version**, visit `https://discourse.example.com/admin/upgrade` in your browser and click the Upgrade button.
Alternatively, you can ssh into your server and rebuild using:
```
cd /var/discourse
git pull
./launcher rebuild app
```
The `launcher` command in the `/var/discourse` folder can be used for various kinds of maintenance:
```text
Usage: launcher COMMAND CONFIG [--skip-prereqs] [--docker-args STRING]
Commands:
start: Start/initialize a container
stop: Stop a running container
restart: Restart a container
destroy: Stop and remove a container
enter: Use nsenter to get a shell into a container
logs: View the Docker logs for a container
bootstrap: Bootstrap a container for the config based on a template
rebuild: Rebuild a container (destroy old, bootstrap, start new)
cleanup: Remove all containers that have stopped for > 24 hours
Options:
--skip-prereqs Don't check launcher prerequisites
--docker-args Extra arguments to pass when running docker
```
### 10. (Optional) Add More Discourse Features
Do you want...
- Users to log in _only_ via your pre-existing website's registration system? [Configure Single-Sign-On](https://meta.discourse.org/t/official-single-sign-on-for-discourse/13045).
* Users to log in via [Google](https://meta.discourse.org/t/configuring-google-oauth2-login-for-discourse/15858), [Twitter](https://meta.discourse.org/t/configuring-twitter-login-for-discourse/13395), [GitHub](https://meta.discourse.org/t/configuring-github-login-for-discourse/13745), or [Facebook](https://meta.discourse.org/t/configuring-facebook-login-for-discourse/13394)?
* Users to post replies via email? [Configure reply via email](https://meta.discourse.org/t/set-up-reply-via-email-support/14003).
* Automatic daily backups? [Configure backups](https://meta.discourse.org/t/configure-automatic-backups-for-discourse/14855).
* Free HTTPS / SSL support? [Configure Let's Encrypt](https://meta.discourse.org/t/setting-up-lets-encrypt-cert-with-discourse-docker/40709). Paid HTTPS / SSL support? [Configure SSL](https://meta.discourse.org/t/allowing-ssl-for-your-discourse-docker-setup/13847).
* Use a plugin [from Discourse](https://github.com/discourse) or a third party? [Configure plugins](https://meta.discourse.org/t/install-a-plugin/19157)
* Multiple Discourse sites on the same server? [Configure multisite](https://meta.discourse.org/t/multisite-configuration-with-docker/14084).
* Webhooks when events happen in Discourse? [Configure webhooks](https://meta.discourse.org/t/setting-up-webhooks/49045).
* A Content Delivery Network to speed up worldwide access? [Configure a CDN](https://meta.discourse.org/t/enable-a-cdn-for-your-discourse/14857). We recommend [Fastly](http://www.fastly.com/).
* Import/migrate old content from vBulletin, PHPbb, Vanilla, Drupal, BBPress, etc? [See our open source importers](https://github.com/discourse/discourse/tree/main/script/import_scripts) and our [migration guide](https://meta.discourse.org/t/how-to-migrate-from-one-platform-forum-to-discourse/197236).
* A user friendly [offline page when rebuilding or upgrading?](https://meta.discourse.org/t/adding-an-offline-page-when-rebuilding/45238)
* To embed Discourse [in your WordPress install](https://github.com/discourse/wp-discourse), or [on your static HTML site](https://meta.discourse.org/t/embedding-discourse-comments-via-javascript/31963)?
Help us improve this guide! Feel free to ask about it on [meta.discourse.org][meta], or even better, submit a pull request.
[dd]: https://github.com/discourse/discourse_docker
[ssh]: https://help.github.com/articles/generating-ssh-keys
[meta]: https://meta.discourse.org
[do]: https://www.digitalocean.com/?refcode=5fa48ac82415
[put]: http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html
[mailconfig]: https://github.com/discourse/discourse/blob/main/docs/INSTALL-email.md
## Install protonmail bridge
We will need root access for the setup
```
su root
```
Import the protonmail bridge public key
```
nano bridge_pubkey.gpg
(copy the content of the file and save) or the public key bridge_pubkey.gpg can found:https://protonmail.com/download/bridge_pubkey.gpg
rpm --import bridge_pubkey.gpg
```
Download the protonmail-bridge package
```
cd /tmp
wget --no-check-certificate https://protonmail.com/download/bridge/protonmail-bridge_2.1.3-1_amd64.deb
```
Check the rpm package is correctly signed
```
rpm --checksig protonmail-bridge_2.1.3-1_amd64.deb
```
Install dependencies for protonmail-bridge
```
apt install qt5-default libqt5designer5 libqt5multimediawidgets5 libqt5quickwidgets5 libpulse-mainloop-glib0 libsecret-1-0 ttf-dejavu net-tools libsecret-common libsecret-tools
```
Install the protonmail bridge client
```
dpkg -i protonmail-bridge_2.1.3-1_amd64.deb
```
## Install additional tools required for the setup
Install the "pass" password manager that protonmail bridge will use to store the passwords
```
apt install pass
```
Install the "screen" utility to daemonize the protonmail bridge client
```
apt install screen
```
## Create a new user
We will create a new user mainly to isolate the access to the passwords of other users.
Notice that the new user will be locked to disable access to this user from outside.
```
useradd protonmail
usermod -L protonmail
```
Create a protonmail directory in /home
```
cd /home
mkdir protonmail
```
Change folder owner
```
chown -R protonmail:protonmail /home/protonmail
```
## Setup "pass" password manager
Login as the new isolated user
```
su protonmail
cd ~
```
Run a script session to avoid the PGP key passphrase prompt to fail (https://bugzilla.redhat.com/show_bug.cgi?id=659512).
This is required if we are not using a graphical interface due to the way our isolated user runs the shell commands
```
script /dev/null
```
Generate PGP key pair for the new user with an empty passphrase.
The empty passphrase is required to run the protonmail bridge on the background on system startup without being prompted for the password and hence causing the process to fail.
```
gpg --full-generate-key
>>>> Choose 1 (1) RSA and RSA (default)
>>>> Choose 2048 (default)
>>>> Choose 0 0 = key does not expire
>>>> Type your name e.g. Proty McProtonFace
>>>> Type your email e.g. a@a.com
>>>> Leave empty comment
>>>> Leave empty passphrase
```
List the keys to ensure they were created correctly
```
gpg --list-keys
```
Init the password manager for the chosen email address in the PGP keys step
```
pass init a@a.com
```
## Setup the protonmail bridge client
At this point we already set up the password manager that will allow the protonmail bridge to store the passwords so we will now setup your protonmail account.
```
protonmail-bridge --cli
>>>> add (add your protonmail account to bridge)
>>>> (enter your protonmail account email address)
>>>> (enter your protonmail account password)
>>>> list (list configured accounts)
>>>> info (list SMTP credentials for configuring any local SMTP compatible service)
>>>> help (get familiarized with the bridge options)
>>>> exit (exit the bridge console which stops the local SMTP server created)
```
Exit the scripted mode of the isolated user if you previously ran "script /dev/null"
```
exit
```
## Daemonize the protonmail bridge client
In order to start automatically the bridge client on system startup we will create a script to run it in the background.
Notice that we will use the "screen" utility since there is no way to run the protonmail linux client in the background currently without a graphical interface.
For this we will need root access again.
```
exit
```
Create a basic script that will be able to launch the protonmail bridge client in the background and kill it.
```
mkdir /var/lib/protonmail
nano /var/lib/protonmail/protonmail.sh
(copy the content of the file and save)
chmod +x /var/lib/protonmail/protonmail.sh
```
Create a systemd service
```
nano /etc/systemd/system/protonmail.service
(copy the content of the file and save)
```
Enable the script so that it can run on system startup
```
systemctl enable protonmail
```
Test the protonmail service
```
systemctl start protonmail
netstat -tulpn | grep 1025
```
Reboot you system and check if protonmail bridge is bound to the default ports
```
reboot
netstat -tulpn | grep 1025
```
Protonmail Bridge cannot listen to IP other than `127.0.0.1` and Discourse in a Docker container cannot send SMTP requests to `127.0.0.1` of the host machine.
We must configure it to send to `172.17.0.1(the Docker's bridge IP on the host machine)`, and then redirect to `127.0.0.1`, on which the Protonmail Bridge is listening
We do it using Socat
# Configure SMTP services
Now that you have the protonmail bridge running in the background you can configure SMTP emails on local instances of Jenkins, Jira, Bitbucket, Thunderbird or any service of your choice.
Remember that required credentials and configuration details can be found by executing:
```
protonmail-bridge --cli
>>>> info
>>>> exit
```
# Problems
**Note:** When sending an email via PHPMailer, the following message is displayed:
```
Connection failed. Error #2: stream_socket_client(): unable to connect to 127.0.0.1:1026 (Connection refused)
SMTP ERROR: Failed to connect to server: Connection refused (111)
```
OR
```
SMTP INBOUND: "454 4.7.0 account is logged out, use the app to login again"
SERVER -> CLIENT: 454 4.7.0 account is logged out, use the app to login again
SMTP ERROR: Password command failed: 454 4.7.0 account is logged out, use the app to login again
SMTP Error: Could not authenticate.
```
**Solution 1 :**
More than one process listens on the same port. Changing the port in Protonmail-bridge may correct the problem.
To solve it I had to:
Login as the new isolated user
```
su protonmail
cd ~
```
This is required if we are not using a graphical interface due to the way our isolated user runs the shell commands
```
script /dev/null
```
Change port setting
```
change port
```
**Solution 2 :**
Two user processes (root and protonmail) are executed at the same time.
1. Stopping the "proton-bridge" process using the killall command
```
killall -9 proton-bridge
```
2. Full uninstall protonmail-bridge
```
apt purge protonmail-bridge
```
2. remove all protonmail folders and configuration files in the 'root' profile
3. remove the protonmail folder in the "home" folder
```
rm -rf /home/protonmail
```
4. reboot
5. Repeat the protonmail-bridge installation procedure
# Problems
**Note:** When running Bridge on command line, I the following message is printed:
```
WARN[0000] Failed to add test credentials to keychain error="exit status 1: gpg: Passwords: skipped: No public key\ngpg: [stdin]: encryption failed: No public key\nPassword encryption aborted.\n" helper="*pass.Pass"
```
He had a bug with your keyring and pass.
**Solution:**
To solve it I had to:
1. uninstall gnupg and pass
`apt remove gnupg pass`
2. delete the `.gnupg` and `.password-store` folders
```
rm -rf /home/protonmail/.gnupg
rm -rf /home/protonmail/.password-store
```
3. reinstall gnupg and pass
`apt install gnupg pass`
4. login as the new isolated user
```
su protonmail
cd ~
```
5. run a script session to avoid the PGP key passphrase prompt to fail
`script /dev/null`
6. run gpg to create the database and its folder
`gpg --list-keys`
7. create a new key
```
gpg --full-generate-key
>>>> Choose 1 (1) RSA and RSA (default)
>>>> Choose 2048 (default)
>>>> Choose 0 0 = key does not expire
>>>> Type your name e.g. Proty McProtonFace
>>>> Type your email e.g. a@a.com
>>>> Leave empty comment
>>>> Leave empty passphrase
```
8. Init the password manager for the chosen email address in the PGP keys step
`pass init a@a.com`
9. List the keys to ensure they were created correctly
`gpg --list-keys`
10. Setup the protonmail bridge client, follow the procedure I described here
---
**set configuration on containers/app.yml**
```
## TODO: List of comma delimited emails that will be made admin and developer
## on initial signup example 'user1@example.com,user2@example.com'
DISCOURSE_DEVELOPER_EMAILS: 'admin@radiant.capital'
## TODO: The SMTP mail server used to validate new accounts and send notifications
# SMTP ADDRESS, username, and password are required
# WARNING the char '#' in SMTP password can cause problems!
DISCOURSE_SMTP_ADDRESS: 172.17.0.1
DISCOURSE_SMTP_PORT: 1026
DISCOURSE_SMTP_USER_NAME: admin@radiant.capital
DISCOURSE_SMTP_PASSWORD: "INsDNvoaYJ6RTiLaxTFApg"
#DISCOURSE_SMTP_ENABLE_START_TLS: true # (optional, default true)
DISCOURSE_SMTP_DOMAIN: radiant.capital
DISCOURSE_NOTIFICATION_EMAIL: admin@radiant.capital
DISCOURSE_SMTP_OPENSSL_VERIFY_MODE: none
## If you added the Lets Encrypt template, uncomment below to get a free SSL certificate
LETSENCRYPT_ACCOUNT_EMAIL: admin@radiant.capital
```
## Install unlock plugin
radiant.rake file
```
# frozen_string_literal: true
namespace :radiant do
desc "Radiant Task: remove user from group" # description.
task :radiant_task => :environment do
proposer_group = Group.find_by(name: "proposer")
read_only_group = Group.find_by(name: "read-only")
proposers = proposer_group.users
readers = read_only_group.users
proposers.each do |user|
if user.lock_timestamp == nil
user.groups.delete(proposer_group)
user.save
elsif Time.now - 1800 > user.lock_timestamp
user.groups.delete(proposer_group)
user.save!
end
end
readers.each do |user|
if user.lock_timestamp == nil
user.groups.delete(read_only_group)
user.save
elsif Time.now - user.lock_timestamp > 1800
user.groups.delete(read_only_group)
user.save!
end
end
end
end
```
Create radiant.rake file by following content and copy it insider radiant docker container /lib/tasks
```
- setup scheduler on crontab
- - - - - /var/discourse/unlock_cron_job
- unlock_cron_job
pid=`docker ps -aqf "name=app"`
docker exec ${pid} rake radiant:radiant_task
echo ${pid} >> /var/discourse/cron.log
```
```
How to build docker container
- build base_slim image
- build actual image and publish
for detailed info, plz check .github/workflows/build.yml
```
## Discourse docker rebuild guidance
1. Switch to test-passed branch in discourse docker repo
2. build base_slim image with the command `ruby auto_build.rb base_slim`
```
# simple build file to be used locally by Sam
#
require 'pty'
require 'optparse'
images = {
base_slim: { name: 'base', tag: "discourse/base:build_slim", squash: true, extra_args: '-f slim.Dockerfile' },
base: { name: 'base', tag: "discourse/base:build", extra_args: '-f release.Dockerfile' },
discourse_test_build: { name: 'discourse_test', tag: "discourse/discourse_test:build", squash: false},
discourse_dev: { name: 'discourse_dev', tag: "discourse/discourse_dev:build", squash: false },
}
def run(command)
lines = []
PTY.spawn(command) do |stdin, stdout, pid|
begin
stdin.each do |line|
lines << line
puts line
end
rescue Errno::EIO
# we are done
end
Process.wait(pid)
end
raise "'#{command}' exited with status #{$?.exitstatus}" if $?.exitstatus != 0
lines
end
def build(image)
lines = run("cd #{image[:name]} && docker build . --no-cache --tag #{image[:tag]} #{image[:squash] ? '--squash' : ''} #{image[:extra_args] ? image[:extra_args] : ''}")
raise "Error building the image for #{image[:name]}: #{lines[-1]}" if lines[-1] =~ /successfully built/
end
def dev_deps()
run("sed -e 's/\(db_name: discourse\)/\1_development/' ../templates/postgres.template.yml > discourse_dev/postgres.template.yml")
run("cp ../templates/redis.template.yml discourse_dev/redis.template.yml")
end
if ARGV.length != 1
puts <<~TEXT
Usage:
ruby auto_build.rb IMAGE
Available images:
#{images.keys.join(", ")}
TEXT
exit 1
else
image = ARGV[0].to_sym
if !images.include?(image)
$stderr.puts "Image not found"
exit 1
end
puts "Building #{images[image]}"
dev_deps() if image == :discourse_dev
build(images[image])
end
```
4. build base image with the command `ruby auto_build.rb base`
```
ARG from=discourse/base
ARG tag=build_slim
FROM $from:$tag
RUN cd /var/www/discourse &&\
sudo -u discourse bundle config --local deployment true &&\
sudo -u discourse bundle config --local path ./vendor/bundle &&\
sudo -u discourse bundle config --local without test development &&\
sudo -u discourse bundle install --jobs 4 &&\
sudo -u discourse yarn install --production --frozen-lockfile &&\
sudo -u discourse yarn cache clean &&\
bundle exec rake maxminddb:get &&\
find /var/www/discourse/vendor/bundle -name tmp -type d -exec rm -rf {} +
```
6. Push the image to the docker hub
## Unlock plugin development
Unlock plugin is used for web3 authentication in discourse community server
https://github.com/radiant-capital/unlock
Discourse plugin is supported by community and the unlock repo is the main part for widget customization
### unlock plugin customization
**The main purpose of customizing unlock plugin is for db operation and api endpoint upgrade**
db configuration looks like this
app/controllers/unlock_controller.rb
```
# frozen_string_literal: true
class UnlockController < ApplicationController
def unlock
lock = params.require(:lock)
wallet = params[:wallet]
transaction = params[:transaction]
return unless current_user
return unless settings = ::Unlock.settings
return if settings["lock_address"].blank? || settings["lock_address"] != lock.downcase
return unless group = Group.find_by(name: settings["unlocked_group_name"])
user = User.find_by(id: current_user.id)
user.lock_timestamp = Time.now
user.save
if group.users.find_by(id: current_user.id)
else
group.users << current_user
end
value = { lock: lock }
value[:wallet] = wallet if wallet.present?
value[:transaction] = transaction if transaction.present?
PluginStore.set(::Unlock::PLUGIN_NAME, "#{::Unlock::TRANSACTION}_#{current_user.id}", value)
render json: success_json
end
def unlock_premium
lock = params.require(:lock)
wallet = params[:wallet]
transaction = params[:transaction]
return unless current_user
return unless settings = ::Unlock.settings
return if settings["lock_address"].blank? || settings["lock_address"] != lock.downcase
return unless group = Group.find_by(name: settings["unlocked_group_name"])
user = User.find_by(id: current_user.id)
user.lock_timestamp = Time.now
user.save
if group.users.find_by(id: current_user.id)
else
group.users << current_user
end
group = Group.find_by(name: "rfp-author")
if group.users.find_by(id: current_user.id)
else
group.users << current_user
end
value = { lock: lock }
value[:wallet] = wallet if wallet.present?
value[:transaction] = transaction if transaction.present?
PluginStore.set(::Unlock::PLUGIN_NAME, "#{::Unlock::TRANSACTION}_#{current_user.id}", value)
render json: success_json
end
end
```
There're two endpoints (unlock, unlock_premium) to keep users data by locked amount of token
Required params in db
1. lock_timestamp: used to refresh the group ids by cron job
2. group: clarify if the user is for premium access
The client side to call the endpoint looks like this
assets/javascripts/discourse/initializers/extend-for-unlock.js.es6
```
window.addEventListener("unlockProtocol.status", ({ detail }) => {
const { state, locks } = detail;
console.log("detail", detail);
if (state === "unlocked" && window._wallet) {
const data = {
lock: window._lock || settings.lock_address,
wallet: window._wallet,
};
if (window._transaction) data["transaction"] = window._transaction;
let url;
if (window._redirectUrl == null) url = document.location.origin;
else url = document.location.origin + _redirectUrl;
console.log("out of if case befor reset", detail);
reset();
console.log("out of if case after reset", detail);
if (locks.length == 2) {
console.log("preimum", data);
return ajax("/unlock_premium.json", { type: "POST", data }).then(
() =>
(window.location.href = url + "?n=" + new Date().getTime()) // random number
);
} else if (locks.length == 1) {
console.log("non-preimum", data);
return ajax("/unlock.json", { type: "POST", data }).then(
() =>
(window.location.href = url + "?n=" + new Date().getTime())
);
}
}
});
```
Depending on the users' lock amount, it calls the api endpoint with dedicated infos
### Unlock modal configuration
The unlock repo has a lot of parts (we only use iframe section) and the main files we should keep focus on is `unlock_app/src/components/`
The starting point of the modal is main component in `unlock_app/src/components/interface/checkout/main/index.tsx`
```
import React, { useCallback, useEffect, useMemo } from 'react'
import type { PaywallConfig } from '~/unlockTypes'
import { checkoutMachine } from './checkoutMachine'
import { Select } from './Select'
import { useActor, useInterpret } from '@xstate/react'
import { useAuth } from '~/contexts/AuthenticationContext'
import { isEqual } from 'lodash'
import { CheckoutHead, CheckoutTransition, TopNavigation } from '../Shell'
interface Props {
injectedProvider: unknown
paywallConfig: PaywallConfig
// eslint-disable-next-line @typescript-eslint/no-explicit-any
communication?: any
redirectURI?: URL
}
export function Checkout({
paywallConfig,
injectedProvider,
communication,
redirectURI,
}: Props) {
console.log('checkout page communication', communication)
const checkoutService = useInterpret(checkoutMachine, {
context: {
paywallConfig,
},
})
const [state] = useActor(checkoutService)
const { account } = useAuth()
const matched = state.value.toString()
const paywallConfigChanged = !isEqual(
paywallConfig,
state.context.paywallConfig
)
useEffect(() => {
if (paywallConfigChanged) {
checkoutService.send({
type: 'UPDATE_PAYWALL_CONFIG',
config: paywallConfig,
})
}
}, [paywallConfig, checkoutService, paywallConfigChanged])
useEffect(() => {
const user = account ? { address: account } : {}
if (communication?.insideIframe) {
communication.emitUserInfo(user)
}
}, [account, communication])
const onBack = useMemo(() => {
const unlockAccount = state.children?.unlockAccount
const canBackInUnlockAccountService = unlockAccount
?.getSnapshot()
.can('BACK')
const canBack = state.can('BACK')
if (canBackInUnlockAccountService) {
return () => unlockAccount.send('BACK')
}
if (canBack) {
return () => checkoutService.send('BACK')
}
return undefined
}, [state, checkoutService])
const onClose = useCallback(
(params: Record<string, string> = {}) => {
if (redirectURI) {
for (const [key, value] of Object.entries(params)) {
redirectURI.searchParams.append(key, value)
}
return window.location.assign(redirectURI)
}
if (!communication?.insideIframe) {
window.history.back()
} else {
window?.top?.location.replace('https://community.radiant.capital')
communication.emitCloseModal()
}
},
[communication, redirectURI]
)
const Content = useCallback(() => {
switch (matched) {
case 'SELECT': {
return (
<Select
injectedProvider={injectedProvider}
checkoutService={checkoutService}
isReady={communication && communication.isReady}
/>
)
}
default: {
return null
}
}
}, [matched, injectedProvider, checkoutService, communication])
return (
<CheckoutTransition>
<div
className="bg-white max-w-md rounded-xl flex flex-col w-full h-[90vh] sm:h-[60vh] min-h-[32rem] max-h-[42rem]"
style={{ height: '56vh' }}
>
<TopNavigation onClose={onClose} onBack={onBack} />
<CheckoutHead
iconURL={paywallConfig.icon}
title={paywallConfig.title}
/>
<Content />
</div>
</CheckoutTransition>
)
}
```
The designs for the popup modal implemented in this file and it will be loaded as iframe
We also use unlock's smart contract but I don't think maintenance is not needed
Currently unlock app is deployed to our own DigitalOcean droplet
You can find deploy script here
```
#!/usr/bin/env bash
# Script fails if any command fails
set -e
# This script deploys a static build to netlify.
# It requires AUTH_TOKEN and SITE_ID to be set (see details on how to set them using deploy.sh)
APP_PATH=$1
DEPLOY_ENV=$2
COMMIT=$3
PUBLISH=$4
BUILD_PATH="out/"
if [ "$DEPLOY_ENV" = "staging" ]; then
if [ "$PUBLISH" = "true" ]; then
# This is a build on master, we deploy to staging as a published build
PROD="--prod"
MESSAGE="Deploying $COMMIT to staging. See logs below."
else
# we deploy as a draft on staging
PROD=""
MESSAGE="Deploying $COMMIT to draft. See draft URL and logs below."
fi
fi
if [ "$DEPLOY_ENV" = "prod" ]; then
PROD="--prod"
MESSAGE="Deploying version $COMMIT to production"
fi
if [ -n "$SITE_ID" ] && [ -n "$AUTH_TOKEN" ]; then
# Package
UNLOCK_ENV="$DEPLOY_ENV" yarn deploy
# And ship!
echo $MESSAGE
npx -y netlify-cli deploy --build -s $SITE_ID -a $AUTH_TOKEN --dir=$BUILD_PATH $PROD --message="$MESSAGE"
else
echo "Failed to deploy to Netlify because we're missing SITE_ID and/or AUTH_TOKEN"
exit 1
fi
```