Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FetureRequest/Add Talawa-api running on Linux as system daemon processs #2795

Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions example/linux/installation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# Talawa API Installation Guide

This guide provides step-by-step instructions for setting up the Talawa API service on a Linux system using systemd.

## Prerequisites

- **fnm** (Fast Version Manager)
- **Node.js** (version specified in your Talawa API's `package.json`)
- **tsx** (TypeScript execution environment, install globally with `npm install -g tsx`)
- A Linux system with **systemd**
- **Root access** or `sudo` privileges for service installation
- **Dedicated system user** `talawa` for running the service (security best practice)
PurnenduMIshra129th marked this conversation as resolved.
Show resolved Hide resolved
- **MongoDB** installed and running (required for Talawa API)
- **Redis** installed and running (required for Talawa API)
- Proper file permissions on `/usr/local/talawa-api` directory . Where your talawa-api is installed.
- For development:
- Ensure `.env` file sets `NODE_ENV=development`.
- Run the service manually to verify functionality.
- For production:
- Build the app to generate the `dist` folder.
- Ensure `.env` file sets `NODE_ENV=production`.
- **Log file setup**:
- Ensure a log file exists at `/var/log/talawa-api.log` with appropriate permissions and ownership.
- Verify Node.js version in your system matches the version required by `package.json`.
- Install `jq` for parsing JSON data (`sudo apt install jq` or equivalent).

## Steps

### 1. Create a Dedicated System User

- Create a user named `talawa` for running the service:
```bash
sudo adduser --system --no-create-home --group talawa
```
- Verify the user creation:
```bash
id talawa
```
PurnenduMIshra129th marked this conversation as resolved.
Show resolved Hide resolved
PurnenduMIshra129th marked this conversation as resolved.
Show resolved Hide resolved

PurnenduMIshra129th marked this conversation as resolved.
Show resolved Hide resolved
### 2. Create the Systemd Service File

- Create the `talawa-api.service` file in the `/etc/systemd/system/` directory with root privileges.
- Update the following placeholders with actual paths:
- `ExecStart` (path to your `Talawa-api.sh` script).
- `WorkingDirectory` (root directory of your Talawa API project).
- Refer to the example in `example/linux/systemd/talawa-api.service` for guidance.
- Copy talawa-api.service edit the path name then paste it inside `/etc/systemd/system/`
- Make sure `talawa-api.service` should be executable.

### 3. Set Up the `Talawa-api.sh` Script

- Edit the script to specify:
- **Project directory** (e.g., `/usr/local/talawa-api`)
- **Log file path** (e.g., `/var/log/talawa-api.log`)
- Ensure that the development (`src/index.ts`) and production (`dist/index.js`) paths are correctly set.
- Make sure `Talawa-api.sh` should be executable

### 4. Configure the Environment

- Ensure the `.env` file exists in the project directory and contains the appropriate configuration.
- Add the following environment variables:
- `NODE_ENV=development` or `NODE_ENV=production`.

### 5. Verify Log File and Permissions

- Create the log file if it does not exist:
```bash
sudo touch /var/log/talawa-api.log
sudo chown talawa:talawa /var/log/talawa-api.log
sudo chmod 664 /var/log/talawa-api.log
```
PurnenduMIshra129th marked this conversation as resolved.
Show resolved Hide resolved
- Ensure the log file owner matches the service user (e.g., `talawa`).

### 6. Install Dependencies

- Install required Node.js version with `fnm`:
```bash
fnm install <node_version>
fnm use <node_version>
```
PurnenduMIshra129th marked this conversation as resolved.
Show resolved Hide resolved
Replace `<node_version>` with the version specified in `package.json` (`engines.node`).
- Install dependencies:
```bash
npm install
```
- Globally install `tsx` if not already installed:
```bash
npm install -g tsx
```
- Install `jq`:
```bash
sudo apt install jq
```

### 7. Enable and Start the Service

1. Reload the systemd configuration:
```bash
sudo systemctl daemon-reload
```
2. Enable the service:
```bash
sudo systemctl enable talawa-api.service
```
3. Start the service:
```bash
sudo systemctl start talawa-api.service
```

### 8. Verify the Installation

- Check the status of the service:
```bash
sudo systemctl status talawa-api.service
```
- View logs in real-time:
```bash
sudo journalctl -u talawa-api.service -f
```
- Check for errors:
```bash
sudo journalctl -u talawa-api.service -p err
```
- Verify the service configuration:
```bash
sudo systemd-analyze verify talawa-api.service
```
- Verify service dependencies:
```bash
sudo systemctl list-dependencies talawa-api.service
```

## Notes

- Ensure the `Talawa-api.sh` script has executable permissions:
```bash
chmod +x /path/to/Talawa-api.sh
```
- Adjust `LimitNOFILE` and security-related settings in the `talawa-api.service` file as needed for your environment.
- For production, ensure the `dist` folder exists by running:
```bash
npm run build
```
- If you encounter any issues, refer to the logs in `/var/log/talawa-api.log` or use `journalctl`.

### Additional Steps for Troubleshooting

1. Verify Node.js and `tsx` installation:
```bash
node -v
tsx -v
```
2. Ensure MongoDB and Redis are running:
```bash
sudo systemctl status mongod
sudo systemctl status redis
```
PurnenduMIshra129th marked this conversation as resolved.
Show resolved Hide resolved
157 changes: 157 additions & 0 deletions example/linux/systemd/Talawa-api.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
#!/bin/bash

# Change to the project directory give your path
PROJECT_DIR="/home/purnendu/Development/talawa-api"
LOG_FILE="/var/log/talawa-api.log"
DEV_PATH="src/index.ts"
PROD_PATH="dist/index.js"
PurnenduMIshra129th marked this conversation as resolved.
Show resolved Hide resolved
PurnenduMIshra129th marked this conversation as resolved.
Show resolved Hide resolved

# Check if the log file exists
if [ ! -f "$LOG_FILE" ]; then
echo "Error: Log file '$LOG_FILE' not found. Exiting."
echo "Please create it first with the correct ownership and permissions, then rerurn."
PurnenduMIshra129th marked this conversation as resolved.
Show resolved Hide resolved
exit 1
fi

# Get the current user
CURRENT_USER=$(whoami)

# Get the owner of the log file
LOG_FILE_OWNER=$(stat -c '%U' "$LOG_FILE")

# Check if the current user matches the owner of the log file
if [ "$CURRENT_USER" != "$LOG_FILE_OWNER" ]; then
echo "Error: Current user '$CURRENT_USER' does not match the owner of the log file '$LOG_FILE_OWNER'. Exiting."
echo "Change ownership or permissions and try again."
exit 1
fi
PurnenduMIshra129th marked this conversation as resolved.
Show resolved Hide resolved

# Check if the user has necessary permissions to read and write to the log file
if [ ! -w "$LOG_FILE" ] || [ ! -r "$LOG_FILE" ]; then
echo "Error: User '$CURRENT_USER' does not have sufficient permissions to read or write to the log file '$LOG_FILE'. Exiting."
echo "Change permissions and try again."
exit 1
fi
echo "-------------------------------***************------------------------------------" | tee -a "$LOG_FILE"
echo "------------------------------>Talawa-API Logs<-----------------------------------" | tee -a "$LOG_FILE"
echo "------------------------------>Current session date: $(date)" | tee -a "$LOG_FILE"
echo "-------------------------------***************------------------------------------" | tee -a "$LOG_FILE"
echo "Log file '$LOG_FILE' is present and writable by user '$CURRENT_USER'. Proceeding..." | tee -a "$LOG_FILE"

# Verify the project directory exists
if [ ! -d "$PROJECT_DIR" ]; then
echo "Error: Project directory '$PROJECT_DIR' not found. Exiting." | tee -a "$LOG_FILE"
exit 1
fi

# Switch to the project directory
cd "$PROJECT_DIR" || { echo "Error: Failed to change to project directory '$PROJECT_DIR'. Exiting." | tee -a "$LOG_FILE"; exit 1; }

echo "Changed to project directory '$PROJECT_DIR'. Proceeding..." | tee -a "$LOG_FILE"

# Check for package.json in the current working directory
if [ ! -f "package.json" ]; then
echo "Error: 'package.json' not found in $(pwd). Exiting." | tee -a "$LOG_FILE"
echo "Please ensure it is present, then return." | tee -a "$LOG_FILE"
exit 1
fi

echo "package.json is present in $(pwd). Proceeding..." | tee -a "$LOG_FILE"

if ! command -v jq >/dev/null 2>&1; then
echo "Error: 'jq' is not installed on this system. Exiting." | tee -a "$LOG_FILE"
echo "It is required to parse the Node.js version from package.json." | tee -a "$LOG_FILE"
echo "Please install 'jq' manually, then rerurn to the script." | tee -a "$LOG_FILE"
exit 1
fi

echo "'jq' is present. Proceeding..." | tee -a "$LOG_FILE"

# Attempt to read the required Node.js version
TARGET_NODE_VERSION=$(jq -r '.engines.node' package.json 2>/dev/null)

# Continue with your script...
if [ -z "$TARGET_NODE_VERSION" ] || [ "$TARGET_NODE_VERSION" == "null" ]; then
echo "Error: Unable to read 'engines.node' from package.json. Exiting." | tee -a "$LOG_FILE"
exit 1
fi

# Remove 'v' prefix if present, e.g. "v20.18.0" -> "20.18.0"
INSTALLED_NODE_VERSION=$(node -v 2>/dev/null | sed 's/^v//')

echo "Installed Node.js version: $INSTALLED_NODE_VERSION" | tee -a "$LOG_FILE"
echo "Target Node.js version: $TARGET_NODE_VERSION" | tee -a "$LOG_FILE"

if [ "$INSTALLED_NODE_VERSION" != "$TARGET_NODE_VERSION" ]; then
echo "Error: Node.js version mismatch. Found $INSTALLED_NODE_VERSION, need $TARGET_NODE_VERSION". Exiting.| tee -a "$LOG_FILE"
PurnenduMIshra129th marked this conversation as resolved.
Show resolved Hide resolved
echo "First install the required Node.js version from package.json in system then proceed further. It should match system Node.js version and Talawa-api Node.js version v$TARGET_NODE_VERSION" | tee -a "$LOG_FILE"
exit 1
fi

echo "Node.js version matched. Proceeding..." | tee -a "$LOG_FILE"

# Check if tsx is installed
if ! command -v tsx >/dev/null 2>&1; then
echo "Error: 'tsx' is not installed on this system. Exiting." | tee -a "$LOG_FILE"
echo "Please install 'tsx' manually, then rerun the script." | tee -a "$LOG_FILE"
exit 1
fi

# Define the path to the tsx executable dynamically
TSX_PATH=$(which tsx)
PurnenduMIshra129th marked this conversation as resolved.
Show resolved Hide resolved

# Check if the TSX_PATH is valid
if [ ! -x "$TSX_PATH" ]; then
echo "Error: Path for 'tsx' is not found or not executable. Verify it is properly installed. Exiting." | tee -a "$LOG_FILE"
exit 1
fi

echo "'tsx' is installed and executable at '$TSX_PATH'. Proceeding..." | tee -a "$LOG_FILE"

# Validate paths for development and production
if [ ! -f "$DEV_PATH" ]; then
echo "Error: Development path '$DEV_PATH' not found. Exiting." | tee -a "$LOG_FILE"
exit 1
fi

if [ ! -f "$PROD_PATH" ]; then
echo "Error: Production path '$PROD_PATH' not found. Exiting." | tee -a "$LOG_FILE"
exit 1
fi

echo "Development and production paths are valid. Proceeding..." | tee -a "$LOG_FILE"

# Check if .env file is present
if [ ! -f ".env" ]; then
echo "Error: '.env' file not found. Exiting." | tee -a "$LOG_FILE"
exit 1
fi
echo ".env file found in "$pwd" directory. Proceeding..." | tee -a "$LOG_FILE"
PurnenduMIshra129th marked this conversation as resolved.
Show resolved Hide resolved

# Load environment variables from .env file securely
while IFS= read -r line || [ -n "$line" ]; do
if [[ ! "$line" =~ ^# && "$line" =~ = ]]; then
export "$line"
fi
done < .env
PurnenduMIshra129th marked this conversation as resolved.
Show resolved Hide resolved
PurnenduMIshra129th marked this conversation as resolved.
Show resolved Hide resolved

# Check if NODE_ENV is set
if [ -z "$NODE_ENV" ]; then
echo "Error: Property 'NODE_ENV' is not present in the .env file. Exiting." | tee -a "$LOG_FILE"
exit 1
fi

echo "Environment variable 'NODE_ENV' is set to '$NODE_ENV'. Proceeding..." | tee -a "$LOG_FILE"
{
# Check the value of NODE_ENV and execute the corresponding command
if [ "$NODE_ENV" == "development" ]; then
echo "Starting Talawa API in development mode..." | tee -a "$LOG_FILE"
exec $TSX_PATH $DEV_PATH
elif [ "$NODE_ENV" == "production" ]; then
echo "Starting Talawa API in production mode..." | tee -a "$LOG_FILE"
exec $TSX_PATH $PROD_PATH
PurnenduMIshra129th marked this conversation as resolved.
Show resolved Hide resolved
else
echo "NODE_ENV is not set to a valid value. Please set it to 'development' or 'production'. Exiting." | tee -a "$LOG_FILE"
exit 1
fi
} 2>&1 | tee -a "$LOG_FILE"
69 changes: 69 additions & 0 deletions example/linux/systemd/talawa-api.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
[Unit]
# Description of the service
Description=Talawa-API Service

# Ensure the service starts after the network is available
After=network.target

PurnenduMIshra129th marked this conversation as resolved.
Show resolved Hide resolved
[Service]
# The type of service. 'simple' means the service will start immediately.
Type=simple

# The command to start the service. This points to the Talawa-api.sh script.User have to set it manually. Inside the talawa-api directory execute the command "pwd" to find the path.Then replace this with your original one (eg:/usr/local/talawa-api/example/linux/systemd/Talawa-api.sh -> /home/purnendu/development/talawa-api/example/linux/systemd/Talawa-api.sh)
ExecStart=/usr/local/talawa-api/example/linux/systemd/Talawa-api.sh

# The working directory for the service.User have to set it manually.Inside the talawa-api directory execute the command "pwd" to find the path.Then replace this with your original one (eg:/usr/local/talawa-api -> /home/purnendu/development/talawa-api)
WorkingDirectory=/usr/local/talawa-api

# Restart the service automatically if it stops.
Restart=always

# The delay before restarting the service.
RestartSec=5

# The user to run the service as. You can find your username by running 'whoami'.Create a user named talawa for better understanding and Security
User=talawa

# The group to run the service as. Usually, this is the same as the username.
Group=talawa

PurnenduMIshra129th marked this conversation as resolved.
Show resolved Hide resolved
# Redirects the output and error to the systemd journal and console.
StandardOutput=journal+console
StandardError=journal+console

# Sets the maximum number of open files.Neither too much nor too less.
LimitNOFILE=15000
PurnenduMIshra129th marked this conversation as resolved.
Show resolved Hide resolved
PurnenduMIshra129th marked this conversation as resolved.
Show resolved Hide resolved

# Security-related directives

# Protect the system by making the root filesystem read-only for the service.
# This prevents the service from modifying system-critical files, reducing the attack surface.
ProtectSystem=strict

# Prevent the service from accessing home directories of other users.
# Only the necessary files in the service's user directory will be accessible.
ProtectHome=yes

# Make the service's working directory read-only.
# This is useful for protecting important directories from being altered by the service.
ReadOnlyPaths=/usr/local/talawa-api

# Ensures the service and its child processes cannot gain new privileges.
# This helps to prevent privilege escalation attacks.
NoNewPrivileges=true

# Isolate the service's temporary files from others by assigning it a private /tmp.
# This prevents other services or users from accessing or interfering with the service's temporary data.
PrivateTmp=true

# Restrict the service to use only IPv4 and IPv6 address families.
# This helps prevent the service from using other network protocols, such as Unix domain sockets, which could be a potential attack vector.
RestrictAddressFamilies=AF_INET AF_INET6

# Allows the service to bind to network ports below 1024 (e.g., HTTP on port 80),
# but it won't allow the service to use any other privileged capabilities.
AmbientCapabilities=CAP_NET_BIND_SERVICE

[Install]
# Specifies the target to which the service should be added. 'multi-user.target' means the service will start in multi-user mode.
WantedBy=multi-user.target
Loading
Loading