Compare commits

...

No commits in common. "main" and "clean-branch" have entirely different histories.

55 changed files with 911 additions and 6999 deletions

8
.env
View File

@ -1,8 +0,0 @@
VITE_CG_API_KEY=CG-6tRLtAyJ9t5YdthDmRPhwKRD
VITE_CG_API_URL=https://pro-api.coingecko.com/api/v3
# VPS Configuration
SERVER_IP="66.179.188.130"
SERVER_USER="root"
# Replace with your actual password
SERVER_PASSWORD="4Kk7r8bi"

View File

@ -1,3 +1,4 @@
SERVER_USER=your_server_username
SERVER_PASSWORD=your_server_password
SERVER_IP=your_server_ip
# VPS Configuration
SERVER_IP="your_server_ip"
SERVER_USER="root"
SERVER_PASSWORD="your_server_password"

58
.gitignore vendored
View File

@ -1,39 +1,43 @@
# Dependencies
node_modules/
/.pnp
.pnp.js
# Testing
/coverage
# Production
/build
/dist
# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
.env
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Dependencies and build artifacts
node_modules
dist
dist-ssr
*.local
.parcel-cache
# Environment files
.env
*.env.local
*.env.development.local
*.env.test.local
*.env.production.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
.idea/
.vscode/
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# Misc
.codegpt
.cache
.temp
.tmp
# Parcel
.cache/
.parcel-cache/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache

121
LICENSE Normal file
View File

@ -0,0 +1,121 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.

182
README.md
View File

@ -1,143 +1,77 @@
# Personal Website - Chris Wylde
# Web3 CV
A modern, responsive personal website built with React, TypeScript, and Vite. This project serves as a professional landing page and link aggregator, featuring sections for professional experience, Web3 achievements, NFT collections, and social connections.
A simple web page for my Web3 CV.
## 🚀 Features
## Deployment
- Modern React with TypeScript and Vite
- Responsive design with Tailwind CSS
- Animated sections and transitions
- Dark theme with custom gradients
- Social media integration
- NFT gallery
- Achievement showcase
- Professional experience timeline
This website is deployed to:
- [boilerhaus.org](https://boilerhaus.org) - Primary domain on VPS
- [chriswylde.xyz](https://chriswylde.xyz) - Secondary domain on Vercel (manually deployed)
## 🛠 Tech Stack
## Setup Instructions
- React 18
- TypeScript
- Vite
- Tailwind CSS
- Lucide Icons
- Recharts for data visualization
### Initial Setup
## 📦 Installation
1. Clone the repository:
```bash
git clone https://git.boilerhaus.org/boiler/Web3CV.git
cd Web3CV
```
**Clone the repository:**
2. Install dependencies:
```bash
npm install
```
```bash
git clone https://git.boilerhaus.org/boiler/Web3CV.git
cd Web3CV
```
3. Set up the VPS:
```bash
./setup-vps.sh
```
This script will:
- Check if Nginx and Certbot are installed on your VPS
- Create the necessary directories
- Configure Nginx for boilerhaus.org, cloud.boilerhaus.org, and git.boilerhaus.org
- Set up SSL certificates using Let's Encrypt
- Configure Nextcloud to use cloud.boilerhaus.org (if installed)
**Install dependencies:**
4. Migrate to Gitea:
```bash
./migrate-to-gitea.sh
```
This script will:
- Create a new repository on your Gitea instance
- Push your code to Gitea
```bash
npm install
```
### Development
**Start the development server:**
1. Run the development server:
```bash
npx parcel index.html
```
```bash
npm run dev
```
2. Open [http://localhost:1234](http://localhost:1234) in your browser.
**Build for production:**
### Deployment
```bash
npm run build
```
1. Deploy to VPS:
```bash
./deploy-all.sh
```
This script will:
- Build the site
- Deploy to your VPS
- Push changes to Gitea
## 🚀 Deployment
2. Deploy only to VPS (same as above):
```bash
./deploy.sh
```
This website is deployed on a VPS at [boilerhaus.org](https://boilerhaus.org) using a custom deployment script.
## Repository Management
### Deployment Setup
This repository is hosted on a self-hosted Gitea instance at git.boilerhaus.org.
1. **Environment Configuration**
- Create a `.env` file based on `.env.example`
- Add your server credentials:
```
SERVER_USER=your_server_username
SERVER_PASSWORD=your_server_password
SERVER_IP=your_server_ip
```
### Remote Setup
2. **Deploy to VPS**
- Run the deployment script:
```bash
./deploy-website.sh
```
- This script will:
- Build the project
- Create a tarball of the built files
- Upload to the server
- Extract files to the web directory
- Set proper permissions
3. **Check Website Status**
- Run the status check script:
```bash
./check-website.sh
```
- This will check the status of all services:
- Portfolio website (boilerhaus.org)
- Gitea (git.boilerhaus.org)
- Vaultwarden (bw.boilerhaus.org)
- Nextcloud (cloud.boilerhaus.org)
### Maintenance Scripts
The repository includes several utility scripts to help with maintenance:
1. **update-from-gitea.sh**
- Updates the local repository from Gitea
- Preserves any local changes using git stash
2. **fix-nginx-config.sh**
- Fixes the Nginx configuration on the server
- Properly configures all subdomains
3. **fix-nextcloud-occ.sh**
- Adds cloud.boilerhaus.org to Nextcloud's trusted domains
- Uses the Nextcloud occ command-line tool
4. **cleanup.sh**
- Removes unnecessary files from the repository
- Updates .gitignore to exclude build artifacts
### Server Configuration
The website is served by Nginx with SSL certificates from Let's Encrypt. The configuration files are located on the server at `/etc/nginx/sites-available/`.
## 🌐 Domain Information
### Current Setup
- Domain: [boilerhaus.org](https://boilerhaus.org)
- Subdomains:
- [git.boilerhaus.org](https://git.boilerhaus.org) - Gitea instance
- [bw.boilerhaus.org](https://bw.boilerhaus.org) - Vaultwarden instance
- [cloud.boilerhaus.org](https://cloud.boilerhaus.org) - Nextcloud dashboard
### Previous Setup (Archived)
- Domains: [www.chriswylde.xyz](https://www.chriswylde.xyz) and [www.boilerrat.xyz](https://www.boilerra.xyz)
- Registrar: Namecheap
- Previous Host: Netlify/Vercel
## 🤝 Contributing
Contributions, issues, and feature requests are welcome! Feel free to check the [issues page](https://git.boilerhaus.org/boiler/Web3CV/issues).
## 📝 License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## 👤 Contact
- Website: [boilerhaus.org](https://boilerhaus.org)
- Twitter: [@boilerrat](https://twitter.com/boilerrat)
- Farcaster: [@boiler](https://warpcast.com/boiler)
- Email: [128boilerrat@gmail.com](mailto:128boilerrat@gmail.com)
Your remote is configured as:
- `origin`: Your Gitea repository (https://git.boilerhaus.org/boiler/Web3CV.git)

49
boilerhaus.org.conf Normal file
View File

@ -0,0 +1,49 @@
server {
listen 80;
listen [::]:80;
server_name boilerhaus.org www.boilerhaus.org;
root /var/www/boilerhaus.org;
index index.html;
location / {
try_files $uri $uri/ =404;
}
# Managed by Certbot
# This section will be updated by Certbot automatically
}
server {
listen 80;
listen [::]:80;
server_name cloud.boilerhaus.org;
# Managed by Certbot
# This section will be updated by Certbot automatically
# Proxy to Nextcloud (will be active after SSL is set up)
location / {
return 503; # Temporary unavailable until SSL is configured
}
}
server {
listen 80;
listen [::]:80;
server_name git.boilerhaus.org;
# Proxy to Gitea
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Managed by Certbot
# This section will be updated by Certbot automatically
}
# SSL configurations will be added by Certbot automatically

View File

@ -1,33 +0,0 @@
#!/bin/bash
# This script checks the status of all services on boilerhaus.org
echo "=== Checking Services Status ==="
echo -e "\n1. Portfolio Website (boilerhaus.org)"
echo "--------------------------------"
curl -I https://boilerhaus.org
echo -e "\nStatus: $(curl -s -o /dev/null -w "%{http_code}" https://boilerhaus.org)"
echo -e "\n2. Gitea (git.boilerhaus.org)"
echo "--------------------------------"
curl -I https://git.boilerhaus.org
echo -e "\nStatus: $(curl -s -o /dev/null -w "%{http_code}" https://git.boilerhaus.org)"
echo -e "\n3. Nextcloud (cloud.boilerhaus.org)"
echo "--------------------------------"
curl -I https://cloud.boilerhaus.org
echo -e "\nStatus: $(curl -s -o /dev/null -w "%{http_code}" https://cloud.boilerhaus.org)"
echo -e "\n4. Vaultwarden (bw.boilerhaus.org)"
echo "--------------------------------"
curl -I https://bw.boilerhaus.org
echo -e "\nStatus: $(curl -s -o /dev/null -w "%{http_code}" https://bw.boilerhaus.org)"
echo -e "\n=== Service Configuration Summary ==="
echo "Portfolio: Port 443 (HTTPS) → /var/www/boilerhaus.org"
echo "Gitea: Port 8080 → http://localhost:8080"
echo "Nextcloud: Port 8081 → http://localhost:8081"
echo "Vaultwarden: Port 8222 → http://localhost:8222"
echo -e "\n=== All checks completed ==="

View File

@ -1,39 +0,0 @@
#!/bin/bash
# This script cleans up unnecessary files from the repository
echo "=== Cleaning up repository ==="
# Remove redundant Nextcloud fix scripts
echo "Removing redundant Nextcloud fix scripts..."
rm -f fix-nextcloud-trusted-domains.sh
rm -f fix-nextcloud-domains.sh
rm -f fix-nextcloud.sh
# Remove build cache
echo "Removing build cache..."
rm -rf .parcel-cache
# Remove unused configuration files
echo "Removing unused configuration files..."
rm -f boilerhaus.org.conf.new
# Clean up build artifacts
echo "Cleaning up build artifacts..."
rm -rf dist
# Add the changes to git
echo "Adding changes to git..."
git add -A
echo "=== Cleanup complete ==="
echo "The following files have been removed:"
echo "- fix-nextcloud-trusted-domains.sh"
echo "- fix-nextcloud-domains.sh"
echo "- fix-nextcloud.sh"
echo "- .parcel-cache/ directory"
echo "- boilerhaus.org.conf.new"
echo "- dist/ directory"
echo ""
echo "To commit these changes, run:"
echo "git commit -m \"Clean up repository by removing unnecessary files\""

108
css/style.css Normal file
View File

@ -0,0 +1,108 @@
body {
background-color: #1d1d1d;
border: none;
color: #444;
font-family: "Inter",sans-serif;
font-size: 20px;
margin: 0;
padding: 0;
-webkit-font-smoothing: antialiased;
}
a:hover {
text-decoration: none;
}
.btn {
background: #333;
padding: 20px; /* Increase Button Height */
font-size: 1.2em; /* Increase button font size */
}
.btn:hover {
background-color: #567730;
}
.header-inner {
position: relative;
text-align: center;
}
.site-title {
font-size: 2.75em;
font-family: "Gloock",cursive; /* Updated to Gloock font */
font-style: normal;
font-weight: 400;
text-align: center;
text-shadow: 1px 1px 2px rgba(0,0,0,.25);
}
.site-title a {
color: #fff;
}
.site-title a:hover {
color: #567730;
}
.site-description {
color: #999;
font-size: 1.1em;
font-family: "Inter", sans-serif;
font-weight: 400;
line-height: 110%;
margin: 20px 0 0;
text-align: center;
text-shadow: 1px 1px 1px rgba(0,0,0,.25);
}
.image-border {
border: 10px solid #567730;
box-sizing: border-box;
}
.section-header {
color: #fff;
font-size: 2em;
font-family: "Gloock", cursive; /* Updated to Gloock font */
font-style: normal;
font-weight: 400;
text-align: center;
margin-top: .5em;
margin-bottom: .5em;
border: 1px solid #fff; /* Add a thin white border */
padding: 10px; /* Add some padding */
box-sizing: border-box; /* Include border and padding in the element's total width and height */
}
.figure img {
height: 400px; /* Set a maximum height for the images */
max-width: 100%;
object-fit: cover; /* Resize the images to cover the entire area while maintaining the aspect ratio */
}
.figure figcaption {
text-align: center; /* Center the caption */
color: #fff; /* Set the text color to white */
font-family: "Inter",sans-serif; /* Match the font with the rest of the webpage */
font-size: 1em; /* Set the font size */
}
/* Media query for screens smaller than 600px */
@media (max-width: 600px) {
body {
font-size: 18px;
}
.btn {
padding: 15px;
font-size: 1em;
}
}
/* Media query for screens smaller than 400px */
@media (max-width: 400px) {
body {
font-size: 16px;
}
.btn {
padding: 10px;
font-size: 0.8em;
}
}

19
deploy-all.sh Executable file
View File

@ -0,0 +1,19 @@
#!/bin/bash
# This script deploys your website to your VPS
# Build the site
echo "Building the site..."
npx parcel build index.html
# Deploy to VPS
echo "Deploying to VPS..."
rsync -avz --delete dist/ root@boilerhaus.org:/var/www/boilerhaus.org/
# Push changes to Gitea
echo "Pushing changes to Gitea..."
git add .
git commit -m "Update website content" || echo "No changes to commit"
git push origin main
echo "Deployment complete!"

View File

@ -1,6 +1,6 @@
#!/bin/bash
# This script deploys the portfolio website from Gitea to the VPS
# This script deploys the portfolio website from GitHub to the VPS
# Load environment variables
if [ -f .env ]; then
@ -12,54 +12,52 @@ fi
# Check if sshpass is installed
if ! command -v sshpass &> /dev/null; then
echo "Error: sshpass is not installed. Please run 'sudo apt-get install sshpass' first."
echo "Error: sshpass is not installed. Please run ./install-sshpass.sh first."
exit 1
fi
# Set SSH and SCP commands with password
SSH_CMD="sshpass -p $SERVER_PASSWORD ssh -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP"
SCP_CMD="sshpass -p $SERVER_PASSWORD scp -o StrictHostKeyChecking=no"
SSH_CMD="sshpass -p \"$SERVER_PASSWORD\" ssh -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP"
SCP_CMD="sshpass -p \"$SERVER_PASSWORD\" scp -o StrictHostKeyChecking=no"
# Set variables
REMOTE_DIR="/var/www/boilerhaus.org"
GITHUB_REPO="https://github.com/boilerrat/portfolio.git"
TEMP_DIR="./portfolio-temp"
# Create a temporary directory for the website files
echo "Creating temporary directory..."
mkdir -p $TEMP_DIR
cd $TEMP_DIR
# Check if TypeScript is installed
if ! command -v tsc &> /dev/null; then
echo "Installing TypeScript globally..."
npm install -g typescript
fi
# Clone the repository
echo "Cloning portfolio repository from GitHub..."
git clone $GITHUB_REPO .
# Install dependencies
echo "Installing dependencies..."
npm install
# Build the project
echo "Building the project..."
npm run build
# Check if dist directory exists
if [ ! -d "dist" ]; then
echo "Error: Build failed. The dist directory does not exist."
exit 1
fi
# Create a tarball of the built files
echo "Creating tarball of the built files..."
cd dist
tar -czf ../portfolio-dist.tar.gz .
cd ..
tar -czf ../../portfolio-dist.tar.gz .
cd ../..
# Upload the tarball to the server
echo "Uploading to server..."
$SCP_CMD portfolio-dist.tar.gz $SERVER_USER@$SERVER_IP:/tmp/
eval "$SCP_CMD portfolio-dist.tar.gz $SERVER_USER@$SERVER_IP:/tmp/"
# Extract the tarball on the server
echo "Extracting files on server..."
$SSH_CMD "mkdir -p $REMOTE_DIR && \
tar -xzf /tmp/portfolio-dist.tar.gz -C $REMOTE_DIR && \
chown -R www-data:www-data $REMOTE_DIR && \
rm /tmp/portfolio-dist.tar.gz"
eval "$SSH_CMD \"mkdir -p $REMOTE_DIR && \
tar -xzf /tmp/portfolio-dist.tar.gz -C $REMOTE_DIR && \
chown -R www-data:www-data $REMOTE_DIR && \
rm /tmp/portfolio-dist.tar.gz\""
# Clean up local files
echo "Cleaning up..."

19
deploy.sh Executable file
View File

@ -0,0 +1,19 @@
#!/bin/bash
# This script deploys your website to your VPS
# Build the site
echo "Building the site..."
npx parcel build index.html
# Deploy to VPS
echo "Deploying to VPS..."
rsync -avz --delete dist/ root@boilerhaus.org:/var/www/boilerhaus.org/
# Push changes to Gitea
echo "Pushing changes to Gitea..."
git add .
git commit -m "Update website content" || echo "No changes to commit"
git push origin main
echo "Deployment complete!"

View File

@ -1,28 +0,0 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
)

25
fix-git-subdomain.sh Executable file
View File

@ -0,0 +1,25 @@
#!/bin/bash
# This script fixes the git.boilerhaus.org subdomain configuration
# Check if we can connect to the VPS
echo "Testing connection to VPS..."
if ! ssh root@66.179.188.130 "echo 'Connection successful'"; then
echo "Failed to connect to VPS. Please check your SSH configuration."
exit 1
fi
# Upload the updated Nginx configuration
echo "Uploading updated Nginx configuration..."
scp boilerhaus.org.conf root@66.179.188.130:/etc/nginx/sites-available/boilerhaus.org
# Add git.boilerhaus.org to the SSL certificate
echo "Adding git.boilerhaus.org to the SSL certificate..."
ssh root@66.179.188.130 "certbot --nginx -d git.boilerhaus.org"
# Restart Nginx
echo "Restarting Nginx..."
ssh root@66.179.188.130 "systemctl restart nginx"
echo "Git subdomain fix complete!"
echo "You should now be able to access git.boilerhaus.org"

View File

@ -1,53 +0,0 @@
#!/bin/bash
# This script adds cloud.boilerhaus.org to Nextcloud's trusted domains using the occ command
# Load environment variables
if [ -f .env ]; then
source .env
else
echo "Error: .env file not found. Please create it based on .env.example."
exit 1
fi
# Check if sshpass is installed
if ! command -v sshpass &> /dev/null; then
echo "Error: sshpass is not installed. Please run 'sudo apt-get install sshpass' first."
exit 1
fi
# Set SSH command with password
SSH_CMD="sshpass -p $SERVER_PASSWORD ssh -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP"
echo "Adding cloud.boilerhaus.org to Nextcloud's trusted domains using occ command..."
# Find the Nextcloud directory
$SSH_CMD "sudo find /var/www -name occ 2>/dev/null" > nextcloud_occ_path.txt
OCC_PATH=$(cat nextcloud_occ_path.txt)
if [ -z "$OCC_PATH" ]; then
echo "Error: Could not find Nextcloud occ command."
echo "Trying alternative approach..."
# Try to directly edit the config.php file
$SSH_CMD "sudo bash -c 'echo \"<?php \\\$CONFIG['trusted_domains'][] = \\\"cloud.boilerhaus.org\\\"; \" > /var/www/nextcloud/config/config.php.additional'"
$SSH_CMD "sudo bash -c 'cat /var/www/nextcloud/config/config.php.additional >> /var/www/nextcloud/config/config.php'"
$SSH_CMD "sudo rm /var/www/nextcloud/config/config.php.additional"
else
echo "Found Nextcloud occ at: $OCC_PATH"
# Get the directory containing occ
NEXTCLOUD_DIR=$(dirname "$OCC_PATH")
# Use the occ command to add the trusted domain
$SSH_CMD "cd $NEXTCLOUD_DIR && sudo -u www-data php occ config:system:set trusted_domains 3 --value=cloud.boilerhaus.org"
fi
# Restart Apache to apply changes
$SSH_CMD "sudo systemctl restart apache2"
# Clean up
rm -f nextcloud_occ_path.txt
echo "Trusted domains configuration updated. You should now be able to access your Nextcloud instance at https://cloud.boilerhaus.org"
echo "If you still see the 'untrusted domain' error, try clearing your browser cache or using a private/incognito window."

View File

@ -1,122 +0,0 @@
#!/bin/bash
# This script fixes the Nginx configuration
# Load environment variables
if [ -f .env ]; then
source .env
else
echo "Error: .env file not found. Please create it based on .env.example."
exit 1
fi
# Check if sshpass is installed
if ! command -v sshpass &> /dev/null; then
echo "Error: sshpass is not installed. Please run 'sudo apt-get install sshpass' first."
exit 1
fi
# Set SSH command with password
SSH_CMD="sshpass -p $SERVER_PASSWORD ssh -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP"
echo "Fixing Nginx configuration..."
# Create a temporary file with the complete configuration
cat > nginx-fix.conf << 'EOF'
server {
server_name boilerhaus.org;
root /var/www/boilerhaus.org;
index index.html;
location / {
try_files $uri $uri/ =404;
}
# Managed by Certbot
listen [::]:443 ssl ipv6only=on; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/boilerhaus.org-0001/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/boilerhaus.org-0001/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
# Nextcloud configuration
server {
server_name cloud.boilerhaus.org;
# Proxy to Nextcloud
location / {
proxy_pass http://localhost:8081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Increase timeout for long-running operations
proxy_connect_timeout 600;
proxy_send_timeout 600;
proxy_read_timeout 600;
send_timeout 600;
# WebDAV support
client_max_body_size 512M;
# Enable WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
# Managed by Certbot
listen [::]:443 ssl; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/boilerhaus.org-0001/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/boilerhaus.org-0001/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = boilerhaus.org) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
listen [::]:80;
server_name boilerhaus.org;
return 404; # managed by Certbot
}
server {
if ($host = cloud.boilerhaus.org) {
return 301 https://$host$request_uri;
} # managed by Certbot
listen 80;
listen [::]:80;
server_name cloud.boilerhaus.org;
return 404; # managed by Certbot
}
EOF
# Upload the temporary file to the server
sshpass -p "$SERVER_PASSWORD" scp -o StrictHostKeyChecking=no nginx-fix.conf $SERVER_USER@$SERVER_IP:/tmp/
# Backup the current configuration
$SSH_CMD "sudo cp /etc/nginx/sites-available/boilerhaus.org.conf /etc/nginx/sites-available/boilerhaus.org.conf.bak.$(date +%s)"
# Replace the configuration
$SSH_CMD "sudo cp /tmp/nginx-fix.conf /etc/nginx/sites-available/boilerhaus.org.conf"
# Test the Nginx configuration
$SSH_CMD "sudo nginx -t"
# Reload Nginx if the configuration is valid
$SSH_CMD "sudo systemctl reload nginx || sudo systemctl restart nginx"
# Clean up
rm nginx-fix.conf
echo "Nginx configuration fixed! cloud.boilerhaus.org should now point to your Nextcloud instance."

View File

@ -1,68 +1,178 @@
<!DOCTYPE html>
<html lang="en">
<! DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<!-- Preload critical fonts -->
<link
rel="preload"
href="https://fonts.googleapis.com/css2?family=Courier+Prime:wght@400;700&display=swap"
as="style"
/>
<link
rel="preload"
href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap"
as="style"
/>
<!-- Load fonts -->
<link
href="https://fonts.googleapis.com/css2?family=Courier+Prime:wght@400;700&family=Space+Mono:wght@400;700&display=swap"
rel="stylesheet"
/>
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css" rel="stylesheet" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.13.0/css/all.min.css" rel="stylesheet" />
<link href="css/style.css" rel="stylesheet" /> <!-- Link to your custom CSS file -->
<script src="https://kit.fontawesome.com/56ab309fdd.js" crossorigin="anonymous"></script>
<link rel="icon" href="https://i.imgur.com/Clv3Y1dt.png" type="image/png">
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Gloock&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Handjet&family=Inter&display=swap" rel="stylesheet">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Primary favicon -->
<link rel="icon" type="image/png" href="https://i.imgur.com/Clv3Y1d.png" />
<!-- iOS/macOS icons -->
<link rel="apple-touch-icon" href="https://i.imgur.com/Clv3Y1d.png" />
<link rel="mask-icon" href="https://i.imgur.com/Clv3Y1d.png" color="#000000" />
<!-- Windows/IE -->
<meta name="msapplication-TileImage" content="https://i.imgur.com/Clv3Y1d.png" />
<meta name="msapplication-TileColor" content="#000000" />
<!-- Viewport and theme -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="theme-color" content="#000000" />
<!-- SEO meta tags -->
<title>Chris Wylde | Web3 Futurist</title>
<meta name="description" content="Web3 Futurist, Nuclear Professional, and DAO Designer" />
<meta name="keywords" content="Web3, Nuclear, DAO, Blockchain, Radiation Protection, Boilermaker" />
<!-- Open Graph / Social Media -->
<meta property="og:type" content="website" />
<meta property="og:title" content="Chris Wylde | Web3 Futurist" />
<meta property="og:description" content="Web3 Futurist, Nuclear Professional, and DAO Designer" />
<meta property="og:image" content="https://i.imgur.com/Clv3Y1d.png" />
<meta property="og:url" content="https://www.chriswylde.xyz" />
<!-- Twitter Card -->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Chris Wylde | Web3 Futurist" />
<meta name="twitter:description" content="Web3 Futurist, Nuclear Professional, and DAO Designer" />
<meta name="twitter:image" content="https://i.imgur.com/Clv3Y1d.png" />
<!-- Font display optimization -->
<style>
.font-courier-prime {
font-family: 'Courier Prime', monospace;
}
.font-space-mono {
font-family: 'Space Mono', monospace;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
<div class="header-inner section-inner my-5">
<h1 class="site-title">
<a href="https://www.chriswylde.xyz/" rel="home">
<img src="https://en.gravatar.com/userimage/28803791/eb0d590383693b2cbaefb2379c9afe7a.jpeg?size=256" class="rounded-circle mx-auto d-block mb-3 image-border" />
Boiler(Chris)
</a>
</h1>
<p class="site-description">Web3 Futurist - DAO Design, Governance and Operations</p>
</div>
<div class="container">
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://rxresu.me/boilerrat/victorious-present-lungfish" target="_blank" role="button">
<i class="fa-solid fa-calendar-plus"></i> Professional Resume
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://calendly.com/boiler-chris/call-with-boilerrat" target="_blank" role="button">
<i class="fa-solid fa-calendar-plus"></i> Book Time - Calendly
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://app.ens.domains/boilerhaus.eth" target="_blank" role="button">
<i class="fa-brands fa-ethereum"></i> ENS - boilerhaus.eth
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://etherscan.io/address/0x6FeD46ed75C1165b6bf5bA21f7F507702A2691cB" target="_blank" role="button">
<i class="fa-solid fa-chart-simple"></i> Etherscan
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://www.metokens.com/profile/0x6FeD46ed75C1165b6bf5bA21f7F507702A2691cB/" target="_blank" role="button">
<i class="fa-solid fa-coins"></i> meToken
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://quest.philand.xyz/account/0x6FeD46ed75C1165b6bf5bA21f7F507702A2691cB" target="_blank" role="button">
<i class="fa-solid fa-cube"></i> PhiLand
</a>
</div>
<div class="row mb-4">
<div class="col-12">
<h2 class="section-header">Social Media and Contacts</h2>
</div>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://twitter.com/boilerrat" target="_blank" role="button">
<i class="fa-brands fa-twitter"></i> Twitter
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://warpcast.com/boiler" target="_blank" role="button">
<i class="fa-solid fa-archway"></i> Farcaster
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://lenster.xyz/u/boilerrat" target="_blank" role="button">
<i class="fa-solid fa-binoculars"></i> Lens
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://mastodon.world/@boilerrat" target="_blank" role="button">
<i class="fa-brands fa-mastodon"></i> Mastadon
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://github.com/boilerrat" target="_blank" role="button">
<i class="fa-brands fa-github"></i> Github
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://www.linkedin.com/in/christopherwylde/" target="_blank" role="button">
<i class="fa-brands fa-linkedin"></i> Linkedin
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://t.me/boilerrat" target="_blank" role="button">
<i class="fa-brands fa-telegram"></i> Telegram
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://discordapp.com/users/boiler_chris" target="_blank" role="button">
<i class="fa-brands fa-discord"></i> Discord
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="mailto:128boilerrat@gmail.com" target="_blank" role="button">
<i class="fa-solid fa-envelope"></i> Email
</a>
</div>
<div class="row mb-4">
<div class="col-12">
<h2 class="section-header">DAO Memberships</h2>
</div>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://admin.daohaus.fun/#/molochv3/0x1/0x54e1826d462570a06a6bc44a78fdfbf0568aa2f3" target="_blank" role="button">
<i class="fa-solid fa-people-roof"></i> Boiler Family DAO
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://www.daomasons.com/" target="_blank" role="button">
<i class="fa fa-chess-rook"></i> DAO Masons - Core Team, DAO Design and Operations
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://publichaus.club/" target="_blank" role="button">
<i class="fa-brands fa-fort-awesome"></i> Public HAUS - Champion (Delegate)
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://www.youtube.com/channel/UC2UTfuf0xkIrLcmdnd96bGA" target="_blank" role="button">
<i class="fa-solid fa-house"></i> DAOhaus - Warcamp (Core Contributor)
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://publicnouns.wtf/" target="_blank" role="button">
<i class="fa-solid fa-glasses"></i> Public Nouns - NFT Holder and Contributor
</a>
</div>
<div class="row mb-4">
<a class="btn btn-dark col-12 py-3" href="https://www.metacartel.org/" target="_blank" role="button">
<i class="fa-solid fa-pepper-hot"></i> Metacartel - Member and Contributor
</a>
</div>
<div class="row mb-4">
<div class="col-12">
<h2 class="section-header">Personal Profile Pictures</h2>
</div>
</div>
<div class="container">
<div class="row">
<div class="col-lg-6 col-sm-12 mb-4">
<figure class="figure">
<img src="https://i.imgur.com/Clv3Y1d.png" class="img-fluid rounded mx-auto d-block" alt="Public Noun 237">
<figcaption class="figure-caption text-center"><a href="https://etherscan.io/nft/0x93ecac71499147627dfec6d0e494d50fcfff10ee/237">Etherscan: Public Noun 237</a></figcaption>
</figure>
</div>
<div class="col-lg-6 col-sm-12 mb-4">
<figure class="figure">
<img src="https://i.imgur.com/qvJL6Rd.png" class="img-fluid rounded mx-auto d-block" alt="Public Noun 111">
<figcaption class="figure-caption text-center"><a href="">Etherscan: Public Noun 111</a></figcaption>
</figure>
</div>
</div>
<div class="row">
<div class="col-lg-6 col-sm-12 mb-4">
<figure class="figure">
<img src="https://i.imgur.com/BRXIjWr.png" class="img-fluid rounded mx-auto d-block" alt="Public Noun 65">
<figcaption class="figure-caption text-center"><a href="https://etherscan.io/nft/0x93ecac71499147627dfec6d0e494d50fcfff10ee/65">Etherscan: Public Noun 65</a></figcaption>
</figure>
</div>
<div class="col-lg-6 col-sm-12 mb-4">
<figure class="figure">
<img src="https://i.imgur.com/adQSaCJ.png" class="img-fluid rounded mx-auto d-block" alt="Boiler's Jerry">
<figcaption class="figure-caption text-center"><a href="https://etherscan.io/nft/0x42c4a46bd0f9c642e852a848a5b730b6e3086ccf/32">Etherscan: Boiler's Jerry</a></figcaption>
</figure>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.5.0/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fontawesome-iconpicker/3.2.0/js/fontawesome-iconpicker.min.js"></script>
</body>
</html>

33
install-sshpass.sh Executable file
View File

@ -0,0 +1,33 @@
#!/bin/bash
# This script installs sshpass, which is needed for password automation
# Check if sshpass is already installed
if command -v sshpass &> /dev/null; then
echo "sshpass is already installed."
exit 0
fi
# Install sshpass based on the detected package manager
if command -v apt-get &> /dev/null; then
echo "Installing sshpass using apt..."
sudo apt-get update
sudo apt-get install -y sshpass
elif command -v dnf &> /dev/null; then
echo "Installing sshpass using dnf..."
sudo dnf install -y sshpass
elif command -v yum &> /dev/null; then
echo "Installing sshpass using yum..."
sudo yum install -y sshpass
elif command -v pacman &> /dev/null; then
echo "Installing sshpass using pacman..."
sudo pacman -S --noconfirm sshpass
elif command -v brew &> /dev/null; then
echo "Installing sshpass using Homebrew..."
brew install hudochenkov/sshpass/sshpass
else
echo "Error: Could not detect package manager. Please install sshpass manually."
exit 1
fi
echo "sshpass has been installed successfully."

52
migrate-to-gitea.sh Executable file
View File

@ -0,0 +1,52 @@
#!/bin/bash
# This script helps migrate your repository to your self-hosted Gitea
# First, check if we can connect to the Gitea server
echo "Testing connection to Gitea server..."
if ! curl -s https://git.boilerhaus.org > /dev/null; then
echo "Failed to connect to Gitea server. Please check your network connection."
exit 1
fi
# Create a new repository on Gitea using the API
echo "Creating a new repository on Gitea..."
# You'll need to generate an API token from your Gitea instance
# Generate one at https://git.boilerhaus.org/user/settings/applications
read -p "Enter your Gitea API token: " API_TOKEN
if [ -z "$API_TOKEN" ]; then
echo "API token is required. Please generate one at https://git.boilerhaus.org/user/settings/applications"
exit 1
fi
curl -X POST "https://git.boilerhaus.org/api/v1/user/repos" \
-H "accept: application/json" \
-H "Content-Type: application/json" \
-H "Authorization: token $API_TOKEN" \
-d "{\"name\":\"Web3CV\",\"description\":\"My Web3 CV website\",\"private\":false,\"auto_init\":false}"
# Wait a moment for the repository to be created
sleep 2
# Reset remotes
echo "Resetting remotes..."
git remote remove origin 2>/dev/null || true
git remote remove github 2>/dev/null || true
git remote remove gitea 2>/dev/null || true
# Add Gitea as the only remote
echo "Adding Gitea as the remote..."
git remote add origin https://git.boilerhaus.org/boiler/Web3CV.git
# Verify the remote was added
echo "Verifying remote..."
git remote -v
# Push all branches and tags to Gitea
echo "Pushing to Gitea..."
git push -u origin main
git push --tags origin
echo "Migration to Gitea complete!"
echo "Your repository is now available at: https://git.boilerhaus.org/boiler/Web3CV"

4663
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,35 +0,0 @@
{
"name": "portfolio",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"lucide-react": "^0.469.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-icons": "^5.4.0",
"recharts": "^2.15.0"
},
"devDependencies": {
"@eslint/js": "^9.17.0",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.20",
"eslint": "^9.17.0",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.16",
"globals": "^15.14.0",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.17",
"typescript": "~5.6.2",
"typescript-eslint": "^8.18.2",
"vite": "^6.0.5"
}
}

View File

@ -1,6 +0,0 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

60
push-in-chunks.sh Executable file
View File

@ -0,0 +1,60 @@
#!/bin/bash
# This script pushes your repository in smaller chunks to avoid HTTP 413 errors
# Set the remote and branch
REMOTE="origin"
BRANCH="main"
# Get the list of commits in reverse order (oldest first)
COMMITS=$(git rev-list --reverse HEAD)
# Initialize variables
LAST_PUSHED=""
CHUNK_SIZE=10
TOTAL_COMMITS=$(echo "$COMMITS" | wc -l)
CURRENT=0
echo "Total commits to push: $TOTAL_COMMITS"
# Process commits in chunks
for COMMIT in $COMMITS; do
CURRENT=$((CURRENT + 1))
# If this is the first commit or we've reached the chunk size
if [ -z "$LAST_PUSHED" ] || [ $((CURRENT % CHUNK_SIZE)) -eq 0 ] || [ $CURRENT -eq $TOTAL_COMMITS ]; then
echo "Pushing commit $CURRENT of $TOTAL_COMMITS: $COMMIT"
if [ -z "$LAST_PUSHED" ]; then
# For the first commit, create a new branch at this commit and push it
git checkout -b temp_branch $COMMIT
git push -f $REMOTE temp_branch:$BRANCH
LAST_PUSHED=$COMMIT
git checkout $BRANCH
git branch -D temp_branch
else
# For subsequent chunks, push the range from last pushed to current
git checkout -b temp_branch $COMMIT
git push -f $REMOTE temp_branch:$BRANCH
LAST_PUSHED=$COMMIT
git checkout $BRANCH
git branch -D temp_branch
fi
# Check if the push was successful
if [ $? -ne 0 ]; then
echo "Error pushing commit $COMMIT. Exiting."
exit 1
fi
echo "Successfully pushed up to commit $CURRENT of $TOTAL_COMMITS"
sleep 2 # Give the server a short break
fi
done
echo "All commits pushed successfully!"
git push -f $REMOTE $BRANCH
# Return to the original branch
git checkout $BRANCH
echo "Done! Your repository should now be fully pushed to $REMOTE/$BRANCH"

46
setup-vps.sh Executable file
View File

@ -0,0 +1,46 @@
#!/bin/bash
# This script sets up the VPS with the correct Nginx configuration and deploys the website
# Check if .env file exists
if [ ! -f .env ]; then
echo "Error: .env file not found. Please create it based on .env.example."
exit 1
fi
# Source the .env file
source .env
# Check if sshpass is installed
if ! command -v sshpass &> /dev/null; then
echo "sshpass is not installed. Installing it now..."
./install-sshpass.sh
# Check if installation was successful
if ! command -v sshpass &> /dev/null; then
echo "Error: Failed to install sshpass. Please install it manually."
exit 1
fi
fi
echo "=== Setting up VPS for boilerhaus.org (IP: $SERVER_IP) ==="
echo ""
# Step 1: Update Nginx configuration
echo "Step 1: Updating Nginx configuration..."
./update-nginx-config.sh
# Step 2: Deploy website
echo ""
echo "Step 2: Deploying website..."
./deploy-website.sh
echo ""
echo "=== Setup complete! ==="
echo "Your domains should now be configured as follows:"
echo "- boilerhaus.org → Main website"
echo "- bw.boilerhaus.org → Vaultwarden instance"
echo "- cloud.boilerhaus.org → Nextcloud dashboard"
echo "- git.boilerhaus.org → Gitea instance"
echo ""
echo "Please allow a few minutes for DNS changes to propagate."

View File

@ -1,42 +0,0 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}

View File

@ -1,63 +0,0 @@
import type { FC } from 'react';
import { Hero } from './components/Hero';
import { Navigation } from './components/Navigation';
import { Expertise } from './components/Expertise';
import { Achievements } from './components/Achievements';
import { Skills } from './components/Skills';
import { NFTGrid } from './components/NFTGrid';
import { Contact } from './components/Contact';
import ReactorStatus from './components/ReactorStatus';
const App: FC = () => {
return (
<div className="bg-black font-['Space_Mono']">
<Navigation />
<main className="relative">
{/* Hero keeps min-h-screen as it's the landing section */}
<section id="home" className="relative z-10 min-h-screen font-['Courier_Prime']">
<Hero />
</section>
{/* Content sections with reduced spacing */}
<div className="space-y-16 relative z-10"> {/* Main content wrapper with consistent spacing */}
<section className="py-12">
<Expertise />
</section>
<section className="py-12">
<ReactorStatus />
</section>
<section className="py-12">
<Achievements />
</section>
<section className="py-12">
<Skills />
</section>
<section className="py-12">
<NFTGrid />
</section>
<section className="py-12">
<Contact />
</section>
</div>
{/* Background Elements */}
<div className="fixed inset-0 pointer-events-none">
{/* Gradient Orbs */}
<div className="absolute top-1/4 left-1/4 w-96 h-96 bg-blue-500 rounded-full mix-blend-multiply filter blur-xl opacity-10 animate-float" />
<div className="absolute top-1/3 right-1/4 w-96 h-96 bg-purple-500 rounded-full mix-blend-multiply filter blur-xl opacity-10 animate-float-delayed" />
{/* Grid Pattern */}
<div className="absolute inset-0 bg-[linear-gradient(to_right,#1a1a1a_1px,transparent_1px),linear-gradient(to_bottom,#1a1a1a_1px,transparent_1px)] bg-[size:4rem_4rem] [mask-image:radial-gradient(ellipse_60%_50%_at_50%_50%,#000_70%,transparent_100%)]" />
</div>
</main>
</div>
);
};
export default App;

View File

@ -1,115 +0,0 @@
import React from 'react';
import { BarChart3, Award } from 'lucide-react';
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts';
import { AchievementsProps, GrantChartProps, AchievementCardProps, TooltipProps } from './types';
import { Grant, Achievement } from '../../types';
const defaultGrants: Grant[] = [
{ name: 'DAOMasons (Plurality Labs)', amount: 150000, color: '#2aae91' },
{ name: 'DAOHaus (Moloch DAO)', amount: 90000, color: '#6366f1' },
{ name: 'DAOhaus (Optimism)', amount: 90000, color: '#ec4899' },
{ name: 'Farcastle (Public Nouns)', amount: 17000, color: '#06b6d4' },
];
const defaultAchievements: Achievement[] = [
{ title: "Grant Ships Launch", description: "Distributed $100K ARB tokens to fund Web3 game development on Arbitrum", date: "2024" },
{ title: "DAO Masons", description: "Co-founded DAO Masons, a product and services DAO, designed to make DAOs better", date: "2023" },
{ title: "Public HAUS Champion", description: "Led governance activities and secured multiple major grants", date: "2022" },
{ title: "Metaguide", description: "Guided Web3 projects in securing grants, building communities, and facilitating funding for blockchain initiatives.", date: "2021" },
{ title: "Radiation Protection Technician: EPRI Certification", description: "Qualified as Radiation Protection Technician at OPG and later at Bruce Power", date: "2016/2019" },
{ title: "Boilermaker Red Seal", description: "Completed Boilermaker apprenticeship and passed Red Seal Exam", date: "2006" },
];
const CustomTooltip: React.FC<TooltipProps> = ({ active, payload }) => {
if (active && payload && payload.length) {
return (
<div className="bg-gray-800 p-4 rounded-lg border border-gray-700">
<p className="text-white font-medium">{payload[0].payload.name}</p>
<p className="text-blue-400">${payload[0].value.toLocaleString()}</p>
</div>
);
}
return null;
};
interface CustomYAxisTickProps {
x: number;
y: number;
payload: { value: string };
}
const CustomYAxisTick: React.FC<CustomYAxisTickProps> = ({ x, y, payload }) => {
const truncatedText = payload.value.length > 20 ? `${payload.value.slice(0, 20)}...` : payload.value;
return (
<text x={x - 10} y={y} fill="#FFFFFF" fontSize={12} dy={5} textAnchor="end">
{truncatedText}
</text>
);
};
const GrantChart: React.FC<GrantChartProps> = ({ data, className = '' }) => (
<div className={`bg-gray-900 rounded-lg p-6 h-80 ${className}`}>
<ResponsiveContainer width="100%" height="100%">
<BarChart data={data} layout="vertical">
<XAxis
type="number"
domain={[0, 150000]}
tickFormatter={(val) => `$${val.toLocaleString()}`}
axisLine={false}
tickLine={false}
/>
<YAxis
dataKey="name"
type="category"
width={200}
tick={(props) => <CustomYAxisTick {...props} />}
/>
<Tooltip content={<CustomTooltip />} />
<Bar dataKey="amount" fill="#3b82f6" radius={[0, 4, 4, 0]} />
</BarChart>
</ResponsiveContainer>
</div>
);
const AchievementCard: React.FC<AchievementCardProps> = ({ title, description, date, className = '' }) => (
<div className={`group bg-gray-900 rounded-lg p-6 hover:bg-gray-800 transition-colors duration-300 ${className}`}>
<div className="flex flex-col h-full">
<div className="flex justify-between items-start mb-4">
<h3 className="text-xl font-semibold text-white">{title}</h3>
<span className="text-sm text-gray-500">{date}</span>
</div>
<p className="text-gray-400 flex-grow">{description}</p>
</div>
</div>
);
export const Achievements: React.FC<AchievementsProps> = ({ className = '', grants = defaultGrants, achievements = defaultAchievements }) => {
return (
<div className={`bg-black text-white ${className}`}>
<div className="max-w-6xl mx-auto space-y-12">
<div className="space-y-6">
<div className="flex items-center gap-4">
<BarChart3 size={32} className="text-green-500" />
<h2 className="text-3xl font-bold bg-gradient-to-r from-green-500 to-blue-500 bg-clip-text text-transparent">
Grants Secured
</h2>
</div>
<GrantChart data={grants} />
</div>
<div className="space-y-6">
<div className="flex items-center gap-4">
<Award size={32} className="text-yellow-500" />
<h2 className="text-3xl font-bold bg-gradient-to-r from-yellow-500 to-orange-500 bg-clip-text text-transparent">
Key Achievements
</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{achievements.map((achievement, index) => (
<AchievementCard key={index} {...achievement} />
))}
</div>
</div>
</div>
</div>
);
};

View File

@ -1,18 +0,0 @@
import type { Grant, Achievement, TooltipProps as SharedTooltipProps } from '../../types';
export interface AchievementsProps {
className?: string;
grants?: Grant[];
achievements?: Achievement[];
}
export interface GrantChartProps {
data: Grant[];
className?: string;
}
export interface AchievementCardProps extends Achievement {
className?: string;
}
export type TooltipProps = SharedTooltipProps;

View File

@ -1,152 +0,0 @@
import type { FC } from 'react';
import {
MdEmail,
MdCalendarToday,
MdLanguage
} from 'react-icons/md';
import {
FaGithub,
FaLinkedin,
FaTelegram
} from 'react-icons/fa';
import { FaXTwitter } from "react-icons/fa6";
import { SiFarcaster } from "react-icons/si";
import { BiSearch } from "react-icons/bi";
import type { ContactProps, SocialLinkCardProps, CategorySectionProps } from './types';
const SocialLinkCard: FC<SocialLinkCardProps> = ({
name,
icon: Icon,
url,
username,
className = ''
}) => (
<a
href={url}
target="_blank"
rel="noopener noreferrer"
className={`group bg-gray-900 rounded-lg p-4 hover:bg-gray-800 transition-all duration-300 ${className}`}
>
<div className="flex items-center gap-4">
<div className="p-2 bg-gray-800 rounded-lg group-hover:bg-gray-700 transition-colors">
<Icon size={20} className="text-blue-400" />
</div>
<div>
<div className="text-sm text-gray-400">{name}</div>
<div className="text-white">{username}</div>
</div>
</div>
</a>
);
const CategorySection: FC<CategorySectionProps> = ({
category,
links,
className = ''
}) => (
<div className={`space-y-4 ${className}`}>
<h3 className="text-xl font-medium text-gray-400">
{category}
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{links.map((link, index) => (
<SocialLinkCard key={index} {...link} />
))}
</div>
</div>
);
export const Contact: FC<ContactProps> = ({
className = '',
socialLinks = defaultSocialLinks
}) => {
return (
<div className={`bg-black text-white ${className}`}>
<div className="max-w-6xl mx-auto">
<div className="flex items-center gap-4 mb-6">
<h2 className="text-3xl font-bold bg-gradient-to-r from-green-500 to-blue-500 bg-clip-text text-transparent">
Connect With Me
</h2>
</div>
<div className="grid gap-8">
{socialLinks.map((category, index) => (
<CategorySection
key={index}
category={category.category}
links={category.links}
/>
))}
</div>
</div>
</div>
);
};
const defaultSocialLinks = [
{
category: "Professional",
links: [
{
name: "Email",
icon: MdEmail,
url: "mailto:128boilerrat@gmail.com",
username: "128boilerrat@gmail.com"
},
{
name: "Calendar",
icon: MdCalendarToday,
url: "https://calendly.com/boiler-chris/call-with-boilerrat",
username: "Schedule a call"
},
{
name: "Resume",
icon: MdLanguage,
url: "https://rxresu.me/boilerrat/victorious-present-lungfish",
username: "Download PDF"
}
]
},
{
category: "Social",
links: [
{
name: "X",
icon: FaXTwitter,
url: "https://twitter.com/boilerrat",
username: "@boilerrat"
},
{
name: "GitHub",
icon: FaGithub,
url: "https://github.com/boilerrat",
username: "boilerrat"
},
{
name: "LinkedIn",
icon: FaLinkedin,
url: "https://www.linkedin.com/in/christopherwylde/",
username: "christopherwylde"
},
{
name: "Telegram",
icon: FaTelegram,
url: "https://t.me/boilerrat",
username: "@boilerrat"
},
{
name: "Farcaster",
icon: SiFarcaster,
url: "https://warpcast.com/boiler",
username: "boiler"
},
{
name: "Lens",
icon: BiSearch,
url: "https://lenster.xyz/u/boilerrat",
username: "boilerrat"
}
]
}
];
export default Contact;

View File

@ -1,33 +0,0 @@
import type { IconType } from 'react-icons';
// Define SocialLink type
export interface SocialLink {
name: string;
icon: IconType; // Correct type for react-icons components
url: string;
username?: string; // Make this optional
}
// Define LinkCategory type
export interface LinkCategory {
category: string;
links: SocialLink[];
}
// Update ContactProps to use LinkCategory[]
export interface ContactProps {
className?: string;
socialLinks?: LinkCategory[];
}
// Update SocialLinkCardProps to extend SocialLink
export interface SocialLinkCardProps extends SocialLink {
className?: string;
}
// Update CategorySectionProps to use the updated SocialLink type
export interface CategorySectionProps {
category: string;
links: SocialLink[];
className?: string;
}

View File

@ -1,143 +0,0 @@
import React from 'react';
import { Atom, Database, Shield, Users, Wrench, Factory, Coins, BatteryCharging } from 'lucide-react';
import { ExpertiseProps, ExperienceCardProps, SectionProps } from './types';
import { ExperienceItem } from '../../types';
const ExperienceCard: React.FC<ExperienceCardProps> = ({
icon: Icon,
title,
company,
period,
description,
className = '',
gradientFrom
}) => (
<div className={`bg-gray-900 rounded-lg p-6 hover:bg-gray-800 transition-colors duration-300 ${className}`}>
<div className="flex items-start gap-4">
<div className={`p-3 bg-${gradientFrom}/10 rounded-lg`}>
<Icon size={24} className={`text-${gradientFrom}`} />
</div>
<div>
<h3 className="text-xl font-semibold text-white">{title}</h3>
<p className="text-gray-400 mt-1">{company}</p>
<p className="text-sm text-gray-500 mt-1">{period}</p>
<p className="text-gray-300 mt-3">{description}</p>
</div>
</div>
</div>
);
const Section: React.FC<SectionProps> = ({
title,
icon: Icon,
items,
gradientFrom,
gradientTo,
id
}) => (
<section id={id} className="space-y-6">
<div className="flex items-center gap-4">
<Icon size={32} className={`text-${gradientFrom}`} />
<h2 className={`text-3xl font-bold bg-gradient-to-r from-${gradientFrom} to-${gradientTo} bg-clip-text text-transparent`}>
{title}
</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{items.map((exp, index) => (
<ExperienceCard key={index} {...exp} gradientFrom={gradientFrom} />
))}
</div>
</section>
);
const defaultIndustryExperience: ExperienceItem[] = [
{
title: "Journeyman Boilermaker",
company: "International Brotherhood of Boilermakers",
period: "2003 - Present",
description: "Two decades of experience across industrial sectors, specializing in pressure vessels, heat exchangers, and critical infrastructure. Led teams in major projects nationwide.",
icon: Wrench
},
{
title: "Industrial Maintenance and Construction",
company: "Multiple Sectors",
period: "2003 - Present",
description: "Extensive experience in oil refineries, steel mills, and power generation facilities. Specialized in maintenance, repairs, and large-scale equipment installations.",
icon: Factory
}
];
const defaultNuclearExperience: ExperienceItem[] = [
{
title: "Radiation Protection",
company: "Makwa-Cahill/Nuvia",
period: "2019 - Present",
description: "Radiation Protection Technician for Bruce Nuclear's Major Component Replacement Project. Managing radiological safety for one of North America's largest nuclear refurbishment projects.",
icon: Shield
},
{
title: "Nuclear Maintenance and Refurbishment",
company: "Multiple Nuclear Facilities",
period: "2003 - Present",
description: "20 years of nuclear experience including Pickering, Darlington, and Bruce Power. Specialized focus on reactor refurbishment and radiation protection for the last 8 years.",
icon: BatteryCharging
}
];
const defaultWeb3Experience: ExperienceItem[] = [
{
title: "DAO Governance and Operations",
company: "Public HAUS",
period: "2022 - Present",
description: "Secured $197K in grants through strategic partnerships with Moloch DAO, Optimism, and Public Nouns. Pioneering governance frameworks and community development initiatives.",
icon: Users
},
{
title: "Web3 Contributor",
company: "DAO Masons & MetaCartel",
period: "2022 - 2024",
description: "Founded DAO Masons, led Grant Ships program distributing $100K ARB tokens. Active contributor to MetaCartel, driving Web3 adoption and innovation in DAOs.",
icon: Coins
}
];
export const Expertise: React.FC<ExpertiseProps> = ({
className = '',
nuclearExperience = defaultNuclearExperience,
web3Experience = defaultWeb3Experience
}) => {
return (
<div className={`bg-black text-white ${className}`}>
<div className="max-w-6xl mx-auto space-y-12">
<Section
id="industry"
title="Industrial Expertise"
icon={Factory}
items={defaultIndustryExperience}
gradientFrom="cyan-500"
gradientTo="indigo-500"
/>
<Section
id="nuclear"
title="Nuclear Expertise"
icon={Atom}
items={nuclearExperience}
gradientFrom="emerald-400"
gradientTo="blue-500"
/>
<Section
id="web3"
title="Web3 Innovation"
icon={Database}
items={web3Experience}
gradientFrom="purple-500"
gradientTo="pink-500"
/>
</div>
</div>
);
};
export default Expertise;

View File

@ -1,22 +0,0 @@
import type { ExperienceItem } from '../../types';
import type { LucideIcon } from 'lucide-react';
export interface ExpertiseProps {
className?: string;
nuclearExperience?: ExperienceItem[];
web3Experience?: ExperienceItem[];
}
export interface ExperienceCardProps extends ExperienceItem {
className?: string;
gradientFrom?: string; // Add this for icon color customization
}
export interface SectionProps {
id: string;
title: string;
icon: LucideIcon;
items: ExperienceItem[];
gradientFrom: string;
gradientTo: string;
}

View File

@ -1,129 +0,0 @@
import React, { useState, useEffect } from 'react';
import { Terminal, ExternalLink, AtSign, Calendar } from 'lucide-react';
import { SiFarcaster } from "react-icons/si";
import { HeroProps, QuickLink, AnimationState } from './types';
const defaultIntroText = `> const profile = {
name: "Christopher Wylde",
alias: "boiler, boilerrat",
roles: [
"Radiation Protection Specialist",
"Journeyman Boilermaker",
"Web3 Innovator",
"DAO Designer"
],
expertise: {
boilermaker: "20+ years across industrial sectors, nationwide experience",
nuclear: "20 years total, 8 years specialized in radiation protection",
web3: "Lead multiple DAOs, $300K+ in grants secured and distributed"
}
};`;
const quickLinks: QuickLink[] = [
{
icon: ExternalLink,
label: "Full Resume",
href: "https://rxresu.me/boilerrat/victorious-present-lungfish",
external: true
},
{
icon: AtSign,
label: "email",
href: "mailto:128boilerrat@gmail.com"
},
{
icon: Calendar,
label: "Schedule a Call",
href: "https://calendly.com/boiler-chris/call-with-boilerrat",
external: true
},
{
icon: SiFarcaster,
label: "DM on Warpcast",
href: "https://www.warpcast.com/boiler",
external: true
}
];
export const Hero: React.FC<HeroProps> = ({
className = '',
customIntroText
}) => {
const [animationState, setAnimationState] = useState<AnimationState>({
text: '',
showContent: false
});
const introText = customIntroText || defaultIntroText;
useEffect(() => {
let currentIndex = 0;
const typeWriter = setInterval(() => {
if (currentIndex < introText.length) {
setAnimationState(prev => ({
...prev,
text: prev.text + introText[currentIndex]
}));
currentIndex++;
} else {
clearInterval(typeWriter);
setTimeout(() => setAnimationState(prev => ({
...prev,
showContent: true
})), 500);
}
}, 50);
return () => clearInterval(typeWriter);
}, [introText]);
return (
<div className={`min-h-screen bg-black text-white flex flex-col items-center justify-center ${className}`}>
<div className="w-full max-w-3xl px-4">
{/* Terminal Window */}
<div className="bg-gray-900 rounded-lg overflow-hidden shadow-xl mb-6">
<div className="bg-gray-800 px-4 py-2 flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-red-500"></div>
<div className="w-3 h-3 rounded-full bg-yellow-500"></div>
<div className="w-3 h-3 rounded-full bg-green-500"></div>
<Terminal className="ml-4" size={14} />
</div>
<div className="p-4 font-mono text-sm">
<pre className="whitespace-pre-wrap">{animationState.text}</pre>
</div>
</div>
{/* Quick Links Section */}
{animationState.showContent && (
<div className="space-y-6 opacity-0 animate-fade-in">
<div className="text-center">
<h1 className="text-4xl font-bold bg-gradient-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent mb-3">
Bridging Industry & Web3
</h1>
<p className="text-gray-400 text-lg">
Bringing decades of industrial expertise to decentralized innovation
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-3">
{quickLinks.map((link, index) => (
<a
key={index}
href={link.href}
target={link.external ? "_blank" : undefined}
rel={link.external ? "noopener noreferrer" : undefined}
className="flex items-center gap-3 p-4 bg-gray-800 rounded-lg hover:bg-gray-700 transition-colors"
>
<link.icon size={20} />
<span>{link.label}</span>
</a>
))}
</div>
</div>
)}
</div>
</div>
);
};
export default Hero;

View File

@ -1,19 +0,0 @@
import { LucideIcon } from 'lucide-react';
import { IconType } from 'react-icons';
export interface HeroProps {
className?: string;
customIntroText?: string;
}
export interface QuickLink {
icon: LucideIcon | IconType; // Allow both Lucide and React icons
label: string;
href: string;
external?: boolean;
}
export interface AnimationState {
text: string;
showContent: boolean;
}

View File

@ -1,111 +0,0 @@
import React, { useState } from 'react';
import { ExternalLink } from 'lucide-react';
import { NFTGridProps, NFTCardProps } from './types';
import { NFT } from '../../types';
const NFTCard: React.FC<NFTCardProps> = ({
image,
name,
link,
isHovered,
onHover,
id,
className = ''
}) => (
<div
className={`relative group ${className}`}
onMouseEnter={() => onHover(id)}
onMouseLeave={() => onHover(null)}
>
{/* NFT Card */}
<div className="relative overflow-hidden rounded-lg bg-gray-900 aspect-square">
{/* Image */}
<img
src={image}
alt={name}
className="w-full h-full object-cover transform group-hover:scale-105 transition-transform duration-500"
/>
{/* Overlay */}
<div
className={`absolute inset-0 bg-gradient-to-t from-black/80 via-black/40 to-transparent
transition-opacity duration-300 flex items-end justify-between p-6
${isHovered ? 'opacity-100' : 'opacity-0'}`}
>
<h3 className="text-white text-xl font-medium">{name}</h3>
{link && (
<a
href={link}
target="_blank"
rel="noopener noreferrer"
className="text-white hover:text-blue-400 transition-colors"
>
<ExternalLink size={24} />
</a>
)}
</div>
</div>
{/* Glow Effect */}
<div className={`absolute inset-0 rounded-lg bg-blue-500 opacity-0 group-hover:opacity-20
blur-xl transition-opacity duration-500 -z-10`} />
</div>
);
const defaultNFTs: NFT[] = [
{
id: 1,
image: "https://i.imgur.com/Clv3Y1d.png",
name: "Public Noun 237",
link: "https://etherscan.io/nft/0x93ecac71499147627dfec6d0e494d50fcfff10ee/237"
},
{
id: 2,
image: "https://i.imgur.com/qvJL6Rd.png",
name: "Public Noun 111",
link: ""
},
{
id: 3,
image: "https://i.imgur.com/BRXIjWr.png",
name: "Public Noun 65",
link: "https://etherscan.io/nft/0x93ecac71499147627dfec6d0e494d50fcfff10ee/65"
},
{
id: 4,
image: "https://i.imgur.com/adQSaCJ.png",
name: "Boiler's Jerry",
link: "https://etherscan.io/nft/0x42c4a46bd0f9c642e852a848a5b730b6e3086ccf/32"
}
];
export const NFTGrid: React.FC<NFTGridProps> = ({
className = '',
nfts = defaultNFTs
}) => {
const [hoveredNFT, setHoveredNFT] = useState<number | null>(null);
return (
<div className={`bg-black ${className}`}>
<div className="max-w-6xl w-full mx-auto">
<div className="mb-6">
<div className="flex items-center gap-4">
<h2 className="text-3xl font-bold bg-gradient-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent">
PFP's
</h2>
</div>
<p className="text-gray-400 text-lg mt-2">My Online Appearances</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{nfts.map((nft) => (
<NFTCard
key={nft.id}
{...nft}
isHovered={hoveredNFT === nft.id}
onHover={setHoveredNFT}
/>
))}
</div>
</div>
</div>
);
};

View File

@ -1,12 +0,0 @@
import { NFT } from '../../types';
export interface NFTGridProps {
className?: string;
nfts?: NFT[];
}
export interface NFTCardProps extends NFT {
isHovered: boolean;
onHover: (id: number | null) => void;
className?: string;
}

View File

@ -1,99 +0,0 @@
import React, { useState, useEffect, useCallback } from 'react';
import { Home, Atom, Database, Award, Image, User, Factory, Briefcase } from 'lucide-react';
import { NavItem } from '../../types';
import { NavigationProps, ScrollState } from './types';
export const Navigation: React.FC<NavigationProps> = ({
className = '',
initialActiveSection = 'home',
navItems: customNavItems
}) => {
const [scrollState, setScrollState] = useState<ScrollState>({
progress: 0,
activeSection: initialActiveSection
});
const defaultNavItems: NavItem[] = [
{ id: 'home', icon: Home, label: 'Home' },
{ id: 'industry', icon: Factory, label: 'Industry' },
{ id: 'nuclear', icon: Atom, label: 'Nuclear' },
{ id: 'web3', icon: Database, label: 'Web3' },
{ id: 'achievements', icon: Award, label: 'Achievements' },
{ id: 'skills', icon: Briefcase, label: 'Skills' },
{ id: 'nfts', icon: Image, label: 'NFTs' },
{ id: 'contact', icon: User, label: 'Contact' }
];
const navItems = customNavItems || defaultNavItems;
const handleScroll = useCallback(() => {
// Calculate scroll progress
const totalHeight = document.documentElement.scrollHeight - window.innerHeight;
const progress = (window.scrollY / totalHeight) * 100;
// Update active section based on scroll position
const sections = document.querySelectorAll('section');
let newActiveSection = scrollState.activeSection;
sections.forEach(section => {
const rect = section.getBoundingClientRect();
if (rect.top >= 0 && rect.top <= window.innerHeight / 2) {
newActiveSection = section.id;
}
});
setScrollState({ progress, activeSection: newActiveSection });
}, [scrollState.activeSection]);
useEffect(() => {
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, [handleScroll]);
const scrollToSection = (id: string) => {
const element = document.getElementById(id);
if (element) {
element.scrollIntoView({ behavior: 'smooth' });
}
};
return (
<nav className={`fixed right-4 top-1/2 transform -translate-y-1/2 z-50 ${className}`}>
{/* Progress Bar */}
<div className="absolute left-0 top-0 w-0.5 h-full bg-gray-800">
<div
className="bg-blue-500"
style={{ height: `${scrollState.progress}%`, transition: 'height 0.1s' }}
/>
</div>
{/* Navigation Items */}
<div className="relative flex flex-col gap-6">
{navItems.map(({ id, icon: Icon, label }) => (
<button
key={id}
onClick={() => scrollToSection(id)}
className={`group relative flex items-center ${
scrollState.activeSection === id ? 'text-blue-500' : 'text-gray-400'
}`}
>
<div className={`p-2 rounded-full transition-all duration-300
${scrollState.activeSection === id
? 'bg-gray-800 text-blue-500'
: 'hover:bg-gray-800 hover:text-blue-500'}`}>
<Icon size={20} />
</div>
<span className="absolute right-full mr-4 px-2 py-1 bg-gray-800
rounded-md text-sm opacity-0 group-hover:opacity-100
transition-opacity duration-300 whitespace-nowrap">
{label}
</span>
</button>
))}
</div>
</nav>
);
};
export default Navigation;

View File

@ -1,13 +0,0 @@
import { NavItem } from '../../types';
export interface NavigationProps {
className?: string; // Optional: for additional styling
initialActiveSection?: string; // Optional: to set initial active section
navItems?: NavItem[]; // Optional: to override default nav items
}
// Local component-specific types
export interface ScrollState {
progress: number;
activeSection: string;
}

View File

@ -1,298 +0,0 @@
import React, { useState, useEffect } from 'react';
import { Activity, Power, Atom, TrendingUp, Users } from 'lucide-react';
import { LineChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts';
import type { ReactorStatusProps } from './types';
const statusMessages = [
'Consensus protocols maintaining criticality',
'Smart contracts properly shielded',
'DAOs operating at optimal temperature',
'Governance tokens well-contained',
'Web3 radiation levels nominal',
'Blockchain fully operational',
'Decentralized cooling systems engaged',
'Token emissions within parameters',
'NFT containment field stable',
'Reactor governance optimized'
];
// Simulated data for the career timeline
// Generate simulated stability data
const generateStabilityData = () => {
const points = 20;
const data = [];
for (let i = 0; i < points; i++) {
data.push({
time: i,
value: 50 + Math.sin(i / 2) * 20 + (Math.random() * 10 - 5)
});
}
return data;
};
// Generate simulated neutron flux data
const generateFluxData = () => {
const points = 20;
const data = [];
for (let i = 0; i < points; i++) {
data.push({
time: i,
value: 70 + Math.cos(i / 3) * 15 + (Math.random() * 8 - 4)
});
}
return data;
};
const generateTimelineData = () => {
const data = [];
const startYear = 2003;
const currentYear = new Date().getFullYear();
for (let year = startYear; year <= currentYear; year++) {
let value = 30; // Base value
// Add significant career events
if (year === 2003) value += 20; // Started as Boilermaker
if (year === 2005) value += 15; // Red Seal certification
if (year === 2016) value += 25; // Radiation Protection certification
if (year === 2019) value += 20; // EPRI certification
if (year === 2022) value += 30; // Entered Web3 space
data.push({
year: year.toString(),
value: value + Math.random() * 10
});
}
return data;
};
const CustomTooltip = ({ active, payload, label }: any) => {
if (active && payload && payload.length) {
const careerEvents: { [key: string]: string } = {
'2003': 'Started as Boilermaker',
'2005': 'Achieved Red Seal certification',
'2016': 'Obtained Radiation Protection certification',
'2019': 'Earned EPRI certification',
'2022': 'Entered Web3 space and founded DAO Masons'
};
return (
<div className="bg-gray-800 p-3 rounded-lg border border-gray-700">
<p className="text-white font-medium">{label}</p>
<p className="text-blue-400">{careerEvents[label] || 'Career progression'}</p>
</div>
);
}
return null;
};
export const ReactorStatus: React.FC<ReactorStatusProps> = ({
}) => {
const [power, setPower] = useState(85);
const [coreTemp, setCoreTemp] = useState(1350);
const [blockHeight, setBlockHeight] = useState(18_500_000);
const [radLevel, setRadLevel] = useState(5.5e7);
const [status, setStatus] = useState('Initializing systems...');
const [isAnimating, setIsAnimating] = useState(false);
const [currentTime, setCurrentTime] = useState(new Date());
const [timelineData] = useState(generateTimelineData());
const careerStats = [
{ label: 'Years Experience', value: '20+' },
{ label: 'Major Projects', value: '50+' },
{ label: 'Grants Secured', value: '$197K' }
];
useEffect(() => {
const intervals = [
setInterval(() => setCurrentTime(new Date()), 1000),
setInterval(() => {
setPower(prev => {
const fluctuation = Math.random() * 10 - 5;
return Math.min(Math.max(prev + fluctuation, 60), 100);
});
}, 3000),
setInterval(() => {
setCoreTemp(() => {
const fluctuation = Math.random() * 20 - 10;
return 1300 + (fluctuation % 100);
});
}, 2000),
setInterval(() => {
setBlockHeight(prev => prev + Math.floor(Math.random() * 3));
}, 5000),
setInterval(() => {
setRadLevel(() => {
const base = 5e7;
const fluctuation = Math.random() * 5e7;
return base + fluctuation;
});
}, 4000),
setInterval(() => {
const randomStatus = statusMessages[Math.floor(Math.random() * statusMessages.length)];
setStatus(randomStatus);
setIsAnimating(true);
setTimeout(() => setIsAnimating(false), 500);
}, 5000)
];
return () => intervals.forEach(clearInterval);
}, []);
const formatRadLevel = (level: number) => {
return `${(level / 1e7).toFixed(2)}E7 Gy/hr`;
};
return (
<div className="max-w-6xl mx-auto">
<div className="flex items-center gap-4 mb-6">
<Atom size={32} className="text-blue-500" />
<h2 className="text-3xl font-bold bg-gradient-to-r from-blue-500 to-purple-500 bg-clip-text text-transparent">
System Status
</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{/* Reactor Status Panel */}
<div className="bg-gray-900 rounded-lg p-6">
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-2">
<Activity className="text-blue-500" />
<span className="text-sm text-gray-400 font-mono">
{currentTime.toLocaleTimeString()}
</span>
</div>
<div className="flex items-center gap-2">
<Power className="text-green-500" />
<span className="text-sm text-gray-400 font-mono">
{power.toFixed(1)}% Capacity
</span>
</div>
</div>
{/* Status Message */}
<div className="bg-gray-800/50 rounded-lg p-4 mb-6">
<div className={`text-lg font-mono text-blue-400 ${isAnimating ? 'animate-pulse' : ''}`}>
{status}
</div>
</div>
{/* Reactor Metrics */}
<div className="grid grid-cols-3 gap-4 mb-6">
<div className="bg-gray-800/50 rounded-lg p-4 text-center">
<div className="text-sm text-gray-400 mb-2">Core Temp</div>
<div className="text-blue-400 font-mono font-medium">
{coreTemp.toFixed(1)}°C
</div>
</div>
<div className="bg-gray-800/50 rounded-lg p-4 text-center">
<div className="text-sm text-gray-400 mb-2">Block Height</div>
<div className="text-blue-400 font-mono font-medium">
#{blockHeight.toLocaleString()}
</div>
</div>
<div className="bg-gray-800/50 rounded-lg p-4 text-center">
<div className="text-sm text-gray-400 mb-2">Rad Level</div>
<div className="text-blue-400 font-mono font-medium">
{formatRadLevel(radLevel)}
</div>
</div>
</div>
{/* Systems Monitor */}
<div className="grid grid-cols-2 gap-4">
<div className="bg-gray-800/50 rounded-lg p-4">
<div className="text-sm text-gray-400 mb-2">Core Stability</div>
<div className="h-[100px]">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={generateStabilityData()}>
<Line
type="natural"
dataKey="value"
stroke="#3B82F6"
strokeWidth={2}
dot={false}
isAnimationActive={true}
/>
</LineChart>
</ResponsiveContainer>
</div>
</div>
<div className="bg-gray-800/50 rounded-lg p-4">
<div className="text-sm text-gray-400 mb-2">Neutron Flux</div>
<div className="h-[100px]">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={generateFluxData()}>
<Line
type="natural"
dataKey="value"
stroke="#8B5CF6"
strokeWidth={2}
dot={false}
isAnimationActive={true}
/>
</LineChart>
</ResponsiveContainer>
</div>
</div>
</div>
</div>
{/* Career Timeline Panel */}
<div className="bg-gray-900 rounded-lg p-6">
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-2">
<TrendingUp className="text-purple-500" />
<h3 className="text-lg font-bold text-white">Career Timeline</h3>
</div>
<div className="flex items-center gap-2">
<Users className="text-purple-500" />
<span className="text-sm text-gray-400">Career Progression</span>
</div>
</div>
{/* Career Stats */}
<div className="grid grid-cols-3 gap-4 mb-6">
{careerStats.map((stat, index) => (
<div key={index} className="bg-gray-800/50 rounded-lg p-4 text-center">
<div className="text-sm text-gray-400">{stat.label}</div>
<div className="text-purple-400 font-medium mt-1">{stat.value}</div>
</div>
))}
</div>
{/* Timeline Chart */}
<div className="bg-gray-800/50 rounded-lg p-4 h-[200px]">
<ResponsiveContainer width="100%" height="100%">
<LineChart data={timelineData}>
<XAxis
dataKey="year"
stroke="#4B5563"
tick={{ fill: '#9CA3AF', fontSize: 12 }}
/>
<YAxis
stroke="#4B5563"
tick={{ fill: '#9CA3AF', fontSize: 12 }}
domain={['auto', 'auto']}
hide
/>
<Tooltip content={<CustomTooltip />} />
<Line
type="monotone"
dataKey="value"
stroke="#8B5CF6"
strokeWidth={2}
dot={false}
/>
</LineChart>
</ResponsiveContainer>
</div>
</div>
</div>
</div>
);
};
export default ReactorStatus;

View File

@ -1,24 +0,0 @@
// src/components/ReactorStatus/types.ts
export interface ReactorStatusProps {
className?: string;
}
export interface TooltipProps {
active?: boolean;
payload?: Array<{
value: number;
dataKey?: string;
}>;
label?: string;
}
export interface PriceStat {
label: string;
value: string;
}
export interface PriceDataPoint {
time: number;
price: number;
}

View File

@ -1,138 +0,0 @@
import type { FC } from 'react';
import { ChevronRight } from 'lucide-react';
import type { SkillsProps, SkillCardProps, SkillBarProps, Skill } from './types';
const SkillBar: FC<SkillBarProps> = ({
level,
maxLevel = 5,
className = ''
}) => {
const percentage = (level / maxLevel) * 100;
return (
<div className={`h-2 w-32 bg-gray-800 rounded-full overflow-hidden ${className}`}>
<div
className="h-full bg-gradient-to-r from-emerald-400 to-cyan-500 rounded-full transform origin-left transition-transform duration-1000 ease-out"
style={{ transform: `scaleX(${percentage / 100})` }}
/>
</div>
);
};
const SkillCard: FC<SkillCardProps> = ({
skill,
className = ''
}) => (
<div className={`bg-gray-900 rounded-lg p-6 hover:bg-gray-800 transition-all duration-300 ${className}`}>
<div className="flex items-center justify-between mb-4">
<div>
<h3 className="text-lg font-medium text-white">{skill.name}</h3>
<p className="text-sm text-gray-400">{skill.description}</p>
</div>
<SkillBar level={skill.level} />
</div>
<div className="space-y-2">
{skill.keywords.map((keyword, index) => (
<div key={index} className="flex items-center text-sm text-gray-400">
<ChevronRight size={16} className="text-blue-500 mr-2" />
<span>{keyword}</span>
</div>
))}
</div>
</div>
);
const defaultSkills: Skill[] = [
{
name: "Radiation Protection",
description: "Expert",
level: 5,
keywords: [
"Radiation Monitoring",
"Contamination Control",
"Radiological Safety",
"Documentation and Procedure"
]
},
{
name: "Rigging",
description: "Advanced",
level: 4,
keywords: [
"Heavy Machinery",
"Safety Protocols",
"Equipment Setup",
"Planning"
]
},
{
name: "Project Management",
description: "Intermediate",
level: 3,
keywords: [
"Task Prioritization",
"Time Management",
"Team Collaboration",
"Project Scheduling",
"Milestone Tracking",
"Problem Solving"
]
},
{
name: "Grant Writing",
description: "Expert",
level: 4,
keywords: [
"Governance",
"Operations",
"Community Building",
"Grants"
]
},
{
name: "Community Building",
description: "Expert",
level: 3,
keywords: [
"Onboarding",
"Content Creation",
"Networking"
]
},
{
name: "Web Development",
description: "Novice",
level: 2,
keywords: [
"Python",
"Javascript",
"Typescript",
"HTML",
"CSS",
"REACT"
]
}
];
export const Skills: FC<SkillsProps> = ({
className = '',
skills = defaultSkills
}) => {
return (
<div className={`bg-black text-white ${className}`}>
<div className="max-w-6xl mx-auto">
<div className="flex items-center gap-4">
<h2 className="text-3xl font-bold bg-gradient-to-r from-emerald-400 to-cyan-500 bg-clip-text text-transparent">
Professional Skills
</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mt-6">
{skills.map((skill, index) => (
<SkillCard key={index} skill={skill} />
))}
</div>
</div>
</div>
);
};
export default Skills;

View File

@ -1,24 +0,0 @@
export interface Skill {
id?: string;
visible?: boolean;
name: string;
description: string;
level: number;
keywords: string[];
}
export interface SkillsProps {
className?: string;
skills?: Skill[];
}
export interface SkillCardProps {
skill: Skill;
className?: string;
}
export interface SkillBarProps {
level: number;
maxLevel?: number;
className?: string;
}

View File

@ -1,24 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
/* Custom animations */
@keyframes float {
0% { transform: translate(0, 0); }
50% { transform: translate(-10px, 10px); }
100% { transform: translate(0, 0); }
}
@keyframes floatDelayed {
0% { transform: translate(0, 0); }
50% { transform: translate(10px, -10px); }
100% { transform: translate(0, 0); }
}
.animate-float {
animation: float 20s ease-in-out infinite;
}
.animate-float-delayed {
animation: floatDelayed 15s ease-in-out infinite;
}

View File

@ -1,10 +0,0 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)

View File

@ -1,60 +0,0 @@
import type { LucideIcon } from 'lucide-react';
export interface NavItem {
id: string;
icon: LucideIcon;
label: string;
}
export interface ExperienceItem {
title: string;
company: string;
period: string;
description: string;
icon: LucideIcon;
}
export interface Grant {
name: string;
amount: number;
color: string;
}
export interface Achievement {
title: string;
description: string;
date: string;
}
export interface TooltipProps {
active?: boolean;
payload?: Array<{
value: number;
payload: Grant;
}>;
}
export interface Skills {
title: string;
description: string;
date: string;
}
export interface NFT {
id: number;
image: string;
name: string;
link: string;
}
export interface SocialLink {
name: string;
icon: LucideIcon;
url: string;
username: string;
}
export interface LinkCategory {
category: string;
links: SocialLink[];
}

View File

@ -1,76 +0,0 @@
// src/utils/api.ts
const API_KEY = import.meta.env.VITE_CG_API_KEY;
export interface PriceData {
prices: [number, number][];
market_caps: [number, number][];
total_volumes: [number, number][];
}
export async function getEthereumData(): Promise<PriceData> {
try {
console.log('Fetching ETH price data...');
const response = await fetch(
`https://pro-api.coingecko.com/api/v3/coins/ethereum/market_chart?vs_currency=usd&days=1&interval=hourly&x_cg_pro_api_key=${API_KEY}`
);
if (!response.ok) {
const errorText = await response.text();
console.error('API Error Response:', errorText);
throw new Error(`API request failed: ${response.status} ${response.statusText}`);
}
const data = await response.json();
console.log('Raw API response:', data);
return data;
} catch (error) {
console.error('Error in getEthereumData:', error);
// Return mock data in case of API failure
const now = Date.now();
const mockData: PriceData = {
prices: Array.from({ length: 24 }, (_, i) => {
const timestamp = now - (23 - i) * 3600000;
const basePrice = 2300;
const randomChange = Math.random() * 100 - 50;
return [timestamp, basePrice + randomChange] as [number, number];
}),
market_caps: Array.from({ length: 24 }, (_, i) => {
const timestamp = now - (23 - i) * 3600000;
return [timestamp, 2300000000] as [number, number];
}),
total_volumes: Array.from({ length: 24 }, (_, i) => {
const timestamp = now - (23 - i) * 3600000;
return [timestamp, 500000000] as [number, number];
})
};
return mockData;
}
}
export function formatPrice(price: number): string {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
maximumFractionDigits: 2
}).format(price);
}
export function getHighLowPrices(prices: [number, number][]): { high: number; low: number } {
const priceValues = prices.map(([, price]) => price);
return {
high: Math.max(...priceValues),
low: Math.min(...priceValues)
};
}
export function formatVolume(volume: number): string {
if (volume >= 1e9) {
return `$${(volume / 1e9).toFixed(1)}B`;
}
if (volume >= 1e6) {
return `$${(volume / 1e6).toFixed(1)}M`;
}
return `$${volume.toFixed(0)}`;
}

1
src/vite-env.d.ts vendored
View File

@ -1 +0,0 @@
/// <reference types="vite/client" />

View File

@ -1,55 +0,0 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./index.html",
"./src/**/*.{js,ts,jsx,tsx}",
],
safelist: [
{
pattern: /from-(cyan|emerald|purple|blue|indigo|pink)-(400|500)/,
variants: ['hover', 'focus'],
},
{
pattern: /to-(indigo|blue|pink)-(400|500)/,
variants: ['hover', 'focus'],
},
{
pattern: /bg-(cyan|emerald|purple|blue|indigo|pink)-(400|500)\/10/,
variants: ['hover', 'focus'],
},
{
pattern: /text-(cyan|emerald|purple|blue|indigo|pink)-(400|500)/,
variants: ['hover', 'focus'],
},
],
theme: {
extend: {
fontFamily: {
'courier-prime': ['Courier Prime', 'monospace'],
'space-mono': ['Space Mono', 'monospace'],
},
animation: {
'fade-in': 'fadeIn 0.5s ease-in forwards',
'float': 'float 20s ease-in-out infinite',
'float-delayed': 'floatDelayed 15s ease-in-out infinite',
},
keyframes: {
fadeIn: {
'0%': { opacity: '0' },
'100%': { opacity: '1' },
},
float: {
'0%': { transform: 'translate(0, 0)' },
'50%': { transform: 'translate(-10px, 10px)' },
'100%': { transform: 'translate(0, 0)' },
},
floatDelayed: {
'0%': { transform: 'translate(0, 0)' },
'50%': { transform: 'translate(10px, -10px)' },
'100%': { transform: 'translate(0, 0)' },
},
},
},
},
plugins: [],
}

View File

@ -1,24 +0,0 @@
{
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"]
}

View File

@ -1,7 +0,0 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}

View File

@ -1,24 +0,0 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}

View File

@ -1,29 +0,0 @@
#!/bin/bash
# This script updates the local repository from Gitea
echo "Updating local repository from Gitea..."
# Fetch the latest changes from Gitea
git fetch origin
# Check if there are any changes
if git diff --quiet HEAD origin/main; then
echo "Local repository is already up to date."
else
echo "New changes detected. Updating local repository..."
# Stash any local changes
git stash
# Pull the latest changes
git pull origin main
# Apply stashed changes if any
if git stash list | grep -q "stash@{0}"; then
echo "Applying stashed changes..."
git stash pop
fi
echo "Local repository updated successfully!"
fi

92
update-nginx-config.sh Executable file
View File

@ -0,0 +1,92 @@
#!/bin/bash
# This script updates the Nginx configuration for boilerhaus.org and its subdomains
# Load environment variables
if [ -f .env ]; then
source .env
else
echo "Error: .env file not found. Please create it based on .env.example."
exit 1
fi
# Check if sshpass is installed
if ! command -v sshpass &> /dev/null; then
echo "Error: sshpass is not installed. Please run ./install-sshpass.sh first."
exit 1
fi
# Set SSH and SCP commands with password
SSH_CMD="sshpass -p \"$SERVER_PASSWORD\" ssh -o StrictHostKeyChecking=no $SERVER_USER@$SERVER_IP"
SCP_CMD="sshpass -p \"$SERVER_PASSWORD\" scp -o StrictHostKeyChecking=no"
# Create main website configuration file
echo "Creating main website configuration file..."
cat > boilerhaus.org.conf.new << 'EOL'
server {
listen 80;
listen [::]:80;
server_name boilerhaus.org;
root /var/www/boilerhaus.org;
index index.html;
location / {
try_files $uri $uri/ =404;
}
# Managed by Certbot
# This section will be updated by Certbot automatically
}
# Nextcloud configuration
server {
listen 80;
listen [::]:80;
server_name cloud.boilerhaus.org;
# Proxy to Nextcloud
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Managed by Certbot
# This section will be updated by Certbot automatically
}
EOL
# Upload the new configuration
echo "Uploading main website configuration..."
eval "$SCP_CMD boilerhaus.org.conf.new $SERVER_USER@$SERVER_IP:/tmp/boilerhaus.org.conf.new"
eval "$SSH_CMD \"mv /tmp/boilerhaus.org.conf.new /etc/nginx/sites-available/boilerhaus.org.conf\""
# Make sure the site is enabled
echo "Ensuring site is enabled..."
eval "$SSH_CMD \"ln -sf /etc/nginx/sites-available/boilerhaus.org.conf /etc/nginx/sites-enabled/boilerhaus.org.conf\""
# Test Nginx configuration
echo "Testing Nginx configuration..."
NGINX_TEST=$(eval "$SSH_CMD \"nginx -t 2>&1\"")
NGINX_TEST_EXIT_CODE=$?
# If the test is successful, reload Nginx
if [ $NGINX_TEST_EXIT_CODE -eq 0 ]; then
echo "Reloading Nginx..."
eval "$SSH_CMD \"systemctl reload nginx\""
echo "Configuration updated successfully!"
else
echo "Nginx configuration test failed:"
echo "$NGINX_TEST"
echo "Please check the configuration and try again."
exit 1
fi
# Run Certbot to ensure SSL certificates are set up for main domain
echo "Running Certbot to ensure SSL certificates are set up..."
eval "$SSH_CMD \"certbot --nginx -d boilerhaus.org -d cloud.boilerhaus.org --non-interactive --agree-tos --email admin@boilerhaus.org\""
echo "Done!"

View File

@ -1,7 +0,0 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
})